数据结构:树形结构(树、堆)详解

数据结构:树形结构(树、堆)详解

  • 一、树
    • (一)树的性质
    • (二)树的种类
      • 二叉树
      • 多叉树
      • 满N叉树
      • 完全N叉树
    • (三)二叉树的实现
      • 1、二叉树结构定义
      • 2、二叉树功能实现
        • (1)前序、中序、后序、层序遍历
        • (2)二叉树结点个数
        • (3) ⼆叉树叶⼦结点个数
        • (4) 二叉树第k层结点个数
        • (5)二叉树的深度/高度
        • (6)⼆叉树查找值为x的结点
        • (7)二叉树销毁
        • (8)判断二叉树是否为完全二叉树
  • 二、堆
    • (一)堆的实现
      • 1、堆的结构定义
      • 2、堆的初始化
      • 3、向上调整操作
      • 4、向下调整操作
      • 5、入堆操作
      • 6、堆的扩容
      • 7、出堆操作
      • 8、堆的销毁
      • 9、堆的判空、查看堆顶元素
    • (二)哈夫曼编码实现
      • 结束语

一、树

树的物理结构和逻辑结构上都是树形结构

(一)树的性质

• ⼦树是不相交的
• 除了根结点外,每个结点有且仅有⼀个⽗结点
• ⼀棵N个结点的树有N-1条边

(二)树的种类

树按照根节点的分支来分,可以分为二叉树和多叉树。

二叉树

二叉树(Binary Tree)
定义:每个节点最多有两个子节点的树结构。可以是空树,或者由一个根节点和左、右子树组成。

多叉树

多叉树(Multiway Tree)
定义:每个节点可以有多个子节点的树结构,节点子节点的数量没有限制。

树按照结点的特性来观察,又可以有满N叉树和完全N叉树

满N叉树

满N叉树是一种深度为K的二叉树,其中每一层的节点数都达到了该层能有的最大节点数。

在这里插入图片描述

完全N叉树

除了最后一层外,每一层都被完全填满,并且最后一层所有节点都尽量靠左排列。

在这里插入图片描述

(三)二叉树的实现

1、二叉树结构定义

用 typedef 可以使得后面的使用范围更广

typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;

2、二叉树功能实现

(1)前序、中序、后序、层序遍历

下面的层序遍历方式采用的是一层一层的处理方式

void PreOrder(BTNode* root) {if (root == NULL) return;printf("%d ", root->data);PreOrder(root->left);PreOrder(root->right);return;
}void InOrder(BTNode* root) {if (root == NULL) return;InOrder(root->left);printf("%d ", root->data);InOrder(root->right);return;
}void PostOrder(BTNode* root) {if (root == NULL) return;PostOrder(root->left);PostOrder(root->right);printf("%d ", root->data);return;
}void LevelOrder(BTNode* root) {queue<BTNode*> q;q.push(root);while (!q.empty()) {int num = q.size();for (int i = 0; i < num; i++) {BTNode* temp = q.front();if(temp->left) q.push(temp->left);if(temp->right) q.push(temp->right);printf("%d ", temp->data);q.pop();}printf("\n");}return;
}
(2)二叉树结点个数

两种方法都可以实现求结点个数,但是第二种需要另外创建变量接收返回值,因此第一种方式比较好

//方法一
int BinaryTreeSize(BTNode* root) {if (root == NULL) return 0;return 1 + BinaryTreeSize(root->left) +BinaryTreeSize(root->right);
}//方法二
void BinaryTreeSize(BTNode* root, int* psize) {if (root == NULL) return;if (root->left) {(*psize)++;BinaryTreeSize(root->left, psize);}if (root->right) {(*psize)++;BinaryTreeSize(root->right, psize);}return;
}
(3) ⼆叉树叶⼦结点个数

只需要统计叶子结点即可,和求普通结点个数相似

