线性时间选择(C++):求第k小的数
方法一:
? ? 思想:首先對整個數組進行劃分,利用Partition函數,以數組中某個數為基準(這里以首項為標準)將數組劃分為兩部分——左邊部分的所有數都小于基準,右邊部分都大于基準,并返回基準數的下標值。
? ? ? ? ? ? ? ?然后,如果要找到第k小個數,就將k的大小與數組左半邊元素的個數(若為?j,包括基準)進行比較,如果k小于j,則對左邊部分進行遞歸,找第k小個數;若k大于j,則對右邊部分進行遞歸,找第k減去j個小數。
? ? ? ? ??
#include<iostream> using namespace std; template<typename T> int Partition(T a[], int l, int r) {int i = l, j = r + 1;T x = a[l];while (true){while (a[++i] < x && i <= r);while (a[--j] > x);if (i >= j)break;T m;m = a[i];a[i] = a[j];a[j] = m;}a[l] = a[j];a[j] = x;return j; } template<typename T> T select(T a[], int left, int right, int k) {if (left == right)return a[left];int i = Partition(a, left, right);int j = i - left + 1;if (k <= j)return select(a, left, i, k);elsereturn select(a, i + 1, right, k - j);} int main() {int m, k;cin>> m >> k;int* p = new int[m];for (int i = 0; i < m; i++)cin >> p[i];int h = select(p, 0, m - 1, k);cout << h;return 0;}分析一下Partition函數:
? ? ? ? ?以數組 a = {3 2 5 1 4 6 9}為例,首先以3為基準,令x=a[0](一般寫為x=a[left], left為數組左邊的最小下標值),i= 0,
j=6+1(最大下標值加一),接著進循環。i自增1,判斷a[i]是否小于x,若小于則繼續往下判斷,否則退出循環。然后,j自減1,判斷a[j]是否大于x,若大于繼續循環,否則退出循環。可知第一次退出時,i=2,j=3。然后交換a[i]與a[j]的值,數組變為{3 2 1 5 4 6 9},繼續循環,直到i>=j時退出循環,可知退出循環時i=3,j=2。將a[0]與a[j]的值交換,返回下標j旳值。數組變為{1 2 3 5 4 6 9}
左半部分(下標值小于2的部分)都小于基準x,即3,右邊都大于3。
方法二:
如果能在線性時間內找到一個劃分基準,使得按這個基準所劃分出的2個子數組的長度都至少為原數組長度的ε倍(0<ε<1是某個正常數),那么就可以在最壞情況下用O(n)時間完成選擇任務。
例如,若ε=9/10,算法遞歸調用所產生的子數組的長度至少縮短1/10。所以,在最壞情況下,算法所需的計算時間T(n)滿足遞歸式T(n)≤T(9n/10)+O(n) 。由此可得T(n)=O(n)。
步驟:
(1)將n個輸入元素劃分成n/5(向上取整)個組,每組5個元素,最多只可能有一個組不是5個元素。用任意一種排序算法,將每組中的元素排好序,并取出每組的中位數,共n/5(向上取整)個。
(2)遞歸調用select來找出這n/5(向上取整)個元素的中位數。如果n/5(向上取整)是偶數,就找它的2個中位數中較大的一個。以這個元素作為劃分基準。
例子:
按遞增順序,找出下面29個元素的第18個元素:8,31,60,33,17,4,51,57,49,35,11,43,37,3,13,52,6,19,25,32,
????54,16,5,41,7,23,22,46,29.
(1) 把前面25個元素分為5(=floor(29/5))組: ?(8,31,60,33,17),(4,51,57,49,35),(11,43,37,3,13),(52,6,19,25,32),(54,16,5,41,7);
(2) 提取每一組的中值元素,構成集合{31,49,13,25,16};
(3) 遞歸地使用算法求取該集合的中值,得到m=25;
(4) 根據m=25, 把29個元素劃分為3個子數組(按原有順序)
P={8,17,4,11, 3,13,6,19,16,5,7,23,22}
Q={25}
R={31,60,33,51,57,49,35,43,37,52,32,54,41,46,29}
(5) 由于|P|=13,|Q|=1,k=18,所以放棄P,Q,使k=18-13-1=4,對R遞歸地執行本算法;
(6) 將R劃分成3(floor(15/5))組:{31,60,33,51,57},{49,35,43,37,52},{32,54,41,46,29}
(7) 求取這3組元素的中值元素分別為:{51,43,41},這個集合的中值元素是43;
(8) 根據43將R劃分成3組:?{31, 33, 35,37,32, 41, 29},{43},{60, 51,57, 49, 52,54, 46}
(9)將左半邊元素的個數與18作比較,若小于18,則對右邊進行遞歸,否則對左邊進行遞歸。
參考文章:https://blog.csdn.net/m0_37579232/article/details/80178000
#include<iostream> using namespace std;template<typename T> void sort(T a[], int m, int n) {for (int i = m; i <= n; i++){int x = i;for (int j = i + 1; j <= n; j++)if (a[x] > a[j])x = j;if (x != i){T num = a[x];a[x] = a[i];a[i] = num;}} } template<typename T> int Partition(T a[], int l, int r,T midnum) {int point=0;for(int q=l;q<=r;q++)if (a[q] == midnum){point = q;break;}T df = a[l];a[l] = a[point];a[point] = df;int i = l, j = r + 1;T x = a[l];while (true){while (a[++i] < x && i <=r); while (a[--j] > x);if (i >= j)break;T m;m = a[i];a[i] = a[j];a[j] = m;}a[l] = a[j];a[j] = x;return j; }template<typename T> T select(T a[], int left, int right, int k) {if(right-left<75){sort(a, left, right);return a[left + k - 1];}for (int i = 0; i <= (right - left - 4) / 5; i++){sort(a, left + 5 * i, left + 5 * i + 4);T jk = a[left + 5 * i + 2];a[left + 5 * i + 2] = a[left + i];a[left + i] = jk;}T x = select(a, left, left + (right - left - 4) / 5, (right - left - 4) / 10);int local = Partition(a, left, right, x), j = local - left + 1;if (k <= j)return select(a, left, local, k);elsereturn select(a, local + 1, right, k - j);}int main() {int m,k;cin >> m>>k;int* p = new int[m];for (int i = 0; i < m; i++)cin >> p[i];int n = select(p, 0, m - 1, k);cout << n;return 0; }?
總結
以上是生活随笔為你收集整理的线性时间选择(C++):求第k小的数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [html] history和hash
- 下一篇: 算法:线性时间选择(C/C++)