接着看删除部分,删除部分比插入要更难搞一些。对于删除,同样需要分情况讨论。
1.如果是在一个平衡节点下删除,只要把这个节点的高度信息修改就可以了
2.如果是在较长的子树下删除,就把这个节点的高度信息修改成平衡
以上两种情况都不需要进行旋转。
3.如果是在较短的子树下删除,除了更新高度信息外,还需要旋转节点。这时又分3种情况讨论。
3种情况的图如下,直接针对编程就可以了
与插入一样,删除时不必从树根开始更新,记录一个path_top即可。如果某个节点是平衡的,那么删除不会影响到它以上的节点,如果某个节点下的情况和图1相同,那么删除也不会影响到它以上的节点。这时可以把path_top设为该节点。另外,treep是指向删除节点右子树中最小的节点,如果用中序遍历,就是下个节点,将用它的值来代替删除节点。
- int avl_delete(node *treep, value_t target)
- {
-
-
-
- node tree = *treep, targetn;
- node *path_top = treep;
- node *targetp = NULL;
- direction dir;
-
- while (tree) {
- dir = (target > tree->value);
- if (target == tree->value)
- targetp = treep;
- if (tree->next[dir] == NULL)
- break;
- if (Balanced(tree)
- || (tree->longer == (1-dir) && Balanced(tree->next[1-dir]))
- ) path_top = treep;
- treep = &tree->next[dir];
- tree = *treep;
- }
- if (!targetp)
- return 0;
-
-
- targetp = avl_rebalance_del(path_top, target, targetp);
-
-
-
-
-
- avl_swap_del(targetp, treep, dir);
-
- return 1;
- }
这一段代码负责重新平衡树,除了最后一个node更新之外(tree-next[dir] == NULL),其余的都要更新高度信息,如有必要,还有旋转。
- node *avl_rebalance_del(node *treep, value_t target, node *targetp)
- {
-
-
-
-
- node targetn = *targetp;
-
- while (1) {
- node tree = *treep;
- direction dir = (target > tree->value);
-
- if (tree->next[dir]==NULL)
- break;
-
- if (Balanced(tree))
- tree->longer = 1-dir;
- else if (tree->longer == dir)
- tree->longer = NEITHER;
- else {
-
- if (tree->next[1-dir]->longer == dir)
- avl_rotate_3_shrink(treep, dir);
- else
- avl_rotate_2_shrink(treep, dir);
-
- if (tree == targetn)
- targetp = &(*treep)->next[dir];
- }
-
-
- treep = &tree->next[dir];
- }
- return targetp;
- }
交换并删除的代码比较简单,如下
- void avl_swap_del(node *targetp, node *treep, direction dir)
- {
- node targetn = *targetp;
- node tree = *treep;
-
- *targetp = tree;
- *treep = tree->next[1-dir];
- tree->next[LEFT] = targetn->next[LEFT];
- tree->next[RIGHT]= targetn->next[RIGHT];
- tree->longer = targetn->longer;
-
- free(targetn);
- }
最后是后种旋转的代码实现,都比较直观,如果对着图看。
- void avl_rotate_2_shrink(node *path_top, direction dir)
- {
- node D, B, C;
-
- D = *path_top;
- B = D->next[1-dir];
- C = B->next[dir];
-
- *path_top = B;
- B->next[dir] = D;
- D->next[1-dir] = C;
-
- if (Balanced(B)) {
- B->longer = dir;
- D->longer = 1-dir;
- } else {
- B->longer = NEITHER;
- D->longer = NEITHER;
- }
- }
-
- void avl_rotate_3_shrink(node *path_top, direction dir)
- {
- node B, C, D, E, F;
- F = *path_top;
- B = F->next[1-dir];
- D = B->next[dir];
- C = D->next[1-dir];
- E = D->next[dir];
-
- *path_top = D;
- D->next[1-dir] = B;
- D->next[dir] = F;
- B->next[dir] = C;
- F->next[1-dir] = E;
-
- B->longer = NEITHER;
- F->longer = NEITHER;
- if (D->longer == dir)
- B->longer = 1-dir;
- if (D->longer == 1-dir)
- F->longer = dir;
- D->longer = NEITHER;
- }
最近在复习数据结构,看到avl树,这样比较复杂的数据结构通常都很搞人。于是上网,找到一个实现看,写这代码的老外叫Neil Brown,一个大胡子,我们知道,在国外,开发人员的水平经常和胡子的多少成正比。还看到他在blog中说,这个代码打算用在linux kernel里,自然不会是普通代码了。
我拿下来,狠看一阵才看懂,不得不说,这个代码很多地方的实现是很精妙的,和教材上只是为了让你理解概念的代码,区别很大。
这个实现有一个特点,就是没有一般树实现中的left和right指针,作者用一个数组next[2]来表示,next[0]就是左子树,next[1]就是右子树,然后每个节点也不保存当前的层高,这个是教科书的做法,int longer:2,极其省俭的表示了是左子树长(值为0),还是右子树长(值为1),或者当前节点上这颗树是平衡的(值了-1)。
-
-
-
-
-
-
-
-
-
- #include
- typedef int value_t;
-
- #define LEFT 0 // 表示左节点,或者左子树长
- #define RIGHT 1 // 表示右节点,或者右子树长
- #define NEITHER -1 // 以当前节点为根的子树是平衡的
- typedef int direction;
-
- typedef struct node_s {
- value_t value;
- struct node_s *next[2];
- int longer:2;
- } *node;
- #define Balanced(n) (n->longer < 0) // 平衡时值了-1
这里开始精妙了,我们平时的tree查找是怎么写的,target小于当前节点就下到左子树
继续找,大于就下到右子树继续找,这里就用一个direction next_step = (target > tree->value)得到了接下来操作的方向。next[next_step]就是接下来要查找的节点
- node avl_find(node tree, value_t target)
- {
- while (tree && target != tree->value) {
- direction next_step = (target > tree->value);
- tree = tree->next[next_step];
- }
- return tree;
- }
作者不大喜欢single rotation和double rotation这样的说法,他把旋转分为两种,一种是2旋,就是single rotation,这个时候用数组的优势就出来了,不用把相似的single rotation写两遍,传入的dir值不同就可以了。
- static node avl_rotate_2(node *path_top, direction dir)
- {
- node B, C, D, E;
- B = *path_top;
- D = B->next[dir];
- C = D->next[1-dir];
- E = D->next[dir];
-
- *path_top = D;
- D->next[1-dir] = B;
- B->next[dir] = C;
- B->longer = NEITHER;
- D->longer = NEITHER;
- return E;
- }
这里是三旋转,也就是double rotation.
注意旋转后面third变量的作用,third表示新入节点在C还是在E上。如果在E上,独立的C的高度必小于A,因为在E上还没有插入新节点时,C只比A高1,而且C上有D,F垫底,所以独立的C的高度要减2,所以独立的C比独立的A高度小1。
- static node avl_rotate_3(node *path_top, direction dir, direction third)
- {
- node B, F, D, C, E;
- B = *path_top;
- F = B->next[dir];
- D = F->next[1-dir];
-
- C = D->next[1-dir];
- E = D->next[dir];
- *path_top = D;
- D->next[1-dir] = B;
- D->next[dir] = F;
- B->next[dir] = C;
- F->next[1-dir] = E;
- D->longer = NEITHER;
-
-
- B->longer = F->longer = NEITHER;
-
- if (third == NEITHER)
- return NULL;
- else if (third == dir) {
-
- B->longer = 1-dir;
- return E;
- } else {
-
- F->longer = dir;
- return C;
- }
- }
插入新节点后已经,树已经不平衡了,需要重新计算节点上的longer值。
-
-
-
- static inline void avl_rebalance_path(node path, value_t target)
- {
-
-
-
-
-
- while (path && target != path->value) {
- direction next_step = (target > path->value);
- path->longer = next_step;
- path = path->next[next_step];
- }
- }
这里是插入新节点后使树重新平衡的全过程,包括旋转节点和更新高度信息。path_top是更新的起点。如果path_top节点是平衡的,不需要旋转,更新高度信息即可。如果在较短和路径上插入,也不需要旋转,还是更新高度信息,path_top变成平衡的,然后对插入的那棵树更新高度信息。如果在较长的路径上插入,就需要旋转了,first和second表示两次的操作方向,如果相同,就是2旋转,否则是3旋转,这时要注意third变量,如果C和E都为空,那么third变量就是NEITHER,不需要特殊处理,否则,third就传入3旋转的函数,用于下面更新高度信息。
- static inline void avl_rebalance_insert(node *path_top, value_t target)
- {
- node path = *path_top;
- direction first, second, third;
- if (Balanced(path))
- ;
- else if (path->longer != (first = (target > path->value))) {
-
- path->longer = NEITHER;
- path = path->next[first];
- } else if (first == (second = (target > path->next[first]->value))) {
-
- path = avl_rotate_2(path_top, first);
- } else {
-
-
-
-
-
- path = path->next[first]->next[second];
- if (target == path->value) third = NEITHER;
- else third = (target > path->value);
- path = avl_rotate_3(path_top, first, third);
- }
- avl_rebalance_path(path, target);
- }
avl_insert是插入接口,这里又有一个技巧,对于avl树,不用从根节点上开始更新高度信息。开始更新高度信息的起点,是path_top。如果查找的一路上下来,每个节点都是平衡的,那么只能从根上开始更新,否则,从第一个不平衡的节点开始更新。插入完成后,再调用avl_rebalance_insert来平衡树。
- int avl_insert(node *treep, value_t target)
- {
-
-
-
- node tree = *treep;
- node *path_top = treep;
- while (tree && target != tree->value) {
- direction next_step = (target > tree->value);
- if (!Balanced(tree)) path_top = treep;
- treep = &tree->next[next_step];
- tree = *treep;
- }
- if (tree) return 0;
- tree = malloc(sizeof(*tree));
- tree->next[0] = tree->next[1] = NULL;
- tree->longer = NEITHER;
- tree->value = target;
- *treep = tree;
- avl_rebalance_insert(path_top, target);
- return 1;
- }
明天再写删除的,太晚了。