int BinaryTreeLeafSize(BTNode* root) {if (root == NULL) return 0;if (root->left == NULL && root->right == NULL) return 1;return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
(4) 二叉树第k层结点个数

需要加一个二叉树层数的变量

int BinaryTreeLevelKSize(BTNode* root, int k) {if (root == NULL) return 0;if (k == 1) return 1;return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
(5)二叉树的深度/高度
int BinaryTreeDepth(BTNode* root) {if (root == NULL) return 0;int a = BinaryTreeDepth(root->left);int b = BinaryTreeDepth(root->right);return (a > b ? a : b) + 1;
}
(6)⼆叉树查找值为x的结点

如果没有找到,不要忘记返回空

BTNode* BinaryTreeFind(BTNode* root, BTDataType x) {if (root == NULL) return NULL;if (root->data == x) return root;BTNode* left = BinaryTreeFind(root->left, x);if (left) return left;BTNode* right = BinaryTreeFind(root->right, x);if (right) return right;return NULL;
}
(7)二叉树销毁

采用二级指针的方式传入,可以避免函数处理后在进行置空处理

void BinaryTreeDestory(BTNode** root) {if (*root == NULL) return;BinaryTreeDestory(&((*root)->left));BinaryTreeDestory(&((*root)->right));free(*root);*root = NULL;return;
}
(8)判断二叉树是否为完全二叉树

这段代码是老夫目前想了许久,觉得很有不错的代码,先不考虑它的实现复杂度以及简洁程度,它实现的功能不错,可以将二叉树包括空结点放在队列之中,自己觉得很好,哈哈,也许你没看到这句,那我就放心了。

bool BinaryTreeComplete(BTNode* root) {queue<BTNode*> q;BinaryTreePushQueue(root, q);while (!q.empty()) {if (q.front() != NULL) q.pop();else break;}while (!q.empty()) {if (q.front() == NULL) q.pop();else return false;}return true;
}void BinaryTreePushQueue(BTNode* root, queue<BTNode*>& q) {vector<vector<BTNode*>> v;BinaryNodePushVector(root, v, 0);for (int i = 0; i < v.size(); i++) {for (auto x : v[i]) {q.push(x);}}return;
}void BinaryNodePushVector(BTNode* root, vector<vector<BTNode*>>& v, int k) {if (v.size() == k) v.push_back(vector<BTNode*>());if (root == NULL) {v[k].push_back(NULL);  //如果不处理空结点,取消这步即可return;}v[k].push_back(root);BinaryNodePushVector(root->left, v, k + 1);BinaryNodePushVector(root->right, v, k + 1);return;
}

二、堆

堆的物理结构是一段连续空间,但是逻辑机构是树形结构

(一)堆的实现

1、堆的结构定义

下面通过宏函数来实现交换,可以使得交换简便,或者用指针也行。

typedef int HeapDataType;typedef struct Heap {HeapDataType* __data;HeapDataType* data;int count;int capacity;
}Heap;
#define SWAP(a ,b){\HeapDataType c = (a);\(a) = (b);\(b) = (c);\
}

2、堆的初始化

用偏移量的方式,节约了内存。
从数组下标为1开始分配结点,使得后面求父节点,左右孩子运算和逻辑更简单

void HeapInit(Heap* pHeap) {assert(pHeap);pHeap->__data = (HeapDataType*)malloc(sizeof(HeapDataType));pHeap->data = pHeap->__data - 1;pHeap->capacity = 1;pHeap->count = 0;return;
}

3、向上调整操作

可以使用递归或者是循环来实现向上调整

void Heap_UP_Update(Heap* pHeap, int i) {assert(pHeap);while (FATHER(i) >= 1 && pHeap->data[FATHER(i)] > pHeap->data[i]) {SWAP(pHeap->data[FATHER(i)], pHeap->data[i]);i = FATHER(i);}return;
}void DG_Heap_UP_Update(Heap* pHeap, int i) {assert(pHeap);if (FATHER(i) < 1) return;if (pHeap->data[FATHER(i)] > pHeap->data[i]) {SWAP(pHeap->data[FATHER(i)], pHeap->data[i]);i = FATHER(i);DG_Heap_UP_Update(pHeap, i);}return;
}

4、向下调整操作

void Heap_DOWN_Update(Heap* pHeap, int i) {assert(pHeap);int size = pHeap->count - 1;while (LEFT(i) <= size) {int l = LEFT(i), r = RIGHT(i), ind = i;if (pHeap->data[ind] > pHeap->data[l]) ind = l;if (r <= size && pHeap->data[ind] > pHeap->data[r]) ind = r;if (ind == i) break;SWAP(pHeap->data[i], pHeap->data[ind]);i = ind;}return;
}

5、入堆操作

void HeapPush(Heap* pHeap, HeapDataType x) {assert(pHeap);HeapCheckCapacity(pHeap);pHeap->data[pHeap->count + 1] = x;DG_Heap_UP_Update(pHeap, pHeap->count + 1);pHeap->count += 1;return;
}

6、堆的扩容

void HeapCheckCapacity(Heap* pHeap) {assert(pHeap);if (pHeap->capacity == pHeap->count) {HeapDataType* temp = (HeapDataType*)realloc(pHeap->__data, 2 * pHeap->capacity * sizeof(HeapDataType));if (!temp) {perror("Heap Realloc Fail!");exit(1);}pHeap->__data = temp;pHeap->capacity *= 2;}return;
}

7、出堆操作

void HeapPop(Heap* pHeap) {assert(pHeap);assert(!HeapEmpty(pHeap));pHeap->data[1] = pHeap->data[pHeap->count];Heap_DOWN_Update(pHeap, 1);pHeap->count -= 1;return;
}

8、堆的销毁

void HeapDestroy(Heap* pHeap) {assert(pHeap);free(pHeap->__data);pHeap->data = NULL;pHeap->__data = NULL;pHeap->capacity = 0;pHeap->count = 0;return;
}

9、堆的判空、查看堆顶元素

int HeapEmpty(Heap* pHeap) {assert(pHeap);return pHeap->count == 0;
}HeapDataType HeapTop(Heap* pHeap) {assert(!HeapEmpty(pHeap));return pHeap->data[1];
}

(二)哈夫曼编码实现

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<string.h>
#include<algorithm>
#include<unordered_map>
#include<vector>
using namespace std;#define FATHER(i) ((i) / 2)
#define LEFT(i) ((i) * 2)
#define RIGHT(i) ((i) * 2 + 1)typedef struct Node {char* c;int freq;struct Node* lchild, * rchild;
}Node;template<typename T>
void Swap(T& a, T& b) {T c = a;a = b;b = c;return;
}//void swap(Node* a, Node* b) {
//	Node* c = a;
//	a = b;
//	b = c;
//	return;
//}Node* getNewNode(int freq,const char* c) {Node* p = (Node*)malloc(sizeof(Node));p->freq = freq;p->c = _strdup(c);p->lchild = p->rchild = NULL;return p;
}void clear(Node* root) {if (root == NULL) return;clear(root->lchild);clear(root->rchild);free(root);return;
}typedef struct heap {Node** data, **__data;int size, count;
}heap;heap* initHeap(int n) {heap* p = (heap*)malloc(sizeof(heap));p->__data = (Node**)malloc(sizeof(Node*) * n);p->data = p->__data - 1;p->size = n;p->count = 0;return p;
}int empty(heap* h) {return h->count == 0;
}int full(heap* h) {return h->count == h->size;
}Node* top(heap* h) {if (empty(h)) return NULL;return h->data[1];
}//void up_update(Node** data, int i) {
//	while (FATHER(i) >= 1) {
//		int ind = i;
//		if (data[i]->freq < data[FATHER(i)]->freq) {
//			swap(data[i], data[FATHER(i)]);
//		}
//		if (ind == i) break;
//		i = FATHER(i);
//	}
//	return;
//}void up_update(Node** data, int i) {while (i > 1 && data[i]->freq < data[FATHER(i)]->freq) {Swap(data[i], data[FATHER(i)]);i = FATHER(i);}return;
}void down_update(Node** data, int i, int n) {while (LEFT(i) <= n) {int ind = i, l = LEFT(i), r = RIGHT(i);if (data[i]->freq > data[l]->freq) ind = l;if (RIGHT(i) <= n && data[r]->freq < data[ind]->freq) ind = r;if (ind == i) break;Swap(data[ind], data[i]);i = ind;}return;
}void push(heap* h, Node* node) {if (full(h)) return;h->count += 1;h->data[h->count] = node;up_update(h->data, h->count);return;
}void pop(heap* h) {if (empty(h)) return;h->data[1] = h->data[h->count];h->count -= 1;down_update(h->data, 1, h->count);return;
}void clearHeap(heap* h) {if (h == NULL) return;free(h->__data);free(h);return;
}Node* build_huffman_tree(Node** nodeArr, int n) {heap* h = initHeap(n);for (int i = 0; i < n; i++) {push(h, nodeArr[i]);}Node* node1, * node2;int freq;for (int i = 1; i < n; i++) {node1 = top(h);pop(h);node2 = top(h);pop(h);freq = node1->freq + node2->freq;Node* node3 = getNewNode(freq, "0");node3->lchild = node1;node3->rchild = node2;push(h, node3);}return h->data[1];
}void output(Node* root) {if (root == NULL) return;output(root->lchild);//if (root->lchild == NULL && root->rchild == NULL) printf("%s : %d\n", root->c, root->freq);output(root->rchild);return;
}char charArr[100];
unordered_map<char*, char*> h;void extract_huffman_code(Node* root, int i) {charArr[i] = 0;if (root->lchild == NULL && root->rchild == NULL) {h[root->c] = _strdup(charArr);return;}charArr[i - 1] = '0';extract_huffman_code(root->lchild, i + 1);charArr[i - 1] = '1';extract_huffman_code(root->rchild, i + 1);return;
}int main() {
#define MAX_CHAR 26//1.首先用一个数组读取相关字符串及其频率Node** charArr = (Node**)malloc(sizeof(Node*)*MAX_CHAR);char arr[10];int freq;for (int i = 0; i < MAX_CHAR;i++) {scanf("%s%d", arr, &freq); charArr[i] = getNewNode(freq, arr);}//2.建立哈夫曼树Node* root = build_huffman_tree(charArr, MAX_CHAR);//3.提取哈夫曼编码进入unordered_mapextract_huffman_code(root, 1);//4.将unordered_map转换成vector排序,可以按照字典序输出编码vector<pair<char*, char*>> v(h.begin(), h.end());sort(v.begin(), v.end(), [&](const pair<char*, char*>& a, const pair<char*, char*>& b) {return strcmp(a.first, b.first) < 0;});for (auto& x : v) {printf("%s : %s\n", x.first, x.second);}return 0;
}

