小肥柴慢慢手写数据结构(C篇)(5-2 AVL树)

小肥柴慢慢学习数据结构笔记(C篇)(5-2 AVL树

  • 目录
    • 5-5 AVL出现的原因
      • 5-5-1 平衡树
      • 5-5-2 平衡二叉树的具体案例
    • 5-6 AVL平衡策略的讨论
    • 5-7 不使用平衡因子的实现(黑皮书,训练思维)
    • 5-8 使用平衡因子的实现(更加常见的实现方法)
    • 5-9 一些数学讨论
    • 参考文献

目录

5-5 AVL出现的原因

5-5-1 平衡树

(1)设想如下场景:使用之前实现的BST,从空树开始向其中插入如下序列 n 1 , n 2 , . . . n k n_1,n_2, ... n_k n1,n2,...nk,且 n i − 1 < n i n_{i-1}<n_{i} ni1<ni,则会形成如下图所示的二叉树,其实已经退化成链表了:
在这里插入图片描述

(2)同理,对删除操作,如果每次都删除树中的最小/最大key节点,那么一棵树也可能会退化成链表。
(3)对于链表,查询效率为 O ( N ) O(N) O(N),远不及BST原始设计时设想的 O ( l o g N ) O(logN) O(logN),即这种不平衡(左右两支节点的深度差异过大,有的节点查询深度过深)是需要改进BST数据结构来避免的。

【如何解决?】==> 保证insert/remove操作后树形结构的左右枝平衡即可(balance)

【注】此处需要读者自学两个概念:“满树”、“完全树”,需要指出的是它们与平衡树的概念是有区别的。

5-5-2 平衡二叉树的具体案例

实现平衡不是一个简单的过程,但有很多策略/算法可以用于解决此问题,为此前人也创造了不少新的数据结构,譬如如下自平衡BST:
(1)AVL树(古老的入门平衡树)
(2)RBTree(红黑树,大名鼎鼎的通用树)
(3)Treap(树堆)
(4)AA树(红黑树变体)

当然,后续我们还要陆续讨论和实现例如:
(1)Trie(字典树)
(2)2-3树、2-3-4树
(3)k-d树

可以说树的知识点是很有分量的,清北的数据结构与算法课程中[1],都安排了大量的时间去讨论相关内容,慢慢学,不着急。

5-6 AVL平衡策略的讨论

【摘自《数据结构与算法分析(C++描述)》】:AVL(Adelson-Velskii and Landis,由阿德尔森一维尔斯和兰迪斯在1962年提出,因此得名)树是带有平衡条件(balance condition)的二叉查找树。

(1)AVL需要通过保持平衡维持树的平均深度为 O ( l o g N ) O(logN) O(logN)

(2)策略1:要求左右子树具有相同的高度。 ==> 这个策略限制条件过于严格了。

(3)策略2:要求左右子树高度相差最多为1。 ==> 这个策略更加实用。

【注】平衡二叉树不一定是具有 2 k − 1 2^k-1 2k1个节点的理想平衡树哦(perfectly balance tree)!
借用主线教材的一张图说明问题:
在这里插入图片描述
(4)我们定义空树的高度为-1,每次可能产生不平衡的操作无外乎两种:添加节点(insert)和删除节点(delete/remove),接下来看图讨论4种调整平衡的策略使得左右子树高度差不超过1(( ∣ h e i g h t ( l e f t ) − h e i g h t ( r i g h t ) ∣ ⩽ 1 |height(left)-height(right)|\leqslant1 height(left)height(right)1)):

【核心思想】找到根节点拎起来,根据BST节点的有序性,合理的挂上左右子树。

(a)LL型(k1<k2)

在这里插入图片描述
说明(参考《黑皮书》描述):
<1> 节点k1<k2,目前不平衡的根因是k2的左枝即以k1为根节点的左子树不平衡(height(X) -height(Y) > 1)。
<2> 我们可以把k1节点拎起来做新的子树根节点,那k2节点自动往下掉。
<3> 此时k2自然成为k1的右枝,若需要维持平衡,那么原来k1的右子树(Y)就应该找一个地方挂上。
<4> 且Y子树所有节点都是大于k1的,Y当然只能放在k1的右枝;且Y中节点肯定都小于k2,自然就成为k2的新左子树啦。

以上过程往往被称为“单旋转”(single rotation),人们也会根据旋转的方向不同划分为“左旋转”和“右旋转”,甚至和后续提及的伸展树一样使用zig/zag的称谓,个人觉得初学者不用记那么多,自己能画图理解就行!

这个过程看似简单,在编码时注意操作节点left和right的指向即可,大致操作过程如下:
step1 暂存k1的右子树Y,因为接下来k1的右子树要被替换成k2;
step2 将k2挂在k1的右枝上;
step3 将step1中暂存的Y挂在k2的左枝上。
==> 完事!

(b)RR型(k1<k2)

如下图示,参考LL型做镜像处理(所以我们要先掌握BST的所有细节,特别是对着floor和ceil去写代码理解呢,哈哈哈,环环相扣!)(height(Z) -height(Y) > 1)
在这里插入图片描述

(c)LR型(k1<k2<k3)

很明显,这种情况仅靠一次旋转是无法实现平衡的我们可以采取以下策略:
step1 首先使用一次单边旋转(右旋),实现k1、k2的平衡,因为此时:height(k1_right) -height(k1_left) > 1
step2 把由k1、A、B组成的新树看做一个整体,即k2的新左子树,对k2、k3使用一次单边旋转(左旋),实现平衡。
在这里插入图片描述

即使用两次单旋转,实现一次双旋转操作。

(d)RL型(k1<k2<k3)

同(3),镜像操作即可。
在这里插入图片描述
(5)更多的时候,人们会定义平衡因子(balance factor),在每次insert/delete操作后,再用平衡因子作为判定条件尝试平衡AVL树,这种思路能够大大降低编码实现难度,核心策略如下(参考[2]的描述):

假设本来这棵树是平衡的,在我们在插入一个结点以后,导致了这棵树的不平衡,那么必然是这棵树根结点的平衡因子从 +1 变成了 +2,或者从 -1 变成了 -2 。实际上,总共有四种情况:
1)LL,根结点的平衡因子 +2,左子树根结点平衡因子 +1;
2)RR,根结点的平衡因子 -2,右子树根结点平衡因子 -1;
3)LR,根结点的平衡因子 +2,左子树根结点平衡因子 -1;
4)RL,根结点的平衡因子 -2,右子树根结点平衡因子 +1。

