树与二叉树学习笔记

树与二叉树

计算机中的树

树的概念

计算机中的树与现实生活中的树结构是类似的,但是计算机中的树型结构更加抽象化。在计算机中的树型结构具备以下几个特点:

  • 除了根节点,每个结点都具有唯一父节点。
  • 每个结点可以有若干个子节点。
  • 树型结构具备层次关系
  • 边与结点之间,具备无循环的特性。

树的类型

  • 二叉树(Binary Tree):

    每个节点最多有两个子节点,通常称为左子节点和右子节点。

  • 多叉树

    每个节点最多不止有两个子节点,比如说字典树、和双数组字典树。

什么是二叉树

二叉树:定义与特点

  • 每个结点最多只有两个子节点
  • 遍历和结构操作方便
  • 满足递归的基本性质,相关操作可以借助递归方式完成。
  • 二叉树在计算机领域应用广泛。

二叉树:前序、中序、后序遍历

  • 前序遍历

所谓前序遍历,指的就是遍历顺序按照:根、左、右 的方式进行。

c 复制代码
	// typedef struct Node {
	//   int val;
	// 	 struct Node *left, *right;	
	// } Node;
	
	void preOrder(Node *root) {
		if (root == NULL) return ;
		printf("%d ", root->val);
		preOrder(root->left);
		preOrder(root->right);
		return ;
	}
  • 中序遍历

中序遍历的遍历顺序按照:左、根、右 的方式进行。

cpp 复制代码
// typedef struct Node {
//   int val;
// 	 struct Node *left, *right;	
// } Node;

void inOrder(Node *root) {
	if (root == NULL) return ;
	inOrder(root->left);
	printf("%d ", root->val);
	inOrder(root->right);
	return ;
}
  • 后序遍历

后序遍历的遍历顺序是按照:左、右、根的方式

cpp 复制代码
// typedef struct Node {
//   int val;
// 	 struct Node *left, *right;	
// } Node;

void lastOrder(Node *root) {
	if (root == NULL) return ;
	lastOrder(root->left);
	lastOrder(root->right);
	printf("%d ", root->val);
	return ;
}	

二叉树:深度、广度优先遍历

  • 深度优先遍历(DFS)

深度优先遍历,其遍历方式就是,沿着其中一条路径一直走到底,当无路可走时,就需要按照原来的路径回退一步,继续搜索其他结点,重复原来的操作。(不撞南墙不回头)

cpp 复制代码
// typedef struct Node {
//    int key;
//    struct Node *lchild, *rchild;
//} Node;

int tot = 0;

void dfs(Node *root) {
    if (root == NULL) return ;
    int l, r;
    l = tot;
    tot += 1;
    dfs(root->lchild);
    dfs(root->rchild);
    r = tot; 
    printf("%d : l[%d] | r[%d]\n", root->key, l, r);
    return ;
}
  • 广度优先遍历(BFS)

广度优先遍历,也可称之为层序遍历,因为将树型结构看成是一个金子塔,那么BFS就是从顶向底层遍历。

cpp 复制代码
// typedef struct Node {
//    int key;
//    struct Node *lchild, *rchild;
//} Node;

#define MAX_QUEUE 15

void bfs(Node *root) {
   if (root == NULL) return ;

    Node *array[MAX_QUEUE] = {0};
    #undef MAX_QUEUE
    int head = 0, tail = 0;
    array[tail++] = root;
    while (tail != head) {
        Node *tmp = array[head++];
        if (tmp->lchild) array[tail++] = tmp->lchild;
        if (tmp->rchild) array[tail++] = tmp->rchild;
        printf("%d ", tmp->key);
    }
    printf("\n");
    return ;
}

二叉树:线索化

二叉树的线索化,其实就是将左右子节点为空的结点,重复利用起来,为特定的遍历方式,建立线索化,这样在完成某一个遍历顺序时,可以达到线性复杂度的效率。摆脱递归式遍历的资源浪费问题。

  • 中序遍历线索化

中序遍历的线索化,可以将当前结点中子节点为空的结点指向中序遍历的前驱或者后继结点,即当左孩子为空时,则指向中序遍历中,当前结点的前驱结点,当右孩子为空时,则指向中序遍历中,当前结点的后继结点。

  • 中序遍历线索化建立
cpp 复制代码
 //typedef struct Node {
 //   int key;
 //    int ltag, rtag;
 //   struct Node *lchild, *rchild;
 // } Node;

