C语言实现二叉树和堆

news2024/10/25 5:33:45

1.二叉树概念及结构

1.1概念

一棵二叉树是结点的一个有限集合,该集合:
1. 或者为空
2. 由一个根结点加上两棵别称为左子树和右子树的二叉树组成
2ed25d6972d8457eb6d68952b65332dc.png
从上图可以看出:
1. 二叉树不存在度大于2的结点
2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
注意:对于任意的二叉树都是由以下几种情况复合而成的:
e52ec47fd7c64423ab896b38bc4e8c5a.png

1.2 特殊的二叉树:

1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是 说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K 的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对 应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

1.3 二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
1. 顺序存储
顺序结构存储就是使用 数组来存储,一般使用数组 只适合表示完全二叉树,因为不是完全二叉树会有空 间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。 二叉树顺 序存储在物理上是一个数组,在逻辑上是一颗二叉树。
2. 链式存储
通过左右孩子指针寻找结点。
typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
struct BinTreeNode* left; // 指向当前结点左孩子
struct BinTreeNode* right; // 指向当前结点右孩子
BTDataType data; // 当前结点值域
}
// 三叉链,其实就是二叉链多了一个寻找父亲的指针
struct BinaryTreeNode
{
struct BinTreeNode* parent; // 指向当前结点的双亲
struct BinTreeNode* left; // 指向当前结点左孩子
struct BinTreeNode* right; // 指向当前结点右孩子
BTDataType data; // 当前结点值域
};
普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。 现实中我们通常把堆 ( 一种二叉树 ) 使用顺序结构的数组来存储。
堆是完全二叉树,是完全二叉树,是完全二叉树。物理结构是数组,逻辑结构被我们抽象成一棵完全二叉树。
注意的是这里的堆和操作系统 虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。这两个只是同名而已,他们没有任何关系。

2.堆的实现的定义

附:完全二叉树孩子父亲关系(在数组中下标关系)

父亲×2+1=左孩子,左孩子+1=右孩子,

(孩子-1)/2=父亲。

2.1 堆向下调整算法

下面给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根结点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。
9a6febfd7213422abce6408c0a59d883.png
但是现在还不是堆,这不就予盾了吗?所以我们可以以下向上,进行向下调整算法,也就是从第一个非叶子结点开始向上依次建堆。
//单次向下调整算法,传入第一个非叶子结点和数组大小,以小堆为例

void AdjustDown(HPDataType* a, int n, int parent)

{

int child = parent * 2 + 1;

//计算左孩子

while (child < n)

{

//找到最小孩子与父亲交换

if (child+1<n&&a[child+1]<a[child]) // ҳСǸ

{

child++;

}

if (a[parent] > a[child])

{

Swap(&a[child], &a[parent]);

parent = child;

child = parent * 2 + 1;

}

else

{

break;

}

}

}

2.2堆的创建

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算 法,把它构建成一个堆。根结点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子结点的 子树开始调整,一直调整到根结点的树,就可以调整成堆。

2.3 建堆时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的
就是近似值,多几个结点不影响最终结果):
cd1de0f8a4d84322b452ae63fbe6f35a.png
因此: 建堆的时间复杂度为 O(N)

2.4 堆的插入

先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆

2.5 堆的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调
整算法。

2.6 堆的代码实现

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* arr;
	int size;
	int capacity;
}Hp;


void Swap(HPDataType* p1, HPDataType* p2);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int n, int parent);//n是数据个数


//堆的初始化
void HeapInit(Hp* php);
// 堆的销毁
void HeapDestory(Hp* php);
// 堆的插入(小堆)
void HeapPush(Hp* php, HPDataType x);
// 堆的删除(小堆)
void HeapPop(Hp* php);
// 取堆顶的数据
HPDataType HeapTop(Hp* php);
// 堆的数据个数
int HeapSize(Hp* php);
// 堆的判空
int HeapEmpty(Hp* php);
  //初始化

void HeapInit(Hp* php)

{

assert(php);

php->arr = NULL;

php->capacity = php->size = 0;

}
//销毁

void HeapDestory(Hp* php)