平衡因子(balance factor) BF= height(left) - height(right)

【注】
(1)此处的平衡因子等于+2或-2,其实也可写作大于1或者小于-1
(2)无论是否使用平衡因子,都需要在每个节点维护以当前节点为根的子树的高度;这点和之前讨论BST常规操作中,在节点中添加记录节点数量的域N非常类似。(咱们的教程真的是循序渐进的,一环扣一环,对新生很友好的好伐。)

5-7 不使用平衡因子的实现(黑皮书,训练思维)

我们依旧本着化繁为简的原则,在第一版简单BST的基础上修改代码实现AVL,不使用key作为比较依据。

(1)ADT(h头文件)

#ifndef _AVL_TREE 
#define _AVL_TREE
typedef int ElementType;

struct AvlNode {
	ElementType Element;
    struct AvlNode *Left;
    struct AvlNode *Right;
    int Height;
};

typedef struct AvlNode *AvlTree;

AvlTree MakeEmpty(AvlTree T);
AvlTree Find(ElementType X, AvlTree T);
AvlTree FindMin(AvlTree T);
AvlTree FindMax(AvlTree T);
AvlTree Insert(ElementType X, AvlTree T);
AvlTree Delete(ElementType X, AvlTree T);
ElementType Retrieve(AvlTree T);
#endif

(2)简单的MakeEmpty和Find操作,无需多言

AvlTree MakeEmpty(AvlTree T){
	if(T != NULL){
		MakeEmpty(T->Left);
		MakeEmpty(T->Right);
		free(T);
	}
	return NULL;
}

AvlTree Find(ElementType X, AvlTree T){
	if(T == NULL)
		return NULL;
		
	if(X < T->Element)
		return Find(X, T->Left);
	else if(X > T->Element)
		return Find(X, T->Right);
	else /* X == T->Element */
		return T;
}

(3)FindMin和FindMax,以及Retrieve

AvlTree FindMin(AvlTree T){
	if(T == NULL)
		return NULL;
	
	if(T->Left == NULL)
		return T;
	else
		return FindMin(T->Left);
}

AvlTree FindMax(AvlTree T){
	if(T == NULL)
		return NULL;
	
	if(T->Right == NULL)
		return T;
	else
		return FindMax(T->Right);
}

ElementType Retrieve(AvlTree T){
	return T->Element;
}

(4)定义求高度和比较大小常用操作

static int Height(AvlTree T){
   return (T == NULL) ? -1 : T->Height;
}

static int Max(int a, int b){
	return a > b ? a : b;
}

注意空树高度为-1,因为节点结构中维护了高度值,所以只要在insert和delete环节及时更新一下就能方便使用了。

(5)4种旋转操作,对照之前的图文描述很容易实现/理解


/* 
* Assume node: K1 < K2 < K3
*/

