树的典型应用
二叉搜索树
静态查找:所查找集合的元素时不动的,在一个集合上主要做的便是find操作,而没有delete一个映射的操作
方法:二分查找,将要查找的数据事先的有序化
动态查找:所查找对象会发生变化,经常要发生插入,删除操作
书上的任意的一个结点他的值比左子树的值都要来的大,比右子树的值都要小,查找过程便成为了对当前结点的判断。
二叉搜索树(BST)—二叉排序树/二叉查找树
- 一颗二叉树,可以为空,如果不为空需满足
- 非空左子树的所有键值小于其根结点的键值
- 非空右子树的所有键值大于其根结点的键值
- 左,右子树都是二叉搜索树
操作函数
- 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),删除
查找操作
- 查找从根结点开始,如果树为空,返回NULL;
- 若搜索树非空,则根结点关键字和X进行比较,并进行不同处理
- 若X小于根结点键值,只需在左子树中继续搜索
- 若X大于根节点的键值,在右子树中进行继续搜索
- 若两者比较结果是相等,搜索完成,返回指向此结点的指针
//尾递归
bintree find(ELementtype x, bintree bst)
{
if (!bst)
return NULL;//查找从根结点开始,如果树为空,返回NULL;
if (x > bst->data)
{
return find(x, bst->right);//若X大于根节点的键值,在右子树中进行继续搜索
}
if (x < bst->data)
{
return find(x, bst->left);//若X小于根结点键值,只需在左子树中继续搜索
}
else
return bst;//若两者比较结果是相等,搜索完成,返回指向此结点的指针
}
由于非递归函数的执行效率高,可将尾递归函数改为迭代函数
//递归实现版
bintree iterfind(ELementtype x, bintree bst)
{
while (bst)
{
if (x > bst->data)
{
bst=bst->right;//若X大于根节点的键值,在右子树中进行继续搜索
}
if (x < bst->data)
{
bst = bst->left;//若X小于根结点键值,只需在左子树中继续搜索
}
else
return bst;//若两者比较结果是相等,搜索完成,返回指向此结点的指针
}
return NULL;//查找从根结点开始,如果树为空,返回NULL;
}
查找的效率决定于树的高度
查找最大和最小元素
- 最大元素一定是在树的最右分支的端结点上
- 最小元素一定是在树的最左分支的端结点上
//递归
bintree findmin(bintree bst)
{
if (!bst)
return NULL;
else if (!bst->left)
return bst;//一直查找有没有左节点,左结点如果没有代表就是最小结点所在的位置
else
return findmin(bst->left);
}
//循环
bintree findmax(bintree bst)
{
if (bst)
{
while (bst->right)
{
bst = bst->right;
}
}
return bst;
}
二叉搜索树插入
分析:关键是要找到元素应该插入的位置
bintree insert(ELementtype x, bintree bst)
{//若原树为空,生成并返回一个结点的二叉搜索树
if (!bst)
{
bst =(bintree)malloc(sizeof(struct treenode));
bst->data = x;
bst->left = bst->right = NULL;
}
else {//开始查找要插入元素的位置
if (x < bst->data)
{
bst->left = insert(x, bst->left);
}
else if (x > bst->data)
{
bst->right = insert(x, bst->right);
}
//当找到了便将x赋给新建的空结点中
}
return bst;
}
二叉搜索树的删除
- 要删除的是叶结点,直接删除,再修改其父结点指针,置为NULL
- 要删除的结点只有一个孩子结点:将其父结点的指针指向要删除结点的孩子结点
- 要删除的结点有左右两颗子树,用另一结点替代被删除结点,右子树的最小元素或则左子树的最大元素,原因,无论左子树的最小值或者右子树的最小值一定不会有两个结点
bintree deletetree(ELementtype x, bintree bst)
{
bintree tmp;
if (!bst)
{
cout << "we can`t finf the num" << endl;
}
else if (x > bst->data)
{
bst->right = deletetree(x, bst->right);//左子树递归删除
}
else if (x < bst->data)
{
bst->left = deletetree(x, bst->left);//右子树递归删除
}
else
{
if (bst->left && bst->right)//左右两边都不空
{
tmp = findmin(bst->right);//找到右子树的最小值进行替代
bst->data = tmp->data;
bst->right = deletetree(bst->data, bst->right);
}
else
{//被删除结点有一个或无子结点
tmp = bst;
if (!bst->left)//有右子树或无子结点
{
bst = bst->right;
}
else if (!bst->right)//有左子树或无子结点
{
bst = bst->right;
}
free(tmp);
}
}
return bst;
}
平衡二叉树
搜索树结点的不同插入次序,会导致不同的深度和平均查找长度ASL不同
左右两边结点数差不多,左右两边的高度差不多
- 平衡因子(BF),BF(T)=hL-hR
- hL,hR分别是T的左右子树的高度
平衡二叉树(AVL树)
任一结点的左右子树高度差的绝对值不超过1,即|BF(T)|<=1
高度为h的最小结点数=高度为h-1时最小结点数+高度为h-2时最小结点数+1
类似于斐波那契数列,nh=f(h+2)-1
给定结点数为n的AVL树的最大高度为O(log2(n))
平衡二叉树的调整
右旋
无论如何调整,都必须保证搜索树的框架能够成立
RR旋转,RR插入,右单旋
LL旋转,LL插入,左单旋
LR旋转,LR插入,左右旋转
RL旋转,RL插入
怎么判别:便是看插入结点把谁给破坏了
小白专场
题目:判断是否为同一颗二叉搜索树
给定一个插入序列可以唯一确定一颗二叉搜索树,然而一颗给定的二叉搜索树可以由多种不同的插入序列得到
对于输入的各种个插入序列,需要判断他们是否可以生成一样的二叉搜索树
求解思路
- 分别建两颗搜索树的判别方法
- 不建树的判别方法
- 建一颗树,再判别其他序列是否与该树一致
- 搜索树表示
- 建搜索树T
- 判别一序列是否与搜索树T一致
程序框架搭建
主函数
- 读取N和L
- 根据第一行序列建树T
- 依据树T分别判别后面的L个序列是否能与T形成同一搜索树并输出结果
- 主要建立两个函数,读入数据建立搜索树T,判别序列是否与T构成一样的搜索树
判别方法
如何判别序列与树t是否一致
方法:再树t中顺序搜索序列中的每个数
- 若每次搜索所经过的结点再前面均出现过,则一致
- 否则(某次搜索中遇到前面你未出现过的结点),则不一致
#include <stdio.h>
#include <stdlib.h>
#include<iostream>
using namespace std;
typedef struct Treenode* tree;
//建立搜索树
struct Treenode
{
int v;
tree left, right;
int flag;//用来判别一个序列是不是与树一致,作为结点有无被访问过的标记
};
int main()
{
int N,L,i;
tree T;
cin >> N;
while (N)
{
cin >> L;
T = maketree(N);
for (i = 0; i < L; i++)
{
if (judge(T,N))
{
cout << "yes" << endl;
}
else
{
cout << "no" << endl;
}
resetT(T);//清除T中的标记flag
}
freetree(T);
cin >> N;
}
return 0;
}
tree maketree(int n)
{
tree t;
int i, v;
cin >> v;
t = newnode(v);
for (i = 1; i < n; i++)
{
cin >> v;
t = insert(t, v);
}
return t;
}
tree newnode(int v)
{
tree t = (tree)malloc(sizeof(struct Treenode));
t->v = v;
t->left = t->right = NULL;
t->flag = 0;
return t;
}
tree insert(tree t, int v)
{
if (!t)
t = newnode(v);
else {
if (v > t->v)
{
t->right = insert(t->right, v);
}
else
t->left = insert(t->left, v);
}
return t;
}
int check(tree t, int v)
{
if (t->flag)
{
if (v < t->v)
return check(t->left, v);
else if (v > t->v)
return check(t->right, v);
else
return 0;
}
else
{
if (v == t->v)
{
t->flag = 1;
return 1;
}
else
return 0;
}
}
//当发现序列中的某个数与T不一致时,必须把该序列后面的数全部都读完
int judge(tree t, int n)
{
int i, v,flag = 0;
//flag=0,代表目前还是一致的,1代表已经不一致了
cin >> v;
if (v != t->v)
flag = 1;
else
t->flag = 1;
for (i = 1; i < n; i++)
{
cin >> v;
if (!flag && !check(t, v))
flag = 1;
}
if (flag)
return 0;
else
return 0;
}
void resetT(tree t)//清除t中的每个结点的flag标记
{
if (t->left)
resetT(t->left);
if (t->right)
resetT(t->right);
t->flag = 0;
}
void freetree(tree t)//释放结点
{
if (t->left)
freetree(t->left);
if (t->right)
freetree(t->right);
free(t);
}