【数据结构初阶第十五节】堆的应用(堆排序 + Top-K问题)

news/2025/2/23 6:21:02

必须有为成功付出代价的决心,然后想办法付出这个代价。云边有个稻草人-CSDN博客

对于本节我们要提前掌握前一节课堆的相关实现才能学好本次的知识,一定要多画图多敲代码看看实现的效果是啥(Crazy!)开始吧!

目录

一、堆排序

(一) 基于原有堆

(二) 原数组上直接建堆

1.向上调整算法建堆

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

3.向下调整算法建堆

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

二、TOP-K问题

        ——————————————《Being in love》——————————————  


一、堆排序

(一) 基于原有堆

结合下面的代码观看——创建一个数组,将数组里面的数据不断地入堆后建立了一个堆(假设是一个小堆),不断取堆顶数据打印后出堆(此操作循环),这样就可以实现排序。为什么这样就实现了排序呢?Because小堆的堆顶是堆里面的最小值,出堆时向下调整又变成了小堆,此时堆顶是剩下元素里面的最小值,就这样不断取堆顶(最小值)实现了升序操作。

但是,这样的排序方法我们必须提前实现一个堆,而且我们实现堆操作时至少要申请一块原排序数组大小的空间,空间复杂度为O(N),那该如何调整排序算法使空间复杂度变为O(1)呢?接下来看第二个排序方法

//1.需要堆的数据结构
//2,空间复杂度为O(N)
void HeapSort(int* arr.int n)
{
    HP hp;
    for (int i = 0; i < n; i++)
    {
    	HPPush(&hp, arr[i]);
    }

    int i = 0;
    while(!HPEmpty(&hp))
    {
	    arr[i++] = HPTop(&hp));
	    HPPop(&hp);
    }
    HPDestroy(&hp);
}

(二) 原数组上直接建堆

1.向上调整算法建堆

原数组里建堆,首尾交换,交换后的堆尾数据从堆中删掉,将堆顶数据向下调整选出次大的数据  。

void HeapSort(int* arr, int n)
{
	//小堆->降序
    //大堆->升序
	for (int i = 0; i < 6; i++)
	{
		AdjustUp(arr, i);
	}

	//堆排序
	int end = n - 1;
	while (end > 0)
	{
		Swap(&arr[0], &arr[end]);//end指向的是最后一个数据
		AdjustDown(arr, 0, end);//有效的数据个数减了1
		end--;
	}
}

上面是实现了排降序的操作,如何实现排升序的操作呢?异曲同工之妙!来看

上面建的是小堆,现在我们要在原数组内建大堆才能实现排升序的操作,如何建大堆呢?见下面的代码(要动手画图看看效果是如何实现建大堆的)

//向上调整数据,实现建堆操作
void AdjustUp(HPDatatype* arr, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
        //建小堆,谁小谁往上放,用 <
        //建大堆,谁大谁往上放,用 >
		if (arr[child] > arr[parent])
		{ 
			Swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
//向下调整数据
void AdjustDown(HPDatatype* arr, int parent, int n)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		//找出左右孩子中最小的->小堆 >
		//找出左右孩子中最大的->大堆 <
		if (child + 1 < n && arr[child] < arr[child + 1])
		{
			child++;
		}
        // < 建小堆
        // > 建大堆 
		if (arr[child] < arr[parent])
		{
			Swap(&arr[parent], &arr[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}	
}

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

上面第二种方法我们实现的堆排序把空间复杂度降为O(1),直接在原数组里面建堆不需要额外申请空间,那时间复杂度如何呢?

void HeapSort(int* arr, int n)
{
	//小堆->降序
    //大堆->升序
	for (int i = 0; i < 6; i++)
	{
		AdjustUp(arr, i);
	}

	//堆排序
	int end = n - 1;
	while (end > 0)
	{
		Swap(&arr[0], &arr[end]);//end指向的是最后一个数据
		AdjustDown(arr, 0, end);//有效的数据个数减了1
		end--;
	}
}

 【大致估算】

结合排序代码思考,排序操作里面先是向上调整算法,假设堆的度为k,那么向上调整算法最差要向上交换k-1次;我们根据节点数来计算交换的次数,我们知道 2^k -1 = n(n为总结点的个数),k = log(n+1) -> k = log(n),这只是插入一个结点,若要插入m个结点,就是m*log(n)次,因为向下调整算法也是这样,所以加起来就是O(2*m*log(n)),也就是O(m*log(n)),这只是大致计算一下时间复杂度。

【细致计算】 

3.向下调整算法建堆

不仅仅可以向上调整算法建堆,还可以向下调整算法建堆。(根据代码原理自己动手画画图)

void HeapSort(int* arr, int n)
{
	//向下调整算法建堆
	for (int i = (n - 2) / 2; i >= 0; i--)
	{
		AdjustDown(arr, i, n);
	}

	//堆排序
	int end = n - 1;
	while (end > 0)
	{
		//end指向的是最后一个数据
		Swap(&arr[0], &arr[end]);
		//有效的数据个数减了1
		AdjustDown(arr, 0, end);
		end--;
	}
}
4.向下调整算法建堆时间复杂度

感觉有点乱理一理先

实现堆排序 = 建堆 + 排序

如何建堆? 两种方法 --> 向上调整算法建堆 / 向下调整算法建堆

建大堆还是建小堆取决于你想排升序还是排降序,自行选择

建大堆 --> 升序
建小堆 --> 降序

二、TOP-K问题

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

(1)用数据集合中前K个元素来建堆(自己好好想清楚)

  • 取前K个最大的元素,则建小堆
  • 取前K个最小的元素,则建大堆 

(2)用剩余N-K个元素依次与堆顶数据来比较,不满足则替换堆顶元素

  •  对于建小堆,若剩余元素比堆顶元素大则交换;
  •  对于建大堆,若剩余元素比对顶元素小则交换。

时间复杂度为:我们采用向下调整算法建堆,时间复杂度为O(K),之后将N-K个数据进行向下调整,时间复杂度为(N-K)log(K) ,加在一起将小影响的忽略就是O(N) 。 注意看自己AdjustDown实现的是大堆还是小堆。

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 Top()
{
	printf("请输入k:");
	int k = 0;
	scanf("%d", &k);
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error!");
		return;
	}
    
    //申请一块堆大小的空间
	int* minHeap = (int*)malloc(k * sizeof(int));
	if (minHeap == NULL)
	{
		perror("malloc file!");
		exit(2);
	}

	//将数据存入堆里面
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &minHeap[i]);
	}

	//建堆,通过向下调整算法建一个小堆
	for (int i = (k - 2) / 2; i >= 0; i--)
	{
		AdjustDown(minHeap, i, k);
	}

	//将剩下的数据进行过比较,满足的与堆顶数据进行交换
	int x = 0;
	while (fscanf(fout, "%d", &x) != EOF)
	{
		if (x > minHeap[0])
		{
			minHeap[0] = x;
			AdjustDown(minHeap, 0, k);
		}
	}

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

	fclose(fout);

}

