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

二叉搜索树

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

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

在这里插入图片描述

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

  • 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;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值