1.堆排序前面的文章講了堆的結(jié)構(gòu)和基礎(chǔ)接口實(shí)現(xiàn),不熟的友友們可以去看看堆(C語(yǔ)言實(shí)現(xiàn)),點(diǎn)擊跳轉(zhuǎn)
1.1向上調(diào)整和向下調(diào)整建堆對(duì)比堆排序即利用堆的思想來進(jìn)行排序
- 升序:建大堆
- 降序:建小堆
上一篇文章我們講了建堆的接口,咱么用的是向下調(diào)整建堆,那為什么不用向上調(diào)整建堆呢?下面我們就來對(duì)比一下那種方法更優(yōu)
向上調(diào)整建堆時(shí)間復(fù)雜度圖解:
向下調(diào)整建堆時(shí)間復(fù)雜度圖解:
由圖解我們得到結(jié)論:
向上調(diào)整建堆的時(shí)間復(fù)雜度為:O(N*log2(N));向下調(diào)整建堆的時(shí)間復(fù)雜度為:O(N)
升序,建大堆
首先交換堆的首尾數(shù)據(jù),把大的數(shù)放在尾部,再?gòu)母?jié)點(diǎn)開始向下調(diào)整size-1
個(gè)數(shù)據(jù)(遞減:從size-1開始每放一個(gè)數(shù)據(jù)就 -1,也就是不對(duì)交換到尾部的元素進(jìn)行調(diào)整)
//交換函數(shù)
void Swap(int* p1, int* p2)
{int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//向下調(diào)整(建大堆)
void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;//假定左孩子
while (child< n)
{//大堆:保證child指向大的那個(gè)孩子
if (a[child + 1] >a[child] && child + 1< n)
{ child++;
}
//大堆:孩子大于父親就交換,并繼續(xù)向下比較調(diào)整,反之則調(diào)整結(jié)束
if (a[child] >a[parent])
{ Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{ break;
}
}
}
//堆排序
//升序:建大堆
void HeapSort(int* a, int n)
{//建堆算法
//從最后一個(gè)元素的父節(jié)點(diǎn)開始依次向前可以遍歷到每顆子樹的父節(jié)點(diǎn)
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{AdjustDown(a, n, i);
}
int end = n - 1;
while (end >0)
{//交換首尾數(shù)據(jù)
Swap(&a[0], &a[end]);
//從首元素開始向下調(diào)整
AdjustDown(a, end, 0);
--end;
}
}
1.2.2降序
降序,建小堆
首先交換堆的首尾數(shù)據(jù),把最小的數(shù)放在尾部,再?gòu)母?jié)點(diǎn)開始向下調(diào)整size-1
個(gè)數(shù)據(jù)(遞減:從size-1開始每放一個(gè)數(shù)據(jù)就 -1,也就是不對(duì)交換到尾部的元素進(jìn)行調(diào)整)
//交換函數(shù)
void Swap(int* p1, int* p2)
{int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//向下調(diào)整(建小堆)
void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;//假定左孩子
while (child< n)
{//小堆:保證child指向小的那個(gè)孩子
if (a[child + 1]< a[child] && child + 1< n)
{ child++;
}
//小堆:孩子小于父親就交換,并繼續(xù)向下比較調(diào)整,反之則調(diào)整結(jié)束
if (a[child]< a[parent])
{ Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{ break;
}
}
}
//堆排序
//降序:建小堆
void HeapSort(int* a, int n)
{//建堆算法
//從最后一個(gè)元素的父節(jié)點(diǎn)開始依次向前可以遍歷到每顆子樹的父節(jié)點(diǎn)
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{AdjustDown(a, n, i);
}
int end = n - 1;
while (end >0)
{//交換首尾數(shù)據(jù)
Swap(&a[0], &a[end]);
//從首元素開始向下調(diào)整
AdjustDown(a, end, 0);
--end;
}
}
2.Top-K問題什么是Top-K問題?
2.1解決思路TOP-K問題:即求數(shù)據(jù)結(jié)合中前K個(gè)大的元素或者最小的元素,一般情況下數(shù)據(jù)量都比較大
比如:專業(yè)前10名、世界500強(qiáng)、富豪榜、游戲中戰(zhàn)力前100的玩家等
對(duì)于Top-K問題,能想到的最簡(jiǎn)單直接的方式就是排序,但是:如果數(shù)據(jù)量非常大,排序就不太可取了(可能數(shù)據(jù)都不能一下子全部加載到內(nèi)存中)。最佳的方式就是用堆來解決,基本思路如下:
2.2代碼實(shí)現(xiàn)1.用數(shù)據(jù)集合中前K個(gè)元素來建堆
- 前k個(gè)大的元素,則建小堆
- 前k個(gè)最小的元素,則建大堆
2.用剩余的N-K個(gè)元素依次與堆頂元素來比較,不滿足則替換堆頂元素
- 將剩余N-K個(gè)元素依次與堆頂元素比完之后,堆中剩余的K個(gè)元素就是所求的前K個(gè)最小或者大的元素
根據(jù)上面的思路,來舉兩個(gè)栗子
1.求有10000個(gè)數(shù)據(jù)的數(shù)組中大的10個(gè)數(shù)
//Top-K問題
void Topk()
{int n = 10000;
int* a = (int*)malloc(sizeof(int) * n);
srand(time(0));
//使數(shù)組中的數(shù)都在1000000以內(nèi)
for (size_t i = 0; i< n; ++i)
{a[i] = rand() % 1000000;
}
//手動(dòng)設(shè)置10個(gè)大于1000000的數(shù)
a[12] = 1000000 + 1;
a[1231] = 1000000 + 2;
a[531] = 1000000 + 3;
a[5121] = 1000000 + 4;
a[115] = 1000000 + 5;
a[2335] = 1000000 + 6;
a[9999] = 1000000 + 7;
a[76] = 1000000 + 8;
a[423] = 1000000 + 9;
a[3144] = 1000000 + 10;
//將數(shù)組a中前k個(gè)數(shù)據(jù)讀到數(shù)組minHeap中
int k = 10;
int* minHeap = (int*)malloc(sizeof(int) * k);
if (minHeap == NULL)
{perror("malloc fail");
return;
}
for (int i = 0; i< k; ++i)
{a[i] = minHeap[i];
}
//建一個(gè)k個(gè)大小的小堆minHeap
for (int h = (k - 1 - 1) / 2; h >= 0; --h)
{AdjustDown(minHeap, k, h);
}
//遍歷數(shù)組中的剩余數(shù)與堆頂比較,大于則替換并向下調(diào)整
for(int j = k;j< n; ++j)
{if (a[j] >minHeap[0])
{ minHeap[0] = a[j];
AdjustDown(minHeap, k, 0);
}
}
//打印小堆數(shù)據(jù)即為數(shù)組a中大的10個(gè)數(shù)
for (int g = 0; g< k; ++g)
{printf("%d ", minHeap[g]);
}
printf("\n");
}
2.從文件中讀取數(shù)據(jù)求大的前k的
//Top-K問題
void Topk()
{//寫數(shù)據(jù)到文件中
int n, k;
printf("請(qǐng)輸入在n個(gè)數(shù)中選取大的k個(gè)數(shù):>");
scanf("%d%d", &n, &k);
srand(time(0));
FILE* fin = fopen("data.txt", "w");
if (fin == NULL)
{perror("fopen fail");
return;
}
for (size_t i = 0; i< n; ++i)
{int val = rand();
fprintf(fin, "%d\n", val);
}
fclose(fin);
//找topk
FILE* fout = fopen("data.txt", "r");
if (fout == NULL)
{perror("fopen fail");
return;
}
//開辟k個(gè)大小的數(shù)組
int* minHeap = (int*)malloc(sizeof(int) * k);
if (minHeap == NULL)
{perror("malloc fail");
return;
}
//將文件中的前k個(gè)數(shù)據(jù)讀到數(shù)組中
for (int i = 0; i< k; ++i)
{fscanf(fout, "%d", &minHeap[i]);
}
//建一個(gè)k個(gè)大小的小堆
for (int i = (k - 1 - 1) / 2; i >= 0; --i)
{AdjustDown(minHeap, k, i);
}
int val = 0;
while (fscanf(fout, "%d", &val) != EOF)
{if (val >minHeap[0])
{ minHeap[0] = val;
AdjustDown(minHeap, k, 0);
}
}
for (int i = 0; i< k; ++i)
{printf("%d ", minHeap[i]);
}
printf("\n");
fclose(fout);
}
運(yùn)行結(jié)果:
TopK問題圖解(大的前k個(gè)數(shù)舉例):
堆排序和TopK問題到這里就介紹結(jié)束了,期待大佬們的三連!你們的支持是我大的動(dòng)力!
文章有寫的不足或是錯(cuò)誤的地方,歡迎評(píng)論或私信指出,我會(huì)在第一時(shí)間改正。
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購(gòu),新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