Glib学习(9) 平衡二叉树 Balanced Binary Trees

时间:2022-10-30 17:31:01

平衡二叉树(Balanced Binary Tree)又被称为AVL树

且具有以下性质:

它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

构造与调整方法 平衡二叉树的常用算法有红黑树、AVL、Treap等。 

最小二叉平衡树的节点的公式如下 F(n)=F(n-1)+F(n-2)+1 这个类似于一个递归的数列,可以参考Fibonacci数列,1是根节点,F(n-1)是左子树的节点数量,F(n-2)是右子树的节点数量。

AVL树是最先发明的自平衡二叉查找算法,是平衡二叉树的一种。在AVL中任何节点的两个儿子子树的高度最大差别为1,所以它又被成为高度平衡树。查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来平衡这棵树。

总的来说平衡二叉树是一种存储结构,这种存储结构会提高遍历和查找的效率,所以今天讲的数据结构是用来存储数据,并且目的是提高查找效率的,作用有点像哈希表。


先来说明文档网址:http://web.mit.edu/barnowl/share/gtk-doc/html/glib/glib-Balanced-Binary-Trees.html


这里需要注意的是我在编译的程序的时候使用的是2.0.6版本,而这个帮助文档的版本是2.16.3,其中g_tree_remove函数的返回值类型不同。


这个数据结构体:

GTree

typedef struct _GTree GTree;

功能函数:

Synopsis

#include <glib.h>


GTree;
GTree* g_tree_new (GCompareFunc key_compare_func);
GTree* g_tree_new_with_data (GCompareDataFunc key_compare_func,
gpointer key_compare_data);
GTree* g_tree_new_full (GCompareDataFunc key_compare_func,
gpointer key_compare_data,
GDestroyNotify key_destroy_func,
GDestroyNotify value_destroy_func);
void g_tree_insert (GTree *tree,
gpointer key,
gpointer value);
void g_tree_replace (GTree *tree,
gpointer key,
gpointer value);
gint g_tree_nnodes (GTree *tree);
gint g_tree_height (GTree *tree);
gpointer g_tree_lookup (GTree *tree,
gconstpointer key);
gboolean g_tree_lookup_extended (GTree *tree,
gconstpointer lookup_key,
gpointer *orig_key,
gpointer *value);
void g_tree_foreach (GTree *tree,
GTraverseFunc func,
gpointer user_data);
void g_tree_traverse (GTree *tree,
GTraverseFunc traverse_func,
GTraverseType traverse_type,
gpointer user_data);
gboolean (*GTraverseFunc) (gpointer key,
gpointer value,
gpointer data);
enum GTraverseType;
gpointer g_tree_search (GTree *tree,
GCompareFunc search_func,
gconstpointer user_data);
gboolean g_tree_remove (GTree *tree,
gconstpointer key);
gboolean g_tree_steal (GTree *tree,
gconstpointer key);
void g_tree_destroy (GTree *tree);

下面先看一小段代码:

#include <glib.h>

struct map {
int key;
char *value;
} m[10] = {
{0,"zero"},
{1,"one"},
{2,"two"},
{3,"three"},
{4,"four"},
{5,"five"},
{6,"six"},
{7,"seven"},
{8,"eight"},
{9,"nine"},
};
typedef struct map map;

static gint
myCompare(gconstpointer p1, gconstpointer p2)
{
const char *a = p1;
const char *b = p2;

return *a - *b;
}

static gint
mySearch(gconstpointer p1, gconstpointer p2)
{
return myCompare(p1, p2);
}

static gint
myTraverse(gpointer key, gpointer value, gpointer fmt)
{
g_printf(fmt, *(gint*)key, (gchar*)value);

return FALSE;
}