{

assert(php);

free(php->arr);

//先释放arr,顺序不能乱,乱了就找不到arr了

php->arr = NULL;

php->capacity = php->size = 0;

php = NULL;

}

//交换元素函数

void Swap(HPDataType* p1, HPDataType* p2)

{

HPDataType tmp = *p1;

*p1 = *p2;

*p2 = tmp;

}

 
   
//从根开始,如果父亲比孩子大就交换

void AdjustUp(HPDataType* a, int child)

{

int parent = (child - 1) / 2;

//while (parent >= 0)

while (child > 0)

{

if (a[child] < a[parent])

{

Swap(&a[child], &a[parent]);

child = parent;

parent = (child - 1) / 2;

}

else

{

break;

}

}

}

//堆的插入

void HeapPush(Hp* php, HPDataType x)

{

assert(php);

判断内存够不够,不够则申请空间

if (php->capacity == php->size)

{

int newcapacity = (php->capacity==0 )? 4:2*php->capacity;

HPDataType* newarr = (HPDataType*)realloc(php->arr,sizeof(HPDataType)*newcapacity);

if (newarr == NULL)

{

perror("realloc fail!");

return;

}

php->arr = newarr;

php->capacity = newcapacity;

}

php->arr[php->size] = x;

//得到结点后用向下调整算法插入

AdjustUp(php->arr,php->size);

php->size++;

}

堆的删除指的是删除堆顶元素,但是并不能直接删除,因为直接删除会导致元素的关系错乱,兄弟并父亲等等,我们往往是通过交换堆顶和堆尾元素后,再将堆尾元素删除,再对堆顶元素进行向下调整算法

//堆删除

void HeapPop(Hp* php)

{

assert(php);

assert(!HeapEmpty(php));

Swap(&php->arr[0], &php->arr[php->size - 1]);

php->size--;

AdjustDown(php->arr, php->size,0);

}

HPDataType HeapTop(Hp* php)

{

assert(php);

assert(!HeapEmpty(php));

return php->arr[0];

}
void HeapPop(Hp* php)

{

assert(php);

assert(!HeapEmpty(php));

Swap(&php->arr[0], &php->arr[php->size - 1]);

php->size--;

AdjustDown(php->arr, php->size,0);

}

HPDataType HeapTop(Hp* php)

{

assert(php);

assert(!HeapEmpty(php));

return php->arr[0];

}
//堆的判空

int HeapEmpty(Hp* php)

{

assert(php);

return php->size == 0;

}

堆的应用

3.1 堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1. 建堆
升序:建大堆
降序:建小堆
2. 利用堆删除思想来进行排序
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序
堆排序是一种选择排序,他是通过堆顶选出最大或最小的值放在最后,然后让堆的大小减一,选出次小或次大的,依此递推,直到把所有元素都选出来。
void heapsort(int* a, int size)
{
    for (int i = (size - 1 - 1) / 2; i >= 0; i--)
    {
        AdjustDown(a, size, i);
    }
    int end = size - 1;
    while (end > 0)
    {
        Swap(&a[0], &a[end]);
        AdjustDown(a, end, 0);
        end--;
    }
}

3.2 TOP-K问题

TOP-K 问题:即求数据结合中前 K 个最大的元素或者最小的元素,一般情况下数据量都比较大
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前 K 个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆
2. 用剩余的 N-K 个元素依次与堆顶元素来比较,不满足则替换堆顶元素
将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素
//以在100个中找最小k个为例
//制造100个随机数,在文件中

void CreateNDate(int k)
{
    srand((unsigned int)time(0));
    FILE* pf = fopen("data.txt", "w");

    for (int i = 0; i < k; i++)
    {
        fprintf(pf, "%d\n", rand() % 100 +i);
    }

    fclose(pf);
    pf = NULL;
}

