top-k问题详解

本文探讨了如何在大数据场景下高效找到前K个最大或最小值,介绍了排序法和堆函数的不足,重点讲解了利用小堆和剩余元素比较的优化算法,时间复杂度降至O(N)。通过实例展示了如何使用C++实现这一方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1.TOP-K问题的定义以及思想:

(1)TOP-K问题的定义:

(2)解决TOP-K问题的思路:

①排序法(不推荐使用):

②堆函数操作法(不推荐使用):

最终算法:

复杂度:

完整实现(每一部都有详细的过程):


1.TOP-K问题的定义以及思想:

(1)TOP-K问题的定义:

即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
我们以求n个数据中前K个最大的元素为例进行说明:(假设n=10000) (假设k=10)

(2)解决TOP-K问题的思路:

①排序法(不推荐使用):

对于Top-K问题,能想到的最简单直接的方式就是排序,有10000个数,我们可以把这10000个数排降序,然后再逐个拿取前10个数就是最大的10个数。用最快的排序qsort等排序法,时间复杂度是O(N*logN),空间复杂度O(1) (解释:因为n是原本数组内就有的数据个数,是个常数,排序就是直接对数组操作了,所以是O(1),如果自己又开了N个大小空间才是O(N))

②堆函数操作法(不推荐使用)

建立N个数的大堆,Pop K次,就可以选出最大的前K个数了,
时间复杂度:O(N+logN*K) (建堆O(N)+从堆顶向下调整一次是高度次logN,一共调整k次,即Pop K次O(logN*K))
空间复杂度:O(1)(相当于直接在数组上建堆了,所以空间复杂度还是O(1))
这两种不适用:当N非常大时,N远大于K ,比如100亿个数里面找出最大的前10个,上面的两种方法的就不能用了,说明100亿个整数是放在磁盘中,也就是文件中。
所以我们用一种更巧妙的方法:

最终算法:

1. 用数据集合中前K个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。
通俗来讲就是:用前K个数建立一个K个数的小堆(前K个数是随机的,我们只管建小堆),然后剩下的N-K个依次遍历 和堆顶最小的数据比较,如果比堆顶的数据大,就把大的数赋给堆顶,再向下调整,最后堆里面的K个数就是最大的K个 ,因为小堆的堆顶一定是最小的数,只要随便拿个数比他大就交换他俩,大的那个数进入堆后再建小堆,大的数就沉在最下面,所以最后堆里面一定是K个最大的数。

复杂度:

时间复杂度:O(K+logK*(N-K))        当N非常大的时候就相当于是O(N)       
解释:建大小K的小堆——O(K),剩下的N-K个数和堆顶比较,比堆顶大就放进堆顶,并向下调整一次,向下调整一次最坏交换h次,h=logK;最坏情况是建完堆里面正好全是最小的数,则剩下N-K个数都比堆顶大,都要放进堆顶并向下调整,所以是O(logK*(N-K)),加起来就是O(K+logK*(N-K))
空间复杂度:O(K)        这个是此方法的牛逼之处,如果从10000个数里面找前10个最大的数,那上面那两种方法空间复杂度是 O(1)就要开10000个额外空间,而此方法只需开10个空间即可,非常优秀!

完整实现(每一部都有详细的过程):

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<time.h>
#include<stdlib.h>

