数据结构——二叉树之c语言实现堆与堆排序

目录

前言:

1.二叉树的概念及结构

1.1 特殊的二叉树 

1.2 二叉树的存储结构

   1.顺序存储

2.链式存储 

2. 二叉树的顺序结构及实现 

2.1 堆的概念 

  ​编辑

2.2 堆的创建

3.堆的实现

3.1 堆的初始化和销毁 

初始化:

销毁: 

插入:

向上调整:

删除: 

向下调整: 

堆顶元素: 

判空: 

 4.堆排序

4.1排序实现

 


前言:

   在上一期我们介绍了有关于树的基础概念,了解了关于树的各名称的含义,然而在现实中树被用得最多的场景还是在我们计算机中的资源管理器的文件存储结构中,在其他场景被使用的情况很少,所以我们这一期要介绍一种被广泛使用的树型结构——二叉树。

1.二叉树的概念及结构

  顾名思义,二叉树是由一个根结点和两棵子树构成,二叉树的每个结点最多只有两个结点:

从上图可以看出:

1. 二叉树不存在度大于2的结点

2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树  

二叉树是由以下几种情况复合而成的:

 

现实中的二叉树:

1.1 特殊的二叉树 

 1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数K次方-1,则它就是满二叉树。

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

1.2 二叉树的存储结构

  二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

   1.顺序存储

  顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。二叉树顺 序存储在物理上是一个数组,在逻辑上是一颗二叉树。 

2.链式存储 

    二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是 链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所 在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面课程 学到高阶数据结构如红黑树等会用到三叉链。

 

2. 二叉树的顺序结构及实现 

  普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结 构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段

2.1 堆的概念 

  

堆的性质:

1.堆中某个结点的值总是不大于或不小于其父结点的值。

2.堆总是一棵完全二叉树。 

 

2.2 堆的创建

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

3.堆的实现

 介绍完堆的概念和性质之后,我们接下来就要来用代码实现堆及堆的各个方法。由于堆是顺序结构实现的,所以我们选择使用顺序表来实现它:

typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;

3.1 堆的初始化和销毁 

  堆是用顺序表来实现的,而顺序表的空间都是我们手动在内存中的堆中开辟的,所以也需要手动释放,而在程序最初运行时我们也要对它进行初始化。

初始化:

void HPInit(HP* php)
{assert(php);php->a = NULL;php->size = php->capacity = 0;
}//初始化

销毁: 

void HPDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->capacity = php->size = 0;
}//销毁

插入:

 在插入数据之前,我们选确定空间够不够,如果city等于cpapcity,我们就判断空间满了,需要扩容,然后插入数据,而要实现建堆的话,我们还需要使用向上调整方法实现:

void HPPush(HP* php, HPDataType x)
{assert(php);if (php->capacity == php->size){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("realloc fail!");}php->a = tmp;php->capacity = newcapacity;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size-1);//向上调整
}//插入

向上调整:

   向上调整是建堆的关键,在我们插入一个数据时,我们之前建的堆可能会遭到破坏,这时就需要重新调整建堆,我们插入操作是尾插,用堆来表示的话它就是在堆低,这时我们就要向上调整,如果我们建的是小堆,那么我们就要判断我们插入的结点与它的父结点的大小关系,如果它比它的父结点小的话。那么就要与它的父结点交换位置,走到下一轮,如果它还是小于自己的父节点,那么继续执行交换操作,直到数组变成一个小堆:

代码实现:

void AdjustUp(HPDataType* a,  int child)
{int parent = (child - 1) / 2;while (child>0){if (a[child] < a[parent]){swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;;}else{break;}}
}

删除: 

  有插入操作就必然有删除操作,那么我们如何实现删除操作呢?如果我们直接进行头删,那么我们建的堆就会被破环,如果尾删的话,那么堆就没有意义了(后面详细解释),所以我们先让堆中第一个元素与最后一个元素交换,然后再让size减一,而这时我们建的堆被破环了,所以还需要使用向下调整方法来重新建堆,而向下建堆的算法也比较简单,先找出第一个结点更小的那个子结点,只要这个结点比它的父结点小就让它们交换位置,如此循环往复,直到走到堆尾:

删除代码实现:

void HPPop(HP* php)
{assert(&php);assert(php->size > 0);swap(&(php->a[0]), &(php->a[php->size - 1]));php->size--;AdjustDown(php->a,php->size,0);//向下调整
}//删除

向下调整: 

