文章目录
(一)二叉树的遍历基础
1、二叉树的先序遍历
- 算法思想
递归式:根结点->左子树->右子树
递归边界 :二叉树是一棵空树
- 代码
void preorder(node* root){
if(root==NULL){
return;//到达空树,递归边界
}
//访问根结点root,例如将其数据域输出
printf("%d\n",root->data);
//访问左子树
preorder(root->lchild);
//访问右子树
preorder(root->rchild);
}
2、二叉树的中序遍历
- 算法思想
递归式: 中序遍历左子树->访问根结点->中序遍历右子树
递归边界: 二叉树是一棵空树
- 代码
void inorder(node* root) {
if(root==NULL){
return;//递归出口
}
//访问左子树
inorder(root->lchild);
//访问根结点root,例如将其数据域输出
printf("%d\n",root->data);
//访问右子树
inorder(root->rchild);
}
3、二叉树的后序遍历
- 算法思想
递归式: 后序遍历左子树->后序遍历右子树->访问根结点。
递归边界: 二叉树是一棵空树
- 代码
void postorder(node* root){
if(root==NULL){
return ;//递归出口
}
//访问左子树
postorder(root->lchild);
//访问右子树
postorder(root->rchild);
//访问根结点
printf("%d\n",root->data);
}
4、二叉树的层序遍历
- 算法思想
按层次的顺序从根结点向下逐层进行遍历,且对同一层的节点为从左到右进行遍历。与BFS相似,需要用到队列。
- 算法步骤
实现步骤:
1.将根节点root加入队列q
2.取出队首结点,访问它
3.如果该结点有左孩子,将其入队
4.如果该结点有右孩子,将其入队
5.返回2.直到队列为空
- 代码
void LayerOrder(node* root){
queue<node*>q;
q.push(root);
while(!q.empty()){
node* now=q.front();
q.pop();
printf("%d",now->data);
if(now->lchild!=NULL) {
q.push(now->lchild);
}
if(now->rchild!=NULL){
q.push(now->lchild);
}
}
}
(二)二叉树遍历相关试题分析
1、已知中序后序求层序
- 试题描述
给出一颗二叉树的后序遍历序列和中序遍历序列,求这颗二叉树的层序遍历序列 。
- 输入样例
7
2 3 1 5 7 6 4
1 2 3 4 5 6 7
- 输出样例
4 1 6 3 5 7 2
- 算法分析
已知中序后序可以唯一确定一个二叉树,所以先构造二叉树再进行遍历。
- 代码
#include <iostream>
#include<cstring>
#include<string.h>
#include<queue>
#include<algorithm>
#include<vector>
#include<cstdio>
using namespace std;
const int maxn=50;
struct node{
int data;
node* lchild;
node* rchild;
};
int pre[maxn],in[maxn],post[maxn];//先序、中序、后序
int n;//结点个数
//当前二叉树的后序序列区间为[postL,postR]
//中序序列区间为[inL,intR]
//create函数返回构建出的二叉树根结点地址
node* create(int postL,int postR,int inL,int inR){
if(postL>postR){
return NULL;//后序序列长度小于等于0时,直接返回
}
node* root=new node;//新建一个新的结点,用来存放当前二叉树的根结点
root->data=post[postR];//新结点的数据域为根结点的值
int k;
for(k=inL;k<=inR;k++){
if(in[k]==post[postR]){
//在中序序列中找到In[k]==preL的结点
break;
}
}
int numLeft=k-inL;//左子树的结点个数
//返回左子树的根结点地址,赋给root的指针
root->lchild=create(postL,postL+numLeft-1,inL,k-1);
//返回右子树的根结点地址,赋值给root的指针
root->rchild=create(postL+numLeft,postR-1,k+1,inR);
return root;//返回根结点地址
}
int num=0;//已输出的结点个数
void layerOrder(node* root){
queue<node*> q;//注意队列中存的为地址
q.push(root);
while(!q.empty()) {
node* now=q.front();//取出队首元素
q.pop();
printf("%d",now->data);
num++;
if(num<n){
printf(" ");
}
if(now->lchild!=NULL){
q.push(now->lchild);
}
if(now->rchild!=NULL){
q.push(now->rchild);
}
}
}
int main(int argc, char** argv) {
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&post[i]);
}
for(int i=0;i<n;i++){
scanf("%d",&in[i]);
}
node* root=create(0,n-1,0,n-1);//建树
layerOrder(root); //层序遍历
return 0;
}
2、已知先序中序求后序
- 试题描述
已知某二叉树的先序序列和中序序列,编程计算并输出该二叉树的后序序列
- 输入说明
仅一组数据,分为两行输入,第一行表示指定二叉树的先序序列,
第二行表示该二叉树的中序序列,序列元素均为大写英文字母,表示二叉树的结点
- 输出说明
一行上输出该二叉树的后序序列
- 输入样例
ABDGCEFH
DGBAECHF
- 输出样例
GDBEHFCA
- 算法分析
需要使用先序后序构造一颗二叉树再遍历。
- 代码
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
struct Node
{
char data;
Node * lchild;
Node * rchild;
};
Node* CreatTree(string pre, string in)
{
Node * root = NULL; //树的初始化
if(pre.length() > 0)
{
root = new Node; //为根结点申请结构体所需要的内存
root->data = pre[0]; //先序序列的第一个元素为根结点
int index = in.find(root->data); //查找中序序列中的根结点位置
//substr(a,b),从第a个下标的元素开始截取b个元素
//所需的子字符串的起始位置。字符串中第一个字符的索引为 0,默认值为0
/**
stringObject.substr(start,length);
start--必需。要抽取的子串的起始下标。必须是数值。若为负数,那么该参数声明从字符串的尾部开始算起的位置
length--可选。字串中的字符数。必需是数值。如果省略了该参数,那么返回从stringObject开始到结尾的字符串
*/
root->lchild = CreatTree(pre.substr(1, index), in.substr(0, index)); //递归创建左子树
root->rchild = CreatTree(pre.substr(index + 1), in.substr(index + 1)); //递归创建右子树
}
return root;
}
void PostOrder(Node * root) //递归后序遍历
{
if(root != NULL)
{
PostOrder(root->lchild);
PostOrder(root->rchild);
cout<<root->data;
}
}
int main()
{
string pre_str, in_str;
Node *root;
while(cin>>pre_str>>in_str)
{
root = CreatTree(pre_str, in_str);
PostOrder(root);
cout<<endl;
}
return 0;
}
(三)哈夫曼树基础
1、哈夫曼树的特点
(1)每个初始结点最终都成为叶结点,并且权值越小的结点到根结点的路径长度越大
(2)构造过程中共新建了N-1个结点(双分支结点),因此哈夫曼树中结点总数为2N-1
(3)每次构造都选择2颗树作为新结点的孩子,因此哈夫曼树中不存在度为1的结点
2、哈夫曼树的构造
给定N个权值分别为w1,w2,w3,…,wn的结点。通过哈夫曼算法可以构造出最优二叉树,算法描述如下:
(1)将这N个节点跟别作为N棵仅含一个结点的二叉树,构成森林F
(2)构造一个新结点,并从F中选取两颗根结点权值最小的树作为新结点的左右子树,并且将新结点的权值置为左右子树上根节点的权值之和
(3)从F中删除刚才选出的两颗树,同时将新得到的树加入到F中
(4)重复步骤(2),(3)直至F中只剩下一棵树为止。
3、哈夫曼树的带权路径长度 (Weighted Length of Tree,WPL )计算方法
WPL = 所有叶子结点的带权路径长度之和
4、哈弗曼编码
1.对于任何一个叶子结点,其编号一定不会成为其他任何一个结点编号的前缀。
(四)、哈夫曼树相关例题分析
1、西电考研2011年机试试题Problem D
- 试题描述
假设用于通信的电文由n(4<n<30)个字符组成,字符在电文中出现的频度(权值)为w1,
w2,wn,试根据该权值构成哈夫曼树,并计算该数的带权路径长度。
- 输入说明
仅一组数据。分为两行输入:第一行为n的值,第二行为n个整数,表示字符出现的频度
- 输出说明
一个整数,表示所构造的哈夫曼树的带权路径长度
- 输入样例
8
7 19 2 6 32 3 21 10
- 输出样例
261
- 试题分析
该题需要使用优先级队列实现,由算法思想和步骤来定义优先级规则,然后需要使用小顶堆实现。每次从待取结点中挑出最小的两个。 - 代码
#include<iostream>
#include<queue>
#include<vector>
#include<cstdio>
using namespace std;
int main(){
int n;
cin>>n;
long long w,front1,front2,sum=0,wpl=0;
//使用优先级队列进行处理,小顶堆实现
priority_queue<long long,vector<long long>,greater<long long> > q;
for(int i=0;i<n;i++){
cin>>w;
q.push(w);
}
while(q.size()>1){
front1 = q.top();
q.pop();
front2=q.top();
q.pop();
sum=front1+front2;
wpl+=sum;
q.push(sum);
}
cout<<wpl<<endl;
return 0;
}
(五)参考文献
【1】2018年数据结构考研复习指导. 王道论坛编.
【2】算法笔记. 胡凡,曾磊 主编.
【3】2020年攻读硕士学位研究生复试资料.