AVL树 c语言版本 插入部分

目录

引入平衡树

为什么要变平衡

怎么判断是否需要变平衡

怎么变平衡

LL型失衡

RR型失衡

LR型失衡

RL型失衡

补充

左旋补充

右旋补充

Code

开辟一个新节点

 初始化

获取树的高度

左旋函数

更新树高

树高的求法

右旋转函数

插入

InsertNode()

更新树高

getbalance()

根据balance判断是否失衡

RL型失衡​编辑如图(f-1)所示:

剩下的LR型失衡,RR型失衡,LL型失衡都同理,写为为如下这个样子:

测试函数

perOrder()

midOrder()

Find()

测试

代码托管


引入平衡树

假设我们有两个节点:AVL树 c语言版本 插入部分_第1张图片当我们插入第三个节点,就失衡了:AVL树 c语言版本 插入部分_第2张图片此刻我们就要把它平衡一下。

为什么要变平衡

为什么说它失衡了呢,又为什么要把它变平衡?

如图a,假设我们要查找30这个节点就要查3次才能找到

但是如果平衡之后(图b)就只需要2次就可以找到了,这样可以提高效率,这两个图不够明显,如果是100个节点的图就够明显了。AVL树 c语言版本 插入部分_第3张图片

怎么判断是否需要变平衡

我们引入 平衡因子的概念

平衡因子 = 根节点的 左孩子高度 -  右孩子高度

当 平衡因子的绝对值 > 1 时,我们认为这个树是失衡的,需要变为平衡。

如图(c-1):AVL树 c语言版本 插入部分_第4张图片

 根节点( 10 ) 的左孩子高度(0) - 右孩子高度(2)= 平衡因子 (-2)

| -2 | > 1,即该数树为失衡的树。

怎么变平衡

首先,假如有这样一个二叉搜索树:

LL型失衡

AVL树 c语言版本 插入部分_第5张图片我们应该怎么平衡呢?

首先,因为根节点最小,所以把根节点变到2的右边,这也符合二叉搜索的特性,即:小了往左走

AVL树 c语言版本 插入部分_第6张图片这个时候20就成了新的根节点,而原先的根节点10就成了现在根节点(20)的左孩子节点。

因此对于这种一边倒的二叉搜索树,并且是向左倒的

AVL树 c语言版本 插入部分_第7张图片

 也就是要往左边分点节点,我们才能保持它的平衡,

对于LL型失衡,我们可以总结有如下 左旋定理:

RR型失衡

同理,有往左边倒的就有往右边倒的,如下图:AVL树 c语言版本 插入部分_第8张图片

 因为40比30大的缘故,我们可以让40到30的右边去,这样就平衡了,并且符合二叉搜索树的特性,即:大了就往右边走:

AVL树 c语言版本 插入部分_第9张图片此时30就是新节点,40就为30的右孩子。

这种往右边一边倒的失衡,我们称之为RR型失衡。

对此,我们可以总结如下右旋定理:

LR型失衡

因为root节点的左孩子的右节点造成的失衡就叫LR型失衡,如图 (b-1):

AVL树 c语言版本 插入部分_第10张图片

因为是由于右节点造成的失衡,所以我们就让右节点到左节点去,可以用左旋定理

变为图 (b-2):AVL树 c语言版本 插入部分_第11张图片

 图(b-2)我们看它的平衡因子,大于1,所以还要再变。

又因为图(b-2)是RR型失衡,所以套用  右旋定理

变为图(b-3):AVL树 c语言版本 插入部分_第12张图片

此时平衡因子<1,是AVL树,因此不再变。

流程图:AVL树 c语言版本 插入部分_第13张图片

RL型失衡

如图(a-1),因为root节点的右孩子的左节点造成的失衡就叫RL型失衡:

AVL树 c语言版本 插入部分_第14张图片对于LR型失衡,因为是左节点造成的失衡,所以要先采用右旋定理把右节点变到左边去:

