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

news2024/11/25 14:51:16

​​​​​​​

🌟个人主页:落叶


目录

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

🔥树的表⽰

🔥孩⼦兄弟表⽰法:

🔥树形结构实际运⽤场景

🔥⼆叉树

🔥概念与结构

🔥特殊的⼆叉树

🔥满⼆叉树

🔥完全二叉树

🔥⼆叉树存储结构

🔥顺序结构

🔥链式结构

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

🔥堆的概念与结构

🔥堆的实现

🔥堆的数据

🔥初始化堆

🔥入堆

🔥向上调整算法

🔥布尔类型判空

🔥出堆

🔥向下调整

🔥取堆顶

🔥销毁堆

🔥向上调整算法

🔥向下调整算法

🔥堆的应用

🔥堆排序

​编辑🔥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) 棵互不相交的树的集合称为森林;
🔥树的表⽰
🔥孩⼦兄弟表⽰法:

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

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 = \log_{2}(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测试文件


🔥堆的数据

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

#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(SH* r);

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

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

🔥入堆

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

🔥向上调整算法


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

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

//向上调整
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 jh(int* x, int* y)
{
	int tab = *x;
	*x = *y;
	*y = tab;
}

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

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

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

//入堆
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++;
}

循环将数组里的数值入堆

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

我们可以看到是一个小堆


🔥布尔类型判空

判断有效个数

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

🔥出堆

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

🔥向下调整

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

删除操作:

调整操作:



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

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

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

//向下调整
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就相当于把最后一个的数据删除。

第三步:向下调整。

//出堆
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);

}

循环取出堆顶然后打印

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

🔥取堆顶

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


//取顶
int q_ding(SL* r)
{
	assert(r && r->arr);
	return r->arr[0];
}

🔥销毁堆

//销毁
void xiaoh(SL* r);

//销毁
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 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)

🔥向下调整算法

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

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

向下调整算法:

  • 将堆顶元素与堆中最后⼀个元素进⾏交换
  • 删除堆中最后⼀个元素
  • 将堆顶元素向下调整到满⾜堆特性为⽌

//向下调整
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)

🔥堆的应用

🔥堆排序

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

// 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);
}

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


建议用版本⼆。

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

//堆排序
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万个数值。

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赋值赋值给堆顶。


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头文件
#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函数文件
#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文件
#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;

}

 

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

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

相关文章

day4JS-数组

1. 什么是数组 数组是值的有序集合。每个值叫做一个元素。每个元素在数组中有一个位置, 以数字表示,称为索引 (有时也称为下标)。数组的元素可以是任何类型。数组索引从 0 开始,数组最大能容纳 4294967295 个元素。 2. 数组的声明与使用 2.1 使用字面量的方式声明数组 语法…

Minio web控制台实现授权管理

启动minio ./minio server /data01/aidacp/apps/minio/data --config-dir /data01/aidacp/apps/minio/conf --address 127.0.0.1:19090 --console-address 127.0.0.1:19091 &WARNING: Detected Linux kernel version older than 4.0.0 release, there are some known pote…

建设项目跟踪与展示系统

这是在翻旧文件时翻到的16年写的一个项目 建设项目跟踪与展示系统 建设方案 一、系统建设目的及意义 建设工程项目进度控制的最终目的是确保建设项目按预定的时间完成。能否在预定的时间内交付使用&#xff0c;直接影响到投资效益。为解决施工组织过程中存在问题&#xff0c;…

PyTorch深度学习实战(25)—— 使用向量化思想解决实际问题

本文将实际应用向量化思想解决深度学习中的几个经典问题,读者可以在这些示例中进一步领悟向量化思想和高级索引思想。 1. Box_IoU Box_IoU是目标检测任务中最基本的评价指标。简单来说,Box_IoU就是模型预测的检测结果框(predicted bbox)与原始标记框(ground truth)之间的…

企业级Nginx源码安装及其实战讲解

一&#xff1a;web服务基础介绍 1.1Web服务介绍 Apache 经典的 Web 服务端 Apache起初由美国的伊利诺伊大学香槟分校的国家超级计算机应用中心开发 目前经历了两大版本分别是1.X和2.X 其可以通过编译安装实现特定的功能 Apache prefork 模型 预派生模式&#xff0c;有一个…

[C++进阶]map和set

一、关联式容器 STL中的部分容器&#xff0c;比如&#xff1a;vector、list、deque、forward_list(C11)等&#xff0c;这些容器统称为序列式容器&#xff0c;因为其底层为线性序列的数据结构&#xff0c;里面存储的是元素本身。 那什么是关联式容器&#xff1f;它与序列式容器…

2.复杂度分析

2.1 算法效率评估 在算法设计中&#xff0c;我们先后追求以下两个层面的目标。 找到问题解法&#xff1a;算法需要在规定的输入范围内可靠地求得问题的正确解。寻求最优解法&#xff1a;同一个问题可能存在多种解法&#xff0c;我们希望找到尽可能高效的算法。 也就是说&a…

