学习笔记_二叉搜索树与平衡二叉树

二叉搜索树(BST)

定义

  • 二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树,又称为排序二叉树、二叉排序树。二叉搜索树的递归定义如下:
  1. 要么一棵二叉搜索树是一棵空树。
  2. 要么二叉查找树由根节点、左子树(可空)、右子树(可空)组成,其中左子树和右子树都是二叉查找树,且左子树上的所有结点的数据域均小于根节点,右子树上的所有结点的数据域均大于根节点。
  • 性质:根据定义,可以知道二叉搜索树的中序遍历序列是一个有序的序列。

二叉搜索树的基本操作

二叉搜索树的基本操作有查找、插入、删除。建树操作可以看做依次插入所有结点。
数的数据结构定义如下:

struct node{
	int val;	//数据域,以int型为例
	node* left;
	node* right;
};

查找

查找的根为root,需要查找的值是x,步骤如下:

  1. 如果结点为空,则查找失败。
  2. 如果结点的值root->val等于x,说明查找成功,返回。
  3. 如果结点的值root->val大于x,则递归查找左子树root->left。
  4. 如果结点的值root->val小于x,则递归查找右子树root->right。
void search(node* root,int x){
	if(!root){
		printf("查找失败\n");
		return;
	}
	if(root->val == x)
		printf("%d查找成功\n",root->val);
	else if(root->val > x)
		search(root->left,x);
	else
		search(root->right,x);
	return;
}

插入

插入结点和查询结点的步骤类似,只不过是要在结点为空的位置插入一个数据域x的结点。

void insert(node*& root,int x){
	if(!root){	//插入 
		root = new node();
		root->val = x;
		return;
	}
	if(root->val == x)
		printf("值为%d的结点已存在\n",x);
	else if(root->val > x)
		insert(root->left,x);
	else
		insert(root->right,x);
	return;
}

删除

当需要删除结点时,如下图。将会变成两棵树,则需要找到一个新的根节点合并这两棵树。有两种方案。
在这里插入图片描述
方案一:找到一个比待删除结点(137)大的最小结点(即后继结点),作为新的根节点。

node* findmax(node* root){
	if(root->right)
		root = root->right;
	return root;
}

方案二:找到一个比待删除结点(137)小的最大结点(即前驱结点),作为新的根节点。

node* findmin(node* root){
	if(root->left)
		root = root->left;
	return root;
}

综上,当需要删除的是叶子结点时,可以直接删除,如果要删除的结点有左孩子,则通过方案一找到前驱结点,代替根节点。之后需要把左子树的对应的前驱结点(肯定是叶子结点)给删除掉,同理,如果左子树不存在的话就从右子树中找到后继结点代替根节点,在把右子树中的后继结点给删除掉。

void deletenode(node*& root,int x){
	if(!root) return;	//不存在值为x的结点
	if(root->val==x){
		if(root->left==NULL&&root->right==NULL){
			delete root;	//叶子结点直接删除
			root=NULL;
		}
		else if(root->left!=NULL){
			node* pre = findmax(root);
			root->val = pre->val;
			deletenode(root->left,pre->val);
		}else{
			node* next = findmin(root);
			root->val = next->val;
			deletenode(root->right,next->val);
		}
	}else if(root->val>x){
		deletenode(root->left,x);
	}else{
		deletenode(root->right,x);
	}
	return;
}

注意:如果需要删除的结点只有左子树或者只有右子树,那么直接把结点删除,根节点指向左孩子/右孩子是也没问题的。

平衡二叉树(AVL)

在上面的二叉搜索树中,如果结点变成了一条链,那么进行各种操作的复杂度将会退化成O(N),显示不是我们需要的。因此,需要对树的结构进行调整,使得每次操作后仍然能保持O(logN)的级。

定义

AVL树是一棵二叉查找树,所谓平衡,是指左右子树的高度之差的绝对值不大于1。其中左子树与右子树的高度之差称为平衡因子