void PrintTopK(int* a, int n, int k)
{
    FILE* pf = fopen("data.txt", "r");
    if (pf == NULL)
    {
        perror("fopen error");
        return;
    }
    //读取前k个数据建堆
    for (int i = 0; i < k; i++)
    {
        fscanf(pf, "%d", &a[i]);
    }
    for (int i = (k - 1 - 1) / 2; i >= 0; i--)
    {
        AdjustDown(a, k, i);
    }

    int tmp = 0;

//读文件大于堆顶进堆
    while (fscanf(pf, "%d", &tmp) > 0)
    {
        if (tmp > a[0])
        {
            a[0] = tmp;
            AdjustDown(a, k, 0);
        }
    }
    fclose(pf);
    pf = NULL;
}

4.二叉树链式结构的实现

链式二叉树中最重要的思想是将树分为左子树和右子树,下面详细介绍这种思想。

4.1 前置说明

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。先手动造出一棵树如下


typedef int BTDataType;
typedef struct BinaryTreeNode
{

BTDataType _data;

struct BinaryTreeNode* _left;

struct BinaryTreeNode* _right;
}BTNode;
BTNode* CreatBinaryTree()
{
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);


node1->_left = node2;
node1->_right = node4;
node2->_left = node3;
node4->_left = node5;
node4->_right = node6;
return node1;
}
​​​​​​​
注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序文章再来讲解
再看二叉树基本操作前,再回顾下二叉树的概念,
二叉树是:
1. 空树
2. 非空:根结点,根结点的左子树、根结点的右子树组成的。
e56b3e17b4bf4348aa8cf3ede892aa5f.png
任何一棵二叉树都是由根,左子树和右子树构成(有一些树的左或右子树是空树)

4.2二叉树的遍历

4.2.1 前序、中序以及后序遍历
学习二叉树结构,最简单的方式就是遍历。所谓 二叉树遍历 (Traversal) 是按照某种特定的规则,依次对二叉 树中的结点进行相应的操作,并且每个结点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
按照规则,二叉树的遍历有: 前序 / 中序 / 后序的递归结构遍历
1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
由于被访问的结点必是某子树的根, 所以 N(Node )、 L(Left subtree )和 R(Right subtree )又可解释为 根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。
// 二叉树前序遍历
一棵二叉树可以分成左子树和右子树
以左子树为跟又能再分……
第一次访问跟结的的左子树,2作为根子点又访问它的左子树3,3又访问它的左子树空树打卬N开始返回,访问3(根),然后又访问3的右子树空树,到此为止2的左子树访问完毕,开始访问2(根),然后访问2的右子树空树,到此为止,1的左子树访问完毕,开始访问1(根),然后开始访问1的右子树……

void PrevOrder(BTNode* root)
{
    if (root == NULL)
    {
        printf("N ");
        return;
    }
    printf("%d ", root->data);
    PrevOrder(root->left);
    PrevOrder(root->right);
}
 

// 二叉树中序遍历

void InOrder(BTNode* root)
{
    if (root == NULL)
    {
        printf("N ");
        return;
    }
    InOrder(root->left);
    printf("%d ", root->data);
    InOrder(root->right);
}


// 二叉树后序遍历
void PostOrder(BTNode* root);


void PostOrder(BTNode* root)
{
    if (root == NULL)
    {
        printf("N ");
        return;
    }
    PostOrder(root->left);
    PostOrder(root->right);
    printf("%d ", root->data);
}

前序遍历结果: 1 2 3 4 5 6
中序遍历结果: 3 2 1 5 4 6
后序遍历结果: 3 2 5 6 4 1

4.3 结点个数以及高度等

// 二叉树结点个数
int BinaryTreeSize(BTNode* root);

int TreeSize(BTNode* root)
{
    if (root == NULL)
    {
        return 0;
    }
    return TreeSize(root->left) + TreeSize(root->right) + 1;
}

// 二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root);

int TreeLeafSize(BTNode* root)
{
    if (root == NULL)
    {
        return 0;
    }
    if(root->right==NULL && root->left==NULL)
        return 1;
    return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}



// 二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k);

int TreelevelkSize(BTNode* root, int k)
{
    if (root == NULL)
        return 0;
    if (k == 1)
        return 1;
    return TreelevelkSize(root->left, k - 1) + TreelevelkSize(root->right, k - 1);
}


// 二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);


