(3)寻找最小的K个数

/*
 * 寻找最小的K个
 */

/*
	1.排序,然后遍历前K个		O(nlogn)
	2.维护一个K大小的数组		O(K+(n-K)*n)
	3.维护一个K大小的最大堆		O(K*logK + (n-k)*logK) = O(nlogK)
	4.类似于在一个数组中找第K大的数的算法	
	5.计数排序
	6.将数组建立一个最小堆,然后取K个
	7.性质:最小堆:第K层最小堆一定全部小于第(K+1)层吗???
*/

#ifndef K_SMALLEST_H
#define K_SMALLEST_H

#include <iostream>
#include <string>

using namespace std;

void TestKSmallest();

// 利用方法7.
// 首先对N个数建立最小堆,然后取前k个,每次下沉的层数为k、k-1、k-2...
void KSmallest(int arr[], int n, int res[], int k);

// 建立最小堆
void BuildHeap(int arr[], int n);

// 元素num上浮,建立最小堆
void ShiftUp(int arr[], int n, int num);

// 根节点下沉,维持最小堆的性质,与传统堆唯一的不同是:限定了下沉的层数k
void ShiftDown(int arr[], int n, int k);

// 交换两个数
inline void MySwap(int &num1, int &num2)
{
	if (num1 == num2)
	{
		return;
	}
	int tmp = num1;
	num1 = num2;
	num2 = tmp;
}

inline void PrintVec(int arr[], int n)
{
	for (int i = 0; i < n; ++i)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}

// 快速选择
void KSmallest_2(int arr[], int n, int res[], int k);
// 用arr[0]作为partition,并将排序后的partition传出去
void QuickSelect(int arr[], int start, int end, int &partition);

// Top-K实现
// 问题:1.hash数组到底开多大?
// 定义最大的HashLen数
// 自己实现

#define HASH_LEN 2807303
#define WORD_LEN 30
#define K		 10
struct HashNode
{
	char *word;
	int count;
	HashNode *next;

	HashNode(const char *str) : count(1), next(NULL)
	{
		int len = strlen(str);
		word = new char[len + 1];
		strcpy(word, str);		
	}
};

 定义hash的节点头。每一个节点头连着一串的hash值相等的节点链
//HashNode *head[HASH_LEN];
//HashNode *minHeap[K + 1];


// 定义Hash函数
unsigned HashFunction(const char *word);

// 将一个word插入到head节点中
void InsertWord(const char *word);

// 从输入文件中读取单词,并插入到head中
void BuildHeadFromFile(char *file);

// 首先取前K个节点建立一个最小堆,然后遍历(N-K)个节点
// 并调整最小堆,其中堆的依据为单词出现的频率
// 还是用指针表示堆内的元素吧



// 建立最小堆,堆中元素个数为n
void BuildMinHeap(int n);
// 上浮函数
void ShiftUpNode(int n, int num);

// 下沉函数

// 扫描hashNode,并最终建立minHeap
void ScanHashToBuildHeap();

// 把填充K个单独拿出来
// void InitMinHeap();

// 根节点下沉,维持最小堆的性质
void ShiftDownNode(int n);
// 释放内存
void ReleaseNode();


// 实现红黑树!!!
// 我什么时候实现自己的红黑树,才能破除心魔!!!
// 这个心魔就是畏难!!!

#endif

#include <algorithm>
#include <iostream>
#include <string>
#include <fstream>


#include "3_KSmallest.h"
using namespace std;

/*
	1.排序,然后遍历前K个		O(nlogn)
	2.维护一个K大小的数组		O(K+(n-K)*n)
	3.维护一个K大小的最大堆		O(K*logK + (n-k)*logK) = O(nlogK)
	4.类似于在一个数组中找第K大的数的算法	
	5.计数排序
	6.将数组建立一个最小堆,然后取K个
	7.性质:最小堆:第K层最小堆一定全部小于第(K+1)层吗???

	编程之美
	编程珠玑
	算法导论
*/

// 定义hash的节点头。每一个节点头连着一串的hash值相等的节点链
HashNode *head[HASH_LEN];
HashNode *minHeap[K + 1];

void TestKSmallest()
{
	/*int arr[] = {0, 5, 3, 7, 4};
	BuildHeap(arr, 4);
	int res[3] = {0};
	KSmallest(arr, 4, res, 3);*/

	int arr[] = {4, 3, 2, 7, 5};
	int partition = 0;
	//QuickSelect(arr, 0, 4, partition);
	//cout << partition << endl;
	int res[5] = {0};
	KSmallest_2(arr, 5, res, 1);	
	KSmallest_2(arr, 5, res, 2);
	KSmallest_2(arr, 5, res, 3);
	KSmallest_2(arr, 5, res, 4);
	KSmallest_2(arr, 5, res, 5);
}

// 利用方法7.
// 首先对N个数建立最小堆,然后取前k个,每次下沉的层数为k、k-1、k-2...
void KSmallest(int arr[], int n, int res[], int k)
{
	for (int i = 0; i < k; ++i)
	{
		res[i] = arr[1];
		arr[1] = arr[n--];
		ShiftDown(arr, n, k - i);
	}
}

// 建立最小堆,这里的数组arr[0],不保存实际数据,而用作临时存储单元
void BuildHeap(int arr[], int n)
{
	for (int i = 1; i <= n; ++i)
	{
		ShiftUp(arr, i, i);
	}
}

