文章目录
引例:查找
查找:根据某个给定关键字K,从集合R中找出关键字与K相同的记录
静态查找:集合中的记录是固定的,没有插入和删除操作,只有查找
动态查找:集合中的记录动态变化,插入查找删除都有
解法:(1)顺序查找
struct LNode{
int arr[100];
int length;
};
int search(list tb1,int k)
{
int i;
tb1->arr[0]=k;
for(i=tbl->length;tbl->arr[i]!=k;i--)
return i;//从后面往前面遍历,直到遇到k的时候停止。如果查找成功,返回下标i失败的话返回arr[o]
}
(2)二分查找 :假设数据有序存放并且连续存放(数组)里面。
int search(List tbl,int k)
{
int left=1,right,mid,nofound=-1;
int right=tbl->length;//初始化左右边界
while(left<=right)
{
mid=(right+left)/2;//计算中间元素坐标
if(arr[mid]<k) left=mid+1;
else if(arr[mid]>k) right=mid-1;
else return mid;
}
return nofound;
}
二分查找判定树:
1)判定树上的每个结点需要查找的次数刚好为该结点所在的层次
2)查找成功的时候查找次数不会超过判定树的深度
<<n个结点的判定树的深度为[log2N]+1
<<ASL(平均查找次数)时间复杂度O(logN)
基础知识
1)树的定义:树是n个结点构成的有限集合,也就是不包含回路的无向图。当n=0:称为空树。
对于任一一颗非空树:
(1)树中有一个称为“根”的特殊节点,用r表示
(2)其余结点可以分为m个互不相交的有限集:T1,T2…Tm.其中每个集合本身又是一棵树,称为原来树的子树
(3)子树是不相交的。除了根结点之外,每个结点往上只有一个结点。(一棵树的任意俩个结点有且仅有一条路径连通)
(4)一颗n个结点的树有n-1条边
(5)在树中加一条边会构成一条回路。
2)树的基本术语:
深度:根到这个结点的层数
结点的度:结点的子树个数
树的度:树的所有结点中最大的度数
叶节点:度数为0的结点
父节点 子节点 兄弟结点 祖先结点(沿着这条路的路劲上的所有点) 子孙节点
路径和路径长度:从结点n1到nk的路径。路径所包含的边的个数。
结点层次:规定根结点在1层,其他结点的层次为其父节点的层次甲1
树的深度:树中结点层次最大的
二叉树
一个有穷的结点集合。
-
这个集合可以为空。若不为空,则它是由根结点和左子树Tl和右子树T R的俩个不相交的二叉树组成。(每个结点最多有俩棵子树)
-
满二叉树:二叉树的每个内部结点都有俩个儿子。(深度为h并且有2^h-1个结点)
-
完全二叉树:有n个结点的二叉树,对树中结点按照从上到下,从左到右的顺序编号,编号为i结点与满二叉树在编号为i结点在二叉树中的位置相同。(二叉树除了最右边位置上一个或者几个叶结点缺失,其他的位置丰满)
-
二叉树的几个重要性质:
1.)一个二叉树第i层的最大结点数为:2^(i-1)
2.)深度为k的二叉树最大结点总数:2^k-1
3.)对于任何非空的二叉树T,若n0表示叶结点数,n2是度为2的非叶节点数,则n0=n2+1; -
二叉树的存储结构:
顺序存储结构
从上到下,从左到右顺序存储n个结点的完美二叉树的结点父子关系。
1)非根结点的父节点的序号是[i/2].
2)结点(序号为i)的左孩子括号的序号是2i(若2i<=n,否则没有左孩子)
右孩子的节点序号为2i+1(若2i+1<=n,否则没有右孩子)
一般的二叉树也可以采用数组结构存储,但会造成空间的浪费。
链式存储结构:
typedef struct TreeNode*BinTree;
struct TreeNode{
int Date;
BinTree Left;
BinTree Right;
};
平衡二叉树
1.搜索树结点不同插入顺序,会导致不同的深度和平均查找长度ASL。
按照“字典顺序”比较大小。树的左右俩边的高度差不多,ASL越高.
2.平衡因子是左树高度-右树高度(BF=hl-hr)
平衡二叉树:空树或者任一结点的左右子树高度差的绝对值不超过1 |BF(T)|<=1.
设高度为h的最少结点数是N(h),则N(h)=N(h-1)+N(h-2)+1
给定结点数为n的AVL树的最大高度是O(log2N)
3.平衡二叉树的调整:
右单旋:** 麻烦结点在发现者的右子树的右子树上,因此叫RR插入**
左单旋:** 麻烦结点在发现者的左子树的左子树上,也叫做LL插入**
LR插入:** 麻烦结点在左子树的右子树上**
RL插入:麻烦结点在右子树的左子树上
4.二叉树的序列化和反序列化
就是内存里的一棵树如何变成字符串的形式,又如何从字符串形式变成内存里的树。
代码实现
遍历方式
先序中序后序遍历
先序遍历: 访问根结点——>先序递归遍历左子树——>先序递归遍历右子树
void order(BinTree BT)
{
if(BT)
{
printf("%d",BT->data);
order(BT->left);
order(BT->right);
}
}
中序遍历: 递归左子树——访问根结点——中序遍历右子树
void order(BigTree BT)
{
if(BT)
{
order(BT->left);
printf("%d",BT->DATA);
order(BT->right);
}
}
后序遍历:后序遍历左子树——后序遍历右子树——访问根结点
void order(BigTree BT)
{
if(BT)
{
order(BT->left);
order(BT->right);
printf("%d",BT->DATA);
}
}
遍历过程:中序前序和后序遍历过程中,经过结点的路线一样,只是访问各个结点的时机不同。
非递归遍历:
使用堆栈实现中序遍历:
遇到一个结点就入栈,然后遍历左子树。
当左子树遍历结束后,从栈顶弹出这个结点并且访问它。
然后按照右指针去中序遍历该节点的右子树。
void order(BinTree BT)
{
BinTree T=BT;
Stack S=CreatStack(MAXSIZE);//创建并且初始化堆栈
while(T||isempty(S))
{
while(T)
{
push(S,T);//一直向左把沿途的结点压入堆栈
T=T—>left;
}
if(!isempty(s){
T=pop(s);//结点弹出堆栈
printf("%d",T->data);//打印结点
T=T—>right;//转向右子树
}
}
}
层序遍历:
二叉树的遍历核心问题:二维结构的线性化(变成一维线性结构的过程)
1)从父亲结点访问左右儿子结点.
2)访问左儿子之后,右儿子需要一个存储结构保存暂时不访问的结点,可以用堆栈或者队列。
下来我们用队列实现:
遍历从根结点开始,首先将根结点入队,然后执行循环:结点出队,将左右儿子入队。
void travel(BinTree T)
{
Queue Q; BinTree T;
if(!BT) return;
Q=Creat(Maxsize);
Add(Q,BT);
while(!empty(Q))
{
T=Delete(Q);
printf("%d",T->data);
if(T->left) Add(Q,T->left);
if(T->right) Add(Q,T->right);
}
}
输出二叉树的叶子节点
void order(BinTree BT)
{
if(BT)
{
if(!BT->left && ! BT->right)
printf("%d",BT->data);
order(BT->left);
order(BT->right);
}
}
求二叉树的高度
Height=max(H左,H右)+1
void height(BigTree BT)
{
int HL,HR,max;
if(BT)
{
HL=height(BT->left);
HR=height(BT->right);
max=(HL>HR)?HL:HR;
return max+1;
}
else return 0;
}
二元运算表达式树及其遍历
中缀表达式会受到运算符优先级的影响. 当输出左子树的时候先输出(,左子树结束的时候输出个)
由俩种遍历序列确定二叉树
必须要有中序遍历!
二叉搜索树(二叉排序树 二叉查找树)
1,非空左子树的所有键值小于其根结点的键值
2,非空右子树的所有键值大于其根结点的键值
3,左右子树都是二叉搜索树
基本操作
Find操作:
查找从根结点开始,如果树为空,返回NULL
如果搜索树为空,则根结点关键字和x进行比较,并且进行不同的处理。
1)如果x小于根结点的键值:只在左子树继续搜索。
2)如果x大于根结点的键值,只在右子树中继续搜索
3)如果俩者的比较结果是相等,搜索完成,并且返回该结点所指的指针。
Bintree Find(int x,Bintree BST)
{
if(!BST) return NULL;
if(x>BST->Data)
return Find(x,BST->Right);//在右子树中继续查找
else if(x<BST->Data)
return Find(x,BST->Left);//在右子树中继续查找
else
return BST;
}
由于非递归函数的执行效率高,可以把尾递归改成迭代函数.
Bintree Find(int x,Bintree BST)
{
while(BST){
if(x>BST->Data)
return Find(x,BST->Right);//在右子树中继续查找
else if(x<BST->Data)
return Find(x,BST->Left);//在右子树中继续查找
else
return BST;
}
return NULL;
}
查找最大元素(树的最右分支端点)和最小元素(树的最左分支端点)
Bintree Findmin(int x,Bintree BST)
{
if(!BST) return NULL;
else if(!BST->Left)
return BST;
else
return Findmin(BST->left);
}
Bintree Findmnax(Bintree BST)
{
if(BST)
while(BST->right) BST=BST->right;
return BST;
]
插入:
BinTree insert(int x,Bintree BST)
{
if(!BST){
//如果原树为空,生成并且返回一个结点的二叉搜索树
BST=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);//递归插入右树
return BST;
}
删除:
(1)叶结点,直接删除,再去修改其父结点的指针——置为NULL。
(2)只有一个孩子的结点A:将A的父结点的指针指向A的儿子.
(3)结点有左右俩棵树:用右子树中的最小元素或者左子树的最大元素替代这个结点.
BinTree Delete(int x,BinTree BST)
{
position t;
if(!BST) printf("要删除的元素未找到");
else if(x<BST->Data)
BST->Left=Delete(x,BST-Left);//左子树递归删除
else if(x>BST->Data)
BST->right=Delete(x,BST-right);//右子树递归删除
else//找到要删除的结点
if(BST->Left&&BST->Right){//如果被删除的子树有俩个结点
{
t=FindMin(BST->Right);//在右子树寻找最小的元素填充删除的结点
BST->Data=t->Data;
BST->Right=delete(BST->Data,BST->Right);//在删除结点的右子树删除最小的元素
}
else{//被删除的结点有一个或者没有结点
t=BST;
if(!BST->left)//有无左右孩子
BST=BST->Right;
else if(!BST->right)//有左孩子或者没有子结点
BST=BST->left;
free(t);
}
return BST;
}
判断是否为搜索二叉树:
给定一个插入序列可以唯一确定二叉搜索树.然而当给定确定的二叉搜索树的时候却可以由多种不同的插入序列得到.
问题是当输入各种插入序列的时候,能否判断它们生成相同的搜索二叉树.
思路:
(1) 表示出俩棵搜素树,看看是否相同
(2) 确定出树的根结点,看左右孩子是否相同
(3) 建一棵树,再判断其他序列与该树是否一致
bool order(BigTree BT)
{
int min1;//min1为整数的最小值
cout<<min1<<endl;
if(BT)
{
bool isleft=order(BT->left);
if(!isleft){
return false;
}//检测左边的树
if(head.value<=min1)
{
return false;//打印的时候变成处理的时候
}else {
min1=head.value;
}
return order(BT->right);//检测右子树
}
}
//思路三:
typedef struct TreeNode *Tree;
struct TreeNode{
int v;
Tree left,right;
int flag;
//这个结点未被访问flag为0,访问之后flag为1
}
Tree make(int N)
{
Tree T;
int i,v;
scanf("%d",&v);
T=NewNode(V);
for(i=1;i<=N;i++)
{
scanf("%d",&V);
T=insert(T,V);
}
}
Tree NewNode(int V)
{
Tree T=(Tree)malloc(sizeof(struct TreeNode));
T->v=V;
T->lefr=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->left=insert(T->right,V);
else
T->right=insert(T->left,V);
}
return T;
}
//判断序列和树是否一致:
//在树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;
}
}
int juage(Tree T,int N)
{
int v,i,flag=0;
//当发现序列中的某个数和T不一致的是惠普,必须把序列后面的数都读完
//flag=0表示目前还不一致
scanf("%d",&v);
if(v!=T->v) flag=1;
else T->flag=1;
for(i=1;i<N;i++)
{
scanf("%d",&N);
if(!check(T,V)&& !flag) flag=1;
}
return !flag;
}
int main()
{
//对每组数据:
int N,l,i;
Tree T;
//读入N和L;
//根据第一行序列建树T;
while(N)
{
scanf("%d",&L);
T=makeTree(N);
for(i=0;i<L;i++)
{
//根据树T判断后面的L个序列能否和T形成统一搜索树输出结果
if(Judge(T,N))
printf("yes");
else printf("No");
Reset(T);//递归清除T标记的flag
}
Free(T);//递归释放树的空间
scanf("%d",&N);
}
}
判断是否为完全二叉树
二叉树按照宽度遍历。
1)任何一个结点如果有右孩子,没有左孩子,马上返回false.
2) 在1)不违规的条件下,遇到了第一个左右孩子不双全的情况,接下来遇到的所有节点都必须是叶结点。
//创建队列
bool isCBT(Node head)
{
if(head==MULL)
return false;
queue<int>q;
//是否遇到俩个孩子不双全的结点
bool leaf=false;//开关
while(!q.empty()){
head=q.poll();
Node l=NULL;//左孩子叫l
Node r=NULL;
if((l==null&r!==null)||(left&&(l!=null||r!=null)))//左孩子为空但是右孩子不为空
//左右孩子不双全,但是左孩子或者右孩子不为空
return false;
if(l!=NULL)
q.add(l);
if(r!=NULL)
q.add(r);
if(l==NULL||r==NULL)
left=true;
}
return true;
}
满二叉树
结点数=2^最大深度-1
平衡二叉树:左子树是平衡二叉树+右子树平衡二叉树+|左高-右高|<=1
class returntype{
public:
bool isbalanced;
int height;
returntype(bool isB,int hei)
{
isbalanced=isB;
height=hei;
}
}
returntype process(Node x)
{
if(x==NULL)
return returntype(tree,0);
returntype left=process(x.left);
returntype right= process(x,right);
bool isba=left.isbalanced&& right.isbalanced&&Math.abs(left.height-right.height)<2;
return returntype(isbalanced,height);
}
搜索二叉树拓展
左搜 √ 右搜√ 左max<x并且右min>x
使得任何一棵树都返回是否为搜索二叉树
class returndata{
bool isBST;
int min;
int max;
returndata(bool is,int mi,int ma)
{
isBST=is;
min=mi;
max=ma;
}
};
returndata process(Node x)
{
if(x==null)
return null;
returndata leftdata=process(x,left);
returndata rightdata=process(x,right);
int min=x.value;
int max=x.value;
if(leftdata!=null)
{
min=Math.min(min,leftdata.min);
max=Math.max(max,leftdata.max);
} if(rightdata!=null)
{
min=Math.min(min,rightdata.min);
max=Math.max(max,rightata.max);
}
bool isBST=true;
if(leftdata!=null&&(!leftdata.isBST||leftdata.max>x.value)){
isBST=false;
}
if(rightdata!=null&&(!rightdata.isBST||x.value>rightdata.min){
isBST=false;
}
int max;
returndata(isBST,min,max);
}
树的同构
给定俩颗树T1,T2.如果T1经过若干次左右孩子交换之后可以变成T2,那么它们就是同构的.
(1)二叉树的表示
结构数组表示:静态链表
struct TreeNode
{
char a;
int left;
int right;
}T1[10],T2[10];
(2)读入数据建立二叉树
Tree Build(struct TreeNode T[]){
int i,N,Root = Null;
char cl, cr;
int check[MaxTree];
scanf("%d",&N);
getchar();
if(N){
for(i = 0;i<N;i++) check[i] = 0;
for(i = 0;i<N;i++){
scanf("%c %c %c",&T[i].Element,&cl,&cr);
getchar();
if(cl != '-'){
T[i].Left = cl-'0';
check[T[i].Left] = 1;
}
else {
T[i].Left = Null;
}
if (cr != '-'){
T[i].Right = cr - '0';
check[T[i].Right] = 1;
}
else
{
T[i].Right = Null;
}
}
for ( i = 0; i < N; i++)
{
if (!check[i]){
Root = i;
break;
}
}
}
return Root;
}
(3)二叉树判断同构
int same(Tree R1, Tree R2)
{
if ((R1 == Null) && (R2 == Null))
return 1;
if (((R1 == Null) && (R2 != Null)) || ((R1 != Null) && (R2 == Null)))
return 0;
if (T1[R1].Element != T2[R2].Element)
return 0;
if ((T1[R1].Left == Null) && (T2[R2].Left == Null))
return same(T1[R1].Right, T2[R2].Right);
if (((T1[R1].Left != Null) && (T2[R2].Left != Null)) && ((T1[T1[R1].Left].Element) == (T2[T2[R2].Left].Element)))
return (same(T1[R1].Left, T2[R2].Left) && same(T1[R1].Right, T2[R2].Right));
else
return (same(T1[R1].Left, T2[R2].Right) && same(T1[R1].Right, T2[R2].Left));
}
int main()
{
Tree R1,R2;
R1=build(T1);
R2=build(T2);
if(same(R1,R2)) printf("yes");
else printf("no");
]