二叉树和堆(2)——TOP—K 问题

什么是TOP—K问题

TOP—K问题:求数据结合中前K个最大的元素或者最小的元素,一般情况下,数据个数远远大于K。

比如:求专业前2名,世界500强,游戏中最活跃的前10名玩家等等。

对于TOP——K问题,首先想到的就是排序,但是,如果数据量非常的大,排序就不太可能了。最佳的方式是用堆解决。

方法思路如下:

1 用数据结合中的前K个元素来建堆。如果是求最大元素,就小堆;如果是最小元素,就大堆。

2 用剩余的元素与堆顶元素比。如果我们求最大的前K个元素,剩余的元素如果大于堆顶,就进堆,如果小于堆顶,就排除。完成全部的遍历后,堆中的元素就是最大的前K个元素了。

接下来,我们要实现一下这个算法。

代码实现

我们找最大的5个数。即K=5.

因为TOP—K的数据比较大,我们这次创造100 000个数据。

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

这样,我们就创建了100 000个数据。

接下来,就是建立小堆,完成该算法。

void TOPK(const char*file,int k)
{
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen");
		return;
	}
	//建一个K个数的小堆
	//由于vs不支持边长数组,我们只能malloc一下
	int* minheap = (int*)malloc(sizeof(int) * k);
	if (minheap == NULL)
	{
		perror("malloc");
		return;
	}
	//从文件中读取前K个数
	for (int i = 0;i < k;i++)
	{
		fscanf(fout, "%d", &minheap[i]);
		//建小堆,也就是要向上调整
		Adjustup(minheap, i);
		int x = 0;
		while (fscanf(fout, "%d", &x) != EOF)
		{
			if (x > minheap[0])
			{
				minheap[0] = x;
				Adjustdown(minheap, k, 0);
			}
		}

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

	fclose(fout);
}

关于建立小堆,我们也能用向下调整的方法建立小堆。

向下调整建立小堆,也就是从最后一个节点的父亲开始调整。

比如上面这个堆,就是先找到8的父亲23,然后从23开始向下调整,之后,23节点减一到11节点,11节点调整后,11节点减一到10节点,这样一次类推。

于是,上面建立小堆的代码可以这样写:

for (int j = (i - 1) / 2;j >= 0;j--)
{
	Adjustdown(minheap, i + 1, 0);
}

这样写的好处是什么呢?

其实,向上调整建立堆的算法的时间复杂度是O(N*logN),而向下调整算法的时间复杂度是O(N)。

可以下去自证一下。

于是,TOP—K问题就算解决了。

完整代码如下:

头文件 heap.h

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<time.h>

void creatdata();
void TOPK(const char*file,int k);

源文件 heap.c

void swap(int* p, int* q)
{

	int tmp = *p;
	*p = *q;
	*q = tmp;
}
void Adjustup(int* arr, int child)//向上调整
{
	//int parent = (child - 1) / 2;
	while (child > 0)
	{
		int parent = (child - 1) / 2;
		if (arr[child] < arr[parent])
		{
			swap(&arr[child], &arr[parent]);
			child = (child - 1) / 2;
			parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
		//child = (child - 1) / 2;
		//parent = (parent - 1) / 2;
	}
}
Adjustdown(int* arr, int size, int parent)//向下调整算法
{
	//设左孩子,假设左孩子最小
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && (arr[child] > arr[child + 1]))
		{
			child++;
		}
		if (arr[parent] > arr[child])
		{
			swap(&arr[parent], &arr[child]);
			//swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}

}




void creatdata()
{
	int n = 100000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (file == NULL)
	{
		perror("fopen");
		return;
	}
	for (int i = 0;i < n;++i)
	{
		int x = (rand() + i) % 100000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}
void TOPK(const char*file,int k)
{
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen");
		return;
	}
	//建一个K个数的小堆
	//由于vs不支持边长数组,我们只能malloc一下
	int* minheap = (int*)malloc(sizeof(int) * k);
	if (minheap == NULL)
	{
		perror("malloc");
		return;
	}
	//从文件中读取前K个数
	for (int i = 0;i < k;i++)
	{
		fscanf(fout, "%d", &minheap[i]);
		//建小堆,也就是要向上调整
		Adjustup(minheap, i);
		/*for (int j = (i - 1) / 2;j >= 0;j--)
		{
			Adjustdown(minheap, i + 1, 0);
		}*/
		int x = 0;
		while (fscanf(fout, "%d", &x) != EOF)
		{
			if (x > minheap[0])
			{
				minheap[0] = x;
				Adjustdown(minheap, k, 0);
			}
		}
	}
	for (int i = 0;i < k;i++)
	{
		printf("%d\n", minheap[i]);
	}

	fclose(fout);
}

大家拜拜!!!

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值