数据结构——二叉树复习

本文详细介绍了二叉树的相关操作,包括创建、遍历、节点交换、共同祖先查找、深度和最远节点距离计算。特别讨论了哈夫曼树的构建及其在编码中的应用,提供了最佳前缀码的解决方案。此外,还涵盖了二叉树的宽度和层次遍历问题。
摘要由CSDN通过智能技术生成

二叉树:一种一对多的非线性结构,主要采取链式结构存储

相关操作(核心:把握递归思想):

  1. 二叉树的创建和遍历(通过含中序的序列组来创建二叉树,以及二叉树的前序中序后序的遍历输出)
  2. 二叉树的左右子树交换操作
  3. 寻找两个节点的共同祖先问题
  4. 求解二叉树的深度和最远的节点距离
  5. 二叉树的宽度和二叉树的层次遍历
  6. 哈夫曼树(难点+重点)
  7. 最佳前缀码(难点+重点)

 二叉树的创建相关操作

1.纯先序创建

void createBiTree_Pre(BiTree &T)
{
    char x;
    cin>>x;
    if(x == '#'){
    	T = NULL;
	}
	else{
		T = new Node;
		T->data = x;
		createBiTree_Pre(T->left);
		createBiTree_Pre(T->right);
	}
}

2.用含中序的序列组建立二叉树(示例用后序和先序)

BiTree createBiTree(char *s1,char *s2,int len)
{ 
    //递归调用的函数中注意一定要给一个出口 
    if(len <= 0)
        return NULL;
    BiTree root = new Node;
    root->data = *s2;
    char *p;
    for(p = s1;p != NULL;p++){
        if(*p == *s2)
            break;
    }
    int sum1 = p - s1;  //记录左子树的节点数量 
    int sum2 = len - sum1 - 1;  //记录右子树的节点数量 
    root->left = createBiTree(s1,s2+len-sum1,sum1);
    root->right = createBiTree(p+1,s2+1,sum2);
    return root;
}
int main()
{
    BiTree T;
    char s1[N],s2[N];
    scanf("%s",s1);
    scanf("%s",s2);
    //将后序逆转,确保每次取得的第一个是当前的根节点 
    reverse(s2,s2+strlen(s2));
    T = createBiTree(s1,s2,strlen(s1));
    preTraver(T);
    return 0;
}

3.遍历序列(以先序为例进行调整)