static void
test_avl_tree(void)
{
GTree *tree;
gint i;

// GTree* g_tree_new(GCompareFunc key_compare_func); 用来创建一个平衡二叉树,key_compare_func函数用来排序节点规则
tree = g_tree_new(myCompare);

// void g_tree_insert(GTree *tree, gpointer key, gpointer value); 插入一个键值到树中
// 需要注意的是如果key值重复的话会将以前的值进行去除然后将新的键值加入树中,去除的包括key值
// 如果使用的是new_full函数并且实现了销毁key和value的函数,在插入重复key时会新的key被销毁
for (i = 0; i < sizeof(m)/sizeof(m[0]); i++)
g_tree_insert(tree, &m[i].key, m[i].value);

// void g_tree_foreach(GTree *tree, GTraverseFunc func, gpointer user_data); 遍历树,将键值依次传入func函数中
g_printf("Now the tree:\n");
g_tree_foreach(tree, myTraverse, "Key:\t%d\t\tVaule:\t%s\n");
// gint g_tree_nnodes(GTree *tree); 取得树中的节点数
g_printf("The tree should have '%d' items now.\t\tResult: %d.\n", 10, g_tree_nnodes(tree));
// gint g_tree_height(GTree *tree); 取得树的高度,如果是空树将返回0,如果只有根节点返回1,以此类推
g_printf("The height of tree is '%d' now.\n", g_tree_height(tree));

// void g_tree_replace(GTree *tree, gpointer key, gpointer value); 将key的值用value替换,功能和g_tree_insert很相似,只是在销毁的时候会去销毁旧的key,而不是新的
g_tree_replace(tree, &m[3].key, "3333");
g_printf("Now the vaule of '%d' should be '3333' now.\n", m[3].key);
g_tree_foreach(tree, myTraverse, "Key:\t%d\t\tVaule:\t%s\n");

gchar *tmp = NULL;
// gpointer g_tree_lookup(GTree *tree, gconstpointer key); 查找key的值
g_printf("Now the vaule of '%d' should be '%s' now[lookup].\n", m[3].key, (tmp = (gchar *)g_tree_lookup(tree, &m[3].key)) != NULL ? tmp : NULL);

// gboolean g_tree_remove(GTree *tree, gconstpointer key); 移除key对应的键值,需要注意的是在2.0版本中这个函数返回类型是void,在之后的版本中返回是bool,代表是否成功
// gboolean b = g_tree_remove(tree, &m[3].key);
g_tree_remove(tree, &m[3].key);
g_printf("The key '%d' has been found and removed now.\n", m[3].key);

// gpointer g_tree_search(GTree *tree, GCompareFunc search_func, gconstpointer user_data); 通过search_func形式去查找树,可以使用这种方式暂时改变查找方式,比如正序或倒序
g_printf("Now the vaule which should be removed of '%d' should be '%s' now[search].\n",
m[3].key,
(tmp = (gchar *)g_tree_search(tree, mySearch, &m[3].key)) != NULL ? tmp : NULL);

g_printf("Now the tree look like:\n");
g_tree_foreach(tree, myTraverse, "Key:\t%d\t\tVaule:\t%s\n");

// void g_tree_destroy(GTree *tree);释放树,如果使用g_tree_new_full()函数创建的树会使用销毁函数同时销毁键值
g_tree_destroy(tree);
}

int
main(void)
{
g_printf("BEGIN:\n************************************************************\n");

test_avl_tree();

g_printf("\n************************************************************\nDONE\n");

return 0;
}

在程序中保存了一个map表,其中是int型的key和字符串类型的value。

看一下运行结果:

linux@ubuntu:~/16021/glibDemo$ gcc GTree.c -o GTree -lglib-2.0
linux@ubuntu:~/16021/glibDemo$ ./GTree
BEGIN:
************************************************************
Now the tree:
Key: 0 Vaule: zero
Key: 1 Vaule: one
Key: 2 Vaule: two
Key: 3 Vaule: three
Key: 4 Vaule: four
Key: 5 Vaule: five
Key: 6 Vaule: six
Key: 7 Vaule: seven
Key: 8 Vaule: eight
Key: 9 Vaule: nine
The tree should have '10' items now. Result: 10.
The height of tree is '4' now.
Now the vaule of '3' should be '3333' now.
Key: 0 Vaule: zero
Key: 1 Vaule: one
Key: 2 Vaule: two
Key: 3 Vaule: 3333
Key: 4 Vaule: four
Key: 5 Vaule: five
Key: 6 Vaule: six
Key: 7 Vaule: seven
Key: 8 Vaule: eight
Key: 9 Vaule: nine
Now the vaule of '3' should be '3333' now[lookup].
The key '3' has been found and removed now.
Now the vaule which should be removed of '3' should be '(null)' now[search].
Now the tree look like:
Key: 0 Vaule: zero
Key: 1 Vaule: one
Key: 2 Vaule: two
Key: 4 Vaule: four
Key: 5 Vaule: five
Key: 6 Vaule: six
Key: 7 Vaule: seven
Key: 8 Vaule: eight
Key: 9 Vaule: nine

************************************************************
DONE
linux@ubuntu:~/16021/glibDemo$

好了,对于功能函数就没有什么多说的了,但是有些同学会想知道存进去的数据在树里面是怎么样的一种存储模型。我也是先来简单的说一下,对于树的遍历分为三种,先序遍历、后序遍历、中序遍历。glib文档中没有给出来遍历的方式,我们是先序遍历来说明。用上面代码中的数据来举例,key是从1到10.

如果使用先序遍历存储模型就是下图:

Glib学习(9) 平衡二叉树 Balanced Binary Trees


从上面这个图中也能明白为什么g_tree_height函数返回值为4了。


在glib中还有一种数据结构叫做n叉树  N-ary Trees

这是树的最一般形式,但是应用却是最少的,现实生活中的N叉树是最多的,但是对于它的操作、算法却是非常的麻烦,在实际使用时往往通过人为方法将n叉树转换为二叉树去进行处理,所以对于n叉树的数据结构就不去了解了。