// 元素num上浮,建立最小堆
// 一共n个元素,要调整的是第num个
void ShiftUp(int arr[], int n, int num)
{
	arr[0] = arr[num];
	int current = num;
	while (current > 1) // 直到根节点
	{
		int father = current / 2;
		if (arr[0] < arr[father])
		{
			arr[current] = arr[father];
			current = father;
		}
		else
		{
			break;
		}
	}
	arr[current] = arr[0];
}

// 根节点下沉,维持最小堆的性质,与传统堆唯一的不同是:限定了下沉的层数k
void ShiftDown(int arr[], int n, int k)
{
	arr[0] = arr[1];
	int current = 1;
	// 一方面保证最多下沉k次,另一方面保证堆不会超出范围
	while (k-- && (current <= n / 2))
	{
		int child = current << 1;			// 左子节点
		if ((child + 1 <= n) && arr[child + 1] < arr[child])	// 如果右子节点更小
		{
			child++;
		}
		if (arr[child] < arr[0])
		{
			arr[current] = arr[child];
			current = child;
		}
		else
		{
			break;
		}
	}
	arr[current] = arr[0];
}

// 设置res多此一举了!
// 因为已经对arr滴前K个排过序了!可以保证结果一定在前K中
void KSmallest_2(int arr[], int n, int res[], int k)
{
	int start = 0;
	int end = n - 1;
	int resIndex = 0;
	while (k != 0)
	{
		int partition = 0;
		QuickSelect(arr, start, end, partition);
		int num = partition - start + 1;
		if (num == k)
		{
			copy(arr + start, arr + partition + 1, res + resIndex);
			break;
		}
		else if (num < k)
		{
			copy(arr + start, arr + partition + 1, res + resIndex);
			resIndex += num;
			k -= num;
			start = partition + 1;
		} 
		else
		{
			end = partition;
		}
	}
}

// 这里我直接用start、end做了指针,不好!
// 可以单独申请两个指针,而不要动start、end
void QuickSelect(int arr[], int start, int end, int &partition)
{	
	int initStart = start;
	int part = arr[start++];
		
	while (start <= end)
	{
		while (arr[start] <= part)
		{
			start++;
		}
		while (arr[end] > part)
		{
			end--;
		}
		if (start < end)
		{
			MySwap(arr[start], arr[end]);
		}
	}
	MySwap(arr[initStart], arr[end]);
	
	partition = end;
}

unsigned HashFunction(const char *word)
{
	unsigned hashNum = 0;
	while (*word)
	{
		hashNum = hashNum * 31 + *word++;
		if (hashNum > HASH_LEN)
		{
			hashNum = hashNum % HASH_LEN;
		}
	}
	return hashNum;
}

void InsertWord(const char *word)
{
	unsigned index = HashFunction(word);
	HashNode *pHashLine = head[index];
	while (pHashLine != NULL)
	{
		if (strcmp(word, pHashLine->word) == 0)
		{
			pHashLine->count++;
			return;
		}
		pHashLine = pHashLine->next;
	}
	HashNode newNode(word);
	newNode.next = head[index];
	head[index] = &newNode;
}

void BuildHeadFromFile(char *file)
{
	ifstream infile(file);
	if (!infile)
	{
		cerr << "open file failed" << endl;
		exit(-1);
	}
	char word[WORD_LEN];
	while (infile >> word)
	{
		InsertWord(word);
	}
}

void BuildMinHeap(int n)
{
	for (int i = 1; i <= n; ++i)
	{
		ShiftUpNode(i, i);
	}
}

void ShiftUpNode(int n, int num)
{
	minHeap[0] = minHeap[num];
	int current = num;
	while (current > 1) // 直到根节点
	{
		int father = current / 2;
		if (minHeap[0]->count < minHeap[father]->count)
		{
			minHeap[current] = minHeap[father];
			current = father;
		}
		else
		{
			break;
		}
	}
	minHeap[current] = minHeap[0];
}

void ScanHashToBuildHeap()
{
	int minHeapIndex = 1;
	// 1. 首先填充K个minHeap
	for (int i = 0; i < HASH_LEN; ++i)
	{		
		HashNode *pHashNode = head[i];
		while (pHashNode != NULL)
		{
			if (minHeapIndex <= K)
			{
				minHeap[minHeapIndex] = pHashNode;
				if (minHeapIndex == K)
				{
					BuildMinHeap(K);
				}
				else
				{
					minHeapIndex++;
				}
			}
			else
			{
				// 如果次数大于minHeap[1],则将其放入minHeap[1],并调整最小堆
				if (pHashNode->count > minHeap[1]->count)
				{
					minHeap[1] = pHashNode;
					ShiftDownNode(K);
				}
			}
			pHashNode = pHashNode->next;
		}
	}
}

void ShiftDownNode(int n)
{
	minHeap[0] = minHeap[1];
	int current = 1;
	// 保证堆不会超出范围
	while ((current <= n / 2))
	{
		int child = current << 1;			// 左子节点
		if ((child + 1 <= n) && minHeap[child + 1]->count < minHeap[child]->count)	// 如果右子节点更小
		{
			child++;
		}
		if (minHeap[child]->count < minHeap[0]->count)
		{
			minHeap[current] = minHeap[child];
			current = child;
		}
		else
		{
			break;
		}
	}
	minHeap[current] = minHeap[0];
}

void ReleaseNode()
{
	for (int i = 0; i < HASH_LEN; ++i)
	{		
		HashNode *pHashNode = head[i];
		
		while (pHashNode != NULL)
		{
			HashNode *pTmp = pHashNode->next;
			delete pHashNode;
			pHashNode = pTmp;
		}
	}
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值