在这里插入图片描述

结束语

关注博主的专栏,博主会分享更多的数据结构知识!
🐾博主的数据结构专栏

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/878417.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows安全中心永久卸载工具分享

使用方法 博客&#xff1a;h0ck1r丶羽~从零到一 卸载工具下载链接&#xff1a; 夸克网盘分享 一路回车&#xff0c;选项Y即可 耐心等待几秒种&#xff0c;自动重启 此时打开windows安全中心&#xff0c;已经完全不能使用了&#xff0c;响应的杀毒功能也关了 往期推荐 【渗透测…

QT做一个USB HID设备识别软件

1.下载 HidApi库&#xff1a;GitHub - yigityuce/HidApi: Human Interface Device Api (HidApi) with C 2.pro文件添加 DEFINES - UNICODE LIBS -lsetupapi 3.建立三个对象 HidApi hidApi;HidDevice hidDev;//HID设备HidDeviceList devList;//HID设备列表 4.对 HID 设备进…

JavaWeb - Spring Boot

Spring 官网​​​​​Spring | Home Spring Boot Spring Boot是一个由Pivotal团队提供的开源框架&#xff0c;旨在简化Spring应用的初始搭建以及开发过程。在Spring Boot项目中&#xff0c;通常会有Controller、Service、Mapper和Entity等层次结构。下面将详细介绍这些层次的…