变为图(a-2):

AVL树 c语言版本 插入部分_第15张图片此时平衡因子为 -2 ,仍然不平衡还需要再变

此时因为向左倾斜,即要向左部分分点,这样才平衡要调用  左旋定理

变为图 (a-3):AVL树 c语言版本 插入部分_第16张图片此时平衡因子 <1,即不用再变。

流程图:AVL树 c语言版本 插入部分_第17张图片

因此,我们可以只有总结:

补充

左旋补充

有如下图:AVL树 c语言版本 插入部分_第18张图片

 此时应该先左旋,然后再按二叉搜索树的特性再排

旧根节点10比  新根节点原先的左子树 19小,所以19应该排到10右边:AVL树 c语言版本 插入部分_第19张图片

因此,左旋定理可以得到补充,如下:AVL树 c语言版本 插入部分_第20张图片

右旋补充

有图(d-1):AVL树 c语言版本 插入部分_第21张图片此时应该先右旋,再按二叉搜索树特性排:

AVL树 c语言版本 插入部分_第22张图片 因此,右旋定理可以得到如下补充:AVL树 c语言版本 插入部分_第23张图片

Code

首先写一下树的结构

val left right是必须的,又新加了一个height,用来求平衡因子用:

typedef  struct Node
{
	int val;//数据域
	int height; //树高
	struct Node* left;
	struct Node* right;
}Node;

开辟一个新节点


Node* newnode = (Node*)malloc(sizeof(Node));

 初始化

node->val=val;
node->height = 1;
node->left = NULL;
node->right = NULL;

 开辟和初始化我们都封装起来:

Node* newnode(int val)
{
Node* node = (Node*)malloc(sizeof(Node));
node->val=val;
node->height = 1;  //只有一个节点时,树高为1
node->left = NULL;
node->right = NULL;

retur node;
}

获取树的高度

如果树高度为空,返回0

否则就把树的高度返回。

//获取树的高度
int getHeight(Node* node)
{
	if (node->height == NULL)
		return 0;
	else
		return node->height;
}

接下来写左旋函数,右旋函数

左旋函数

左旋函数先按左旋定理写:

int LeftRtate(Node* root)
{
	//新节点为旧节点的右子树
	Node* newroot = root->right;

	//T2时新根节点的左子树
	Node* T2 = newroot->left;

}

 开始旋转:




开始旋转:
	//旧根节点变为新根节点的左子树
	newroot->left=root;

	//如果新根节点原来有右子树,那么新根节点原来右子树变为旧节点的右子树
	root->right = T2;

更新树高

经过左旋之后root和newroot的高度都变了,我们需要更新一下。

怎么更改呢?首先需要先求树高。

树高的求法

拿图(b-1)举例:AVL树 c语言版本 插入部分_第24张图片

 图(b-1)的树高为3,其中,左子树的树高为2,右子树的树高为0。

我们取左右子树最大的树高+1就是整个树的树高:

