3.12二叉树与堆
3.12二叉树与堆
目录- 3.12二叉树与堆
- 二叉树
- 1336:【例3-1】找树根和孩子
- 根据先序遍历建树
- 根据层序遍历和中序遍历建树
- 根据前序遍历和中序遍历建树
- 堆
二叉树
树的定义:树 (Tree) 是 n(n>=0) 个结点的有限集。n=0 时称为空树。
在任意一颗非空树中:
(1) 有且仅有一个特定的称为根 (root) 的结点;
(2) 当 n>1时,其余结点可分为 m(m>0) 个互不相交的有限集 T1、T2、......、Tn,
其中每一个集合本身又是一棵树,并且称为根的子树。
此外,树的定义还需要强调以下两点:
(1) n>0 时根结点是唯一的,不可能存在多个根结点,数据结构中的树只能有一个根结点。
(2) m>0 时,子树的个数没有限制,但它们一定是互不相交的。
对于任意一棵包含n节点树来说,都有且只有 n-1 条边。
二叉树是每个结点最多有两个子树的树结构。
通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。
二叉树常被用于实现二叉查找树和二叉堆。
二叉树是递归定义的,其结点有左右子树之分,逻辑上二叉树有五种基本形态:
(1) 空二叉树
(2) 只有一个根结点的二叉树
(3) 只有左子树
(4) 只有右子树
(5) 完全二叉树
二叉树的类型
(1) 满二叉树——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。
从图形形态上看,满二叉树外观上是一个三角形。
从数学上看,满二叉树的各个层的结点数形成一个首项为 1,公比为 2 的等比数列。
因此由等比数列的公式,满二叉树满足如下性质。
- 一个层数为 k 的满二叉树总结点数为:\(2^k?1\)。因此满二叉树的结点数一定是奇数个。
- 第 i 层上的结点数为:\(2^i?1\).
- 一个层数为 k 的满二叉树的叶子结点个数(也就是最后一层):\(2^k?1\).
(2) 完全二叉树——若设二叉树的高度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,
第 h 层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
- 所有的叶结点都出现在第 k 层或 k-1 层(层次最大的两层)
- 对任一结点,如果其右子树的最大层次为 L,则其左子树的最大层次为 L 或 L+1。
树的一些名词
节点的层次:从根开始定义起,根为第 1 层,根的子节点为第 2 层,以此类推;
节点的度:一个节点含有的子树的个数称为该节点的度;
树的高度或深度:树中节点的最大层次;
树的度:一棵树中,最大的节点的度称为树的度;
叶节点或终端节点:度为 0 的节点称为叶节点;
非终端节点或分支节点:度不为 0 的节点;
节点的祖先:从根到该节点所经分支上的所有节点;
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;
兄弟节点:具有相同父节点的节点互称为兄弟节点;
堂兄弟节点:双亲在同一层的节点互为堂兄弟;
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
森林:由m(m>=0)棵互不相交的树的集合称为森林;
二叉树的遍历分为以下几种:
- 先序遍历:遍历顺序规则为【根左右】
- 中序遍历:遍历顺序规则为【左根右】
- 后序遍历:遍历顺序规则为【左右根】
- 层序遍历:遍历顺序规则为【上下左右】
【根左右】就是先遍历根,再遍历左子树,最后遍历右子树;所谓的先中后都是针对根节点的顺序。
【层序遍历的实现是通过队列 BFS 实现的】
将当前节点的左右子节点入队后依次出队,将出队节点的左右子节点继续入队,直到遍历完成。
如上图的三种遍历,答案如下:
先序遍历:ABDECFG
中序遍历:DBEAFCG
后序遍历:DEBFGCA
层序遍历:ABCDEFG
//定义树的节点
typedef struct Node{
char data;
struct Node *lch, *rch;
}Node, *Tree;
//先序遍历
void preorderTraversal(Tree node){
if(node!=NULL){
cout<data;
preorderTraversal(node->lch);
preorderTraversal(node->rch);
}
}
//中序遍历
void inorderTraversal(Tree node){
if(node!=NULL){
inorderTraversal(node->lch);
cout<data;
inorderTraversal(node->rch);
}
}
//后序遍历
void postorderTraversal(Tree node){
if(node!=NULL){
postorderTraversal(node->lch);
postorderTraversal(node->rch);
cout<data;
}
}
1336:【例3-1】找树根和孩子
【题目描述】
给定一棵树,输出树的根root,孩子最多的结点max以及他的孩子。
【输入】
第一行:n(结点个数≤100),m(边数≤200)。
以下m行:每行两个结点x和y,表示y是x的孩子(x,y≤1000)。
【输出】
第一行:树根:root;
第二行:孩子最多的结点max;
第三行:max的孩子(按编号由小到输出)。
【输入样例】
8 7
4 1
4 2
1 3
1 5
2 6
2 7
2 8
【输出样例】
4
2
6 7 8
【参考程序】
#include
using namespace std;
const int N=110;
int n,m,x,y, root=0, max_v=0, max_root=0;
int father[N];// father[i]表示 i的父节点
int main(){
cin>>n>>m;
for(int i=1; i<=m; i++){
cin>>x>>y; father[y]=x;//x是y的父节点
}
for(int i=1; i<=n; i++){
if(father[i]==0){//没有父亲,则为根节点
root=i; break;
}
}
for(int i=1; i<=n; i++){
int sum=0;
for(int j=1; j<=n; j++){
if(father[j]==i) {
sum++;
}
}
if(sum>max_v){//孩子节点最多的节点
max_v=sum, max_root=i;
}
}
cout<
根据先序遍历建树
【题目描述】
输入一串先序遍历字符串,根据此字符串建立一个二叉树。
例如如下的先序遍历字符串:ABC##DE#G##F###
其中 '#' 表示的是空格,空格字符代表空树。
建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。
【输入格式】
输入包括 1 行字符串,长度不超过 100。
【输出格式】
可能有多组测试数据,对于每组数据,
输出将输入字符串建立二叉树后中序遍历的序列,每个字符后面都有一个空格。
每个输出结果占一行。
【样例输入】abc##de#g##f###
【样例输出】c b e g d f a
【参考程序】
#include
using namespace std;
const int N=110;
char a[N];
int cnt=0;
typedef struct Node{
char data;
struct Node *lch, *rch;
}Node, *Tree;
//递归建树
struct Node* build(){
struct Node *root=NULL;
if(a[cnt++]!='#'){
root = (struct Node*)malloc(sizeof(struct Node));
root->data = a[cnt-1];
root->lch = build();
root->rch = build();
}
return root;
}
//中序遍历
void inorder(struct Node *root){
if(root!=NULL){
inorder(root->lch);
printf("%c ", root->data);
inorder(root->rch);
}
}
//销毁二叉树
void del(struct Node* &root){
if(root!=NULL){
del(root->lch);
del(root->rch);
delete root;
root = NULL;
}
}
int main(){
while(scanf("%s", a)!=EOF){
cnt=0;
struct Node *root=build();
inorder(root);
del(root);
printf("\n");
}
return 0;
}
根据层序遍历和中序遍历建树
【题目描述】
给一棵二叉树的层序遍历序列和中序遍历序列,
求这棵二叉树的先序遍历序列和后序遍历序列。
【输入格式】
第一行一个正整数N(1<=N<=30),代表二叉树的结点个数(结点编号为1~N)。
接下来两行,每行N个正整数,分别代表二叉树的层序遍历序列和中序遍历序列。
数据保证序列中1~N的每个数出现且只出现一次。
【输出格式】
输出一行,包含N个正整数,代表二叉树的先序遍历序列。
每行末尾不输出额外的空格。
【样例输入】
7
3 5 4 2 6 7 1
2 5 3 6 4 7 1
【样例输出】
3 5 2 4 6 7 1
【参考程序】
根据前序遍历和中序遍历建树
【题目描述】
已知一棵二叉树的前序遍历和中序遍历,求二叉树的后序遍历。
【输入格式】
输入数据有多组,第一行是一个整数t (t<1000),代表有t组测试数据。
每组包括两个长度小于50 的字符串,
第一个字符串表示二叉树的先序遍历序列,
第二个字符串表示二叉树的中序遍历序。
【输出格式】
每组第一行输出二叉树的后序遍历序列,第二行输出二叉树的层次遍历序列
【样例输入】
2
abdegcf
dbgeafc
xnliu
lnixu
【样例输出】
dgebfca
abcdefg
linux
xnuli
【参考程序】
堆
堆的定义:堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。
堆通常是一个可以被看做一棵树的数组对象,总是满足下列性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
将根节点最大的堆叫做最大堆或大根堆,
根节点最小的堆叫做最小堆或小根堆。
注意:堆内的元素并不一定数组下标顺序来排序的!!
具体操作:(我们以小根堆为例)
-
上浮 shift_up
从当前结点开始,和它的父亲节点比较,若是比父亲节点来的小,就交换,
然后将当前询问的节点下标更新为原父亲节点下标;否则退出。 -
下沉 shift_down
让当前结点的左右儿子(如果有的话)作比较,哪个比较小就和它交换,
并更新询问节点的下标为被交换的儿子节点下标,否则退出。 -
插入 push
每次插入的时候呢,我们都往最后一个插入,让后使它上浮。 -
弹出 pop
把堆顶元素弹掉,让根节点元素和尾节点进行交换,然后让当前的根元素下沉就可以了。
堆插入和删除,每次O(logN)
程序实现:
#include
using namespace std;
const int N=1e5+10;
int heap[N], size=0;
//建立大根堆
void put_less(int x){
int son, father;
size++;
heap[size]=x;
son=size;
while(son>1){
father = son/2;// 大根堆,父亲大于儿子
if(heap[son]<=heap[father]) break;
int temp=heap[son]; heap[son]=heap[father]; heap[father]=temp;
son=father;
}
}
//得到大根堆堆顶元素
int get_less(){
int son, father, ans=0;
ans=heap[1];
heap[1]=heap[size];
size--;
father=1;
while(father*2<=size){
son = father*2;
if(son1){
father=son/2;
if(heap[son]>=heap[father]) break;
int temp=heap[son]; heap[son]=heap[father]; heap[father]=temp;
son = father;
}
}
//得到小根堆堆顶元素
int get_greater(){
int son, father, ans=0;
ans=heap[1];
heap[1]=heap[size];
size--;
father=1;
while(father*2<=size){
son = father*2;
if(sonheap[son+1]) son++;//找寻小的节点
if(heap[son]>=heap[father]) break;
int temp=heap[son]; heap[son]=heap[father]; heap[father]=temp;
father=son;
}
return ans;
}
int main(){
freopen("data.in", "r", stdin);
int n, x; cin>>n;
for(int i=1; i<=n; i++) cin>>a[i];
memset(heap, 0, sizeof(heap)), size=0; //初始化
for(int i=1; i<=n; i++) put_less(a[i]);//建立大根堆
while(size) cout<