之前介紹了堆的一些操作,對堆排序的操作是在提前建立了堆的資料結構的基礎上直接調用以前的資料結構,但是今天我們要做的是,假如現在你去面試或者遇到問題的時候沒有堆這個資料結構可以直接調用的時候怎麼辦呢。難道要現場建立一個嗎。也不是說不可以,但是畢竟你的時間是有限的,而且其實可以通過直接對數組的操作來進行堆的操作。
其實TopK問題就是複雜之後的堆排序問題,所以今天我主要給大家介紹一個TopK問題,其實TopK問題簡單來說就是在海量的資料中找到最大的K個數值。就那我們正常的排序來看例如:冒泡排序,在解決海量資料的時候我們就要考慮你程式的複雜度。包括現在很多程式也是,你實現你的思想並不難,難的是你的演算法,你有一個好的演算法就等於讓你的程式有了更高的智商來解決這個問題。就能又快有準解決你所想要解決的問題。繼續說我們之前所熟悉的冒泡排序,這裡大家學了時間複雜度之後都知道,冒泡排序是一個時間複雜度為O(n)的處理常式,當你有1500個資料的時候你就需要2250000次計算,並且當隨著你的資料的不斷增加,這個數量是急劇增長的。所以在我們處理更多的資料的時候就需要好的演算法。
這裡就是用到了我們的堆,首先還是之前的問題,TopK是找到最大的K個資料,既然是這樣,那是不是就意味著,和我們之前對堆的操作一樣,現在將你的海量資料進行建造大堆等堆建立好了之後,選出來最大的一個,也就是堆頂的資料,然後讓最後一個補上來,繼續開始一次向下調整。這樣實現起來是沒有任何問題的,但是在我們看起來他並沒有那麼的簡單,那麼的方便。這裡有沒有更好的辦法呢。我其實你想假如你現在的前K個元素正好就是最大的元素,那當你建立好整個堆之後,你還是需要不斷的去掉頂,然後不斷的去向下調整。這裡有沒有有點更好的辦法呢。
其實說到這裡我想大家也有一定的思路了,那就是我要TOP的K個元素也就是最大的K個,那我能不能只建立一個K大小的堆,其實完全可以,那大家想一下我是要建立一個大堆還是一個小堆呢。-------------小堆。為什麼呢。舉個例子假如你現在要找最大的前十個數,然後你建立了一個十個數位大堆,現在堆頂是十個數裡邊最大的數,堆頂其實可以想象一個棧頂,他才是你對資料操作的關鍵位置,假如你建立的是大堆,也就是前十個數最大的在堆頂,那假如第十一個數比堆頂小,那你能證明什麼呢。他該不該入堆呢。萬一他比十個數字所有都小。那萬一他只比堆頂元素小呢。但是你又不能把這個數和堆裡所有元素都比較呢。所以你要建立一個小堆,堆頂是最小的元素,如果你接下來比較的數堆頂大,那就說明這個數要替代了堆頂的數了,當他替換堆頂元素之後進行一次向下調整,這時候堆裡最小的元素又跑到堆頂了,只要比他大那就把它替換掉,當整個數組遍曆完畢之後,堆裡的元素就是最大的十個元素了。
那我們應該如何在直接在數組上進行堆的一系列操作呢。
void HeapTopK(int *a, int n, int k){int num = n;int i= (k - 2) / 2;for (; i >= 0; i--){ AdjustDown(a, k, i);}for (i = k; i < num; i++){if (a[i]>a[0]){int temp = a[0];a[0] = a[i];a[i] = temp;AdjustDown(a, k, 0);}}for (i = 0; i < k; i++){ printf("%d\n", a[i]);}}
這裡用到的向下調整函數我之前雖然有些過,但是,等下我還是會放在後邊,方便大家看。
這裡首先我在數組上的前K個數建立一個堆。
for (; i >= 0; i--){ AdjustDown(a, k, i);}
其實這裡和我們直接在堆資料結構進行操作是一樣的,不過這裡操作的是數組的前K個元素。
for (i = k; i < num; i++){if (a[i]>a[0]){int temp = a[0];a[0] = a[i];a[i] = temp;AdjustDown(a, k, 0);}}
然後通過第K個開始往後每個元素和堆頂進行比較,如果打的話就進行交換,交換之後一定要記著再次進行向下調整函數。才能保證交換之後的數組堆仍然是一個小堆,堆頂元素仍然可以進行比較。
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h>#include<stdlib.h> void AdjustDown(int *a, int n, int root){int child = root * 2 + 1;int parent = root;while (child < n){if ((child+1<n)&&(a[child + 1] < a[child]))child++;if (a[parent]>a[child]){int temp = a[child];a[child] = a[parent];a[parent] = temp;;parent = child;child = parent * 2 + 1;}elsebreak;}}
這是向下調整函數。
int main(){int arr[10] = { 1, 23, 43, 45, 65, 76, 78, 98, 90, 123 };HeapTopK(arr, 10, 5);system("pause");return 0;}
這是主函數進行測試的時候使用的。