数据结构

为了得到平衡因子,因此需要在数据结构体加入height记录以当且结点为根节点时,子树的高度。

struct node{
	int val;	//数据域,以int型为例
	int height;	//当前子树的高度,只有一个结点时为1 
	node* left;
	node* right;
};

创建一个新结点时,使用如下写法:

node* newnode(int v){
	node* root = new node;
	root->val = v;
	root->height = 1;
	root->left=root->right = NULL;
	return root;
}

有个子树的高度,就可以得到每个结点的平衡因子,如下:

int getheight(node* root){	//获得root结点的高度 
	if(!root) return 0;
	else return root->height;
}

int getbalancefactor(node* root){
	//左子树高度减右子树
	return getheight(root->left)-getheight(root->right);
}

更新结点的height的函数如下:

void updateheight(node* root){
	root->height = max(getheight(root->left),getheight(root->right)) + 1;
	return;
}

AVL树的基本操作

查找操作过于简单,略

插入操作

在进行插入操作之前,先了解一下二叉树的左旋和右旋。

左旋

见下图,即根节点A的右孩子B代替A成为根节点,需要进行左旋。
图片来自《算法笔记》
根据上图可以很容易的得到左旋的代码(记得更新height),如下:

void L(node*& root){	//左旋 
	node* rs = root->right;	//rs:root的右孩 
	root->right = rs->left;
	rs->left = root;
	updateheight(root);	//此时root是rs的左孩子,因此要先更新root 
	updateheight(rs);
	root = rs;
	return;
}
右旋

同理,右旋是让root的左边孩子代替root成为根节点,和右旋相反,可以得到如下代码.

void R(node*& root){	//右旋
	node* ls = root->left;
	root->left = ls->right;
	ls->right = root;
	updateheight(root);	//此时root是ls的右孩子,因此要先更新root 
	updateheight(ls);
	root = ls;
	return;
}
左右旋的意义

观察上面的左旋示意图,可以发现左旋以后A结点和B结点的高度(加或减1了!)

插入结点分析

当插入一个结点时,不满足了AVL树的定义,则需要对树进行调整,此时距离插入结点的不符合定义结点的平衡因子一定是2或者-2(因为插入结点只可能使得高度差加或者减1)。则该结点的插入结点的那个孩子(比如在左子树插入结点就是指左孩子),的平衡因子一定是1或-1(因为如果是0那么最大深度没有发生变化,就不会让父结点的平衡因子变成2),最终可以分情况进行调整。
算法笔记
在这里插入图片描述
当A结点平衡因子是2时,见上图,

  1. 对于左边情况1(LL型)只需要对结点A进行右旋。
  2. 对于情况2(LR)型,先对C进行做旋,再对A进行右旋即可。

如果A结点的平衡因子是-2时,对应RL型,RR型两种情况。都可以通过左右旋解决。
算法笔记

插入操作代码
void insert(node*& root,int x){
	if(!root){	//插入 
		root = newnode(x);
		return;
	}
	if(root->val == x)
		printf("值为%d的结点已存在\n",x);
	else if(root->val > x){	//往左子树插 
		insert(root->left,x);
		updateheight(root);
		if(getbalancefactor(root->left)==2){
			if(getbalancefactor(root->left)==1){	//LL型 
				R(root);
			}
			else if(getbalancefactor(root->left)==-1){	//LR型 
				L(root->left);
				R(root); 
			}
		}
	}
	else{	//往右子树插 
		insert(root->right,x);
		updateheight(root);
		if(getbalancefactor(root->right)==-2){
			if(getbalancefactor(root->right)==-1){	//RR型 
				L(root);
			}
			else if(getbalancefactor(root->right) == 1){	//RL型 
				R(root->right);
				L(root); 
			}
		}
	}
	return;
}

删除操作过于复杂,先略

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值