二叉树中的topk问题(带图详解)

CSDN主页:d1ff1cult.

代码云仓库:d1ff1cult.

文章栏目:数据结构专栏

TopK问题

在给定的n的数据中,求出这n个数据中最大的k个数字

TopK的代码:

需要注意的是,我们需要建立一个名为data.txt的文件,在里面写入需要进行比较的数据,然后通过fopen,fscanf等函数将文件中的数据读取出来

typedef int HPDataType;

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 找出小的那个孩子
		if (child + 1 < n && 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(const char* filename, int k)
{
	// 1. 建堆--用a中前k个元素建堆
	FILE* fout = fopen(filename, "r");
	if (fout == NULL)
	{
		perror("fopen fail");
		return;
	}

	int* minheap = (int*)malloc(sizeof(int) * k);
	if (minheap == NULL)
	{
		perror("malloc fail");
		return;
	}

	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &minheap[i]);
	}

	// 前k个数建小堆
	for (int i = (k - 2) / 2; i >= 0; --i)
	{
		AdjustDown(minheap, k, i);
	}


	// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
	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 ", minheap[i]);
	}
	printf("\n");

	free(minheap);
	fclose(fout);
}

// fprintf  fscanf

void CreateNDate()
{
	// 造数据
	int n = 10000000;
	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) % 10000000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

int main()
{
	//CreateNDate();
	PrintTopK("data.txt", 5);

	return 0;
}


TopK详解

这个问题我们是通过建小堆来实现的

1.建小堆

整个TopK问题的核心思想:

Q:啊?这里为什么要建一个小堆而不是一个大堆呢?? 

A:首先小堆的特性我们都知道,所有的父节点都小于它的左右孩子,也就是说小堆中的根节点是整个树中 值最小的。那么很好,比如我们要比较前20个数据里面最大的8个数据。

那我们需要建一个结点数为8的小堆。然后将20个数据一个一个的进入小堆,并进行向下调整使其再次成为一个小堆,每插入一个数据都会进行一次向下调整使得创建的这个堆为小堆

也就能保证根节点永远是最小的,插入8个数据之后,后面插入的数据都要与这8个数形成的堆(小堆)的根节点进行比较,如果数据大于根节点,那么堆的根节点的值替换为插入的数据,然后再次进行向下调整,将堆中最小的数据放在根节点的位置上,如此循环,最后堆中会剩下20个数字中最大的8个数字

建了一个8个数据的小堆。然后让剩下数据依次比较,比根节点小就不管了,比根节点大的话就与根节点交换,并且进行向下调整。

2.详解

本来有这样一个数组,我们想求这个数组里面最大的8个数字

那我们先将前8个数字建小堆

二叉树中的topk问题(带图详解)_第1张图片

然后我们将剩余的数据与根节点进行比较,下一个数字是8,大于根节点,8赋值给根节点,然后进行向下调整。

替换后:

二叉树中的topk问题(带图详解)_第2张图片

向下调整后:

二叉树中的topk问题(带图详解)_第3张图片

下面是这一段的代码实现

// 前k个数建小堆
	for (int i = (k - 2) / 2; i >= 0; --i)
	{
		AdjustDown(minheap, k, i);
	}


	// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
	int x = 0;
	while (fscanf(fout, "%d", &x) != EOF)
	{
		if (x > minheap[0])
		{
			// 替换你进堆
			minheap[0] = x;
			AdjustDown(minheap, k, 0);
		}
	}

如此循环,最后就能的到存了最大的8个数据的堆。


以上便是TopK问题了。

你可能感兴趣的:(数据结构,算法)