求第k小元素

2021-09-26 01:18:47 字數 4634 閱讀 2130

題目:

給定線性序集中n個元素和乙個整數k,其中1<=k<=n,要求找出這n個元素中第k小的元素。

如果將這n個元素線性序排列時,如果不存在重複的數或者求第k個元素的時候,那麼第k個位置即為要找的元素。

當k = 1時,要找的就是最小值;而當k = n時,則要找的則是最大值。

憑藉著快速排序中的劃分函式,可以實現上面的功能。

對於序列a[p : q],分治演算法randomizedselect會隨機選擇乙個值作為基準值(pivot),在劃分之後得到的i,它為基準值所在序列的索引,而基準值把整個序列分成了三部分:分別是小於等於基準值的 a[p: i] 以及基準值 a[ i ] 和大於等於基準值的 a[ i+1 : r ]。

/**

* 模仿快速排序對陣列進行遞迴劃分,並對其一子陣列遞迴處理得到第k小元素

* @param nums: 陣列

* @param start: 起始索引 基準值

* @param end: 結束索引

* @param k: 第k個元素的索引

* @return: 返回第k個元素的值

*/templatetype randomizedselect(vector& nums, int start, int end, int k)

注:randompartition中除了形參由type nums改為了vectornums外,其餘不變。randomizedselect函式會在每次都丟棄一半的值。

快速排序是對氣泡排序的改進,所以它也有著氣泡排序的特點:每次劃分後的基準值就是它的最終位置。在randomizedselect函式中,為了得到基準值的位置,由start和基準值i的位置得到了當前基準值的排名,即

int rank = i - start + 1;
接著就可以判斷k == rank,如果相等的話,那麼直接返回該值即可;如果k < rank,則表示第k個值在左半部分,否則則在右半部分。如果在右半部分的話,由於已經知道了i的位置為rank,所以在換到右半部分的時候,還應該讓k=k-rank。

由遞迴形式改為非遞迴形式,一般會用到迴圈,有的還會使用到棧。

templatetype randomizedselect(vector& nums, int start, int end, int k) 

else

} return nums[start];

}

**思路和上面的相同。

求第k小元素主要考慮的就是重複值的處理。這裡給出的解決辦法是在每次按照基準值分成三部分的時候,把兩側的與基準值相同的值聚合到一起,這裡需要知道相同值的起始索引left和個數count,之後一次遍歷知道左半部分總共有多少不重複的值rank。

首先,我們可以根據rank就是left的真實位置,然後可以判斷rank == left,如果相等的話,則nums[left]就是第k小元素;如果k < rank,那麼k一定散落在左半部分;否則則散落在右半部分。另外,還可以完全去掉基準值和它的相同值們。

其他用到的函式不變,主要改變的就是randomizedselect函式。

/**

* 模仿快速排序對陣列進行遞迴劃分,並對其一子陣列遞迴處理得到第k小元素

* @param nums: 陣列

* @param start: 起始索引 基準值

* @param end: 結束索引

* @param k: 第k個元素的索引

* @return: 返回第k個元素的值

*/templatetype randomizedselect(vector& nums, int start, int end, int k)

**結構和之前類似,相對於之前增加了乙個聚合相同值函式together()。其餘則不變。

/**

* 聚合與基準值相同的值,重新設定基準值為第乙個相同值的索引 並返回相同的個數

* @param nums: 陣列

* @param start: 起始索引

* @param end: 結束索引

* @param pivot: 基準值索引 可能會被重寫

* @param left_rank: 左半部分的真實個數

* @return :返回了與基準值相同的個數 未算上基準值

*/templateint together(vector& nums, int start, int end, int& pivot, int* left_rank = nullptr)

} //右邊相同值聚合

for (int m = end; m > right && right + 1 < end; m--)

} setunique;

//基準值的真實排名

int rank = 1;

for (int m = start; m < pivot; m++)

} //重寫基準值

pivot = left;

if (left_rank != nullptr)

* left_rank = rank;

return right - left;

}

對於聚合函式together而言,它每次都會遍歷nums[start: end]一遍多一點。

前兩個迴圈是為了聚合相同值到一塊,第三個迴圈是為了確定nums[left]的真實排名,這裡使用到了c++提供的集合庫#include,通過它來確定不重複的值有多少。

接下來則可以簡單測試一下:

int main() ;

int len = a.size();

int k = 5;

int value = randomizedselect(a, 0, len - 1, k);

cout << value << endl;

for (int i = 0; i < len; i++)

cout << a[i] << " ";

cout << endl;

return 0;

}

按照之前的思路,在使用partition函式後,會再有乙個聚合相同值並且獲取真正排名的together,其實有關於聚合的功能可以合併到partition函式之中,把相同的值分散在最左邊和最右邊。

假設有陣列 nums = ,以nums[0]為基準值,那麼在第一次partition後,nums的值為 {},

左右兩邊都會聚合與基準值相同的值。

/**

* 對a[p:r]進行劃分 擴充套件兩個區域a[p:i]和a[j:r]

* @param nums: 陣列

* @param start: 基準值索引 在這裡也是第乙個值的索引

* @param end: 陣列最後乙個值的索引

* @return: 基準值所在的索引

*/templateint partition(vector& nums, int start, int end, int* pleft, int* qright)

if (nums[right] == x)

} //nums[start] = nums[right];

nums[p] = nums[right];

if (nums[p] != x)

p--;

//基準值放在最終位置

nums[right] = x;

if (pleft) *pleft = p;

if (qright) *qright = q;

return right;

}

在新的partition函式中,它內部還會把相同的值放在最左邊和最右邊,在求得第k小元素時,放在兩邊和聚合在一起區別並不明顯。

templateint getleft(vector& nums, int start, int end) 

index++;

} return rank;

}

該函式獲取[start, end)的不重複元素的個數。

/**

* 模仿快速排序對陣列進行遞迴劃分,並對其一子陣列遞迴處理得到第k小元素

* @param nums: 陣列

* @param start: 起始索引 基準值

* @param end: 結束索引

* @param k: 第k個元素的索引

* @return: 返回第k個元素的值

*/templatetype randomizedselect(vector& nums, int start, int end, int k)

另外,本**通過了牛客網的關於第k小元素的題目。

接下來說說效率,在一般情況下,大約兩次迴圈能去掉至少一半的值,理論上來說應該要比單純地使用排序演算法然後再遍歷要快的。

【排序演算法】快速排序的分析改進

分治演算法求第k小元素

1.問題 用分治的演算法求乙個陣列s n 中第k小的元素。2.解析 採取和快速演算法相同的思路,但是結合分治的思想,選取恰當的基準值。找到基準值以後,再按照快速排序的方法進行查詢就好了。3.設計 int r 5int r group ceil high low 1 1.0 r ceil取上限,總共分...

分治法求第k小元素(vc )

演算法 求一列數中的第k小元素,利用分治的策略進行遞迴求解。首先隨便指定乙個數,這裡我指定的是第乙個數為第k小元素記為randk,將陣列中其他的數與findk進行比較,比他小的放在左邊,大的放在右邊,如果randk左邊的元素個數為k 1個,說明findk就是你所要找的元素,如果左邊的元素個數 k 1...

求第 K 大元素

問題 給定乙個長度為 n 的陣列,求第 k 大元素。普通的 o n 隨機分治應該廣為人知,這裡介紹一種確定性的 o n 做法。我們考慮分治,每五個元素一組,我們 o 1 求出每組五個元素的中位數,隨後我們遞迴呼叫該做法,求出每一組中位數的中位數,記為 x 然後我們 o n 掃瞄求出 x 的排名,我們...