void AdjustDown(HPDataType* a, int n, int parent)
{//假设更小的孩子是左孩子int child = parent * 2 + 1;while (child<n)//child>=n说明孩子已经不存在{if (child+1<n&&a[child + 1] < a[child]){child++;}if (a[child] < a[parent]){swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}//向下调整

堆顶元素: 

HPDataType HPTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}//堆顶元素

判空: 

bool HPEmpty(HP* php)
{assert(php);return php->size = 0;
}//判空

 4.堆排序

 堆排序是一种速度很快的排序算法,冒泡排序的时间复杂度为O(N^2),而堆排序的时间复杂度仅为O(logN),学完堆,我们就可以来试着实现堆排序了。

4.1排序实现

 我们先创建一个无序数组:

int a[] = { 8,6,5,3,9,0,7,1,4,2 };

现在这个数组不是堆,我们堆排序的第一步就是先建堆呢,可以使用向下调整吗,答案是不可以,只有下面的子树都是堆时才可以使用,而现在这棵树仅是一个无序数组,所以我们选择从后往前建堆,什么意思呢,我们可以把这组树看成一棵一棵树:

 

我们发现,从9开始,往上每一个结点都有自己的子结点,这就意味着从就开始,每往前走一步就是一棵树,所以我们只要从9开始使用向下调整建堆,每往前走一步就可以实现一棵树的建堆,而走到8时,整棵树也就完成了建堆:

int a[] = { 8,6,5,3,9,0,7,1,4,2 };
int len = sizeof(a) / sizeof(int);
for (int i = (len - 1 - 1) / 2; i >= 0; i--)
{AdjustDown(a, len, i);
}//建堆

这个算法到底怎么样呢?我们运行一下程序看看:

 

我们将这些数字摆成一棵二叉树:

 

从上图可以看出,这组数字摆成一棵二叉树它就是一个标准的堆。 

     成功建堆之后,我们就可以来使用堆来排序了,从上图可以看出,我们建的是小堆,如果我们要实现降序,可以使用小堆实现吗?答案是可以,而且经过实验,我们得出结论:升序:建大堆降序:建小堆,所以我们使用小堆来实现降序是没有问题的。如何实现呢,我们可以先创建一个变量end指向最后一个结点,然后让第一个结点和尾结点交换,因为第一个结点是整个堆最小的数,交换位置之后,最小的数就在最后一个结点了,我们让end向前走一步,然后使用向下调整让堆第二小的数字走到第一个结点,然后再和end指向的结点交换,循环往复之后最大的数就走到了第一个结点,而我们也完成了降序排序:

while (end > 0){swap(&a[0], &a[end]);end--;AdjustDown(a, end, 0);}//调整

来看看结果:

 

 下面是完整代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
void swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
void AdjustDown(int* 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[child] < a[parent]){swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void test()
{int a[] = { 8,6,5,3,9,0,7,1,4,2 };int len = sizeof(a) / sizeof(int);for (int i = (len - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, len, i);}//建堆int end = len - 1;while (end > 0){swap(&a[0], &a[end]);end--;AdjustDown(a, end, 0);}//调整for (int i = 0; i < len; i++){printf("%d ", a[i]);}
}
int main()
{test();return 0;
}

到这里我们的堆就结束了,我将代码放在下面,感兴趣的小伙伴可以试试哦。

Heap.h :

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;void HPInit(HP* php);//初始化
void HPDestroy(HP* php);//销毁
void HPPush(HP* php, HPDataType x);//插入
void HPPop(HP* php);//删除
HPDataType HPTop(HP* php);//堆顶元素
void AdjustUp(HPDataType* a, int child);//向上调整
void AdjustDown(HPDataType* a, int n,int parent);//向下调整
bool HPEmpty(HP* php);//判空

Heap.c :

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"void HPInit(HP* php)
{assert(php);php->a = NULL;php->size = php->capacity = 0;
}//初始化
void swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}//交换
void AdjustDown(HPDataType* a, int n, int parent)
{//假设更小的孩子是左孩子int child = parent * 2 + 1;while (child<n)//child>=n说明孩子已经不存在{if (child+1<n&&a[child + 1] < a[child]){child++;}if (a[child] < a[parent]){swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}//向下调整
void AdjustUp(HPDataType* a,  int child)
{int parent = (child - 1) / 2;while (child>0){if (a[child] < a[parent]){swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;;}else{break;}}
}
//向上调整
void HPPush(HP* php, HPDataType x)
{assert(php);if (php->capacity == php->size){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("realloc fail!");}php->a = tmp;php->capacity = newcapacity;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size-1);//向上调整
}//插入
void HPPop(HP* php)
{assert(&php);assert(php->size > 0);swap(&(php->a[0]), &(php->a[php->size - 1]));php->size--;AdjustDown(php->a,php->size,0);//向下调整
}//删除
HPDataType HPTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}//堆顶元素
bool HPEmpty(HP* php)
{assert(php);return php->size = 0;
}//判空void HPDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->capacity = php->size = 0;
}//销毁