JavaScript_7_练习:随机抽奖案例

效果图 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>练习&#xff1a;随机抽奖案例</tit…

做谷歌seo如何选择好的服务器?

做谷歌seo如何选择好的服务器&#xff0c;如果你选择自托管平台&#xff0c;那么服务器的选择就非常关键了&#xff0c;服务器的好与坏影响着你的网站的表现&#xff0c;所以选择服务器要慎重。以下是一些建议&#xff0c;帮你做出明智的选择&#xff1a; 安全性&#xff1a;安…

Python Web框架 Django学习记录:1 项目安装,启动

Windows上学习Django # 创建一个虚拟环境 python -m venv tutorial-env# 激活虚拟环境 tutorial-env\Scripts\activate# 安装Django py -m pip install Django# 查看Django版本 py -m django --version# 使用脚手架创建一个项目 django-admin startproject mysite# 启动项目 cd…

linux出现sql密码被忘记的解决方法

目录 前言正文 前言 此处放置在运维篇章&#xff0c;对应sql的修改密码&#xff0c;推荐阅读&#xff1a;修改sql密码&#xff08;涵盖多个版本&#xff09; 如果补充Sql的基本知识&#xff0c;推荐阅读&#xff1a;Mysql底层原理详细剖析常见面试题&#xff08;全&#xff0…

git本地仓库同步到远程仓库

整个过程分为如下几步&#xff1a; 1、本地仓库的创建 2、远程仓库的创建 3、远程仓库添加key 4、同步本地仓库到远程仓库 1、本地仓库的创建&#xff1a; 使用如下代码创建本地仓库&#xff1a; echo "# test" >> README.md git init git add README.md …

shell脚本的编写规范和变量类型

1.shell的作用 shell是Linux系统中后台运行的一种特殊程序也可以理解 成一种特殊的软件&#xff0c;提供了用户与内核进行 交互操作的 一种接口。(简单的说就是shell把人类的高级语言转换成二进制数据&#xff0c;让机器明白你的指令) 过程&#xff1a;用户发出指令&#xff0c…

图像数据处理19

四、形态学图像处理 4.6 灰度图像的形态学处理 4.6.1灰度图像的腐蚀操作 灰度图像的腐蚀处理会让图像整体变暗&#xff0c;增强较暗的细节&#xff0c;抑制较亮的细节。其有助于分割图像、平滑图像边缘。 import cv2 import numpy as np# 读取图像 image cv2.imread(fu.jp…

魔珐科技出席WWEC教育者大会,给出AI时代教培行业精细化运营赋能方案

AI与教育的结合&#xff0c;已经成为教育行业发展的关键增长点。头部机构纷纷寻求AI技术与产品融合&#xff0c;以增强市场竞争力&#xff0c;希望在这场技术引发的行业洗牌中保持领先。 喜忧之中&#xff0c;展望未来&#xff0c;教培机构如何继续找准航向&#xff0c;贴近政…

表格解析调研

表格解析调研 TextInTools TextInTools&#xff1a;https://tools.textin.com/table 可以将表格图片解析成可编辑的表格/json&#xff0c;效果不错 白描 地址&#xff1a;https://web.baimiaoapp.com/image-to-excel 可以将表格图片识别成可编辑的表格&#xff0c;可复制、…

OpenCV4特征匹配

目录 一.特征检测的基本概念二.Harris角点检测三.Shi-Tomasi角点检测四.SIFT关键点检测五.SURF特征检测&#xff08;属于opencv_contrib&#xff09;六.ORB特征检测七.特征匹配方法八.FLANN特征匹配 流程梳理 一.特征检测的基本概念 OpenCV特征的场景 1.图像搜索&#xff0c;…

“论软件的可靠性评价”写作框架,软考高级,系统架构设计师

论文真题 软件可靠性评价是软件可靠性活动的重要组成部分&#xff0c;既适用于软件开发过程&#xff0c;也可针对最终软件系统。在软件开发过程中使用软件可靠性评价&#xff0c;可以使用软件可靠性模型&#xff0c;估计软件当前的可靠性&#xff0c;以确认是否可以终止测试并…

数据结构与算法(算法篇)

学数据结构与算法不是仅仅学算法本身&#xff08;经验&#xff09;&#xff0c;而是学习思维&#xff08;解决问题的能力)。 数据结构与算法&#xff08;算法篇&#xff09; 1、算法的性能分析1.1 时间复杂度1.2 空间复杂度1.3 小结 2、高精度2.1 高精度加法2.2 高精度减法2.3…

【鸿蒙学习】HarmonyOS应用开发者高级认证 - 一次开发,多端部署

一、学习目的 掌握鸿蒙的核心概念和端云一体化开发、数据、网络、媒体、并发、分布式、多设备协同等关键技术能力&#xff0c;具备独立设计和开发鸿蒙应用能力。 二、总体介绍 HarmonyOS 系统面向多终端提供了“一次开发&#xff0c;多端部署”&#xff08;后文中简称为“一…