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

news2024/9/21 8:00:21

目录

前言:

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.coloradmin.cn/o/1907328.html

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

相关文章

React Hooks:上天在提醒你,别再用Class组件了!

React Hooks&#xff1a;上天在提醒你&#xff0c;别再用Class组件了&#xff01; React Hooks 的出现可以说是前端界的一场革命。它不仅让我们告别了繁琐的 Class 组件&#xff0c;还让代码变得更加简洁、易读、易维护。如果你还在固守 Class 组件的阵地&#xff0c;那么这篇…

如何处理 PostgreSQL 中由于表锁定导致的并发访问问题?

文章目录 一、表锁定的类型二、表锁定导致的并发访问问题三、解决方案&#xff08;一&#xff09;使用合适的锁定模式&#xff08;二&#xff09;优化事务处理&#xff08;三&#xff09;避免不必要的锁定&#xff08;四&#xff09;使用索引&#xff08;五&#xff09;监控和分…

Java-链表反转

题目&#xff1a; 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 图示&#xff1a; 输入&#xff1a; head [1,2,3,4,5] 输出&#xff1a; [5,4,3,2,1] 解题思路&#xff1a; 情况一&#xff1a; 只有一个节点或者没有节点 …

小白学C++(第一天)基础入门

温馨提醒&#xff1a;本篇文章&#xff0c;请各位c基础不行的童鞋不要贸然观看 C的第一个程序 第一个关键字namespace namespace 是定义空间的名字的关键字&#xff0c;使用格式格式如下&#xff1a; namespace 空间名 { } 其中{ }内的命名空间的成员&#xff0c;可以定义…

计算机图形学入门26:高级光线传播

1.有偏与无偏 在做光线追踪很多方法都是用蒙特卡洛积分去估计&#xff0c;蒙特卡洛积分有些是无偏的(Unbiased)&#xff0c;所谓无偏估计就是无论使用多少个样品&#xff0c;所估计的期望值都是正确的。那么&#xff0c;所有其他情况都是有偏的(Biased)&#xff0c;就是估计的期…

MySQL存储与优化 一、MySQL架构原理

1.MySQL体系架构 MySQL Server架构自顶向下大致可以分网络连接层、服务层、存储引擎层和系统文件层 (1)网络连接层 客户端连接器&#xff08;Client Connectors&#xff09;&#xff1a;提供与MySQL服务器建立的支持。目前几乎支持所有主流的服务端编程技术&#xff0c;例如常…

《初级C++》(一)

初级C&#xff08;一&#xff09; 1: C参考⽂档2&#xff1a;C创建与实现创建C的第一套程序命名空间的理解空间命名的实现C输⼊&输出缺省参数 1: C参考⽂档 https://legacy.cplusplus.com/reference/ 《非官方》 https://zh.cppreference.com/w/cpp 《官方中文版》 https:/…

前端面试题28(Vue3的Teleport功能在什么场景下特别有用?能给个例子吗?)

Vue 3 的 Teleport 功能在需要将组件的渲染结果放置在 DOM 树中与当前组件位置无关的任意位置时特别有用。这通常涉及到需要将某些UI元素&#xff08;如模态框、弹出菜单、通知、工具提示等&#xff09;从其逻辑上的父级组件中“提取”出来&#xff0c;放置到页面的更高层级或完…

PCIe 入门 Demo(一):基础知识

PCIe 入门 Demo&#xff08;一&#xff09;&#xff1a;基础知识 主要参考 up主 芯片人阿伟 的教程【针对 RISC-V 架构】 视频介绍&#xff1a;https://www.bilibili.com/video/BV1Xm4y1V757 源码仓库&#xff1a;https://github.com/oldawei/show_me_the_code 本文主要补充一些…

前端JS特效第24集:jquery css3实现瀑布流照片墙特效

jquery css3实现瀑布流照片墙特效&#xff0c;先来看看效果&#xff1a; 部分核心的代码如下(全部代码在文章末尾)&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8" /> <title>jquerycss3实现瀑…

java中==和equals()的区别探究

目录 一、Object对象 二、 三、String类的equals()方法 四、示例 4.1直接定义两个相同的值比较 4.2直接定义两个值不同的字符串进行比较 4.3直接定义一个字符串和new一个字符串进行比较&#xff08;两者值相同&#xff09; 4.4直接定义一个字符串和new一个字符串进行…

文华财经盘立方期货通鳄鱼指标公式均线交易策略源码

文华财经盘立方期货通鳄鱼指标公式均线交易策略源码&#xff1a; 新建主图幅图类型指标都可以&#xff01; VAR1:(HL)/2; 唇:REF(SMA(VAR1,5,1),3),COLORGREEN; 齿:REF(SMA(VAR1,8,1),5),COLORRED; 颚:REF(SMA(VAR1,13,1),8),COLORBLUE;

Vue3 项目中 svg 图标的封装及使用

安装 npm install vite-plugin-svg-icons -D在 vite.config.ts 中配置插件&#xff1a; import { createSvgIconsPlugin} from vite-plugin-svg-icons; import path from path;plugins: [createSvgIconsPlugin({iconDirs: [path.resolve(process.cwd(), src/assets/icons)],s…

解决win10报“无法加载文件……profile.ps1,因为在此系统上禁止运行脚本”的问题

打开命令行报错 解决方法 使用管理员权限打开PowerShell&#xff1a;WinX, 选择“Windows PowerShell&#xff08;管理员&#xff09;” 输入&#xff1a;Set-ExecutionPolicy -ExecutionPolicy RemoteSigned 输入&#xff1a;y确认修改安全策略 &#xff1a;y确认修改安全策略…

CAD、CAE、CAM介绍——定义,概述,历史,相关软件,应用与前景

目录 引出CAD/CAE/CAM计算机辅助设计 Computer-aided design (CAD)1.定义&#xff1a;2.概述3.类别4.科技5.软件&#xff1a; 计算机辅助工程 Computer-Aided Engineer (CAE)1.定义&#xff1a;2.概述3.CAE领域&#xff1a;4.应用与前景 计算机辅助制造 Computer-aided manufac…

Python自动化测试系列[v1.0.0][高效自动化设计]

Python多线程应用于自动化测试 将多线程在测试巧妙地应用&#xff0c;确实会带来很多好处&#xff0c;并且这是充分利用机器资源执行高效率测试很好的方式 # -*- coding: utf-8 -*- import threading from time import ctime import time from selenium import webdriverdef …

百度网盘非会员,享受视频倍速

百度网盘会员过期了&#xff0c;看视频不能倍速很难受&#xff0c;下面就是跨过会员机制&#xff0c;享受倍速的方法。 Edge浏览器 在浏览器设置中找到扩展选项 在扩展中搜索视频速度控制 global speed&#xff0c;安装后即可使用

品牌推广必备:软文案例撰写与文案策划全解析!

做品牌推广&#xff0c; 不仅需要有推广渠道&#xff0c;文案的策划也是必不可少的一部分。文案是属于灵魂的部分。 作为一名手工酸奶品牌的创始人&#xff0c;目前全国也复制了100多家门店&#xff0c;这篇文章&#xff0c;详细和大家拆解&#xff0c;文案创作的要点&#xf…

金融科技赋能:加马智能质检系统引领金融机构迈向高效合规新时代

为了保护消费者合法权益、促进市场稳定健康发展&#xff0c;近年来监管机构相继发布了《银行保险机构消费者权益保护管理办法》、《银行业金融机构销售专区录音录像管理暂行规定》、《保险销售行为管理办法》等多项法律法规&#xff0c;对于银行、保险等金融机构的服务、销售行…

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

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