test.c :

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
void test()
{int a[] = { 4,9,0,2,5,3,7,1,8,6 };HP hp;HPInit(&hp);for (size_t i = 0; i < sizeof(a) / sizeof(int); i++){HPPush(&hp, a[i]);}while (hp.size){printf("%d ", hp.a[hp.size - 1]);hp.size--;}HPDestroy(&hp);
}
void test02()
{int a[] = { 4,9,0,2,5,3,7,1,8,6 };HP hp;HPInit(&hp);for (size_t i = 0; i < sizeof(a) / sizeof(int); i++){HPPush(&hp, a[i]);}while (hp.size>0){int top = HPTop(&hp);printf("%d ", top);HPPop(&hp);}HPDestroy(&hp);}
void test03()
{int a[] = { 4,9,0,2,5,3,7,1,8,6 };size_t len = sizeof(a) / sizeof(int);for (int i = (len - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, len, i);}int end = len - 1;while (end>0){swap(&a[0], &a[end]);AdjustDown(a, end, 0);end--;}for (int i = 0; i < len; i++){printf("%d ", a[i]);}
}
int main()
{//test02();test03();return 0;
}

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

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

相关文章

华为HCIP Datacom H12-821 卷28

1.单选题 下面是一台路由器的部分配置,关于该部分配置描述正确的是,[HUAWEI]ip ip-prefx pl permit 10.0.192.0 8greater-equal17 less-equal 18 A、10.0.192.0/8网段内,掩码长度为18的路由会匹配到该前缀列表,匹配规则为允许 B、10.0.192.0/8网段内掩码长度为21的路…

开源屏幕分享项目:轻量好用无延迟!!【送源码】

想必大家在日常的工作中&#xff0c;会经常需要分享代码、演示项目或者进行在线教学&#xff0c;这就需要一个既高效又便捷的屏幕共享工具。然而&#xff0c;现有的一些解决方案往往存在延迟高、画质差等问题。 今天就分享一个开源的屏幕共享项目 - screego&#xff0c;不但免…

WIN32核心编程 - 文件系统 - 文件操作 - 目录操作

公开视频 -> 链接点击跳转公开课程博客首页 -> 链接点击跳转博客主页 目录 文件系统基本概念 文件操作(基本) 文件操作(属性) 文件系统基本概念 文件&#xff08;File&#xff09;&#xff1a;计算机中存储数据的基本单位。文件可以是文本文件、图像文件、音频文件、视…

PHP灵活用工任务小灵通微信小程序系统源码

&#x1f4bc;灵活赚钱新风尚&#xff01;灵活用工任务小灵通微信小程序&#xff0c;兼职自由两不误&#x1f680; &#x1f50d; 一、海量任务&#xff0c;随时随地接单赚外快 还在为找不到合适的兼职而烦恼吗&#xff1f;&#x1f914; 灵活用工任务小灵通微信小程序&#…

Java-Sql注入以及如何解决

sql脚本注入: 如果sql语句使用字符串拼接&#xff0c;可能会出现字符串的拼接&#xff0c;导致sql注入。 #是会先进行预编译&#xff0c;传进来的参数通过占位符填入到已经完成编译的语句中去。

paddleocr运行报错?谈谈解决思路。

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

代码随想录算法训练营第四十七天|1143.最长公共子序列、 1035.不相交的线、53. 最大子序和、392.判断子序列

1143.最长公共子序列 题目链接&#xff1a;1143.最长公共子序列 文档讲解&#xff1a;代码随想录 状态&#xff1a;一开始没想明白为啥要 max(dp[i - 1][j], dp[i][j - 1]) 思路&#xff1a; 如果text1[i - 1] 与 text2[j - 1]相同&#xff0c;那么找到了一个公共元素&#xff…

亚马逊个人卖家掌控物流,教你在单个ERP端口上实现全自动发货

亚马逊个人卖家可对接20多家国际物流&#xff0c;个人如何发货打单&#xff1f; 大家好&#xff0c;今天介绍这款erp有了订单后怎么发货。个人ERP在选择发货的时候只能选择中转仓&#xff0c;这是要把货发给ERP的商家&#xff0c;由商家代打包&#xff0c;打包费。这块开发了自…

协议转换网关的工作原理-天拓四方

