O(n)级选排名第k位数(附上算法复杂度分析)
算法簡述
如果想要拿到第k位,一般說復雜度都比較高。例如,用快排等方式,要用了O(nlogn)水平的時間復雜度。就算是用快排改進,每次在快排的基礎上,只排剩下的一部分,在平均水平上,也會變成了O(nlogn)。
原因是,如果是第一個數本來就比較小,這樣在快排的基礎上,第一步根本都沒有能夠發生多少的移動。那么,這樣子算法復雜度降低會特別慢的。
一個比較好的方式就是,如果能保證每次都能有很大一個比例(重點!!)的數據會被排開,而不是一開始的快排的改進只做常數級別的降維。
算法描述:
假設
- 數據規模是:n,查找的是第k位的數。
第一步:
- 先將所有的數據都進行分組,這里不妨假設每個組的數據的數值都是一個固定的數額。(對于不恰好等分的數據,另外再做處理,但是這里先不妨假設每個數據組的規模都是一致的。每個組的數據規模都是一個固定的數!,這里假設為5。在每個組內進行排序。
那么總的時間為:
n5?5log(5)\frac{n}{5}*5log(5)5n??5log(5)
其中,后者為每個組內排序的時間(其實無論是怎樣的排序方式,但是由于每個組都是固定的數額,所以,后者怎么說都是一個參數),所以,這個步驟的算法復雜度為O(n)。
第二步:
- 對于每個組的可以通過線性時間直接拿到對應的中位數。
n5\frac{n}{5}5n?
復雜度也是O(n)
第三步:(關鍵的一步)
想到這里,大家肯定能猜到了,我們現在就是要拿到這些中位數的中位數。
但是由于,這個數組的長度是 O(n) 的,如果我們要用以前的方法的話,那么最后就會使得整個東西變成了更高的時間復雜度了。
但是,這里,我們注意到,中位數本身就是一個選k的問題。
所以,就可以用一個遞歸來操作。
假設整個操作的復雜度為f(n)f(n)f(n)
那么,這里的操作就是
f(n5)f(\frac{n}{5})f(5n?)
第四步:遞歸關鍵步驟
- 再用快排的分步的那種操作,將所找到的那個數字來放在中間,將整個數組進行劃分。
- 我們很容易可以留意到,這個操作最少可以把n4\frac{n}{4}4n?的數據給不用考慮了。最多甚至連3n4\frac{3n}{4}43n?的數據都不用考慮了。
但是這一步的操作,所需要的時間復雜度是:
O(n)O(n)O(n)
通過這一步的描述,任然可以得到算法的復雜度問題。(多個O(n)相加任然是O(n))
遞推公式為:
f(n)=f(n5)+f(3n4)+O(n)f(n) = f(\frac{n}{5}) + f (\frac{3n}{4}) + O(n)f(n)=f(5n?)+f(43n?)+O(n)
通過這個遞推公式,我們很容易就可以推理出來,f(n)f(n)f(n)的復雜度為O(n)O(n)O(n)。
這里為 O(n) 的原因就是每次的降低比例和算法中前面的系數乘起來是小于1的。
簡單推理計算遞推公式
對這塊沒興趣的,可以跳過了,直接看代碼就好了。
- 首先,我們假設整個算法的復雜度為多項式級的復雜度。(這里很容易通過放縮來獲得證明。)
- 對于多項式的指數大于等于1的情況下,下面的兩個式子很容易發現是成立的。
- f(n5)<f(21n100)f(\frac{n}{5})<f(\frac{21n}{100})f(5n?)<f(10021n?)
- f(a)+f(b)<=f(a+b)f(a) +f(b)<= f(a+b)f(a)+f(b)<=f(a+b)
- 所以f(n5)+f(3n4)<f(96n100)f(\frac{n}{5}) + f (\frac{3n}{4})<f(\frac{96n}{100})f(5n?)+f(43n?)<f(10096n?),即得證。
- 部分人發信息給我,到這一步可能還有些不懂
- f(n)=f(α?n)+O(n)f(n) = f(\alpha * n) + O(n)f(n)=f(α?n)+O(n) 當α\alphaα在(0,1)之間時,不妨設O(n) = cn ,c是一個固定常數。(可能有余項b,但是就算是n個也只會是O(n)所以這里就不用考慮了)
- f(n)=f(α?n)+c?n=f(αk?n)+c?n?1?αk1?αf(n) = f(\alpha * n) + c*n =f(\alpha^k * n) + c*n*\frac{1-\alpha^k}{1-\alpha}f(n)=f(α?n)+c?n=f(αk?n)+c?n?1?α1?αk?
- 因為只有一個數字的時候,排序為O(1)的,這里就有 k=log?α1nk = \log_{\alpha}{\frac{1}{n}}k=logα?n1?
- 就有f(n)=O(1)+c?n?1?1/n1?α<O(1)+c?n?11?αf(n)=O(1) + c*n*\frac{1-1/n}{1-\alpha} < O(1) + c*n*\frac{1}{1-\alpha}f(n)=O(1)+c?n?1?α1?1/n?<O(1)+c?n?1?α1?
- 很容易就知道,當α\alphaα屬于(0,1)之間的時候,即為O(n)的復雜度
代碼
#include <iostream> #include <algorithm> using namespace std; int select(int n, int *arr, int begin, int k); int partition(int *arr, int begin, int end); int main(){int n, *arr, k;cin >> n;arr = new int[n];for (int i = 0; i < n; ++i) cin >> arr[i];cin >> k;cout << select(n, arr, 0, k)<< endl;delete[] arr; }int partition(int *arr, int begin, int end) {if (begin >= end) return arr[begin];int i = begin, j = end;int flag = arr[begin];while (i < j) {while (i < j && flag < arr[j]) j--;if (i < j) arr[i] = arr[j];while (i < j && arr[i] < flag) i++;if (i < j) arr[j] = arr[i];}arr[i] = flag;return i - begin; }int select(int n, int *arr, int begin, int k) {if (n == 1) return arr[begin];int end_ = 0;for (int i = 0; i < n; i += 5) {end_ = (i + 5 <= n? i + 5: n);sort(arr + begin + i, arr + begin + end_);}int mid_len = n / 5 + (n % 5 != 0), mid_index;int *arr_ = new int [mid_len];for (int i = 0; i < mid_len; ++i) {if (n - 5*i - 2 > 0) arr_[i] = arr[begin + 5*i + 2];else arr_[i] = arr[begin + 5*i + 1];}int mid_k = select(mid_len, arr_, 0, mid_len / 2 + (mid_len % 2 != 0));int index = 0;for (int i = 0; i < n; i ++) {if (mid_k == arr[begin + i]){ index = i; break; }}int temp = arr[begin];arr[begin] = mid_k;arr[begin + index] = temp;temp = partition(arr, begin, begin + n-1);delete []arr_;if (temp == k) return mid_k;else if (temp > k) select(temp, arr, begin, k);else if (temp < k) select(n - temp - 1, arr, temp + 1, k - temp - 1); }又根據書上的算法描述寫了另外一個版本。
#include <iostream> #include <algorithm> using namespace std; int select(int *arr, int n, int k); int main() {int n, *arr, k;cin >> n;arr = new int[n];for (int i = 0; i < n; ++i) cin >> arr[i];cin >> k;cout << select(arr, n, k) << endl;delete[] arr;system("pause"); }int select(int *arr, int n, int k) {if (n <= 5) {sort(arr, arr + n);return arr[k - 1]; // 返回第k個元素}// 滿五個才湊成一組int *mid_ = new int[n / 5];for (int i = 0; i < n / 5; ++i){sort(arr + i * 5, arr + i * 5 + 5);mid_[i] = arr[5 * i + 2];}int m = select(mid_, n / 5, n / 10 + (n / 5) % 2);int* bigger = new int[3 * n / 4];int* smaller = new int[3 * n / 4];int* equal = new int[3 * n / 4];int i = 0, j = 0, s = 0, t = 0;for (t = 0; t < n; ++t) {if (arr[t] > m)bigger[i++] = arr[t];else if (arr[t] == m)equal[j++] = arr[t];elsesmaller[s++] = arr[t];}int ans;if (s > k)ans = select(smaller, s, k);else if (s + j > k)ans = m;elseans = select(bigger, i, k - s - j);delete[] mid_; delete[] bigger; delete[] smaller; delete[] equal;return ans; }總結
以上是生活随笔為你收集整理的O(n)级选排名第k位数(附上算法复杂度分析)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pytorch安装-Windows(pi
- 下一篇: selenium.common.exce