int max(int A,int B)
{
	if (A > B ? A : B);

}
int LeftRtate(Node* root)
{
	//新节点为旧节点的右子树
	Node* newroot = root->right;

	//T2时新根节点的左子树
	Node* T2 = newroot->left;

	//旧根节点变为新根节点的左子树
	newroot->left=root;

	//如果新根节点原来有右子树,那么新根节点原来右子树变为旧节点的右子树
	root->right = T2;

	//树高
 root->height = 1+max(getHeight(root->left),getHeight(root->right));
 newroot->height = 1+max(getHeight(newroot->left), getHeight(newroot->right));

 return newroot;//返回新根节点

右旋转函数

同样的道理,按照右旋定理就可以了:AVL树 c语言版本 插入部分_第25张图片

Node* RightRatate(Node* root)
{
	//新根节点为原先旧节点的左子树
	Node* newroot = root->left;

	//新根节点的右子树
	Node* T2 = newroot->right;

	//开始旋转
	//旧根节点变为新根节点的右子树
	newroot->right = root;

	//3.如果新根节点原来有右子树,那么新根节点的右子树就会作为旧根节点的左子树
	root->left = T2;
	//树高
	root->height = 1 + max(getHeight(root->left), getHeight(root->right));
	newroot->height = 1 + max(getHeight(newroot->left), getHeight(newroot->right));

	return newroot;
}

接下来就是重头戏

插入

首先按照搜二叉的方式插入,

如果key值比当前节点值大就插在当前节点右边,否则就插在当前节点的左边。

插入完之后可能会造成失衡。

怎么判断失衡呢?根据平衡因子。

然后再去按照时哪种失衡,进行对应的调整。

欧克,一步一步来,首先插入

InsertNode()

//插入
Node* InsertNode(Node* node,int key)
{
	
	

}

如果当前节点为空,那么就调用我们的newnode()函数,malloc了一个节点并且对节点进行初始化


Node* InsertNode(Node* node,int key)
{
	
	if (node==NULL)return newnode(key);

}

如果key值比当前节点值大就插在当前节点右边,否则就插在当前节点的左边:

Node* InsertNode(Node* node,int key)
{
	if (node==NULL)return newnode(key);
    
	if (key > node->val)(node->left = InsertNode(node->right, key));
	if (key < node->val)(node->left = InsertNode(node->left, key));
	
}

接下来就是用平衡因子判断插入之后是否失衡了。

写一个求平衡因子的函数:

但是在此之前需要先更新一下树高,因为平衡因子是根据树高求出来的。

更新树高

树高的求法:左右子树树高最大的那一个+1。

Node* InsertNode(Node* node,int key)
{
	if (node==NULL)return newnode(key);
    
	if (key > node->val)(node->left = InsertNode(node->right, key));
	if (key < node->val)(node->left = InsertNode(node->left, key));
	
	//更新树高
	//因为root改变了,所以它俩的树高需要更新
	node->height = 1 + max(getHeight(node->left), getHeight(node->right));
}

getbalance()

平衡因子的求法在刚开始就说过,就是左子树的树高-右子树的树高。


int getbalance(Node* node)
{
	return getHeight(node->left)- getHeight(node->right);

}

根据balance判断是否失衡

 拿RL型失衡举例:

RL型失衡AVL树 c语言版本 插入部分_第26张图片如图(f-1)所示:

根节点的 balance(平衡因子) =-2   根节点的右子树的balance=1

此时为RL型失衡。

balance<-1 && getbalance(node->right) < 0

RL失衡了就按照我们之前写的那个平衡方法就平衡一下:

先右旋,再左旋

   node->right=RightRatate(node->right);  即node链接右边的节点,让要旋转的节点旋到右边
		return LeftRtate(node);

剩下的LR型失衡,RR型失衡,LL型失衡都同理,写为为如下这个样子:

if (balance< -1 && getbalance(node->right)>0)
	{
		//RL失衡
		//需要先右旋,再左旋
       node->right=RightRatate(node->right);
		return LeftRtate(node);
		
	}
	if (balance >1 && getbalance(node->left) <0)
	{
		//LR失衡
		//需要先左旋旋,再右旋
	
		node->left=LeftRtate(node->left);	
		return RightRatate(node);
	}
	if (balance<-1 && getbalance(node->right) < 0)
	{
		//RR型失衡
		//右旋
		return LeftRtate(node);
	}
	if (balance>1 && getbalance(node->right) >0)
	{
		//LL失衡
		//左旋
		return RightRatate(node);
	}

最后把根节点返回就可以了。


	return node;

至此,Insert()就全部写完了,总结为如下:

//插入
Node* InsertNode(Node* node,int key)
{
	if (node==NULL)return newnode(key);
    
	if (key > node->val)
	{
		node->right = InsertNode(node->right, key);
	}
	else if (key < node->val)
	{
		node->left = InsertNode(node->left, key);
	}
	else
	{
		return node;
	}
	
	//更新树高
	//因为root改变了,所以它俩的树高需要更新
	node->height = 1 + max(getHeight(node->left), getHeight(node->right));

	//根据balance判断是否失衡

	int balance = getbalance(node);

	if (balance< -1 && getbalance(node->right)>0)
	{
		//RL失衡
		//需要先右旋,再左旋
       node->right=RightRatate(node->right);
		return LeftRtate(node);
		
	}
	if (balance >1 && getbalance(node->left) <0)
	{
		//LR失衡
		//需要先左旋旋,再右旋
	
		node->left=LeftRtate(node->left);	
		return RightRatate(node);
	}
	if (balance<-1 && getbalance(node->right) < 0)
	{
		//RR型失衡
		//右旋
		return LeftRtate(node);
	}
	if (balance>1 && getbalance(node->right) >0)
	{
		//LL失衡
		//左旋
		return RightRatate(node);
	}

	return node;
}

然后我们来写测试函数

测试函数

我们可以用前序和后续就可以打印看看是否出错了,先写前序

perOrder()


void perOrder(Node* root)
{
	if (root == NULL)return;
	printf("%d ", root->val);
	perOrder(root->left);
	perOrder(root->right);

	
}

midOrder()

void midOrder(Node* root)
{
	if (root == NULL)return;
	midOrder(root->left);
	printf("%d ",root->val);
	midOrder(root->right);
	
}

Find()

find有三个参数

写一个根节点,把根节点赋值给当前节点,让当前节点去遍历。一个count参数统计查找次数。

key为要查找的值。

注意,count类型为int*而不是Int.

因为我们想让count的改变影响我们外面的实参值。

Node* Find(Node* root,int* count,int key)
{

}

如果要查找的值比当前节点小,就往左走,否则如果比当前节点大,就往右走,如果不大不小那就说明要找的就是当前节点,把当前节点return。

如果找了一圈都没有找到,即当前节点跌代到空节点都找不到,那就返回NULL。

注意不是返回false,因为我们的Find()函数还要统计次数的功能,因此不是并没有给它一个bool类型。

Node* Find(Node* root,int* count,int key)
{
	Node* cur = root;
	while (cur)
	{
		if (key> cur->val)
		{
			cur = cur->right;
			(*count)++;
		}
		else if (key > cur->val)
		{
			cur = cur->left;
			(*count)++;
		}
		else
		{
			return cur;
		}
		
	}
	return NULL;
}

测试


void test()
{
	Node* root = NULL;
	root= InsertNode(root,  10);
	root = InsertNode(root, 20);
	root = InsertNode(root, 30);
	root = InsertNode(root, 40);
	root = InsertNode(root, 50);
	root = InsertNode(root, 60);
	root = InsertNode(root, 70);
	

	int count = 0;
	int key = 70;
	Node* result=Find(root, &count, key);
	printf("要找的值是:%d\n",key);
	printf("找了%d 次\n",count);

	printf("前序遍历:_______ _ __  _\n");
	perOrder(root);
	printf("\n中序遍历:_______ _ __  _\n");
	midOrder(root);
}

报错:AVL树 c语言版本 插入部分_第27张图片这是因为我们应该判断node==NULL,而不该去判断node->height;

当node为空时,node->height就是空指针指向height就会报错。

改为如下图:运行:

我们根据前序和中序打印的结果,可以画出这样一个二叉树:

AVL树 c语言版本 插入部分_第28张图片

 这个二叉树是一个平衡二叉树,即AVL树,并且找7这个节点找3次就能找到。

而我们打印出来的要找两次是因为我们根节点那次没算,当cur=node也让count++就可以了。

代码托管

AVLTreeinsert.c · 




 

你可能感兴趣的:(c++)