static AvlTree SingleRotateWithLeft(AvlTree K2){
	AvlTree K1 = K2->Left;
	K2->Left = K1->Right;
	K1->Right = K2;
	
	K2->Height = Max(Height(K2->Left), Height(K2->Right)) + 1;
	K1->Height = Max(Height(K1->Left), K2->Height) + 1;
	
	return K1;
}

static AvlTree SingleRotateWithRight(AvlTree K1){
	AvlTree K2 = K1->Right;
    K1->Right = K2->Left;
    K2->Left = K1;

    K1->Height = Max(Height(K1->Left), Height(K1->Right)) + 1;
    K2->Height = Max(Height(K2->Right), K1->Height) + 1; // Max(Height(K2->Left), Height(K2->Right)) + 1; 

    return K2;
}

static AvlTree DoubleRotateWithLeft(AvlTree K3){
	K3->Left = 	SingleRotateWithRight(K3->Left);
	
	return SingleRotateWithLeft(K3);
}

static AvlTree DoubleRotateWithRight(AvlTree K1){
	K1->Right = SingleRotateWithLeft(K1->Right);
	
	return SingleRotateWithRight(K1);
}

(6)插入操作,在原有BST的insert基础上修改,即插入成功后及时平衡;注意最后一步需要维护根节点的高度(这步带有非常明显的递归思路==>左+右+中)。

AvlTree Insert(ElementType X, AvlTree T){
	if(T == NULL){
		T = malloc(sizeof(struct AvlNode));
		if(T == NULL){
			printf("Create AVL Tree ERROR\n");
			exit(0);
		}
		
		T->Element = X;
		T->Height = 0;
		T->Left = T->Right = NULL;
	} else if(X < T->Element){
		T->Left = Insert(X, T->Left);
		
		if(Height(T->Left) - Height(T->Right) == 2){ // or > 1
			if(X < T->Left->Element) // or Height(T->Left->Left) > Height(T->Left->Right)
				return SingleRotateWithLeft(T);
			else
				return DoubleRotateWithLeft(T);
		}
	} else if(X > T->Element){
		T->Right = Insert(X, T->Right);
		
		if(Height(T->Right) - Height(T->Left) == 2){
			if(X > T->Right->Element) // or Height(T->Right->Left) < Height(T->Right->Right)
				return SingleRotateWithRight(T);
			else
				return DoubleRotateWithRight(T);
		}
	}
	
	T->Height = Max(Height(T->Left), Height(T->Right)) + 1;
	return T;
}

(7)同理,在原来BST的delete基础上完善平衡操作,注意要避开删掉叶子节点后的边界情况。

AvlTree Delete(ElementType X, AvlTree T){
	if(T == NULL){
		printf("Tree is null, delete fail\n");
		return NULL;
	}
	
	if(X < T->Element){
	    T->Left = Delete(X, T->Left);
	    if(Height(T->Right) - Height(T->Left) == 2){
	    	if(Height(T->Right->Left) > Height(T->Right->Right))
	    		return DoubleRotateWithRight(T);
	    	else
				return SingleRotateWithRight(T);
		}
	} else if(X > T->Element){
		T->Right = Delete(X, T->Right);
		if(Height(T->Left) - Height(T->Right) == 2){
			if(Height(T->Left->Right) > Height(T->Left->Left))
				return DoubleRotateWithLeft(T);
			else
				return SingleRotateWithLeft(T);
		}
	} else {
		AvlTree TmpCell;
		if(T->Left && T->Right){
			TmpCell = FindMin(T->Right);
			T->Element = TmpCell->Element;
			T->Right = Delete(T->Element, T->Right);
		} else {
			TmpCell = T;
			if(T->Left == NULL) // conbime leaf node case: Tl=TR=NULL
				T = T->Right;
			else if(T->Right == NULL)
				T = T->Left;
			free(TmpCell);
		}
	}
	if(T != NULL) // case: leaf node has been deleted!!!
		T->Height = Max(Height(T->Left), Height(T->Right)) + 1;
	return T;
}

(8)测试代码和运行结果

#include <stdio.h>
#include <stdlib.h>
#include "AvlTree.h"