int TreeHeight(BTNode* root)
{
    if (root == NULL)
    {
        return 0;
    }
    int left = TreeHeight(root->left);
    int right = TreeHeight(root->right);
    return left > right ? left+1 : right + 1;
}


BTNode* TreeFind(BTNode* root, BTDataType x)
{
    if (root == NULL)
        return NULL;
    if (root->data == x)
        return root;

    BTNode* ret1= TreeFind(root->left, x);
    if (ret1)
        return ret1;

    BTNode* ret2 = TreeFind(root->right, x);
    if (ret2)
        return ret2;

    return NULL;
}

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

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

相关文章

案例分析-系统开发基础

案例分析考点分类&#xff1a; 软件架构设计&#xff1a;考质量属性、软件架构分析(第一题)、软件架构评估、MVC架构、SOA架构、ESB、J2EE架构、DSSA、ABSD等(第二题)、系统开发基础&#xff1a;考UML的图、关系的识别&#xff0c;尤其是类图、用例图、活动图、状态图、设计模式…

Flutter 状态管理框架Get

状态管理框架 Get的使用 目录 状态管理框架 Get的使用 GetMaterialApp 路由的注册 路由的跳转 middlewares的使用 组件使用 defaultDialog bottomSheet snackbar 状态刷新有很多种方式 ValueBuilder Obx 基础使用 是时候引入GetxController, 也是Get里面的常用的 G…

DevOps实践:在GitLab CI/CD中集成静态分析Helix QAC的工作原理与优势

基于云的GitLab CI/CD平台使开发团队能够简化其CI/CD流程&#xff0c;并加速软件开发生命周期&#xff08;SDLC&#xff09;。 将严格的、基于合规性的静态分析&#xff08;如Helix QAC所提供&#xff09;作为新阶段添加到现有的GitLab CI/CD流程中&#xff0c;将进一步增强SD…

华为云购买弹性云服务器(教程)

配置弹性云服务器 基础配置 实例 操作系统

1.CentOS安装

CentOS安装 新建虚拟机 选择安装方式 指定镜像方式 选择操作系统类型 设置虚拟机名称和位置 指定磁盘大小 点击“自定义硬件” 指定内存大小 指定镜像位置 点击“开启此虚拟机” 选择“Install CentOS 7”并回车 选择语言 选择安装“GNOME桌面”环境 配置安装位置 配置网络和…

排序05 排序模型的特征

特征介绍&#xff1a; id embedding&#xff0c;通常用32或64维向量 特征处理 线上服务的系统架构 统计数据是有时效性的&#xff0c;不能缓存在服务器本地。画像可以&#xff0c;保证读取快就好。 tf serving 这里 tensorflow会给笔记打分&#xff0c;分数返回给排序服务器&…

本地docker部署中间件和应用

Docker Desktop搭建 安装完成之后使用docker下载镜像&#xff0c;报以下错误&#xff1a; 解决办法&#xff1a; Docker Engine配置能访问的镜像地址&#xff1a; {"builder": {"gc": {"defaultKeepStorage": "20GB","enabled…

搭建 mongodb 副本集,很详细

搭建 mongodb 副本集&#xff0c;很详细 一、前言二、创建用户1、创建 root 用户2、创建测试用户3、修改用户密码 三、修改配置文件&#xff08;主节点&#xff09;1、开启登录认证2、加上副本集3、最终配置文件 四、副本节点1、创建副本节点目录2、编辑配置文件3、启动副本节点…

【python实操】python小程序之参数化以及Assert(断言)

引言 python小程序之参数化以及Assert&#xff08;断言&#xff09; 文章目录 引言一、参数化2.1 题目2.2 代码2.3 代码解释 二、Assert&#xff08;断言&#xff09;2.1 概念2.1.1 Assert语句的基本语法&#xff1a;2.1.2 基本断言2.1.3 断言函数参数2.1.4 断言前后状态一致 2…

网页HTML编写练习:华语榜中榜

网页效果 HTML代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice…

NLP--一起学习Word Vector【实践】