void preTravel(Bitree root)
{
    if(root){
        cout<<root->data;
        preTravel(root->left);
        preTravel(root->right);
}

寻找两个节点的共同祖先

//注意理解寻找祖先节点的思想!!!!!(属于自己写是绕晕了但是
//代码利用了递归后是很简洁的!重点在于return 的设置,也就是递归回调和递归出口的设置)

int flag = 0;BiTree anc;

bool Search_Ancient(BiTree &root,char p,char q)
{
    if(root == NULL)
        return 0;
    //一直采用递归来到最底层寻找,本题由于是查找祖先节点
    //所以应该自底向上,从叶子节点开始检索

    bool left = Search_Ancient(root->left,p,q);
    bool right = Search_Ancient(root->right,p,q);

    //如果左子树有,右子树有,则该节点即为祖先节点
    if(left && right){
        anc = root;
        flag = 1;
    }
    //找到字符所在节点,或者当前节点的左树存在目标元素,或者当前节点的右树存在目标元素,该节点都 
    //有可能为祖先节点
    return (root->data == p || root->data == q) || left || right;
}

 

二叉树的深度和最远节点信息

问题描述】考研真题:求二叉树的深度及二叉树中最远两个结点的距离。

【输入形式】拓展的前序遍历序列

【输出形式】深度和距离

【样例输入】AB#C##DE#G#H##F##

【样例输出】5 6

tip:求距离,其实也就是求左右节点的高度差,可应用于二叉排序树的平衡因子求法


#include<iostream>
using namespace std;
typedef char ElemType;
struct Node{
	Node *right;
	Node *left;
	ElemType data;
};
typedef Node* BiTree;
void createBiTree_Pre(BiTree &T)
{
    char x;
    cin>>x;
    if(x == '#'){
    	T = NULL;
	}
	else{
		T = new Node;
		T->data = x;
		createBiTree_Pre(T->left);
		createBiTree_Pre(T->right);
	}
}
int depth = 0;
void TreeDepth(BiTree root,int h)
{
    if(root != NULL){
        if(h > depth) depth = h;
        TreeDepth(root->left,h+1);
        TreeDepth(root->right,h+1);
    }
}
//该做法是跨越根节点的
/*
 *注意理解算法思想
 *    最开始的版本适用于跨越root节点的,但若没有跨越root节点(注意递归思想!!)
 *        -左子树为root节点寻找+右子树为root节点寻找
*/
static int maxDistance = 0;
int LongestDistance(BiTree root)
{
    if(root == NULL)
        return -1;
    int max_left = 1 + LongestDistance(root->left);
    int max_right = 1 + LongestDistance(root->right);
    int distance = max_left + max_right;
    if(distance > maxDistance) maxDistance = distance;
    return (max_left > max_right ? max_left : max_right);
}
int main()
{
    BiTree T;
    createBiTree_Pre(T);
    TreeDepth(T,1);
    LongestDistance(T);
    cout<<depth<<" ";
    cout<<maxDistance;
    return 0;
}

线索二叉树

二叉树的线索化

//将二叉树线索化
Node *pre = NULL;
//始终设置一个先行节点 
void Inthread(BiTree root)
{
    if(root != NULL){
        Inthread(root->left);
        if(root->left == NULL){
            root->left = pre;
            root->Ltag = 1;
        }
        if(pre != NULL && pre->right == NULL){
            pre->right = root;
            pre->Rtag = 1;
        }
        pre = root;
        Inthread(root->right);
    }
}
//寻找后继节点
BiTree In_next(BiTree p)
{
	Node *q;
    if(p->Rtag == 1){
    	//cout<<p->data<<"的后继节点1:"<<p->right->data<<endl;
    	return p->right;
	}

    else{
        //按照左中右输出,该节点的后继节点是右子树的最左子树节点
        for(q = p->right;q && q->Ltag == 0;q = q->left);
        //cout<<p->data<<"后继节点2:"<<q->data<<endl;
        return q;
    }
}

二叉树的层次遍历

层次遍历
 *  指按照每层,从左到右的顺序遍历所有的节点的一种遍历方式,按照这种方式,可以很好的求解树的宽度
 * 层次遍历中,需要用到 队列 的结构
 *  保留上一层的根节点信息,取队首元素,如果左孩子有,入队,如果右孩子有,右孩子入队;队列中的元素数量就是宽度,并且入队方式是从左到右的
 * 可设立一个数组保留每层的元素数量


int Tree_maxWidth(BiTree root)
{
    queue<BiTree> Q;
    int nowWidth = 1,tempWidth,maxWidth = 0;
    BiTree p;
    p = root;
    if(p != NULL)
        Q.push(p);
    while(!Q.empty()){
        //当前循环是为了遍历二叉树中的所有节点
        tempWidth = nowWidth;
        //now标记上一个层次的节点个数
        while(tempWidth){
            p = Q.front();   Q.pop();
            if(p->left != NULL)
                Q.push(p->left);
            if(p->right != NULL)
                Q.push(p->right);
            tempWidth--;
        }
        //上一层已经完全出队,取当前队列的长度即下一层次的宽度
        nowWidth = Q.size();
        if(nowWidth > maxWidth)
            maxWidth = nowWidth;
    }
    return maxWidth;
}

哈夫曼树 

【问题描述】
 已知输入一串正整数,正整数之间用空格键分开,请建立一个哈夫曼树,以输入的数字为叶节点,求这棵 哈夫曼树 的带权路径长度。
【输入形式】
 首先输入正整数的个数,然后接下来为接下来的正整数,正整数个数不超过10个
【输出形式】
 输出相应的权值
【样例输入】
 5 4 5 6 7 8
【样例输出】
 69*/
 /*解题关键
  *    1.哈夫曼树的构建(采用一维结构数组的方法)
  *    2.如何生成哈夫曼树
  *        - 原理:找出最小的两个节点(select函数),相加得到新的生成节点
  *    3.代码实现
  *        - select函数:遍历前面有数据的节点,返回两个较小值,这里可以不用启用一个备用的weight数组,可以用找到节点的
  *          parent域是否为0来判断。
  *    4.实现原理(其实权重和就等于新的生成节点的值相加,可以证明,一些值其实在反复相加,反复的过程包含了权重的思想(应该可以证明))

#include<bits/stdc++.h>
#define N 1000
#define MAX 10001
using namespace std;
struct HTNode{
    int weight;
    int parent;
    int left;
    int right;
};
int n;
HTNode ht[N];
void select(int &s1,int &s2, int m)
{
    int min1 = MAX,min2 = MAX,flag1,flag2,i;
    //跑一遍找到最小的
    for(i = 1;i <= n+m;i++){
        if(ht[i].weight < min1 && ht[i].parent == 0){
            min1 = ht[i].weight;
            flag1 = i;
        }
    }
    //跑一遍找到次小的
    for(i = 1;i <= n+m;i++){
        //头几次写没有用到parent,老是得出错误值
        if(ht[i].weight < min2 && i != flag1 && ht[i].parent == 0){
            min2 = ht[i].weight;
            flag2 = i;
        }
    }
    s1 = flag1;
    s2 = flag2;
}

int main()
{
    int s1,s2,sum = 0;
    //完成前面数据二叉树的基本创建
    cin>>n;
    for(int i = 1;i <= n;i++){
        cin>>ht[i].weight;
        ht[i].parent = 0;
        ht[i].left = 0;
        ht[i].right = 0;
    }
    //完成哈夫曼树的创立
    for(int i = n+1;i <= 2*n - 1;i++)
    {
        select(s1,s2,i-n-1);
        ht[i].weight = ht[s1].weight + ht[s2].weight;
        ht[i].left = s1; ht[i].right = s2;
        ht[s1].parent = i; ht[s2].parent = i;
    }
    for(int i = n+1;i <= 2*n - 1;i++){
        sum += ht[i].weight;
        //cout<<sum<<endl;
     }
     cout<<sum;
    return 0;
}

最佳前缀码

/*【问题描述】
读入n个字符所对应的权值,构造一棵哈夫曼树,自底向上生成每一个字符对应的哈夫曼编码,并依次输出。
【输入形式】
输入的第一行包含一个正整数n,表示共有n个字符需要编码。其中n不超过100。
第二行中有n个用空格隔开的正整数,分别表示n个字符的权值。
【输出形式】
共n行,每行一个字符串,表示对应字符的哈夫曼编码。
【注意】保证每次左子树比右子树的权值小;如出现相同权值的,则先出现的在左子树。
【样例输入】
8
5 29 7 8 14 23 3 11
【样例输出】
0001
10
1110
1111
110
01
0000
001*/
#include<bits/stdc++.h>
#define N 1000
#define MAX 10000
using namespace std;
struct HTNode{
    int weight;
    int parent;
    int left;
    int right;
};
int n;
HTNode ht[N];
vector<int> prefix[2*N];
void select(int &s1,int &s2, int m)
{
    int min1 = MAX,min2 = MAX,flag1,flag2,i;
    //跑一遍找到最小的
    for(i = 1;i <= n+m;i++){
        if(ht[i].weight < min1 && ht[i].parent == 0){
            min1 = ht[i].weight;
            flag1 = i;
        }
    }
    //跑一遍找到次小的
    for(i = 1;i <= n+m;i++){
        if(ht[i].weight < min2 && i != flag1 && ht[i].parent == 0){
            min2 = ht[i].weight;
            flag2 = i;
        }
    }
    s1 = flag1;
    s2 = flag2;
}
void Search_Prefix()
{
    int i,j;
    for(i = 1;i <= n;i++){
        j = i;
        while(ht[j].parent){
            //只有根节点的parent值为0;
            int parent = ht[j].parent;
            if(j == ht[parent].left){
                prefix[i].push_back(0);
            }
            else if(j == ht[parent].right){
                prefix[i].push_back(1);
            }
            j = parent;
        }
    }
}
int main()
{
    int s1,s2;
    //完成前面数据二叉树的基本创建
    cin>>n;
    for(int i = 1;i <= n;i++){
        cin>>ht[i].weight;
        ht[i].parent = 0;
        ht[i].left = 0;
        ht[i].right = 0;
    }
    //完成哈夫曼树的创立
    for(int i = n+1;i <= 2*n - 1;i++)
    {
        select(s1,s2,i-n-1);
        ht[i].weight = ht[s1].weight + ht[s2].weight;
        ht[i].left = s1; ht[i].right = s2;
        ht[s1].parent = i; ht[s2].parent = i;
    }
    //标识根节点
    ht[2*n-1].parent = 0;
    Search_Prefix();
    for(int i = 1;i <= n;i++){
        for(int j = prefix[i].size() - 1;j >= 0;j--){
            //逆向输出
            cout<<prefix[i][j];
        }
        cout<<endl;
    }
    /*for(int i = 1;i <= 2*n - 1;i++){
        cout<<ht[i].weight<<"节点的parent的编号"<<ht[i].parent<<" 以及它的权重"<<ht[ht[i].parent].weight<<;
        cout<<endl;
    }*/
    return 0;
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值