HELLO WORLD--一起加油(🍺)!|

kingwzun

园龄:3年6个月粉丝:111关注:0

数据结构 五、树与二叉树

基本概念

树和森林

森林:
是m(m>=0)棵互不相交的树的集合
树(Tree):
是n(n>=0)个结点的有限集。
n=0时称为空树。
在任意一颗非空树中:
(1)有且仅有一个特定的称为根(Root)的结点。
(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、.....、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。

基本术语

image
根结点: 非空树中无前驱结点的结点
结点的度: 结点拥有的子树数
树的度: 树内各结点的度的最大值
叶子: 度为0的, 终端结点
分支结点: 度不为0, 非终端结点,内部结点
孩子, 双亲: 结点的子树的根称为该结点的孩子,该结点称为孩子的双亲
兄弟结点: 有共同的双亲
堂兄弟: 双亲在同一层的结点
树的深度: 树中结点的最大层次

有序树: 树中结点的各子树从左到右有次序
无序树:树中结点的各子树无次序.

二叉树

为什么要使用二叉树

image

定义
二叉树不是树的特殊情况,是另一个概念(因为二叉树区分左右子树)
二叉树(Binary Tree)
是n(n>=0)个结点的有限集合,
该集合或者为空集(空二叉树),
或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成(子树也为二叉树)。

特点
每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点。
左子树和右子树是有顺序的,次序不能任意颠倒。
即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。

五种基本形态
空二叉树
只有一个根结点
根结点只有左子树

根结点只有右子树
根结点既有左子树又有右子树

抽象数据类型定义
image

性质
性质1:
在二叉树的第i层上至多有2i1个结点(i>=1)
(请问第i层最少有几个结点? 1个)
性质2:
深度为k的二叉树至多有2k1个结点(k>=1)

性质3:
对任何一棵二叉树T,如果其
叶子结点数为n0,度为2的结点数为n2,则n0=n2+1

证明:
一棵二叉树,除了叶子结点,就是度为1或2的结点。再设n1度为1的结点数,
则树T 的结点总数nn=n0+n1+n2
我们再换个角度,
求解树T的总边数m:
从入度角度:
由于根结特殊,没有分支进入,所以总边数为结点总数减去1。也就是 m=n1
从出度角度:m=n1+2×n2

可得n1=n1+2×n2
可推导出:n0+n1+n21=n1+2×n2
继续推导可得n0=n2+1

满二叉树

定义:
除最后一层无任何子节点外,每一层上的所有结点都有两个子结点。
或者说:
是一棵深度为k并且有2k1个结点的二叉树。

特点:

  1. 每层都铺满结点
  2. 所有叶子结点在同一层上。

性质:
  1) 一颗树深度为h,最大层数为k,深度与最大层数相同,k=h;

  2) 叶子数为2h;

  3) 第k层的结点数是:2k-1;

  4) 总结点数是:2k-1,且总节点数一定是奇数。

完全二叉树

定义:
若设二叉树的深度为h,除第 h 层外,其它各层的结点数都达到最大个数,第h层所有的结点都连续集中在最左边
也就是说:
在满二叉树中,从最后一个结点开始,连续的去掉任意数量的结点,得到的树就完全二叉树。

性质
性质4:
具有n个结点的完全二叉树的深度为log2n+1
(x向下取整:表示不大于X的最大整数)。
证明:
image

性质5:
如果对一颗有n个结点的完全二叉树的结点按层序编号(从第1层到第log2n+1层,每层从左到右),对任一结点i(1<=i<=n)有:

  • 如果i=1,则结点i是二叉树的根,无双亲;
    • 如果i>1,则其双亲是结点i2
  • 如果2×i>n,则结点i无左孩子(结点i为叶子结点);
    • 否则其左孩子是结点2×i。
  • 如果2×i+1>n,则结点i无右孩子;
    • 否则其右孩子是结点2×i+1。

线索二叉树

image

定义:
利用二叉链表中的空指针域:
如果某个结点的左孩子为空,则将空的左孩子指针域改为指向其前驱;
如果某结点的右孩子为空,则将空的右孩子指针域改为指向其后继;
——这种改变指向的指针称为“线索”
加上了线索的二叉树称为线索二叉树(Threaded Binary Tree)
对于叉树按基种遍历次序使其变为线索二叉树的过程叫线索化

存储结构

顺序存储

方式:
按满二叉树的结点层次编号,一次存放二叉树。
(使用maxINF代表不存在的结点。)
缺点:

  1. 不能反应逻辑关系;
  2. 对于特殊的二叉树(左斜树、右斜树),浪费存储空间。
    结论
    所以二叉树顺序存储结构一般只用于满二叉树/完全二叉树。

链式存储

使用二叉链表

/*二叉树的二叉链表结点结构定义*/
typedef struct BiNode
{
char data; /*结点数据*/
struct BiNode *lchild;//左孩子指针
struct BiNode *rchild;//右孩子指针
}BiNode,*BiTree;

注意:
二叉链表储存效率不是100%;
因为:在n个结点的二叉链表中,有(n+1)个空指针域。
证明:
一定有2n个链域,除了根节点入度一定为1,则入度一定为n-1,即有n-1个链域存放了指针。
2n -(n-1)=n+1