int main(int argc, char *argv[]) {
	AvlTree T;
    AvlTree P;
    int i;
    int j = 0;

    T = MakeEmpty(NULL);
    for(i = 0; i < 50; i++, j = (j + 7) % 50 ){
		T = Insert(j, T);
		printf("after insert %d ==> root is %d, Height is %d || ", j, T->Element, T->Height);
		if(T->Left != NULL)
			printf("Tl = %d  ", T->Left->Element);
		else
			printf("Tl = NULL  ");
		if(T->Right != NULL)
			printf("Tr = %d  ", T->Right->Element);
		else
			printf("Tr = NULL");
		printf("\n");
	}
	
    for(i = 0; i < 50; i++)
        if((P = Find(i, T)) == NULL || Retrieve(P) != i)
            printf("Error at %d\n", i);
    
	printf("Min is %d, Max is %d\n", Retrieve(FindMin(T)), Retrieve(FindMax(T)));
    printf("Height is %d \n", T->Height);
    
    printf("\n\n\n===================================================\n\n");
    for(i = 1; i < 50; i += 2){
    	T = Delete(i,T);
    	printf("after delete %d ==> root is %d, Height is %d || ", i, T->Element, T->Height);
		if(T->Left != NULL)
			printf("Tl = %d  ", T->Left->Element);
		else
			printf("Tl = NULL  ");
		if(T->Right != NULL)
			printf("Tr = %d  ", T->Right->Element);
		else
			printf("Tr = NULL");
		printf("\n");
	}
	
    for(i = 0; i < 50; i += 2)
        if((P = Find( i, T )) == NULL || Retrieve(P) != i )
            printf( "Error at %d\n", i);
    for(i = 1; i < 50; i += 2)
        if((P = Find(i, T)) != NULL)
            printf("Error at %d\n", i);
    printf("Min is %d, Max is %d\n", Retrieve(FindMin(T)), Retrieve(FindMax(T)));
    printf("Height is %d \n", T->Height);
      
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5-8 使用平衡因子的实现(更加常见的实现方法)

核心思想:先照常BST操作,完成insert和delete之后统一做一次balance,如果符合条件就真的执行旋转,否则不动。

(1)ADT,基本没有变化

#ifndef _AVL_TREE_2
#define _AVL_TREE_2
typedef int ElementType ;
struct AvlNode {
	ElementType Element;
    struct AvlNode *Left;
    struct AvlNode *Right;
    int Height;
};

typedef struct AvlNode *AvlTree;

AvlTree MakeEmpty(AvlTree T);
AvlTree Find(ElementType X, AvlTree T);
AvlTree FindMin(AvlTree T);
AvlTree FindMax(AvlTree T);
AvlTree Insert(ElementType X, AvlTree T);
AvlTree Delete(ElementType X, AvlTree T);
ElementType Retrieve(AvlTree T);
#endif

(2)具体实现,关注一下balance操作的位置和各种旋转的判定;相对上一小节的实现思路更加简单,所以很多人使用这一版。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "AvlTree.h"

AvlTree MakeEmpty(AvlTree T){
	if(T != NULL){
		MakeEmpty(T->Left);
		MakeEmpty(T->Right);
		free(T);
	}
	return NULL;
}

AvlTree Find(ElementType X, AvlTree T){
	if(T == NULL)
		return NULL;
		
	if(X < T->Element)
		return Find(X, T->Left);
	else if(X > T->Element)
		return Find(X, T->Right);
	else
		return T;
}

AvlTree FindMin(AvlTree T){
	if(T == NULL)
		return NULL;

	return (T->Left == NULL) ? T : FindMin(T->Left);
}

AvlTree FindMax(AvlTree T){
	if(T == NULL)
		return NULL;
	
	return (T->Right == NULL) ? T : FindMax(T->Right);
}

ElementType Retrieve(AvlTree T){
	return T->Element;
}

static int Height(AvlTree T){
   return (T == NULL) ? -1 : T->Height;
}

static int Max(int a, int b){
	return a > b ? a : b;
}

static void resetHeight(AvlTree T){
	if(T != NULL)
		T->Height = Max(Height(T->Left), Height(T->Right)) + 1;
}

static int caculatelBF(AvlTree T){
	return (T == NULL) ? 0 : (Height(T->Left) - Height(T->Right));
}

static AvlTree SingleRotateWithLeft(AvlTree K2){ //LL
	AvlTree K1 = K2->Left;
	K2->Left = K1->Right;
	K1->Right = K2;

	resetHeight(K2);
	resetHeight(K1);
	return K1;
}

static AvlTree SingleRotateWithRight(AvlTree K1){ //RR
	AvlTree K2 = K1->Right;
    K1->Right = K2->Left;
    K2->Left = K1;
	
	resetHeight(K1);
	resetHeight(K2);
    return K2;
}

static AvlTree DoubleRotateWithLeft(AvlTree K3){  //LR
	K3->Left = 	SingleRotateWithRight(K3->Left);
	return SingleRotateWithLeft(K3);
}

static AvlTree DoubleRotateWithRight(AvlTree K1){ //RL
	K1->Right = SingleRotateWithLeft(K1->Right);
	return SingleRotateWithRight(K1);
}

static AvlTree doBalance(AvlTree T){
	if(T == NULL)
		return NULL;
		
	resetHeight(T);
	int BF = caculatelBF(T);
	if(BF > 1){
		if(caculatelBF(T->Left) > 0)
			T = SingleRotateWithLeft(T);  // LL
		else
			T = DoubleRotateWithLeft(T);  // LR
	} 
	
	if(BF < -1){
		if(caculatelBF(T->Right) < 0)
			T = SingleRotateWithRight(T); // RR
		else
			T = DoubleRotateWithRight(T); // RL
	}

	return T;
}

AvlTree Insert(ElementType X, AvlTree T){
	if(T == NULL){
		T = malloc(sizeof(struct AvlNode));
		if(T == NULL){
			printf("Create AVL Tree ERROR\n");
			exit(0);
		}
		T->Element = X;
		T->Height = 0;
		T->Left = T->Right = NULL;
	} else if(X < T->Element){
		T->Left = Insert(X, T->Left);	
	} else if(X > T->Element){
		T->Right = Insert(X, T->Right);		
	}

	return doBalance(T);
}

AvlTree Delete(ElementType X, AvlTree T){
	if(T == NULL){
		printf("Tree is null, delete fail\n");
		return NULL;
	}
	
	if(X < T->Element){
	    T->Left = Delete(X, T->Left);
	} else if(X > T->Element){
		T->Right = Delete(X, T->Right);
	} else {
		AvlTree TmpCell;
		if(T->Left && T->Right){
			TmpCell = FindMin(T->Right);
			T->Element = TmpCell->Element;
			T->Right = Delete(T->Element, T->Right);
		} else {
			TmpCell = T;
			if(T->Left == NULL){
				T = T->Right;
			} else if(T->Right == NULL){
				T = T->Left;
			}
			free(TmpCell);
		}
	}

	return doBalance(T);
}

(3)测试代码如下,测试结果就不再展示了

#include <stdio.h>
#include <stdlib.h>
#include "AvlTree.h"

int main(int argc, char *argv[]) {
    AvlTree T;
    AvlTree P;
    int i;
    int j = 0;

    T = MakeEmpty(NULL);
    for(i = 0; i < 50; i++, j = (j + 7) % 50 ){
		T = Insert(j, T);
		printf("after insert %d ==> root is %d, Height is %d || ", j, T->Element, T->Height);
		if(T->Left != NULL)
			printf("Tl = %d  ", T->Left->Element);
		else
			printf("Tl = NULL  ");
		if(T->Right != NULL)
			printf("Tr = %d  ", T->Right->Element);
		else
			printf("Tr = NULL");
		printf("\n");
	}
	
    for(i = 0; i < 50; i++)
        if((P = Find(i, T)) == NULL || Retrieve(P) != i)
            printf("Error at %d\n", i);
    
	printf("Min is %d, Max is %d\n", Retrieve(FindMin(T)), Retrieve(FindMax(T)));
    printf("Height is %d \n", T->Height);
    
    printf("\n\n\n===================================================\n\n");
    for(i = 1; i < 50; i += 2){
    	T = Delete(i,T);
    	printf("after delete %d ==> root is %d, Height is %d || ", i, T->Element, T->Height);
		if(T->Left != NULL)
			printf("Tl = %d  ", T->Left->Element);
		else
			printf("Tl = NULL  ");
		if(T->Right != NULL)
			printf("Tr = %d  ", T->Right->Element);
		else
			printf("Tr = NULL");
		printf("\n");
	}
	
    for(i = 0; i < 50; i += 2)
        if((P = Find( i, T )) == NULL || Retrieve(P) != i )
            printf( "Error at %d\n", i);
    for(i = 1; i < 50; i += 2)
        if((P = Find(i, T)) != NULL)
            printf("Error at %d\n", i);
    printf("Min is %d, Max is %d\n", Retrieve(FindMin(T)), Retrieve(FindMax(T)));
    printf("Height is %d \n", T->Height);
        
	return 0;
}

5-9 一些数学讨论

(1)推导:高度为h的AVL树的最少节点数 S ( h ) S(h) S(h)的递推式

从AVL树的构型出发,很容易得到高度为h的AVL树,与其左右两边字数在节点函数 S ( h ) S(h) S(h)的关系为:

S ( h ) = S ( h − 1 ) + S ( h − 2 ) + 1 S(h)=S(h-1) + S(h-2) + 1 S(h)=S(h1)+S(h2)+1

这个关系怎来的呢?考虑一般情况的AVL树:
1)AVL整棵树的平衡因子假设为1,那么左子树高度比右子树高度多1;
2)而整棵树的高度为h,除去根节点,那么左子树只能是h-1,右子树为h-2,;
3)综合以上两项,总结点数 = 左子树节点数 + 右子树节点数 + 1,此处的1就是根节点自己。