纸上得来终觉浅&#xff0c;绝知此事要躬行。 《冬夜读书示子聿》 值此1024的程序员节&#xff0c;我们一起学习 Word Vector。 本章一起学习文本向量化&#xff0c;掌握文本向量的相关概念&#xff0c;了解各个文本向量&#xff0c;实现文本向量的算法 我开启了一个NLP共学坊…

arm ubuntu22.04 安装es7.16.2

1、更新软件包 sudo apt update && sudo apt upgrade -y 2、安装jdk11 sudo apt install openjdk-11-jdk -y 安装查看版本 java -version 输出应该是这样的 openjdk version "11.0.11" 2021-04-20 OpenJDK Runtime Environment (build 11.0.119-Ub…

【主机漏洞扫描常见修复方案】:Tomcat安全(机房对外Web服务扫描)

文章目录 引言I SSL/TLS Not ImplementedTomcat 服务器 SSL 证书安装部署(JKS 格式)Tomcat 服务器 SSL 证书安装部署(PFX 格式)HTTP 自动跳转 HTTPS 的安全配置(可选)修复SSL证书版本低II 主机漏洞扫描常见修复方案Apache JServ protocol serviceSlow HTTP DEnial of Ser…

Ubuntu 上安装 Redmine 5.1 指南

文章目录 官网安装文档&#xff1a;命令步骤相关介绍GemRubyRailsBundler 安装 Redmine更新系统包列表和软件包&#xff1a;安装必要的依赖&#xff1a;安装 Ruby&#xff1a;安装 bundler下载 Redmine 源代码&#xff1a;安装 MySQL配置 Redmine 的数据库配置文件&#xff1a;…

54万字WORD电力数字化转型智慧电力一体化监管云平台整体解决方案

▲关注智慧方案文库&#xff0c;学习9000多份最新解决方案&#xff0c;其中 PPT、WORD超过7000多份 &#xff0c;覆盖智慧城市多数领域的深度知识社区&#xff0c;稳定更新4年&#xff0c;日积月累&#xff0c;更懂行业需求。 1459页54万字WORD丨电力行业数字化转型 智慧电力…

【ArcGIS Pro实操第4期】绘制三维地图

【ArcGIS Pro实操第4期】绘制三维地图 ArcGIS Pro绘制三维地图-以DEM高程为例参考 如何使用ArcGIS Pro将栅格数据用三维的形式进行表达&#xff1f;在ArcGIS里可以使用ArcScene来实现&#xff0c;ArcGIS Pro实现原理跟ArcScene一致。由于Esri未来将不再对ArcGIS更新&#xff0c…

53页 PPT煤炭行业数字化转型规划方案

▲关注智慧方案文库&#xff0c;学习9000多份最新解决方案&#xff0c;其中 PPT、WORD超过7000多份 &#xff0c;覆盖智慧城市多数领域的深度知识社区&#xff0c;稳定更新4年&#xff0c;日积月累&#xff0c;更懂行业需求。 53页 PPT煤炭行业数字化转型规划方案 通过对煤企高…

手机玩使命召唤21:黑色行动6?GameViewer远程玩使命召唤教程

使命召唤21&#xff1a;黑色行动 6这个第一人称射击游戏&#xff0c;将于10月25号上线&#xff01;如果你是使命召唤的老玩家&#xff0c;是不是也在期待这部新作&#xff1f;其实这个游戏不仅可以用电脑玩&#xff0c;还可以用手机玩&#xff0c;使用网易GameViewer远程就能让…

【Qt6聊天室项目】 主界面功能实现

1. 获取当前用户的个人信息 1.1 前后端逻辑分析&#xff08;主界面功能&#xff09; 主界面上所有的前后端交互逻辑相同&#xff0c;分析到加载会话列表后其余功能仅实现。 核心逻辑总结 异步请求-响应模型 客户端发起请求&#xff0c;向服务器发送包含会话ID的请求服务端处…

python画图|曲线动态输出

【1】引言 前序教程中的曲线动态输出&#xff0c;其实是把曲线按照左右移动的形式输出&#xff08;波的传递形式&#xff09;。 python画图|曲线动态输出基础教程_python 动态曲线-CSDN博客 但有些时候我们更期待的是曲线不移动&#xff0c;随着自变量的增加而输出因变量&am…