用 Higress AI 网关降低 AI 调用成本 - 阿里云天池云原生编程挑战赛参赛攻略

作者介绍&#xff1a;杨贝宁&#xff0c;爱丁堡大学博士在读&#xff0c;研究方向为向量数据库 《Higress AI 网关挑战赛》正在火热进行中&#xff0c;Higress 社区邀请了目前位于排行榜 top5 的选手杨贝宁同学分享他的心得。下面是他整理的参赛攻略&#xff1a; 背景 我们…

Serilog文档翻译系列(三) - 基础配置

Serilog 使用简单的 C# API 来配置日志记录。当需要外部配置时&#xff0c;可以&#xff08;慎用&#xff09;通过使用 Serilog.Settings.AppSettings 包或 Serilog.Settings.Configuration 包进行混合配置。 创建日志记录器 日志记录器是通过 LoggerConfiguration 对象创建的…

【RabbitMQ】快速上手

目 录 一. RabbitMQ 安装二. RabbitMQ 核心概念2.1 Producer 和 Consumer2.2 Connection 和 Channel2.3 Virtual host2.4 Queue2.5 Exchange2.6 RabbitMQ 工作流程 三. AMQP四. web界面操作4.1 用户相关操作4.2 虚拟主机相关操作 五. RabbitMQ 快速入门5.1 引入依赖5.2 编写生产…

stm32 8080时序驱动lcd屏幕

PSAM使用的硬件接口 PSAM读时序 PSAM写时序 相关时序 PSAM_RCRx NOR 和PSRAM控制寄存器

28. 双耳配对 - 配置

1. 概述 通过MAC地址的最后一位的奇偶来判断左右耳 2. 验证 右耳:奇数(主耳)-》BT ADDR: 12:42:22:34:34:6d 左耳:偶数(从耳)-》BT ADDR: 12:42:22:34:34:6c

TPH-YOLOv5:基于Transformer预测头的改进YOLOv5,用于无人机捕获场景的目标检测

摘要 提出了TPH-YOLOv5。在YOLOv5的基础上&#xff0c;增加了一个预测头来检测不同尺度的目标。然后用Transformer Prediction Heads&#xff08;TPH&#xff09;代替原有的预测头&#xff0c;探索自注意机制的预测潜力。还集成了卷积块注意力模型&#xff08;CBAM&#xff09;…

