第九章 数据结构专题 —— 树(中)
9.4 二叉查找树(BST)
9.4.1 BST的递归定义:
1)要么二叉查找树是一颗空数
2)要么二叉查找树由根节点、左子树、右子树组成,其中左子树和右子树都是二叉查找树,且左子树上所有结点的数据域均小于或等于根节点的数据域,右子树上所有结点的数据域均大于根结点的数据域。
9.4.2 BST的基本操作
查找操作
void search(node* root,int x){
if(root==NULL){
printf("search failed");
}
if(x==root->data){
printf("%d\n",root->data);
}else if(x<root->data){ //如果x比根结点的数据域小,说明x在左子树
search(root->lchild,x);
}else{ //如果x比根结点的数据域大,说明x在右子树
search(root->rchild,x);
}
}
插入操作
void insert(node* &root,int x){
if(root==NULL){ //空树,说明查找失败,即插入位置
root==newNode(x);
return;
}
if(x==root->data){ //查找成功,说明结点已存在,直接返回
return;
}else if(x<root->data){
insert(root->lchild,x);
}else{
insert(root->rchild,x);
}
}
二叉树的建立,但是同一组相同的数字,如果插入顺序不同,最后生成的二叉树也可能不同
node* create(int data[],int n){
node* root = NULL;
for(int i=0;i<n;i++){
insert(root,data[i]);
}
}
二叉查找树的删除
把以二叉查找树中比结点权值小的最大结点称为该结点的前驱,而把比结点权值大的最小结点称为该结点的后继。显然,结点的前驱是该结点左子树的最右结点,右子树同理
//寻找以root为根节点的树中的最大权值
node* findMax(node* root){
while(root->rchild!=NULL){
root=root->rchild;
}
return root;
}
//寻找以root为根结点的树种的最小权值结点
node* findMin(node* root){
while(root->lchild!=NULL){
root=root->lchild;
}
return root;
}
//假设决定用结点N的前驱P来替换N,于是就把问题转化为在N的左子树中删除结点P,就可以递归下去了
//直到递归到一个叶子结点,就可以直接把他删除了
void deleteNode(node* &root,int x){
if(root==NULL) return;
if(root->data == x){
if(root->lchild==NULL&&root->rchild==NULL){
root=NULL; //叶子结点直接删除,把root地址设为NULL,父结点就找不到它了
}else if(root->lchild != NULL){
node* pre = findMax(root->lchild); //找root前驱
root->data = pre->data; //用前驱覆盖root
deleteNode(root->lchild,pre->data);
}else{
node* next = findMin(root->rchild); //找root后继
root->data = next->data; //用后继覆盖root
deleteNode(root->rchild,next->data); //在右子树中删除结点next
}
}else if(root->data > x){
deleteNode(root->lchild,x);
}else{
deleteNode(root->rchild,x);
}
}
9.4.3 二叉查找树的性质
对于二叉查找树进行中序遍历,遍历的结果是有序的
9.5 平衡二叉树(AVL)
9.5.1 平衡二叉树的定义
对AVL树的任意结点来说,其左子树和右子树的高度之差的绝对值不超过1,其中左右子树的高度之差称为该结点的平衡因子
struct node{
int v,height; //v为结点权值,height为当前子树高度
node *lchild,*rchild;
};
node* newNode(int v){
node* Node = new node;
Node->v = v;
Node->height = 1; //结点初始高度为1
Node->lchild=Node->rchild = NULL;
return Node;
}
//获取以root为根结点的子树当前height
int getHeight(node* root){
if(root==NULL) return 0;
return root->height;
}
//计算结点root的平衡因子
int getBalanceFactor(node* root){
return getHeight(root->lchild) - getHeight(root->rchild);
}
//更新结点root的height
void updateHeight(node* root){
root->height = max(getHeight(root->lchild),getHeight(root->rchild))+1;
}
9.5.2 平衡二叉树的基本操作
查找操作,时间复杂度为O(logn)
void search(node* root,int x){
if(root==NULL){
cout<<"search failed"<<endl;
return;
}
if(x==root->data){
cout<<root->data<<endl; //查找成功,访问
}else if(x<root->data){
search(root->lchild,x);
}else{
search(root->rchild,x);
}
}
插入操作
void insert(node* &root,int v){
if(root == NULL){
root = newNode(v);
return;
}
if(v < root->data){ //v比根结点的权值小
insert(root->lchild,v); // 往左子树插入
updateHeight(root); // 更新树高
if(getBalanceFactor(root) == 2){
if(getBalanceFactor(root->lchild) == 1){ //LL型
R(root);
}else if(getBalanceFactor(root->lchild) == -1){ //LR型
L(root->lchild);
R(root);
}
}
}else{ //v比根结点的权值大
insert(root->rchild,v);
updateHeight(root); //更新树高
if(getBalanceFactor(root) == -2){
if(getBalanceFactor(root->rchild) == -1){ //RR型
L(root);
}else if(getBalanceFactor(root->rchild) == 1){ //RL型
R(root->rchild);
L(root);
}
}
}
}
AVL树的建立
node* create(int data[],int n){
node* root = NULL;
for(int i=0;i<n;i++){
insert(root,data[i]);
}
return root;
}
9.6 并查集
并查集(Union Find Set),用一个数组 int father[N] ,father[i]表示元素i的父结点,例如father[1]=2就表示元素1的父结点是元素2,以这种父亲关系来表示元素所属的集合。另外,如果father[i] == i,则说明元素i是该集合的根节点,但对同一个集合来说只存在一个根结点,且将其作为所属集合的标识。
9.6.1 并查集的操作
初始化
//1.初始化,一开始,每个元素都是独立的一个集合,因此需要令所有father[i]等于i
for(int i=1;i<=N;i++){
father[i]=i;
}
查找
由于规定同一个集合中只存在一个根结点,因此查找操作就是对给定的结点寻找其根结点的过程。可以用递归或者递推实现
//递推,返回元素x所在集合的根结点
int findFather(int x){
while(x != father[x]){
x = father[x];
}
return x;
}
//递归
int find_father(int x){
if(x == father[x]) return x; //如果找到根结点,则返回根结点编号x
else return find_father(father[x]); //否则,递归判断x的父亲结点是否是根结点
}
合并
合并是指把两个集合合并成一个集合,题目中一般给出两个元素,要求把这两个元素所在的集合合并。
void Union(int a,int b){
int faA = findFather(a);
int faB = findFather(b);
if(faA!=faB){ //如果不属于一个集合
father[faA] = faB; //合并他们
}
}
在合并过程中,只对两个不同的集合进行合并,如果两个元素在相同的集合中,那么就不会对它们进行操作。这就保证了在同一个集合中一定不会产生环,即并查集产生的每一个集合都是一颗树
9.6.2 路径压缩
把当前查询结点的路径上的所有结点的父亲都指向根结点,查找的时候就不需要一直回溯去找父亲了,查询的复杂度可以降为O(1)
步骤:1.按原先写法获得x的根结点r。 2.重新从x开始走一遍寻找根结点的过程,把路径上经过的所有结点的父亲全部改为根结点r
//压缩路径
int find_Father(int x){
int a = x; //由于x在下面的while中会变成根结点,因此先把原先的x保存一下
while(x != father[x]){
x = father[x];
}
//到这里,x存放的是根结点。下面把路径上的所有结点的father改成根结点
while(a != father[a]){
int z = a; //因为a要被father[a]覆盖,所以先保存a的值,以修改father[a]
a = father[a]; //a回溯父亲结点
father[z] = x; //将原先的结点a的父亲改为根结点
}
return x; //返回根结点
}
//压缩路径,递归
int Find_father(int x){
if(x == father[x]) return x; //找到根结点
else{
int F = Find_father(father[x]); //递归寻找father[x]的根结点F
father[x] = F; //将根节点F赋给father[x]
return F; //返回根结点F
}
}