Node *pre_node = NULL, *in_node = NULL;
void __build_thread(Node *root) {
    if (root == NULL) return ;
    if (root->ltag == 0) __build_thread(root->lchild);
    if (in_node == NULL) in_node = root;
    if (pre_node && root->lchild == NULL) {
        root->lchild = pre_node;
        root->ltag = 1;
    }
    if (pre_node && pre_node->rchild == NULL) {
        pre_node->rchild = root;
        pre_node->rtag = 1;
    }
    pre_node = root;
    if (root->rtag == 0) __build_thread(root->rchild);
    return ;
}

void build_thread(Node *root) {
    __build_thread(root);
    pre_node->rchild = NULL;
    pre_node->rtag = 1;
    return ;
}
  • 中序线索化遍历
cpp 复制代码
 //typedef struct Node {
 //   int key;
 //    int ltag, rtag;
 //   struct Node *lchild, *rchild;
 // } Node;

Node *getNextNode(Node *root) { 
    if (root->rtag == 1) return root->rchild;
    root = root->rchild;
    while (root->ltag == 0 && root->lchild) root = root->lchild;
    return root;
}

二叉树:序列化与反序列化

  • 序列化(Serialize)

二叉树的序列化,指的是将二叉树的表示方法转化为字符串形式,即广义表表示方法。

cpp 复制代码
//序列化
#define MAX_CHR 1000

char buff[MAX_CHR + 5];
int len = 0;

void serialize(Node *root) {
    if (root == NULL) return ;
    len += sprintf(buff + len, "%d", KEY(root));
    if (root->lchild == NULL && root->rchild == NULL) return ;
    len += sprintf(buff + len, "(");
    serialize(root->lchild);
    len += sprintf(buff + len, ",");
    serialize(root->rchild);
    len += sprintf(buff + len, ")");
    return ;
}
  • 反序列化(Deserialize)

二叉树的反序列化,就是将字符串(广义表形式)表示的二叉树又转换为树节点的形式。由于二叉树满足递归的性质,因此可以借助栈完成反序列化

cpp 复制代码
//反序列化

Node *deserialize(char *str) {
    Node *root = NULL, *preNode = NULL;
    
    Node **array = (Node **)malloc(sizeof(Node *) * MAX_CHR);

    int top = -1, scode = 0, flag = 0;

    for (int i = 0; str[i]; i++) {
        switch (scode) {
            case 0: {
                //读取关键词
                if (str[i] <= '9' && str[i] >= '0') {
                    scode = 1;//读取数字
                } else if (str[i] == '(') {
                    scode = 2;//入栈新元素
                } else if (str[i] == ',') {
                    scode = 3;//修改flag
                } else if (str[i] == ')') {
                    scode = 4;//完成栈顶元素的弹出
                }
                i -= 1;
            } break;

            case 1: {
                //读取数字
                int num = 0;
                while (str[i] <= '9' && str[i] >= '0') {
                    num = num * 10 + (str[i] - '0');
                    i++;
                }
                
                preNode = getNewNode(num);
                
                if (root == NULL) {
                    root = preNode;
                } 

                if (top != -1 && flag == 0) {
                    array[top]->lchild = preNode;
                }
                
                if (top != -1 && flag == 1) {
                    array[top]->rchild = preNode;
                }
                scode = 0;
                i -= 1;
            } break;
            case 2: {
                //入栈新元素 "("
                array[++top] = preNode;
                flag = 0;
                scode = 0;
            } break;
            case 3: {
                //遇到逗号 ","
                flag = 1;
                scode = 0;
            } break;
            case 4: {
                //出栈 ")"
                --top;
                flag = 0;
                scode = 0;
            } break;
        }
    }

    return root;
}

haffman树

平均编码长度

  • 定长编码:指的是无论是何种字符,每个字符的编码都是固定长度。
  • 变长编码:即每种字符的编码长度都不是固定的,
  • 问题1:这两种编码方式哪种更加适合用于网络传输?
  • 问题2:如何衡量两种编码方式的优劣?

我们知道最重要的衡量标准,就是单位时间内网络传输数据量的大小之分。假设两种编码方式,都是在相同网络传输环境下,哪种编码方式传输效率更高呢?不言而喻,就是平均编码长度最小的编码方式。

a v g ( L ) = ∑ L i × P i avg(L) = \sum{L_i \times P_i} avg(L)=∑Li×Pi