而节点数用函数 S ( h ) S(h) S(h)表达,则明显有以上递推关系。

同理,在平衡因子为-1时,也存在相同的递推式;若平衡因子为0,则左右子树都是h-1,退化成 S ( h ) = 2 S ( h − 1 ) + 1 S(h) = 2S(h-1) + 1 S(h)=2S(h1)+1

(2)能否得到 S ( h ) S(h) S(h)的具体解析式呢?

当然可以,零 T ( h ) = S ( h ) + 1 T(h)=S(h)+1 T(h)=S(h)+1,将上式两边同时加1,整理可得: T ( h ) = T ( h − 1 ) + T ( h − 2 ) T(h) = T(h-1) + T(h-2) T(h)=T(h1)+T(h2),这不就是斐波那契数列吗?

在《黑皮书》第1/2两章的参考文献中,已经给出方法推导斐波那契数列的通项式(高中数学竞赛小姥们已经学过),这里我们就再推导一遍,需要准备的母函数(也叫生成函数,Generating Function)等数学知识(见[12],[13],[14])。

定义成函数 G ( z ) = ∑ n = 0 ∞ F n z n G(z)=\sum_{n=0}^{ \infty}F_nz^n G(z)=n=0Fnzn F n F_n Fn为斐波那契数列的第 n n n项, F 0 = 0 F_0=0 F0=0 F 1 = 1 F_1=1 F1=1
考虑到递推关系 F n = F n − 1 + F n − 2 F_n=F_{n-1}+F_{n-2} Fn=Fn1+Fn2,可以尝试使用如下3个式子做变换:
G ( z ) = F 0 + F 1 z + F 2 z 2 + F 3 z 3 + F 4 z 4 + F 5 z 5 + . . . \begin{align*}G(z)=F_0+F_1z+F_2z^2+F_3z^3+F_4z^4+F_5z^5+...\end{align*} G(z)=F0+F1z+F2z2+F3z3+F4z4+F5z5+...
z G ( z ) = F 0 z + F 1 z 1 + F 2 z 3 + F 3 z 4 + F 4 z 5 + F 5 z 6 + . . . \begin{align*}zG(z)=F_0z+F_1z^1+F_2z^3+F_3z^4+F_4z^5+F_5z^6+...\end{align*} zG(z)=F0z+F1z1+F2z3+F3z4+F4z5+F5z6+...
z 2 G ( z ) = F 0 z 2 + F 1 z 3 + F 2 z 4 + F 3 z 5 + F 4 z 6 + F 5 z 7 + . . . \begin{align*}z^2G(z)=F_0z^2+F_1z^3+F_2z^4+F_3z^5+F_4z^6+F_5z^7+...\end{align*} z2G(z)=F0z2+F1z3+F2z4+F3z5+F4z6+F5z7+...
易有
( 1 − z − z 2 ) G ( z ) = F 0 + ( F 1 − F 0 ) z + ( F 2 − F 1 − F 0 ) z 2 + ( F 3 − F 2 − F 1 ) z 3 + ( F 4 − F 3 − F 2 ) z 4 + . . . \begin{align*}(1-z-z^2)G(z)=F_0+(F_1-F_0)z+(F_2-F_1-F_0)z^2+(F_3-F_2-F_1)z^3+(F_4-F_3-F_2)z^4+...\end{align*} (1zz2)G(z)=F0+(F1F0)z+(F2F1F0)z2+(F3F2F1)z3+(F4F3F2)z4+...
带入条件,等式右端仅留下 z z z系数项,则
G ( z ) = z / ( 1 − z − z 2 ) \begin{align*}G(z)=z/(1-z-z^2)\end{align*} G(z)=z/(1zz2)
求解方程 1 − z − z 2 = 0 1-z-z^2=0 1zz2=0,得到两根: ϕ = 1 2 ( 1 + 5 ) \phi= \frac{1}{2}(1+\sqrt{5}) ϕ=21(1+5 ) ϕ ^ = 1 2 ( 1 − 5 ) \hat{\phi}=\frac{1}{2}(1-\sqrt{5}) ϕ^=21(15 ),带入 G ( z ) G(z) G(z)拆解分式
G ( z ) = 1 5 ( 1 1 − ϕ z − 1 1 − ϕ ^ z ) \begin{align*}G(z)=\frac{1}{\sqrt{5}}(\frac{1}{1-\phi{z}}-\frac{1}{1-\hat{\phi}z})\end{align*} G(z)=5 1(1ϕz11ϕ^z1)
利用等比数列级数展开式
1 1 − z = 1 + z + z 2 + z 3 + z 4 + . . . . \begin{align*}\frac{1}{1-z}=1+z+z^2+z^3+z^4+....\end{align*} 1z1=1+z+z2+z3+z4+....
G ( z ) = 1 5 ( 1 + ϕ z + ϕ 2 z 2 + . . . − 1 − ϕ ^ z − ϕ 2 ^ z 2 − . . . ) \begin{align*}G(z)=\frac{1}{\sqrt{5}}(1+\phi{z}+\phi^2{z^2}+...-1-\hat{\phi}z-\hat{\phi^2}z^2-...)\end{align*} G(z)=5 1(1+ϕz+ϕ2z2+...1ϕ^zϕ2^z2...)
对比系数有
F n = 1 5 ( ϕ n − ϕ n ^ ) \begin{align*}F_n=\frac{1}{\sqrt{5}}(\phi^n-\hat{\phi^n})\end{align*} Fn=5 1(ϕnϕn^)
那么
S ( h ) = F h − 1 = 1 5 ( ϕ n − ϕ n ^ ) − 1 \begin{align*}S(h)=F_h-1=\frac{1}{\sqrt{5}}(\phi^n-\hat{\phi^n})-1\end{align*} S(h)=Fh1=5 1(ϕnϕn^)1

