您的位置:首页 > 理论基础 > 数据结构算法

数据结构--树(中)平衡树、搜索树、判断搜索树是否相同

2020-02-29 19:12 423 查看

二叉搜索树

什么是二叉搜索树?
一颗二叉树,可以为空,如果不为空,满足以下性质:

  • 非空左子树的所有键值小于其根结点的键值
  • 非空右子树的所有键值大于其根结点的键值
  • 左右子树都是二叉搜索树

二叉搜索树操作的特别函数

  • 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
递归写法:

  1. 判断是否为空,空则代表未找到
  2. 判断当前结点的大小关系,大则从右子树找,小则从左子树找,相等说明找到了。
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

  1. 树的最左边即为最小值,因为任何一个结点的右子树存在都会比该结点大,最大值同理
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

  1. 首先先按上面查找的方式找到合适插入元素的位置
  2. 递归到待插入结点,因为没有元素,所以是空,所以会进入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;
}

哈哈,这么投机取巧的方法当然不是做这个题目的目的啦,还是规规矩矩的建个树判断吧,锻炼下建树能力!

解法二:建树判断

  1. 建树,只需建第一颗树 ,每个结点多了一个flag ,用于后面判断树是否相同
  2. 判断,每输入一个元素都在模板树中进行一次搜索,如果如果每次搜索所经过的结点在前面均出现过,则一致,否则(某次搜索中遇到前面未出现的结点),则不一致。
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;
}
  • 点赞
  • 收藏
  • 分享
  • 文章举报
代码噜噜噜 发布了17 篇原创文章 · 获赞 1 · 访问量 341 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