数据结构【顺序结构二叉树:堆】(1)

​​​​​​​

🌟个人主页:落叶


目录

🔥树的概念与结构​​​​​​​

🔥树的表⽰

🔥孩⼦兄弟表⽰法:

🔥树形结构实际运⽤场景

🔥⼆叉树

🔥概念与结构

🔥特殊的⼆叉树

🔥满⼆叉树

🔥完全二叉树

🔥⼆叉树存储结构

🔥顺序结构

🔥链式结构

🔥实现[【顺序结构】⼆叉树

🔥堆的概念与结构

🔥堆的实现

🔥堆的数据

🔥初始化堆

🔥入堆

🔥向上调整算法

🔥布尔类型判空

🔥出堆

🔥向下调整

🔥取堆顶

🔥销毁堆

🔥向上调整算法

🔥向下调整算法

🔥堆的应用

🔥堆排序

​编辑🔥TOP-K问题

🔥找前k个最大的数据

🔥找前k个最小的数据

🔥堆的实现代码

Heap.h头文件

Heap.c函数文件

test.c文件


🔥树的概念与结构​​​​​​​

树是⼀种⾮线性的数据结构,它是由 n(n>=0) 个有限结点组成⼀个具有层次关系的集合。把它叫做 树是因为它看起来像⼀棵倒挂的树,也就是说它是根朝上,⽽叶朝下的。

  • 有⼀个特殊的结点,称为根结点,根结点没有前驱结点。
  • 除根结点外,其余结点被分成 M(M>0) 个互不相交的集合 T1、T2、......、Tm ,其中每⼀个集合 Ti(1 ⼜是⼀棵结构与树类似的⼦树。每棵⼦树的根结点有且只有⼀个前驱,可以 有 0 个或多个后继。因此,树是递归定义的。

树形结构中,⼦树之间不能有交集,否则就不是树形结构


非树形结构:

  • ⼦树是不相交的(如果存在相交就是图了)
  • 除了根结点外,每个结点有且仅有⼀个⽗结点 。
  • ⼀棵N个结点的树有N-1条边。
  • **⽗结点/双亲结点:**若⼀个结点含有⼦结点,则这个结点称为其⼦结点的⽗结点;如上图:A是B的⽗ 结点
  • ⼦结点/孩⼦结点:⼀个结点含有的⼦树的根结点称为该结点的⼦结点;如上图:B是A的孩⼦结点
  • 结点的度:⼀个结点有⼏个孩⼦,他的度就是多少;⽐如A的度为6,F的度为2,K的度为0 树的度:⼀棵树中,最⼤的结点的度称为树的度;如上图:树的度为 6
  • **叶⼦结点/终端结点:**度为 0 的结点称为叶结点;如上图: B、C、H、I... 等结点为叶结点
  • **分⽀结点/⾮终端结点:**度不为 0 的结点;如上图: D、E、F、G... 等结点为分⽀结点
  • **兄弟结点:**具有相同⽗结点的结点互称为兄弟结点(亲兄弟);如上图: B、C 是兄弟结点
  • **结点的层次:**从根开始定义起,根为第 1 层,根的⼦结点为第 2 层,以此类推;
  • **树的⾼度或深度:**树中结点的最⼤层次;如上图:树的⾼度为 4
  • **结点的祖先:**从根到该结点所经分⽀上的所有结点;如上图: A 是所有结点的祖先
  • 路径:⼀条从树中任意节点出发,沿⽗节点-⼦节点连接,达到任意节点的序列;⽐如A到Q的路径为: A-E-J-Q;H到Q的路径H-D-A-E-J-Q
  • **⼦孙:**以某结点为根的⼦树中任⼀结点都称为该结点的⼦孙。如上图:所有结点都是A的⼦孙
  • **森林:**由 m(m>0) 棵互不相交的树的集合称为森林;
🔥树的表⽰
🔥孩⼦兄弟表⽰法:

树结构相对线性表就⽐较复杂了,要存储表⽰起来就⽐较⿇烦了,既然保存值域,也要保存结点和结 点之间的关系,实际中树有很多种表⽰⽅式如:双亲表⽰法,孩⼦表⽰法、孩⼦双亲表⽰法以及孩⼦ 兄弟表⽰法等。我们这⾥就简单的了解其中最常⽤的孩⼦兄弟表⽰法

cpp 复制代码
struct TreeNode 
{ 
 struct Node* child; // 左边开始的第⼀个孩⼦结点 
 struct Node* brother; // 指向其右边的下⼀个兄弟结点 
 int data; // 结点中的数据域 
}; 

🔥树形结构实际运⽤场景

⽂件系统是计算机存储和管理⽂件的⼀种⽅式,它利⽤树形结构来组织和管理⽂件和⽂件夹。在⽂件 系统中,树结构被⼴泛应⽤,它通过⽗结点和⼦结点之间的关系来表⽰不同层级的⽂件和⽂件夹之间 的关联。

🔥⼆叉树

🔥概念与结构

在树形结构中,我们最常⽤的就是⼆叉树,⼀棵⼆叉树是结点的⼀个有限集合,该集合由⼀个根结点 加上两棵别称为左⼦树和右⼦树的⼆叉树组成或者为空。

从上图可以看出⼆叉树具备以下特点:

  1. ⼆叉树不存在度⼤于 2 的结点
  2. ⼆叉树的⼦树有左右之分,次序不能颠倒,因此⼆叉树是有序树

注意:对于任意的⼆叉树都是由以下⼏种情况复合⽽成的、


现实中的⼆叉树:

🔥特殊的⼆叉树
🔥满⼆叉树

⼀个⼆叉树,如果每⼀个层的结点数都达到最⼤值,则这个⼆叉树就是满⼆叉树。也就是说,如果⼀ 个⼆叉树的层数为 K ,且结点总数是2 − ,则它就是满⼆叉树。

满二叉树就是每个节点都是满的

🔥完全二叉树

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

⼆叉树性质

根据满⼆叉树的特点可知:

  1. 若规定根结点的层数为 1 ,则⼀棵⾮空⼆叉树的第i层上最多有2的i次方-1个结点
  2. 若规定根结点的层数为 1 ,则深度为 h 的⼆叉树的最⼤结点数是2的h次方减1
  3. 若规定根结点的层数为 1 ,具有 n 个结点的满⼆叉树的深度h = (n+1) ( log 以2为底, n+1 为对数)

🔥⼆叉树存储结构

⼆叉树⼀般可以使⽤两种结构存储,⼀种顺序结构,⼀种链式结构。

🔥顺序结构

顺序结构存储就是使⽤数组来存储,⼀般使⽤数组只适合表⽰完全⼆叉树,因为不是完全⼆叉树会有 空间的浪费,完全⼆叉树更适合使⽤顺序结构存储。

现实中我们通常把堆(⼀种⼆叉树)使⽤顺序结构的数组来存储,需要注意的是这⾥的堆和操作系统 虚拟进程地址空间中的堆是两回事,⼀个是数据结构,⼀个是操作系统中管理内存的⼀块区域分段。

🔥链式结构

⼆叉树的链式存储结构是指,⽤链表来表⽰⼀棵⼆叉树,即⽤链来指⽰元素的逻辑关系。通常的⽅法 是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别⽤来给出该结点左孩⼦和右孩 ⼦所在的链结点的存储地址。链式结构⼜分为⼆叉链和三叉链,当前我们学习中⼀般都是⼆叉链。后 ⾯课程学到⾼阶数据结构如红⿊树等会⽤到三叉链。

🔥实现[【顺序结构】⼆叉树

⼀般堆使⽤顺序结构的数组来存储数据,堆是⼀种特殊的⼆叉树,具有⼆叉树的特性的同时,还具备 其他的特性。

🔥堆的概念与结构

小堆是从小到大

大堆是从大到小

堆具有以下性质:

  • 堆中某个结点的值总是不⼤于或不⼩于其⽗结点的值;
  • 堆总是⼀棵完全⼆叉树。
    ⼆叉树性质:

对于具有 n 个结点的完全⼆叉树,如果按照从上⾄下从左⾄右的数组顺序对所有结点从 0 开始编号,则对于序号为 i 的结点有:

  1. 若 i>0 , i 位置结点的双亲序号: (i-1)/2 ; i=0 , i 为根结点编号,⽆双亲结点
  2. 若 2i+1,左孩⼦序号: 2i+1 , 2i+1>=n 否则⽆左孩⼦
  3. 若 2i+2,右孩⼦序号: 2i+2 , 2i+2>=n 否则⽆右孩⼦

🔥堆的实现

堆底层结构为数组。

创建3个文件,Heap.h头文件,Heap.c函数文件,test.c测试文件


🔥堆的数据

堆底层结构为数组,所以我们用顺序结构。

cpp 复制代码
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<time.h>

typedef int data;
typedef struct Heap
{
	data* arr;//数组
	int size;//有效个数
	int koj;//空间大小
}SL;

🔥初始化堆
cpp 复制代码
//初始化
void csh(SH* r);

把arr置为空,size和koj置为0

cpp 复制代码
//初始化
void csh(SH* r)
{
	assert(r);
	r->arr = NULL;
	r->koj = r->size = 0;
}

🔥入堆
cpp 复制代码
//入堆
void r_duei(SL* r, data x);

🔥向上调整算法

第一步:当前节点数值size-1除2就能得到父亲节点,赋值给fu。

第二步:判断父节点大于当前节点,就交换数据,然后让size走到父亲节点,再找当前节点的父亲节点。

cpp 复制代码
//向上调整
void s_tz(data* arr, int size)
{
	//找父节点
	int fu = (size - 1) / 2;
	while (size > 0)
	{
		//小堆:>
		//大堆: <
		if (arr[fu] < arr[size])
		{
			//交换数据
			jh(&arr[fu], &arr[size]);
			//把父节点的值给size
			size = fu;
			//找当前size的父亲节点
			fu = (size - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

cpp 复制代码
//交换函数
void jh(int* x, int* y)
{
	int tab = *x;
	*x = *y;
	*y = tab;
}

第一步:申请一块数组的空间。

第二步:往数组插入数值。

第三步:调用向上调整函数。调整完后++。

cpp 复制代码
//入堆
void r_duei(SL* r, data x)
{
	assert(r);
	//申请数组空间
	if (r->koj == r->size)
	{
		int a = r->koj == 0 ? 4 : 2 * r->koj;
		data* tab = (data*)realloc(r->arr, a * sizeof(data));
		if (tab == NULL)
		{
			perror("realloc");
			exit(1);
		}
		r->arr = tab;
		r->koj = a;
	}
	//插入数据
	r->arr[r->size] = x;
	//向上调整
	s_tz(r->arr, r->size);
	r->size++;
}

循环将数组里的数值入堆

cpp 复制代码
	SH add;
	//初始化
	csh(&add);
	//小堆
	int arr[] = { 17,20,10,13,19,15 };
	for (int i = 0; i < 6; i++)
	{
		r_duei(&add, arr[i]);
	}

我们可以看到是一个小堆


🔥布尔类型判空

判断有效个数

cpp 复制代码
//判空
bool buer(SL* r)
{
	assert(r);
	return r->size == 0;
}

🔥出堆
cpp 复制代码
//出堆
void c_duei(SL* r);

🔥向下调整

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

删除操作:

调整操作:



第一步:找堆顶的左孩子,赋值给zuo,循环左孩子不能小于有效个数。

第二步:zuo+1大于size说明没有右孩子,左孩子大于右孩子的话,让zuo++到右孩子。

第三步:判断pr下标打印zuo下标进行交换,然后把zuo给pr,让zuo找下一个左孩子。

cpp 复制代码
//向下调整
void x_tz(data* arr, int pr, int size)
{
	//找左孩子
	int zuo = (pr * 2) + 1;
	//小于有效个数
	while (zuo < size)
	{
		//zuo+1就是右孩子,如果大于size就说明没有右孩子 && 大堆:< , 小堆:>
		if (zuo + 1 < size && arr[zuo] < arr[zuo + 1])
		{
			zuo++;
		}
		//大堆:<
		//小堆:>
		if (arr[pr] < arr[zuo])
		{
			//交换
			jh(&arr[pr], &arr[zuo]);
			pr = zuo;
			//找下一个左孩子
			zuo = (pr * 2) + 1;
		}
		else
		{
			break;
		}
	}
}

第一步:堆顶和最后一个的数据进行交换。

第二步:size-1就相当于把最后一个的数据删除。

第三步:向下调整。

cpp 复制代码
//出堆
void c_duei(SL* r)
{
	//判断r不能为空,数组也不能为空
	assert(r && r->arr);

	//堆顶和最后一个元素交换
	jh(&r->arr[0], &r->arr[r->size - 1]);

	//size - 1
	r->size--;

	//向下调整
	x_tz(r->arr, 0, r->size);

}

循环取出堆顶然后打印

cpp 复制代码
	//循环取出堆顶然后打印
	while (!buer(&add))
	{
		//取出堆顶给a
		int a = q_ding(&add);
		printf("%d ", a);
		//出堆
		c_duei(&add);
	}

🔥取堆顶
cpp 复制代码
//取堆顶
int q_ding(SL* r);
cpp 复制代码
//取顶
int q_ding(SL* r)
{
	assert(r && r->arr);
	return r->arr[0];
}

🔥销毁堆
cpp 复制代码
//销毁
void xiaoh(SL* r);
cpp 复制代码
//销毁
void xiaoh(SL* r)
{
	assert(r && r->arr);
	//判断arr是不是空
	if (r->arr != NULL)
	{
		free(r->arr);
	}
	r->arr = NULL;
	r->koj = r->size = 0;
}

🔥向上调整算法

将新数据插⼊到数组的尾上,再进⾏向上调整算法,直到满⾜堆。

向上调整算法

  • 先将元素插⼊到堆的末尾,即最后⼀个孩⼦之后
  • 插⼊之后如果堆的性质遭到破坏,将新插⼊结点顺着其双双亲往上调整到合适位置即可
cpp 复制代码
//向上调整
void s_tz(data* arr, int size)
{
	//找父节点
	int fu = (size - 1) / 2;
	while (size > 0)
	{
		//小堆:>
		//大堆: <
		if (arr[fu] > arr[size])
		{
			//交换数据
			jh(&arr[fu], &arr[size]);
			//把父节点的值给size
			size = fu;
			//找当前size的父亲节点
			fu = (size - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//入堆
void r_duei(SL* r,data x)
{
	assert(r);
	//申请数组空间
	if (r->koj == r->size)
	{
		int a = r->koj == 0 ? 4 : 2 * r->koj;
		data* tab = (data*)realloc(r->arr, a * sizeof(data));
		if (tab == NULL)
		{
			perror("realloc");
			exit(1);
		}
		r->arr = tab;
		r->koj = a;
	}
	//插入数据
	r->arr[r->size] = x;
	//向上调整
	s_tz(r->arr, r->size);
	r->size++;
}

计算向上调整算法建堆时间复杂度

因为堆是完全⼆叉树,⽽满⼆叉树也是完全⼆叉树,此处为了简化使⽤满⼆叉树来证明(时间复杂度本 来看的就是近似值,多⼏个结点不影响最终结果)

分析: 第1层,2 个结点,需要向上移动0层 0

第2层,2 个结点,需要向上移动1层 1

第3层,2 个结点,需要向上移动2层 2

第4层,2 个结点,需要向上移动3层 3 ......第h层,2 个结点,需要向上移动h-1层

由此可得: 向上调整算法建堆时间复杂度为:O(n ∗ log2 n)

🔥向下调整算法

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

向下调整算法有⼀个前提:左右⼦树必须是⼀个堆,才能调整。

向下调整算法:

  • 将堆顶元素与堆中最后⼀个元素进⾏交换
  • 删除堆中最后⼀个元素
  • 将堆顶元素向下调整到满⾜堆特性为⽌
cpp 复制代码
//向下调整
void x_tz(data* arr,int pr,int size)
{
	//找左孩子
	int zuo = (pr * 2) + 1;
	//小于有效个数
	while (zuo < size)
	{
		//zuo+1就是右孩子,如果大于size就说明没有右孩子 && 大堆:< , 小堆:>
		if (zuo + 1 < size   && arr[zuo] > arr[zuo + 1])
		{
			zuo++;
		}
		//大堆:<
		//小堆:>
		if (arr[pr] > arr[zuo])
		{
			//交换
			jh(&arr[pr], &arr[zuo]);
			pr = zuo;
			//找下一个左孩子
			zuo = (pr * 2) + 1;
		}
		else
		{
			break;
		}
	}
}



//出堆
void c_duei(SL* r)
{
	//判断r不能为空,数组也不能为空
	assert(r && r->arr);

	//堆顶和最后一个元素交换
	jh(&r->arr[0], &r->arr[r->size - 1]);

	//size - 1
	r->size--;

	//向下调整
	x_tz(r->arr, 0, r->size);

}

计算向下调整算法建堆时间复杂度

向下调整算法建堆时间复杂度为:O(n)

🔥堆的应用

🔥堆排序

版本⼀:基于已有数组建堆、取堆顶元素完成排序版本

cpp 复制代码
// 1、需要堆的数据结构
// 2、空间复杂度 O(N)
void HeapSort(int* a, int n)
{
 HP hp;
 for(int i = 0; i < n; i++)
 {
 HPPush(&hp,a[i]);
 }
 int i = 0;
 while (!HPEmpty(&hp))
 {
 a[i++] = HPTop(&hp);
 HPPop(&hp);
 }
 HPDestroy(&hp);
}

该版本有⼀个前提,必须提供有现成的数据结构堆


建议用版本⼆。

版本⼆:数组建堆,⾸尾交换,交换后的堆尾数据从堆中删掉,将堆顶数据向下调整选出次⼤的数据

cpp 复制代码
//堆排序
void duei_px(int *arr,int size)
{
	//建堆
	//升序用--大堆
	//降序用--小堆
	
	//向上调整算法
	//for (int i = 0; i < size; i++)
	//{
	//	//向上调整成
	//	s_tz(arr, i);
	//}
	
	//向下调整算法
	for (int i = (size-1-1)/2; i >= 0; i--)
	{
		x_tz(arr, i, size);
	}

	int i = size - 1;
	//循环将堆顶和最后一个位置(会变化,每次减少一个数值)的数据进行交换
	while (i > 0)
	{
		//交换
		jh(&arr[0], &arr[i]);
		//从堆顶位置开始向下调整
		x_tz(arr, 0, i);
		i--;
	}
}

向上调整算法建堆

每插入一个数值都会判断,要不要向上调整


向下调整算法建堆

通过(size-1)-1/2就能得到父亲节点,通过父亲节点往后进行向下调整。

向上调整算法建堆和向下调整算法建堆,都能建堆,随便选择一个就行了。

我们可以看到堆已经弄好了


开始排序

第一步:size-1就是最后一个数值的下标,赋值给i。

第二步:堆顶和最后一个数值进行交换。

第三步:从堆顶位置开始向下调整。

第四步:i减1。


堆排序时间复杂度计算

🔥TOP-K问题

TOP-K问题:即求数据结合中前K个最⼤的元素或者最⼩的元素,⼀般情况下数据量都⽐较⼤。 ⽐如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。 对于Top-K问题,能想到的最简单直接的⽅式就是排序,但是:如果数据量⾮常⼤,排序就不太可取了 (可能数据都不能⼀下⼦全部加载到内存中)。最佳的⽅式就是⽤堆来解决,基本思路如下:

⽤数据集合中前K个元素来建堆:

  • 前k个最⼤的元素,则建⼩堆
  • 前k个最⼩的元素,则建⼤堆

⽤剩余的N-K个元素依次与堆顶元素来⽐较,不满⾜则替换堆顶元素。

将剩余N-K个元素依次与堆顶元素⽐完之后,堆中剩余的K个元素就是所求的前K个最⼩或者最⼤的元 素

我们先在文件中创建很多个数据:

往文件里数据写入10万个数值。

cpp 复制代码
void CreateNDate()
{
	// 造数据
	int n = 100000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}
	for (int i = 0; i < n; ++i)
	{
		int x = (rand() + i) % 1000000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}
🔥找前k个最大的数据

k是要读多少个数值,然后读取文件信息。


申请一块数组空间用来存放数据。


读取前k个数值放到koj数组里


从最后一个数值的父亲节点,进行向下调整把数组变成堆。


循环取出数值到x里,让x和堆顶进行比较,x大于堆顶就把x数值给堆顶。

然后向下调整。


最后循环打印和关闭文件就好了。

我们可以看到打印了4个很大的数值。


🔥找前k个最小的数据

前k个最⼩的元素,则建⼤堆

建堆的时候建个大堆就行了


这个堆顶大于(>)x的时候,把x赋值赋值给堆顶。


cpp 复制代码
void ppp()
{
	int k = 0;
	printf("k: ");
	scanf("%d", &k);
	const char* a = "data.txt";
    //读取文件信息
	FILE* tab = fopen(a, "r");
	if (tab == NULL)
	{
		perror("fopen");
		exit(1);
	}
	//给k申请空间
	int* koj = (int*)malloc(k * sizeof(int));
	if (koj == NULL)
	{
		perror("malloc");
		exit(2);
	}
	//从文件里读取前k个数据,存放到koj里
	for (int i = 0; i < k; i++)
	{
		fscanf(tab, "%d", &koj[i]);
	}
	//把koj里数据,建堆
	for (int i = ((k - 1) - 1) / 2; i >= 0; i--)
	{
		//向下调整
		x_tz(koj, i, k);
	}

	int x = 0;
	//循环取出文件数据放到x
	while (fscanf(tab, "%d", &x) != EOF)
	{
		//把x数据和堆顶进行比较
		if (koj[0] < x)
		{
			koj[0] = x;
			//向下调整
			x_tz(koj, 0, k);
		}
	}
	//打印
	for (int i = 0; i < k; i++)
	{
		printf("%d ", koj[i]);
	}

	//关闭文件
	fclose(tab);
}

🔥堆的实现代码

Heap.h头文件
cpp 复制代码
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<time.h>

typedef int data;
typedef struct Heap
{
	data* arr;//数组
	int size;//有效个数
	int koj;//空间大小
}SL;

//初始化
void csh(SL* r);

//交换函数
void jh(int* x, int* y);

//入堆
void r_duei(SL* r,data x);

//出堆
void c_duei(SL* r);

//判空
bool buer(SL* r);

//取堆顶
int q_ding(SL* r);

//向上调整
void s_tz(data* arr, int size);
//向下调整
void x_tz(data* arr, int pr, int size);

//销毁
void xiaoh(SL* r);
Heap.c函数文件
cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"

//初始化
void csh(SL* r)
{
	assert(r);
	r->arr = NULL;
	r->koj = r->size = 0;
}


//销毁
void xiaoh(SL* r)
{
	assert(r && r->arr);
	//判断arr是不是空
	if (r->arr != NULL)
	{
		free(r->arr);
	}
	r->arr = NULL;
	r->koj = r->size = 0;
}


//交换函数
void jh(int* x, int* y)
{
	int tab = *x;
	*x = *y;
	*y = tab;
}


//向上调整
void s_tz(data* arr, int size)
{
	//找父节点
	int fu = (size - 1) / 2;
	while (size > 0)
	{
		//小堆:>
		//大堆: <
		if (arr[fu] < arr[size])
		{
			//交换数据
			jh(&arr[fu], &arr[size]);
			//把父节点的值给size
			size = fu;
			//找当前size的父亲节点
			fu = (size - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//入堆
void r_duei(SL* r,data x)
{
	assert(r);
	//申请数组空间
	if (r->koj == r->size)
	{
		int a = r->koj == 0 ? 4 : 2 * r->koj;
		data* tab = (data*)realloc(r->arr, a * sizeof(data));
		if (tab == NULL)
		{
			perror("realloc");
			exit(1);
		}
		r->arr = tab;
		r->koj = a;
	}
	//插入数据
	r->arr[r->size] = x;
	//向上调整
	s_tz(r->arr, r->size);
	r->size++;
}



//向下调整
void x_tz(data* arr,int pr,int size)
{
	//找左孩子
	int zuo = (pr * 2) + 1;
	//小于有效个数
	while (zuo < size)
	{
		//zuo+1就是右孩子,如果大于size就说明没有右孩子 && 大堆:< , 小堆:>
		if (zuo + 1 < size   && arr[zuo] < arr[zuo + 1])
		{
			zuo++;
		}
		//大堆:<
		//小堆:>
		if (arr[pr] < arr[zuo])
		{
			//交换
			jh(&arr[pr], &arr[zuo]);
			pr = zuo;
			//找下一个左孩子
			zuo = (pr * 2) + 1;
		}
		else
		{
			break;
		}
	}
}



//出堆
void c_duei(SL* r)
{
	//判断r不能为空,数组也不能为空
	assert(r && r->arr);

	//堆顶和最后一个元素交换
	jh(&r->arr[0], &r->arr[r->size - 1]);

	//size - 1
	r->size--;

	//向下调整
	x_tz(r->arr, 0, r->size);

}



//判空
bool buer(SL* r)
{
	assert(r);
	return r->size == 0;
}



//取顶
int q_ding(SL* r)
{
	assert(r && r->arr);
	return r->arr[0];
}
test.c文件
cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
//堆排序
void duei_px(int* arr, int size)
{
	//建堆
	//升序用--大堆
	//降序用--小堆

	向上调整算法
	//for (int i = 0; i < size; i++)
	//{
	//	//向上调整成
	//	s_tz(arr, i);
	//}

	//向下调整算法
	for (int i = (size - 1 - 1) / 2; i >= 0; i--)
	{
		//向下调整
		x_tz(arr, i, size);
	}

	int i = size - 1;
	//循环将堆顶和最后一个位置(会变化,每次减少一个数值)的数据进行交换
	while (i > 0)
	{
		//交换
		jh(&arr[0], &arr[i]);
		//从堆顶位置开始向下调整
		x_tz(arr, 0, i);
		i--;
	}
}


void CreateNDate()
{
	// 造数据
	int n = 100000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}
	for (int i = 0; i < n; ++i)
	{
		int x = (rand() + i) % 1000000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}



void ppp()
{
	int k = 0;
	printf("k: ");
	scanf("%d", &k);
	const char* a = "data.txt";
	//读取文件信息
	FILE* tab = fopen(a, "r");
	if (tab == NULL)
	{
		perror("fopen");
		exit(1);
	}


	//申请空间一块空间
	int* koj = (int*)malloc(k * sizeof(int));
	if (koj == NULL)
	{
		perror("malloc");
		exit(2);
	}


	//从文件里读取前k个数据,存放到koj里
	for (int i = 0; i < k; i++)
	{
		fscanf(tab, "%d", &koj[i]);
	}


	//把koj里数据,建堆
	for (int i = ((k - 1) - 1) / 2; i >= 0; i--)
	{
		//向下调整
		x_tz(koj, i, k);
	}


	int x = 0;
	//循环取出文件数据放到x
	while (fscanf(tab, "%d", &x) != EOF)
	{
		//把x数据和堆顶进行比较
		if (koj[0] > x)
		{
			koj[0] = x;
			//向下调整
			x_tz(koj, 0, k);
		}
	}


	//打印
	for (int i = 0; i < k; i++)
	{
		printf("%d ", koj[i]);
	}

	//关闭文件
	fclose(tab);
}

void p()
{
	SL add;
	//初始化
	csh(&add);
	//小堆
	int arr[] = { 17,20,10,13,19,15 };
	for (int i = 0; i < 6; i++)
	{
		r_duei(&add, arr[i]);
	}


	//循环取出堆顶然后打印
	while (!buer(&add))
	{
		//取出堆顶给a
		int a = q_ding(&add);
		printf("%d ", a);
		//出堆
		c_duei(&add);
	}

	打印
	//for (int i = 0; i < 6; i++)
	//{
	//	printf("%d ", arr[i]);
	//}
	//printf("\n");
}



int main()
{
	p();
	//CreateNDate();
	//int arr[] = { 17,20,10,13,19,15 };
	//duei_px(arr, 6);
	//for (int i = 0; i < 6; i++)
	//{
	//	printf("%d ", arr[i]);
	//}
	//ppp();
	return 0;

}
相关推荐
鸣弦artha几秒前
蓝桥杯——数组
java·数据结构·算法·蓝桥杯·eclipse·排序算法
JovaZou14 分钟前
[Python学习日记-67] 封装
开发语言·python·学习
hlsd#14 分钟前
go 集成swagger 在线接口文档
开发语言·后端·golang
Java Fans19 分钟前
深入探索R语言在机器学习中的应用与实践
开发语言·机器学习·r语言
算你狠 - ZGX24 分钟前
软件设计师 - 第3章 数据结构
数据结构·软件设计师
草原上唱山歌25 分钟前
C++需要学习哪些内容?
开发语言·c++·学习
lly20240631 分钟前
SQLite Where 子句
开发语言
Clrove.1143 分钟前
C++初阶——queue
开发语言·c++
挥剑决浮云 -1 小时前
Linux 网络编程
linux·运维·c语言·网络·笔记
析木不会编程1 小时前
【数据结构】【线性表】循环链表(附C语言源码)
c语言·数据结构·链表