(3)接下来参考文献 [6] 、[7]可得到树高 h h h的渐进关系,其实也代表了AVL的查询深度
h = O ( l o g N ) \begin{align*}h=O(logN)\end{align*} h=O(logN)
这与BST的结论一致,其实AVL就是一种BST,此处我们演示了另一种思路而已。

参考文献

[1] (北大)“数据结构与算法”教学大纲
[2] 平衡二叉树 —— 如何优雅的进行旋转(我和中意的一个有意思的博主,哈哈哈哈)
[3] 数据结构与算法分析学习笔记(二)–AVL树的算法思路整理
[4] 彻底搞懂AVL树
[5] 一文搞懂AVL树详解
[6] AVL tree 高度上下界推导
[7] AVL树的树高上下界极清晰推导
【注】相比之前BST的推导,这是很简单的啦
[8] 《算法4》
[9] 《黑皮书》
[10] 《计算机程序设计艺术》
[11] 清华大学,邓俊辉老师《数据结构》==> 这个讲的真的挺好的,点到为止,留足空间自学,但总能点中问题关键。
[12] 母函数——生成函数——形式级数
[13] 组合数学3 母函数
[14] 浅讲母函数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/289080.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【RocketMQ每日一问】RocketMQ SQL92过滤用法以及原理?