void swap(int* pa, int* pb)
{
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

void AdjustDown(int* a,size_t size, size_t parent)//向下调整法建小堆前提还是左右子树是小堆
{
	size_t child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && 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 PrintTopK(int* a, int n, int k)
{
//3.自己建一个大小是K的堆
	int* kminHeap = (int*)malloc(sizeof(int) * k);
	int i = 0;
//4.用前K个数建立一个K个数的小堆
//(1)放入K个数
	for (i = 0; i < k; i++)
	{
		kminHeap[i] = a[i];
	}
//(2)向下调整法建小堆
	for (i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(kminHeap,k,i);
	}
	//for (i = 0; i < k; i++)        这个打印可以检查建堆代码是否正确
	//{
	//	printf("%d ", kminHeap[i]);
	//}
	//printf("\n");
//5.用剩下的N-K个数依次遍历和堆顶最小的数据比较,比堆顶大就把大的数赋给堆顶,再向下调整
	for (i = k; i < n; i++)
	{
		if (a[i] > kminHeap[0])
		{
			kminHeap[0] = a[i];
			AdjustDown(kminHeap, k, 0);
		}
	}
//6.打印堆的K个数据就是最大的K个数
	for (i = 0; i < k; i++)
	{
		printf("%d ", kminHeap[i]);
	}
	printf("\n");
	free(kminHeap);
}

int main()
{
	int n = 10000;
//1.srand和下面rand搭配时间戳生成随机数,如果是无符号整数通常用srand((unsigned int)time(NULL))
	srand(time(0));            //srand和下面rand -<stdlib.h>
	int* a = (int)malloc(sizeof(int) * n);
	for (int i = 0; i < n; i++)
	{
		a[i] = rand() % 1000000;
	}
//2.给这10000个数中随机放入10个大于1000000,这10个数时最大的10个,最后完成后堆里面一定是这10个数
	a[5] = 1000000 + 1;
	a[1231] = 1000000 + 2;
	a[531] = 1000000 + 3;
	a[5121] = 1000000 + 4;
	a[115] = 1000000 + 5;
	a[2305] = 1000000 + 6;
	a[99] = 1000000 + 7;
	a[76] = 1000000 + 8;
	a[423] = 1000000 + 9;
	a[0] = 1000000 + 1000;
	PrintTopK(a, n, 10);
	return 0;
}

<think>好的,我现在需要详细解答用户关于Linux的top命令中-H和-p参数的用法。首先,我得回忆一下自己对这两个参数的了解,确保信息准确。 我记得top命令用于实时监控系统进程状态,而-H参数通常用于显示线程信息,而不是进程。当启用-H时,每个线程会单独显示一行,这对分析多线程应用的性能很有帮助。例如,Java应用或数据库服务经常有多个线程,这时候用-H可以更详细地查看每个线程的资源使用情况。 然后是-p参数,这个参数用来指定要监控的进程ID。用户可以输入多个-p参数来同时监控多个进程,或者使用逗号分隔的PID列表。比如top -p 1234,5678 可以监控PID为1234和5678的两个进程。这个参数在需要专注于特定进程时非常有用,避免其他进程信息干扰。 接下来,我需要确认这些参数的详细用法和示例。可能还需要查阅相关资料或手册页来确保正确性。例如,top的man页面中提到,-H选项会开启线程模式,显示各个线程的信息,而-p后面可以跟一个或多个PID,用逗号分隔或者多次使用-p参数。 另外,用户可能想知道如何组合使用这两个参数。例如,如果用户想监控某个进程的所有线程,可以同时使用-H和-p,比如top -H -p 1234。这样,top会只显示PID为1234的进程及其所有线程的详细信息。 需要注意,-H参数可能需要与其他参数配合使用。例如,在top运行时按H键也可以切换线程视图,但命令行参数-H会在启动时直接启用。此外,使用-p参数时,如果指定的进程结束,top会提示该进程已终止,并可能继续显示其他指定的进程。 还需要考虑用户可能的常见使用场景。比如,开发人员调试多线程程序时,使用-H查看各个线程的CPU和内存使用情况;系统管理员监控特定服务的进程资源消耗时,使用-p指定PID。 最后,要确保回答结构清晰,分点说明参数的作用、语法、示例和使用场景,并给出组合使用的例子,帮助用户全面理解这两个参数的应用方法。</think>### Linux top命令 -H 和 -p 参数详解 #### 1. **-H 参数** **作用**:显示线程级资源占用情况,将进程中的每个线程单独列为一行显示。 **语法**:`top -H` **说明**: - 默认情况下,`top` 显示进程级别的信息。添加 `-H` 后,会展开进程内所有线程的详细信息,包括线程ID(TID)、CPU占用、内存占用等。 - 特别适用于分析多线程程序的性能问题,如Java应用、数据库服务等。 **示例**: ```bash top -H ``` **输出特点**: - 线程ID(TID)替代进程ID(PID)显示在第一列。 - 可结合排序(如按CPU占用率排序 `P`)快速定位高负载线程[^2]。 --- #### 2. **-p 参数** **作用**:监控指定进程ID(PID)的资源使用情况。 **语法**:`top -p PID1,PID2,...` **说明**: - 支持同时监控多个进程,PID之间用逗号分隔,或通过多次 `-p` 指定。 - 过滤无关进程信息,专注于目标进程的CPU、内存等指标。 **示例**: ```bash top -p 1234,5678 # 监控PID为1234和5678的进程 top -p 1234 -p 5678 # 等效写法 ``` --- #### 3. **组合使用 -H 和 -p** **场景**:监控特定进程的所有线程。 **语法**:`top -H -p PID` **示例**: ```bash top -H -p 1234 # 显示PID=1234进程的所有线程 ``` **输出效果**: - 仅显示目标进程的线程信息,便于分析线程级别的资源竞争或泄漏问题- 可结合 `-u` 参数(按用户过滤)进一步缩小监控范围。 --- #### 4. **使用技巧** - **动态操作**:在 `top` 运行时按 `H` 键切换线程视图(等效于 `-H` 参数)。 - **单位切换**:按 `E` 切换内存单位(KB/MB/GB),按 `e` 切换进程列表中的单位。 - **终止进程**:按 `k` 后输入PID可终止指定进程。 ---
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值