建立和遍历二叉树

遍历二叉树定义:
二叉树的遍历是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。
目的:
因为计算机只会处理线性序列,而我们研究遍历,就是把树中的结点变成某种意义的线性序列,这给程序的实现带来了好处。
二叉树

正常建树和遍历

先序遍历

void Pre(node *F){
if(!F)return;
cout<<F->value;
Pre(F->L);
Pre(F->R);
}

代码

/**
*输入格式:ABC##DE#G##F###
**/
#include<bits/stdc++.h>
using namespace std;
//定义链树
struct btnode
{
char value;
btnode *lc;
btnode *rc;
};
//先序建树
void build(btnode *&F)
{
char ch;
cin>>ch;
if(ch=='#') F=NULL;
else {
F=new btnode;
F->value=ch;
build(F->lc);
build(F->rc);
}
}
//中序输出
void output(btnode *F)
{
if(F)
{
output(F->lc);
cout<<F->value;
output(F->rc);
}
}
int main()
{
btnode *F;
build(F);
output(F);
return 0;
}

实战

7-6 交换二叉树中每个结点的左孩子和右孩子 (20 分)

7-7 列出叶结点 (25 分)

7-9 完全二叉树的层序遍历 (25 分)

根据先序/后序+中序建树

前提:
若二叉树的节点值各不相同,则二叉树先序,中序,后序均唯一。
由先序和中序序列 或者 中序和后序序列可以确定唯一的二叉树

思想

先序和中序:先序判断根,中序判断左右
中序和后序:后序判断根,中序判断左右
一、已知二叉树的前序序列和中序序列,建树。

在二叉树的前序遍历序列中,第一个数字总是树的根结点的值。
但在中序遍历序列中,根结点的值在序列的中间,左子树的结点的值位于根结点的值的左边,而右子树的结点的值位于根结点的值的右边。
因此我们扫描中序遍历序列,就能找到根结点的值。
image
 分别找到了左、右子树的前序遍历序列和中序遍历序列,我们就可以用递归方法分别去构建左右子树。

代码步骤:
1、确定树的根节点。 树根是当前树中所有元素在前序遍历中最先出现的元素。

2、求解树的子树。
找出根节点在中序遍历中的位置,
根左边的所有元素就是左子树,根右边的所有元素就是右子树。
若根节点左边或右边为空,则该方向子树为空;
若根节点左边和右边都为空,则根节点已经为叶子节点。

3、递归求解树。 将左子树和右子树分别看成一棵二叉树,重复1、2、3步,直到所有的节点完成定位。

代码

#include <bits/stdc++.h>
using namespace std;
struct node
{
char value;
node *L;
node *R;
};
char pre[58];
char pos[58];
int n;
int ans = 0;
node *buildtree(char pre[], char pos[], int n, int cnt)
{
if (n == 0)
return NULL;
node *F = new node;
char root = pre[0];
F->value = root;
int k = 0;
for (int i = 0; i < n; i++)
{
if (pos[i] == root)
{
k = i;
break;
}
}
cnt++;
F->L = buildtree(pre + 1, pos, k, cnt);
//左子树的先序头结点为pre+1,,左子树的中序头结点为pos,左子树的长度为k,
if (F->L)
{
ans = max(cnt, ans);
}
F->R = buildtree(pre + k + 1, pos + k + 1, n - k - 1, cnt);
//左子树的先序头结点为pre+k+1,左子树的中序头结点为pos+k+1,左子树的长度为n-k-1,
if (F->R)
{
ans = max(cnt, ans);
}
return F;
}
int main()
{
cin >> n;
cin >> pre;
cin >> pos;
// cout<<ans;
node *F = buildtree(pre, pos, n, 1);
if (n == 1)
ans = 1;
cout << ans;
return 0;
}

实战

7-1 还原二叉树 (25 分)

给定一棵二叉树的前序遍历序列和中序遍历序列,要求计算该二叉树的高度。

输入格式:
输入首先给出正整数 n(≤50),为树中结点总数。随后 2 行先后给出前序和中序遍历序列,均是长度为 n 的不包含重复英文字母(区别大小写)的字符串。

输出格式:
输出为一个整数,即该二叉树的高度。

输入样例:
9
ABDFGHIEC
FDHGIBEAC
输出样例:
5

根据两序遍历得出树的深度

#include<bits/stdc++.h>
using namespace std;
int dfs(char a[],char b[],int n){
int i;
if(n==0)return 0;
for(i=0;i<n;i++){
if(b[i]==a[0]){
break;
}
}
int x=dfs(a+1,b,i)+1;//求左子树的深度
int y=dfs(a+i+1,b+i+1,n-i-1)+1;//求右子树的深度
return x>y?x:y;
}
int main()
{
char a[101];
char b[101];
int n;
cin>>n;
cin>>a>>b;
int cnt=dfs(a,b,n);
cout<<cnt<<endl;
}

二叉树与树的转化

树变二叉树

二叉树变树

并查集

参考:
https://www.tspweb.com/key/层次遍历序列重建二叉树.html

哈夫曼树和哈夫曼编码

本文作者:kingwzun

本文链接:https://www.cnblogs.com/kingwz/p/15726453.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   kingwzun  阅读(163)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起