1、排序
對陣列進行排序
,然後前k個元素就是需要查詢的元素,排序的方法可以採用快速排序,但是我們知道在快速
排序中如果已經是有序的陣列,採用快速排序的時間複雜度是o(n^2),為了解決這種問題,通常選擇隨機選擇乙個
陣列值pivot作為基準,將陣列分為s1 =< pivot和s2 > pivot,這樣就能避免快速排序中存在的問題,或者採
用隨機選擇三個元素,然後取中間值作為基準就能避免快速演算法的最差時間複雜度,這種方法的前k個數字是有序的。
2、利用快排中的pivot特性
既然是選擇前k個物件,那麼就沒必要對所有的物件進行排序,可以採用快速選擇的思想獲得前k個物件,
比如首先採用快速排序的集合劃分方法劃分集合:s1,pivot,s2,然後比較k是否小於s1的個數,如何小於,則
直接對s1進行快速排序,如果k的個數超過s1,那麼對s2進行快速排序,排序完成之後,取陣列的前k個元素就是數
組的前k個最小值。這種實現方法肯定比第一種的全快速排序要更快速。
3、將陣列轉換為最小堆的情況
根據最小堆的特性,第乙個元素肯定就是陣列中的最小值,這時候我們可以
將元素儲存起來,然後將最後乙個元素提公升到第乙個元素,重新構建最小堆,這樣進行k次的最小堆建立,就找到
了前k個最小值,這是運用了最小堆的特性,實質上是最小堆的刪除實現方法。這種演算法的好處是實現了陣列的原
地排序,並不需要額外的記憶體空間。
4、接下來的這種思想有點類似桶排序
首先給定乙個k個大小的陣列b,然後複製陣列a中的前k個數到陣列b
中,將這k個數當成陣列a的前k個最小值,對陣列b建立最大堆,這時候再次比較陣列a中的其他元素,如果其他元
素小於陣列b的最大值(堆頂),則將堆頂的值進行替換,並重新建立最大堆。這樣遍歷一次陣列就找到了前k個
最小元素。這種方法運用了額外的記憶體空間,特別當選擇的k值比較大時,這種方法有待於權衡一下。
這種方法對於海量資料來說是有較好的作用,對於海量資料不能全部存放在記憶體中,這時候建立乙個較小的
陣列空間,然後建立最大堆,從硬碟中讀取其他的資料,進而實現前k個資料的查詢。
#include#include#include#include#include#define len 500000
#define k 100
/*堆的性質*/
#define leftson(i) (2*(i)+1)
#define rightson(i) (2*((i)+1))
#define parent(i) (((i)-1)/2)
void swap(int *a, int *b)
}int partition(int *a, int left, int right)
}swap(&a[j + 1],&a[right]);
return (j + 1);
}void quicksort(int *a, int left, int right)
}int quicksort(int *a, int size)
void quickselect(int *a, int left, int right, int k)
}void quickselect(int *a, int size, int k)
/*最大堆*/
void max_heapify(int *a, int left, int right)
a[parent] = tmp;
}/*建立最大堆*/
void build_maxheap(int *a, int size)
/*最小堆的實現*/
void min_heapify(int *a, int left, int right)
a[parent] = tmp;
}/*建立最小堆*/
void build_minheap(int *a, int size)
/*採用快速排序查詢*/
void find_kmin_num_1(int *a , int size, int k)
/*採用快速選擇實現*/
void find_kmin_num_2(int *a, int size, int k)
/*採用最大堆實現*/
void find_kmin_num_3(int *a, int size, int k)
}#if 0
for(i = 0; i < k ; ++ i)
printf("%d\t",b[i]);
printf("\n");
#endif
}/*採用最小堆刪除元素的方式實現*/
void find_kmin_num_4(int *a ,int size, int k)
// printf("\n");
}int main()
// printf("\n");
_start = clock();
find_kmin_num_1(a,len,k);
times = (double)(clock() - _start)/clocks_per_sec;
printf("快速排序的查詢需要:%f\n",times);
_start = clock();
find_kmin_num_2(b,len,k);
times = (double)(clock() - _start)/clocks_per_sec;
printf("快速選擇的查詢需要:%f\n",times);
_start = clock();
find_kmin_num_3(c,len,k);
times = (double)(clock() - _start)/clocks_per_sec;
printf("最大堆的查詢需要:%f\n",times);
_start = clock();
find_kmin_num_4(d,len,k);
times = (double)(clock() - _start)/clocks_per_sec;
printf("最小堆的查詢需要:%f\n",times);
return 0;
}5、select
演算法,它能在時間複雜度為
o(n)
的情況下找出第
k大的數
第一步:把陣列分成
n/5這麼多子陣列,每個子陣列裡包含
5個數,因為會有無法整出的可能,所以最後乙個子陣列會小於5.
第二步:用
insertionsorting把這5
個數排序,然後找出中位數,也就是第3個。
第三步:把獲得的中位數又排序,找出中位數的中位數。如果中位數的個數是偶數,那麼取排好序的第
m/2個數,
m指的是中位數的個數。
第四步:然後呢,把原來的陣列分成兩個部分,一部分比那個
「中位數的中位數
」大,一部分比那個
「中位數的中位數
」小。我們可以假設左邊的數大,右邊的數小。然後我們可以得到
「中位數的中位數
」的位置i.
第五步:如果
i = k,
那麼那個
「中位數的中位數
」就是第
k大的數。如果
i < k
,不用說,第
k大的在
「中位數的中位數
」的右邊,否則就在左邊。我們一直
recursely
這麼做,那麼就一定能夠找到第
k大的值了。
其實,演算法還是比較容易懂得,關鍵的關鍵,是複雜度的分析。如果能夠知道複雜度如何求出來的,那麼,對演算法本身就了解得更清楚。
要講複雜度,首先看乙個圖。
圖中的x 就是「中位數的中位數」, 而且箭頭的方向是從大數指到小數。所以,我們可以知道,至少灰色區域的都比x大,這是整個複雜度分析的關鍵,而,其它點能否說它比x大,我們不能保證。而灰色區域裡最多有多少個數呢?因為x是中位數的中位數,所以,比x大的中位數最少有 [(n/5l)* (1/2) - 2] 個(這個值也是關鍵),這裡減2是因為要去除x本身,第二呢,還要去除乙個中位數---這個中位數所在的子陣列個數小於5. 所以,最壞最壞的情況,第k大的值不在灰色區域裡,那麼我們就要對剩下部分進行不斷的select。剩餘部分就是n -3 [(\lfloor n/5 \rfool) * (1/2) - 2] = o(7n/10) .
整個過程中,第1,2,4步所需時間為o(n), 注意第2步的複雜度不為o(n^2),第3步的複雜度為 t(n/5),第五步的複雜度為 t(7n/10)。
所以,複雜度的遞迴公式為: t(n)= t(n/5) + t(7n/10) + o(n), 算出來以後t(n) =o(n).
無序數字中位數 如何在無序陣列中查詢第K小的值
如題 給定乙個無序陣列,如何查詢第k小的值。例子如下 在乙個無序陣列,查詢 k 3 小的數 輸入 arr 輸出 7在乙個無序陣列,查詢 k 4 小的數 輸入 arr 輸出 10幾種思路如下和複雜度分析如下 1 最簡單的思路直接使用快排,堆排或者歸併排,排序之後取陣列的k 1索引的值即可,時間複雜度為...
尋找無序陣列的第K項(分治)
尋找n個數的第k項只需要對它們進行排序,然後找到即可。但是耗時o nlongn 在書上看到了很好的方法,想著實現一下 輸入 乙個數列a,乙個整數k 輸出 數列a中第k小的數 我們假定陣列的乙個數v,現在把陣列a分成三份。a2 a3 例如任意乙個陣列a 假設v等於5 a1 2 4 1 a2 5 5 a...
快速排序 尋找無序陣列中的第k大的數
思路是利用快速排序 因為快速排序的分治思想可以將查詢的範圍縮小 快速排序的思想 low為陣列的起始點,high為陣列的尾部點。交替掃瞄 1.固定陣列的第乙個數為定點,從陣列的尾部high開始往左查詢,直到第乙個比定點小的數,和定點交換,因此當前點為空 high 2.從陣列的起始處,low找到第乙個比...