int main()
{
	CreateNDate();
	Top();
	return 0;
}

终于结束了! (我要疯了真的)               

完——


   ——————————————《Being in love》——————————————  

 Being in Love_Synth Monsters、川岛三离_高音质在线试听_Being in Love歌词|歌曲下载_酷狗音乐

 [正是你对你的玫瑰花费的时光,才使你的玫瑰如此重要]                                                                          

至此结束!

我是云边有个稻草人

期待与你的下一次相遇。。。 

                                                  


http://www.niftyadmin.cn/n/5863103.html

相关文章

qt-C++笔记之创建和初始化 `QGraphicsScene` 和 `QGraphicsView` 并关联视图和场景的方法

qt-C笔记之创建和初始化 QGraphicsScene 和 QGraphicsView 并关联视图和场景的方法 code review! 参考笔记 1.qt-C笔记之创建和初始化 QGraphicsScene 和 QGraphicsView 并关联视图和场景的方法 2.qt-C笔记之QGraphicsScene和 QGraphicsView中setScene、通过scene得到view、通过…

C++面试笔记(持续更新...)

1.C的特性 封装&#xff1a;将数据和具体实现在类中隐藏&#xff0c;对外只留出接口方便调用。 继承&#xff1a;子类继承父类的方法和全部数据&#xff0c;提高软件按复用率 多态&#xff1a;自继承的条件下&#xff0c;继承自同一父类的类的同一的方法对同一个事物具有不同的…

【LeetCode】239. 滑动窗口的最大值

239. 滑动窗口的最大值 单调队列的模板题&#xff0c;但是我一直记不住&#x1f622;。 题目描述如下&#xff1a; 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右…

使用ArcGIS Pro自动矢量化水系

在地理信息系统&#xff08;GIS&#xff09;领域&#xff0c;自动矢量化是一项至关重要的技术&#xff0c;它能够将栅格图像中的要素转换为矢量数据&#xff0c;从而方便后续的分析和处理。本文将详细介绍如何使用ArcGIS Pro自动矢量化水系&#xff0c;适用于那些颜色相对统一、…

探索YOLO技术:目标检测的高效解决方案

第一章&#xff1a;计算机视觉中图像的基础认知 第二章&#xff1a;计算机视觉&#xff1a;卷积神经网络(CNN)基本概念(一) 第三章&#xff1a;计算机视觉&#xff1a;卷积神经网络(CNN)基本概念(二) 第四章&#xff1a;搭建一个经典的LeNet5神经网络(附代码) 第五章&#xff1…

DVWA 靶场

DVWA 靶场的通关 刚建立和使用 输入 http://dvwa:8898/setup.php //进入用户名 密码 dvwa 你自己设计的想要进入数据库 点击creat 用户名 密码 admin passwordAttack type Sniper模式 在Sniper模式下&#xff0c;Payload字典用于逐个替换请求中标记的位置。例如&#x…

华为发力中端,上半年nova14下半年nova15,大力普及原生鸿蒙

在智能手机市场的激烈竞争中&#xff0c;华为始终以创新者的姿态不断突破。2025年&#xff0c;华为在中端手机领域投下两颗重磅炸弹——上半年推出nova14系列&#xff0c;下半年接力发布nova15系列&#xff0c;且全系搭载原生鸿蒙系统&#xff0c;售价覆盖2000-4000元价格带&am…

【Java】File 类

目录 File 类File 类构造方法常见成员方法判断与获取创建与删除获取并遍历 File 类 File 对象表示一个路径&#xff0c;可以是文件的路径&#xff0c;也可以是文件夹的路径 这个路径可以是存在的&#xff0c;也允许是不存在的 绝对路径和相对路径的区别&#xff1a; 绝对路径…