1.生产端 public class SQLProducer {public static int count 10;public static String topic "xiao-zou-topic";public static void main(String[] args) {DefaultMQProducer producer MQUtils.createLocalProducer();IntStream.range(0, count).forEach(i -&g…

管程-第三十五天

目录 为什么要引入管程 管程的定义和基本特征 用管程解决生产者消费者问题 结论 本节思维导图 为什么要引入管程 原因&#xff1a;在解决进程的同步与互斥问题时&#xff0c;信号量机制存在编写困难和易出错的问题 能不能设计一种机制&#xff0c;让程序员写程序时不再需…

从 YOLOv1 到 YOLO-NAS 的所有 YOLO 模型:论文解析

在计算机视觉的浩瀚领域&#xff0c;有一支耀眼的明星&#xff0c;她的名字传颂着革新与突破的传奇——YOLO&#xff08;You Only Look Once&#xff09;。回溯时光&#xff0c;走进这个引人注目的名字背后&#xff0c;我们仿佛穿越进一幅画卷&#xff0c;一幅展现创新魅力与技…

【elfboard linux开发板】4. 文件点灯与创建多进程

ps&#xff1a;提升效率的小tips&#xff1a; 灵活运用vim操作命令&#xff0c;gg快速跳转到文件开头&#xff0c;G跳转到结尾 多行操作 ctrl V shift i 插入修改内容 esc退出编辑 sudo vi /etc/vim/vimrc 在文件中添加如下内容省略重复工作&#xff1a; autocmd BufNewFile …

飞腾Ubantu22.04.3安装OpenNebula测试

1.概述 因OpenneBula官方镜像源只有AMD架构的镜像包不存在ARM的镜像包&#xff0c;借此用源码编译进行测试。 2.官网github地址 下载解压存放在服务器上&#xff1a; https://github.com/OpenNebula/minione/blob/master文件目录&#xff1a; 3.安装依赖包 sudo apt -y …

智慧农田使用的自动虫情测报灯的作用

TH-CQ3S随着科技的不断进步&#xff0c;智慧农业正在全球范围内兴起。作为智慧农业的重要组成部分&#xff0c;智慧农田已经成为提高农业生产效率、保障农产品质量安全的重要手段。而在智慧农田中&#xff0c;自动虫情测报灯的作用不可忽视。 自动虫情测报灯&#xff0c;顾名思…

腾讯云轻量服务器2核2G4M带宽118元一年,3年540「新老用户均可」

它急了&#xff0c;腾讯云急了&#xff0c;继阿里云推出99元新老用户同享的云服务器后&#xff0c;腾讯云轻量应用服务器2核2G4M配置也支持新老用户同享了&#xff0c;一年118元&#xff0c;3年540元&#xff0c;老用户也能买&#xff0c;50GB SSD系统盘&#xff0c;300GB 月流…

数据分析中常见的问题之一:怎么用SPSS来读取Stata数据文件?

我们以本书附带的“数据1F”为例进行读取Stata数据文件的讲解。“数据1F”是一个Stata数据文件&#xff0c;如图所示。 首先启动SPSS软件或者在一个已经打开的SPSS数据文件的数据视图中从菜单栏选择“文件| 打开 | 数据”命令&#xff0c;如图所示。 然后就会出现如图1.77所示的…

php安装扩展event 提示 No package ‘openssl‘ found 解决方法

在使用pecl编译安装最新版event模块的时候提示 No package openssl found , 可是本机是安装了openssl的, 编译时找不到, 大概率就是环境配置的问题了, 增加 OPENSSL_CFLAGS OPENSSL_LIBS环境变量即可解决. 异常提示信息: checking for openssl > 1.0.2... no configure: …

基于SSM的网络游戏交易平台设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

线程的深入学习(二)

前言 上一篇讲了线程池的相关知识&#xff0c;这篇文章主要讲解一个 1.并发工具类如CountDownLatch、CyclicBarrier等。 2.线程安全和并发集合&#xff1a; 3.学习如何使用Java提供的线程安全的集合类&#xff0c;如ConcurrentHashMap、CopyOnWriteArrayList等。 并发工具类 …

2023-12-11 LeetCode每日一题(最小体力消耗路径)

2023-12-11每日一题 一、题目编号 1631. 最小体力消耗路径二、题目链接 点击跳转到题目位置 三、题目描述 你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights &#xff0c;其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格…

免费部署私人 ChatGPT的项目:LobeChat 14K+

前言 随着ChatGPT的快速风靡&#xff0c;所有人都对AI高度关注&#xff0c;那么你想不想部署一个属于自己的私人ChatGPT&#xff0c;用更美观&#xff0c;更高效&#xff0c;更好玩的方式来体验AI呢&#xff1f; 今天我们推荐的就是可以帮你实现在本地部署私人ChatGPT&#x…

深度学习框架解读—Yolov5/Yolov7/Halcon对比分析

作为一名机器视觉深度学习算法工程师&#xff0c;我从技术实现、性能、适用场景和易用性等方面来评价YOLOv5、YOLOv7和Halcon中的深度学习框架。以YOLOv5和YOLOv7进行比较&#xff0c;并结合Halcon的深度学习功能进行综合评价。 Yolov5 优点&#xff1a; 1. 速度快&#xff1a…

Consul

简介 Consul是一套开源的分布式服务发现和配置管理系统&#xff0c;由HashiCorp 公司用Go语言开发。 提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用&#xff0c;也可以一起使用以构建全方位的服务网格&#xff0c;总之…

Tomcat Notes: Deployment File

This is a personal study notes of Apache Tomcat. Below are main reference material. - YouTube Apache Tomcat Full Tutorial&#xff0c;owed by Alpha Brains Courses. https://www.youtube.com/watch?vrElJIPRw5iM&t801s 1、Tomcat deployment1.1、Two modes of …

Python 热力图的绘制(Matplotlib篇-12)

Python 热力图的绘制(Matplotlib篇-12)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ�…

NLP论文阅读记录 - 2021 | SimCLS:抽象概括对比学习的简单框架

文章目录 前言0、论文摘要一、Introduction1.1目标问题1.2相关的尝试1.3本文贡献 二.相关工作2.1优势 三.本文方法——抽象概括的对比学习框架3.1 第一阶段&#xff1a;候选生成3.2 第二阶段&#xff1a;无参考评估3.3对比训练 四 实验效果4.1数据集4.2 对比模型4.3实施细节4.4…

工作中redis相关知识总结

这里写目录标题 一、Redis数据持久化概念二、redis数据类型三、redis缓存的应用流程四、什么样的数据适合存放到redis中&#xff1f;1、什么情况下&#xff0c;redis中会没有数据&#xff1f;2、redis缓存项目在测试中的注意事项a、更新缓存b、淘汰缓存 五、什么是缓存击穿1、缓…

AUTOSAR中 CAN总线数据通过COM模块收发流程

目录 AUTOSAR中CAN总线数据通过COM模块收发流程1、AUTOSAR中 CAN总线数据通过COM模块发送流程2、AUTOSAR中 CAN总线数据通过COM模块接收流程 AUTOSAR中CAN总线数据通过COM模块收发流程 printf("欢迎关注公众号&#xff1a;车载嵌入式探索者&#xff0c;博主建立了一个车规…
最新文章