在当今数字化和网络化的社会中&#xff0c;不同系统和设备之间的通信至关重要。然而&#xff0c;由于技术多样性、厂商差异以及应用需求的复杂性&#xff0c;不同的系统和设备常常采用不同的通信协议&#xff0c;这使得它们之间的直接通信变得困难。为了解决这一问题&#xff0…

新型过滤沉淀池设备优点

新型过滤沉淀池设备优点 磁混凝一体化设备优点&#xff1a; &#xff08;1&#xff09;磁混凝水头损失较少&#xff0c;本质上是混凝沉淀工艺&#xff0c;较过滤水头损失很少&#xff0c;而出水达到过滤的效果。磁混凝滤池低水位差约0.6m&#xff0c;主要体现在沉淀池出水槽跌水…

如何利用Java进行大数据处理?

如何利用Java进行大数据处理&#xff1f; 大家好&#xff0c;我是微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 1. 引言 在当今信息爆炸的时代&#xff0c;处理大数据是许多应用程序和系统的核心需求之一。Java作为一种…

单片机软件架构连载(5)-队列

前面讲了指针、结构体之类的基础知识。 这篇内容开始&#xff0c;就要对这些基础知识&#xff0c;做一些复杂的应用了&#xff0c;比如说队列。 其实&#xff0c;在2018年的时候&#xff0c;我录制过一套程序架构的视频&#xff0c;里面有手把手写队列的教程&#xff0c;讲了一…

中国计量大学理学院访问赛氪网:共探校企合作新篇章来

2024年7月5日&#xff0c;中国计量大学理学院代表团莅临环球赛乐&#xff08;北京&#xff09;科技有限公司&#xff0c;进行了一场深入的调研交流活动。代表团成员包括中国计量大学理学院副院长王义康教授、数据科学系副主任刘学艺副教授以及金世举老师。此次访问旨在进一步强…

暑期限定|get你的联邦学习技能,隐私计算暑期夏令营开启报名!

伴随着数字经济时代的来临&#xff0c;数据的安全流通和隐私保护也迎来了新的发展和挑战。隐私技术作为关键技术&#xff0c;可以在保护数据安全的同时&#xff0c;联合多方进行安全计算。 “隐语”是蚂蚁集团于2022年开源的一套可信隐私计算技术框架&#xff0c;支持了包括多…

复合机器人:手脚眼脑的完美结合

在现代工业制造的舞台上&#xff0c;复合机器人如同一位精密而高效的工匠&#xff0c;以其独特的手脚眼脑&#xff0c;正深刻改变着传统的生产方式。这些机器人不仅仅是机械臂的简单延伸&#xff0c;它们汇聚了先进的机械结构、智能的感知系统、精密的控制技术和灵活的思维能力…

js 数组合并方式

1. 使用 concat 方法 》不改变原数组 concat 方法可以将多个数组或值合并成一个新数组。 const arr1 [1, 2, 3] const arr2 [4, 5, 6] const mergedArr arr1.concat(arr2) // [1, 2, 3, 4, 5, 6]2. 使用展开运算符 (...) 》不改变原数组 展开运算符 ... 可以用于展开数组…

Web应用安全实用建议

引言 随着互联网的飞速发展&#xff0c;Web应用已成为企业与用户互动的重要桥梁。然而&#xff0c;Web应用面临着各种安全威胁&#xff0c;这些威胁不仅可能导致数据泄露&#xff0c;还会损害企业的声誉&#xff0c;甚至造成经济损失。为了确保Web应用的安全性&#xff0c;必须…

【虚拟机安装centos7.6 yum使用报错 配置镜像源 错误记录】

错误 [rootlocalhost ~]# yum -y update 已加载插件&#xff1a;fastestmirror Loading mirror speeds from cached hostfileOne of the configured repositories failed (未知),and yum doesnt have enough cached data to continue. At this point the onlysafe thing yum c…

「数据结构」和「数据类型两个概念的本质是什么区别与联系是什么

貌似数据结构中包含了数据类型&#xff0c;而数据类型又建立在数据结构之上&#xff1f; 就像有人在其他评论里说的&#xff0c;数据本质上是没有类型的。我们都知道&#xff0c;数据在存储上是一堆01的数字&#xff0c; 刚好我有一些资料&#xff0c;是我根据网友给的问题精心…

神卓互联共享文件使用教程

#文件共享# 文件共享已成为我们日常生活和工作中不可或缺的一部分。它如同一条无形的纽带&#xff0c;将人们紧密地联系在一起&#xff0c;促进了信息的快速传播和交流。 文件共享的魅力在于其打破了地域和时间的限制。无论我们身处世界的哪个角落&#xff0c;只要有网络连接&a…