Gland安装与Debug

下载地址&#xff1a;https://www.jetbrains.com.cn/go/download/#sectionwindows debug官方文档: https://www.jetbrains.com/help/go/debugging-code.html 创建项目 选择新建项目 填写项目本地路径&#xff0c;以及选择go SDK 项目创建后检查项目设置 添加main包以及…

安装MySQL,navicat以及Django配置遇到的一些问题

MySQL安装问题 安装MySQL按照了此文章&#xff1a; MySQL数据库下载及安装教程&#xff08;最最新版&#xff09;_mysql下载安装-CSDN博客https://blog.csdn.net/weixin_39289696/article/details/128850498首先是遇到了starting the server红色叉号显示 按照上面文章的介绍…

Linux--IO多路复用(select,poll,epoll)

IO多路复用——select&#xff0c;poll&#xff0c;epoll IO多路复用是一种操作系统技术&#xff0c;旨在提高系统处理多个输入输出操作的性能和资源利用率。与传统的多线程或多进程模型相比&#xff0c;IO多路复用避免了因阻塞IO而导致的资源浪费和低效率问题。它通过将多个IO…

Linux awk案例

目录 1. 查询时间超过2000毫秒的请求2. 查询指定列组合出现的次数3. 统计所有文件的大小4. 获取大于指定大小的文件名&#xff0c;并按照从大到小排序5. grep指定字段后&#xff0c;使用awk列转行6. 查询第四个字段等于指定值的内容 1. 查询时间超过2000毫秒的请求 ✅log: 202…

[Leetcode 216][Medium]组合总和 III--回溯

目录 一、题目描述 二、整体思路 三、代码 一、题目描述 原题地址 二、整体思路 对于组合问题&#xff0c;首先要想到回溯法。那么可以根据回溯法模版进行设计。 void backtrace(元素){if(满足题目要求的条件){保存目前路径/状态/结果;return;}for循环,往目前状态相邻的所…

区块链通证系统功能分析

区块链通证系统功能分析涉及多个关键方面&#xff0c;以确保系统能够满足不同的业务需求和合规性要求。 同质与非同质通证&#xff1a;区块链通证系统需要支持同质通证&#xff08;如ERC-20&#xff09;和非同质通证&#xff08;如ERC-721&#xff09;&#xff0c;以适应不同类…

《NLP自然语言处理》—— 关键字提取之TF-IDF算法

文章目录 一、TF-IDF算法介绍二、举例说明三、示例&#xff1a;代码实现四、总结 一、TF-IDF算法介绍 TF-IDF&#xff08;Term Frequency-Inverse Document Frequency&#xff09;是一种用于信息检索与文本挖掘的常用加权技术。TF-IDF是一种统计方法&#xff0c;用以评估一个词…

机器人大会引领产业动向,卓翼飞思绘制无人系统教科研新蓝图

8月21日&#xff0c;万众瞩目的2024世界机器人大会暨博览会在北京亦创国际会展中心盛大开幕。这场为期5天&#xff0c;集“展览”“论坛”“赛事”于一体的机器人盛会&#xff0c;反映了当下机器人领域的繁荣生态。据官方统计数据&#xff0c;今年现场逛展观众高达25万人次&…

揭秘!糖尿病:从绝望到希望的治愈之路

在这个快节奏、高压力的时代&#xff0c;糖尿病这一“甜蜜的负担”正悄然影响着越来越多人的生活。面对这一全球性的健康挑战&#xff0c;许多患者心中都萦绕着一个共同的疑问&#xff1a;“糖尿病&#xff0c;真的能治好吗&#xff1f;”今天&#xff0c;就让我们一起揭开糖尿…

《黑神话:悟空》:30%抽成真相

《黑神话&#xff1a;悟空》自建服务器出售&#xff1f;揭秘游戏界的30%抽成真相&#xff01; 近年来&#xff0c;随着游戏行业的迅猛发展&#xff0c;游戏开发商与发行平台之间的利益分配问题逐渐成为业界关注的焦点。其中&#xff0c;《黑神话&#xff1a;悟空》作为一款备受…

排序算法:

冒泡排序&#xff1a; 从列表的第一个数字开始进行比较&#xff0c;判断该数和下一个数之间的大小关系&#xff0c;如果该数比右边的数大&#xff0c;则交换位置&#xff1b;否则不变。一般一轮可以确定最大的数字&#xff0c;在列表的最后一位。 代码&#xff1a; 注意&…