数据结构--树(中)平衡树、搜索树、判断搜索树是否相同
二叉搜索树
什么是二叉搜索树?
一颗二叉树,可以为空,如果不为空,满足以下性质:
- 非空左子树的所有键值小于其根结点的键值
- 非空右子树的所有键值大于其根结点的键值
- 左右子树都是二叉搜索树
二叉搜索树操作的特别函数
- Position Find( ElementType X, BinTree BST ):从二叉搜索树BST中查找元素X,返回其所在结点的地址;
- Position FindMin( BinTree BST ):从二叉搜索树BST中查找并返回最小元素所在结点的地址;
- Position FindMax( BinTree BST ) :从二叉搜索树BST中查找并返回最大元素所在结点的地址。
- BinTree Insert( ElementType X, BinTree BST )
- BinTree Delete( ElementType X, BinTree BST )
下面对这些函数的功能进行实现!
1.Find
递归写法:
- 判断是否为空,空则代表未找到
- 判断当前结点的大小关系,大则从右子树找,小则从左子树找,相等说明找到了。
BinTree Find(ElementType x, BinTree BST) { if (!BST) //未找到 { return NULL; } if (x < BST->date) //小 查找左子树 { return Find(x, BST->left); } else if (x > BST->date)//大 查找右子树 { return Find(x, BST->right); } else // 相等则找到 return BST; }
递归效率较低,尾递归可以用循环实现
BinTree IterFind(ElementType x, BinTree BST) //尾递归函数可改为迭代函数 { while (BST) { if (BST->date < x) BST = BST->left; else if (BST->date > x) BST = BST->right; else return BST; } return NULL; }
2.FindMax/Min
- 树的最左边即为最小值,因为任何一个结点的右子树存在都会比该结点大,最大值同理
BinTree FindMin(BinTree BST) { if (!BST) return NULL; // 空树 if (BST->left != NULL) return FindMin(BST->left); else return BST; } BinTree FindMax(BinTree BST) { if (!BST) return NULL; // 空树 if (BST->right != NULL) return FindMax(BST->right); else return BST; }
3.Insert
- 首先先按上面查找的方式找到合适插入元素的位置
- 递归到待插入结点,因为没有元素,所以是空,所以会进入if (!BST) 语句中,分配空间,赋值,最后一定要把结点返回回去。
BinTree Insert(ElementType x, BinTree BST) { if (!BST)//空 插入 { BST = (BinTree)malloc(sizeof(Tree)); BST->date = x; BST->left = BST->right = NULL; } else { if (x < BST->date) BST->left = Insert(x, BST->left); //小 插入左子树 else BST->right = Insert(x, BST->right);//大 插入右子树 //相等则不操作 } return BST; }
4.Delete(难点)
- 首先找到待删除的结点 ,未找到则返回空
- 找到结点后又下面几种情况
1.当前为子叶结点,则直接把当前结点的空间释放即可
2.当前结点左子树为空,右子树不为空,则需要把当前结点先用一个变量保存起来,然后把当前位置赋值为当前位置的右子树,最后将待删除的结点空间释放,反过来的处理方法类似
3.当左右子树都非空时,找到右子树的最小值和待删除的结点值替换,然后再把找到的那个最小值删掉。理由:当前结点的右子树的最小值和当前节点替换,仍然保持左小右大的性质,最重要的时,右子树的最小值一定是是叶子结点或者只有左子树,这就转化成上面那种情况的删除方法了。(寻找左子树的最大值也可以)
BinTree Delete(ElementType x, BinTree BST) { if (!BST) return NULL;// 未找到 else if (BST->date > x) BST->left = Delete(x, BST->left); else if (BST->date < x) BST->right =Delete(x, BST->right); else //找到待删除结点,分类处理 { if (BST->left && BST->right) //左右子树都非空的情况 { BinTree tem = FindMin(BST->right); //寻找右子树的最小值 BST->date = tem->date;//替换 BST->right = Delete(BST->date, BST->right);//删除替换的结点 } else // 叶子结点或者只有左子树或只有右子树 { BinTree temp = BST; if (!BST->left) //只有右子树 BST = BST->right; else if (!BST->right)//只有左子树 BST = BST->left; //叶子结点直接删除即可 free(temp); } } return BST; }
5.InTraverse
这里采用先序遍历,方法上篇博客讲过这里不再解释
void InTraverse(BinTree BST) // 中序遍历 { if (!BST) return; InTraverse(BST->left); cout << BST->date << " "; InTraverse(BST->right); }
总代码:
#include<iostream> #include <stdlib.h> #include <string.h> #include <string> using namespace std; #define ElementType int #define Maxn 10000 typedef struct node { ElementType date; struct node* left; struct node* right; }*BinTree, Tree; BinTree Find(ElementType x, BinTree BST) { if (!BST) //未找到 { return NULL; } if (x < BST->date) //小 查找左子树 { return Find(x, BST->left); } else if (x > BST->date)//大 查找右子树 { return Find(x, BST->right); } else // 相等则找到 return BST; } BinTree IterFind(ElementType x, BinTree BST) //尾递归函数可改为迭代函数 { while (BST) { if (BST->date < x) BST = BST->left; else if (BST->date > x) BST = BST->right; else return BST; } return NULL; } BinTree FindMin(BinTree BST) { if (!BST) return NULL; // 空树 if (BST->left != NULL) return FindMin(BST->left); else return BST; } BinTree FindMax(BinTree BST) { if (!BST) return NULL; // 空树 if (BST->right != NULL) return FindMax(BST->right); else return BST; } BinTree Insert(ElementType x, BinTree BST) { if (!BST)//空 插入 { BST = (BinTree)malloc(sizeof(Tree)); BST->date = x; BST->left = BST->right = NULL; } else { if (x < BST->date) BST->left = Insert(x, BST->left); //小 插入左子树 else BST->right = Insert(x, BST->right);//大 插入右子树 //相等则不操作 } return BST; }BinTree Delete(ElementType x, BinTree BST) { if (!BST) return NULL;// 未找到 else if (BST->date > x) BST->left = Delete(x, BST->left); else if (BST->date < x) BST->right =Delete(x, BST->right); else { if (BST->left && BST->right) { BinTree tem = FindMin(BST->right); BST->date = tem->date; BST->right = Delete(BST->date, BST->right); } else { BinTree temp = BST; if (!BST->left) BST = BST->right; else if (!BST->right) BST = BST->left; free(temp); } } return BST; } void InTraverse(BinTree BST) // 中序遍历 { if (!BST) return; InTraverse(BST->left); cout << BST->date << " "; InTraverse(BST->right); }int main() { BinTree root =NULL; for (int i = 1; i < 10; i++) root = Insert(i, root); InTraverse(root); cout <<endl; BinTree item = FindMax(root); cout <<"最大:" <<item->date <<endl; item = FindMin(root); cout <<"最小:"<<item->date<<endl; Delete(2,root); InTraverse(root); cout <<endl; item = Find(5,root); cout <<"查找5的结果:"<< item->date ; Insert(-5,root); InTraverse(root); cout <<endl; return 0; }
我们来观察下上面我们画的树:
上次博客树的介绍中可以知道,树的深度等于搜索的次数,如果我们要查找9,需要查找9次,这和顺序查找的效率相同,原因很显然,这颗树左右不平衡是导致搜索次数增加的原因,因此下面介绍一种平衡树,平衡树可以很好的避免这个问题。
搜索树结点不同插入次序,将导致不同的深度和平均查找长度ASL
不同的结构的搜索树,ASL的值会不同,树左右越平衡,平均搜索次数越小,搜索效率越高。
平衡树二叉树
平衡因子:BF(T) = Hl - Hr,为左右子树高度的差
平衡二叉树(AVL树)
空树,或者任一结点左、右子树高度差的绝对值不超过1,即|BF(T)|<=1
给定结点数为n的AVL树的最大高为O(log2n)也就是说搜索次数最大为log2n。
平衡二叉树的调整
一共有下面四种调整方法:
RR旋转
插入后,A结点不平衡,不平衡的地方位于右子树的右子树,因而叫右右插入,需要RR旋转(右单旋)个人理解是按着右手旋转。
AVLTree SingleRightRotation ( AVLTree A ) {/* 注意:A必须有一个右子结点B */ /* 将A与B做右单旋,更新A与B的高度,返回新的根结点B */ AVLTree B = A->Right; A->Right = B->Left; B->Left = A ; A->Height = Max(GetHeight(A->Left) , GetHeight(A->Right))+1; B->Right = Max(A->Height , GetHeight(B->Right))+1; return B ; }
LL旋转
理解了RR旋转,LL旋转是一个道理的。
插入后,A结点不平衡,不平衡的地方位于左子树的左子树,因而叫左左插入,需要LL旋转(左单旋)
AVLTree SingleLeftRotation ( AVLTree A ) { /* 注意:A必须有一个左子结点B */ /* 将A与B做左单旋,更新A与B的高度,返回新的根结点B */ AVLTree B = A->Left; A->Left = B->Right; B->Right = A; A->Height = Max( GetHeight(A->Left), GetHeight(A->Right) ) + 1; B->Height = Max( GetHeight(B->Left), A->Height ) + 1; return B; }
LR旋转
插入后,A结点不平衡,导致不平衡的位置位于A左子树的右子树上,因而叫LR插入,需要LR旋转。
LR旋转方法:
1.从不平衡结点 以及该结点的左结点和左节点的右结点中找一个中间值(左节点的右结点:Mar)
2.然后将中间值作为其余两个结点的父节点
注意观察Cl /Cr挂的方式 , Cl和Cr只会存在一种,只是挂在左边和右边旋转后挂的地方也会不同
AVLTree DoubleLeftRightRotation ( AVLTree A ) //LR旋转 { /* 注意:A必须有一个左子结点B,且B必须有一个右子结点C */ /* 将A、B与C做两次单旋,返回新的根结点C */ /* 将B与C做右单旋,C被返回 */ A->Left = SingleRightRotation(A->Left); /* 将A与C做左单旋,C被返回 */ return SingleLeftRotation(A); }
RL旋转
RL则和LR类似
AVLTree DoubleRightLeftRotation ( AVLTree A ) //LR旋转 { /* 注意:A必须有一个左子结点B,且B必须有一个右子结点C */ /* 将A、B与C做两次单旋,返回新的根结点C */ /* 将B与C做右单旋,C被返回 */ A->Right = SingleLeftRotation(A->Right); /* 将A与C做左单旋,C被返回 */ return SingleRightRotation(A); }
元素插入
AVLTree Insert( AVLTree T, ElementType X ) { /* 将X插入AVL树T中,并且返回调整后的AVL树 */ if ( !T ) { /* 若插入空树,则新建包含一个结点的树 */ T = (AVLTree)malloc(sizeof(struct AVLNode)); T->Data = X; T->Height = 0; T->Left = T->Right = NULL; } /* if (插入空树) 结束 */ else if ( X < T->Data ) { /* 插入T的左子树 */ T->Left = Insert( T->Left, X); /* 如果需要左旋 */ if ( GetHeight(T->Left)-GetHeight(T->Right) == 2 ) if ( X < T->Left->Data ) T = SingleLeftRotation(T); /* 左单旋 */ else T = DoubleLeftRightRotation(T); /* 左-右双旋 */ } /* else if (插入左子树) 结束 */ else if ( X > T->Data ) { /* 插入T的右子树 */ T->Right = Insert( T->Right, X ); /* 如果需要右旋 */ if ( GetHeight(T->Left)-GetHeight(T->Right) == -2 ) if ( X > T->Right->Data ) T = SingleRightRotation(T); /* 右单旋 */ else T = DoubleRightLeftRotation(T); /* 右-左双旋 */ } /* else if (插入右子树) 结束 */ /* else X == T->Data,无须插入 */ /* 别忘了更新树高 */ T->Height = Max( GetHeight(T->Left), GetHeight(T->Right) ) + 1; return T; }
代码:
#include<iostream> #include <stdlib.h> #include <string> #include <string.h> using namespace std; #define ElementType int typedef struct AVLNode* Position; typedef Position AVLTree; /* AVL树类型 */ AVLTree Root = NULL; struct AVLNode { ElementType Data; /* 结点数据 */ AVLTree Left; /* 指向左子树 */ AVLTree Right; /* 指向右子树 */ int Height; /* 树高 */ }; int Max(int a, int b) { return a > b ? a : b; } int GetHeight(AVLTree A) { if (!A) return 0; return Max(GetHeight(A->Left),GetHeight(A->Right))+1; } AVLTree SingleLeftRotation(AVLTree A) { /* 注意:A必须有一个左子结点B */ /* 将A与B做左单旋,更新A与B的高度,返回新的根结点B */ AVLTree B = A->Left; A->Left = B->Right; B->Right = A; A->Height = Max(GetHeight(A->Left), GetHeight(A->Right)) + 1; B->Height = Max(GetHeight(B->Left), A->Height) + 1; return B; } AVLTree SingleRightRotation(AVLTree A) {/* 注意:A必须有一个右子结点B */ /* 将A与B做右单旋,更新A与B的高度,返回新的根结点B */ AVLTree B = A->Right; A->Right = B->Left; B->Left = A; A->Height = Max(GetHeight(A->Left), GetHeight(A->Right)) + 1; B->Height = Max(A->Height, GetHeight(B->Right)) + 1; return B; } AVLTree DoubleLeftRightRotation(AVLTree A) //LR旋转 { /* 注意:A必须有一个左子结点B,且B必须有一个右子结点C */ /* 将A、B与C做两次单旋,返回新的根结点C */ /* 将B与C做右单旋,C被返回 */ A->Left = SingleRightRotation(A->Left); /* 将A与C做左单旋,C被返回 */ return SingleLeftRotation(A); } AVLTree DoubleRightLeftRotation(AVLTree A) //LR旋转 { /* 注意:A必须有一个左子结点B,且B必须有一个右子结点C */ /* 将A、B与C做两次单旋,返回新的根结点C */ /* 将B与C做右单旋,C被返回 */ A->Right = SingleLeftRotation(A->Right); /* 将A与C做左单旋,C被返回 */ return SingleRightRotation(A); } AVLTree Insert(AVLTree &T, ElementType X) { /* 将X插入AVL树T中,并且返回调整后的AVL树 */ if (!T) { /* 若插入空树,则新建包含一个结点的树 */ T = (AVLTree)malloc(sizeof(struct AVLNode)); T->Data = X; T->Height = 0; T->Left = T->Right = NULL; } /* if (插入空树) 结束 */ else if (X < T->Data) { /* 插入T的左子树 */ T->Left = Insert(T->Left, X); /* 如果需要左旋 */ if (GetHeight(T->Left) - GetHeight(T->Right) == 2) if (X < T->Left->Data) T = SingleLeftRotation(T); /* 左单旋 */ else T = DoubleLeftRightRotation(T); /* 左-右双旋 */ } /* else if (插入左子树) 结束 */ else if (X > T->Data) { /* 插入T的右子树 */ T->Right = Insert(T->Right, X); /* 如果需要右旋 */ if (GetHeight(T->Left) - GetHeight(T->Right) == -2) if (X > T->Right->Data) T = SingleRightRotation(T); /* 右单旋 */ else T = DoubleRightLeftRotation(T); /* 右-左双旋 */ } /* else if (插入右子树) 结束 */ /* else X == T->Data,无须插入 */ /* 别忘了更新树高 */ T->Height = Max(GetHeight(T->Left), GetHeight(T->Right)) + 1; return T; } void preTraverse(AVLTree T) { if (!T) return; cout << "数据:" << T->Data << " 高度:" << T->Height << " ||"; preTraverse(T->Left); preTraverse(T->Right); } void inTraverse(AVLTree T) { if (!T) return; inTraverse(T->Left); cout << "数据:" << T->Data << " 高度:" << T->Height << " ||"; inTraverse(T->Right); } int main() { for (int i = 0; i < 10; i++) { Insert(Root, i); } cout << "先序:"; preTraverse(Root); cout << endl; cout << "中序:"; inTraverse(Root); cout << endl; }
这是该程序生成的AVL树。
习题:是否同一棵二叉搜索树
题目:
给定一个插入序列就可以唯一确定一棵二叉搜索树。然而,一棵给定的二叉搜索树却可以由多种不同的插入序列得到。例如分别按照序列{2, 1, 3}和{2, 3, 1}插入初始为空的二叉搜索树,都得到一样的结果。于是对于输入的各种插入序列,你需要判断它们是否能生成一样的二叉搜索树。
输入格式:
输入包含若干组测试数据。每组数据的第1行给出两个正整数N (≤10)和L,分别是每个序列插入元素的个数和需要检查的序列个数。第2行给出N个以空格分隔的正整数,作为初始插入序列。最后L行,每行给出N个插入的元素,属于L个需要检查的序列。
简单起见,我们保证每个插入序列都是1到N的一个排列。当读到N为0时,标志输入结束,这组数据不要处理。
输出格式:
对每一组需要检查的序列,如果其生成的二叉搜索树跟对应的初始序列生成的一样,输出“Yes”,否则输出“No”。
输入样例:
4 2
3 1 4 2
3 4 1 2
3 2 4 1
2 1
2 1
1 2
0
输出样例:
Yes
No
No
一个序列对应唯一一颗判断树,但是一颗树不一定对应一个序列!
解法一:不建树判别方法
思路:
只需要同过比对数字,就可以判断是否相同,我是用递归的方法进行判断的。
#include <iostream> #include <algorithm> using namespace std; bool cmp(int T1[], int T2[], int n) { if (n == 0) //n==0 没元素 返回true return true; if (T1[0] != T2[0]) //首元素不同 则树不同 return false; if (T1[0] == T2[0] && n == 1) //首元素相同,且只有一个元素,则相同 return true; int T1L[100], T1R[100], T2L[100], T2R[100]; int L1 = 0, R1 = 0, L2 = 0, R2 = 0; for (int i = 1; i < n; i++) // 分别获取每个树的左子树和右子树 { if (T1[i] > T1[0]) T1R[R1++] = T1[i]; else T1L[L1++] = T1[i]; } for (int i = 1; i < n; i++) { if (T2[i] > T2[0]) T2R[R2++] = T2[i]; else T2L[L2++] = T2[i]; } if (L1 != L2 || R1 != R2)//如果长度不同则子树肯定不同返回false return false; return cmp(T1L, T2L, L1) && cmp(T1R, T2R, R1);//递归子树进行比较 } int main() { int n, l; int T1[100], T2[100]; while (cin >> n && n) { cin >> l; for (int i = 0; i < n; i++) cin >> T1[i]; while (l--) { for (int i = 0; i < n; i++) cin >> T2[i]; if (cmp(T1, T2, n)) cout << "Yes" << endl; else cout << "No" << endl; } } return 0; }
哈哈,这么投机取巧的方法当然不是做这个题目的目的啦,还是规规矩矩的建个树判断吧,锻炼下建树能力!
解法二:建树判断
- 建树,只需建第一颗树 ,每个结点多了一个flag ,用于后面判断树是否相同
- 判断,每输入一个元素都在模板树中进行一次搜索,如果如果每次搜索所经过的结点在前面均出现过,则一致,否则(某次搜索中遇到前面未出现的结点),则不一致。
bool Find(int date, Tree& T) { //flag ==0 代表未访问, ==1代表访问过 if (!T) //未找到 false return false; else if (T->date == date) //找到了 true { T->flag = 1; //设为访问 return true; } else if (T->date != date && T->flag == 0) //不相等 且未访问 false return false; else { if (T->date > date) //继续往下找 return Find(date, T->left); else return Find(date, T->right); } } void check(Tree T, int n) { clean(T); //将每个flag 都先清0 int flag = 0; //标记树是否相同 int date; while (n--) { cin >> date; if (!flag && !Find(date, T)) //如果判断出树不同,则无需比较了,但还是要继续循环,因为数据还没输入完 { flag = 1; } } if (!flag) cout << "Yes" << endl; else cout << "No" << endl; }
解题代码:
#include <iostream> #include <algorithm> using namespace std; typedef struct BiTree { int date; int flag; struct BiTree* left; struct BiTree* right; }*Tree, Treenode; Tree Insert(int date, Tree& T) { if (!T) { T = (Tree)malloc(sizeof(Treenode)); T->date = date; T->left = T->right = NULL; } else { if (T->date > date) { T->left = Insert(date, T->left); } else if (T->date < date) { T->right = Insert(date, T->right); } } return T; } void clean(Tree& T) { if (!T) return; T->flag = 0; clean(T->left); clean(T->right); } bool Find(int date, Tree& T) { if (!T) return false; else if (T->date == date) { T->flag = 1; return true; } else if (T->date != date && T->flag == 0) return false; else { if (T->date > date) return Find(date, T->left); else return Find(date, T->right); } } void check(Tree T, int n) { clean(T); int flag = 0; int date; while (n--) { cin >> date; if (!flag && !Find(date, T)) { flag = 1; } } if (!flag) cout << "Yes" << endl; else cout << "No" << endl; } int main() { int n, date; while (cin >> n && n) { Tree Root =NULL ; int l; cin >> l; for (int i = 0; i < n; i++) { cin >> date; Insert(date, Root); } while (l--) { check(Root,n); } } return 0; }
- 点赞
- 收藏
- 分享
- 文章举报
- 【判断两棵二叉排序树是否相同】数据结构实验之查找一:二叉排序树
- 数据结构——二叉树入门(二)判断两颗二叉树是否相同
- IOS开发之判断两个数组中数据是否相同实例详解
- C++算法之 求二叉树中叶子节点的个数 与 判断两棵二叉树是否结构相同
- js数组判断提交数据是否存在相同数据
- 数据结构课程设计(C语言)——判断两序列是否为同一二叉搜索树
- 数据结构—Java语言判断单链表是否有环与寻找结环节点
- 数据结构——算法之(005)(判断字符串是否是对称的即(回文字符串))
- 数据结构实验之图论十:判断给定图是否存在合法拓扑序列
- 数据结构实验之图论十:判断给定图是否存在合法拓扑序列
- 判断两个集合中是否存在相同的数据并去重
- 判断一个字符串(超过80个字符)是否是回文结构(正序和逆序相同)
- js使用数组判断提交数据是否存在相同数据
- 看数据结构写代码(44) 判断无向图是否有环路
- 两个二叉树结构是否相同(结构和数据都相同) -- 递归和非递归方法
- 数据结构之链表(c语言实现),判断两个非空单向链表是否有公共结点,是,返回结点,否则返回NULL。
- 数据结构实验之图论十:判断给定图是否存在合法拓扑序列
- 数据结构——判断单链表是否有环
- 数据结构实验之图论十:判断给定图是否存在合法拓扑序列
- C++数据结构实验2.3 判断左右括号是否匹配