avg(L)表示平均编码长度 = 每一种字符的编码长度与对应字符出现概率的乘积累加和

构建haffman树

  • 首先,统计得到每一种字符的概率
  • 每次将最低频率的两个结点合并成一颗子树
  • 经过了n - 1轮合并,就得到了一颗哈夫曼树
  • 按照左0右1的形式,将编码读取出来

haffman树:代码演示

  • 完整代码
cpp 复制代码
/*************************************************************************
	> File Name: 04.haffmantree.cpp
	> Author: 
	> Mail: 
	> Created Time: Wed 17 Jul 2024 10:23:16 AM CST
 ************************************************************************/

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define MAX_CHR 128

typedef struct Node {
    char ch;
    int freq;
    struct Node *lchild, *rchild;
} Node;

Node *getNewNode(char ch, int freq) {
    Node *p = (Node *)malloc(sizeof(Node));
    p->ch = ch;
    p->freq = freq;
    p->lchild = p->rchild = NULL;
    return p;
}

int findMinNode(Node **arr, int n) {
    int ind = 0;
    for (int i = 1; i <= n; i++) {
        if (arr[ind]->freq > arr[i]->freq ) ind = i;
    }
    return ind;
} 

void swap_node(Node **arr, int i, int j) {
    Node *tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
    return ;
}


Node *buildHaffManTree(Node **arr, int n) {

    for (int i = 1; i < n; i++) {
        //find two node
        int ind1 = findMinNode(arr, n - i);
        swap_node(arr, ind1, n - i);
        
        int ind2 = findMinNode(arr, n - i - 1);
        swap_node(arr, ind2, n - i - 1);
        
        //build new node
        int freq_tmp = arr[n - i]->freq + arr[n - i - 1]->freq;
        Node *new_node = getNewNode(0, freq_tmp);
        new_node->lchild = arr[n - i - 1];
        new_node->rchild = arr[n - i];
        arr[n - i - 1] = new_node;
    }

    return arr[0];
}


char *ch_code[MAX_CHR + 5] = {0};
int k = 0;

void extractHaffManTree(Node *root, char buff[], int k) {
    buff[k] = 0;
    if (root->lchild == NULL && root->rchild == NULL) {
        ch_code[root->ch] = strdup(buff);
        return ;
    }

    buff[k] = '0';
    extractHaffManTree(root->lchild, buff, k + 1);
    buff[k] = '1';
    extractHaffManTree(root->rchild, buff, k + 1);

    return ;
}


void clear(Node *root) {
    if (root == NULL) return ;
    clear(root->lchild);
    clear(root->rchild);
    free(root);
    return ;
}

int main() {
    int n;
    char s[10] = {0};
    scanf("%d", &n);
    
    Node **array = (Node **)malloc(sizeof(Node *) * n);

    for (int i = 0; i < n; i++) {
        int freq;
        scanf("%s%d", s, &freq);
        array[i] = getNewNode(s[0], freq);
    }

    Node *root = buildHaffManTree(array, n);
    char buff[1000] = {0};
    extractHaffManTree(root, buff, 0);
    
    for (int i = 0; i < MAX_CHR; i++) {
        if (ch_code[i] == NULL) continue;
        printf("%c : %s\n", i, ch_code[i]);
    }

    clear(root);
    #undef MAX_CHR
    return 0;
}
  • 运行效果展示
相关推荐
别NULL7 分钟前
机试题——疯长的草
数据结构·c++·算法
亦枫Leonlew35 分钟前
微积分复习笔记 Calculus Volume 2 - 5.1 Sequences
笔记·数学·微积分
爱码小白1 小时前
网络编程(王铭东老师)笔记
服务器·网络·笔记
ZSYP-S1 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
唐叔在学习2 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
LuH11242 小时前
【论文阅读笔记】Learning to sample
论文阅读·笔记·图形渲染·点云
ALISHENGYA2 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
一棵开花的树,枝芽无限靠近你3 小时前
【PPTist】组件结构设计、主题切换
前端·笔记·学习·编辑器
犬余4 小时前
设计模式之桥接模式:抽象与实现之间的分离艺术
笔记·学习·设计模式·桥接模式
数据爬坡ing5 小时前
小白考研历程:跌跌撞撞,起起伏伏,五个月备战历程!!!
大数据·笔记·考研·数据分析