中位數算法O(N)有許多妙用,能夠在一些場合下替代 排序O(NlgN)
1. 中位數算法
求N個數組中的中位數即求第n/2大的數
算法導論中給出了兩種求第k大的數的算法
算法1: 隨機算法 平均復雜度O(n)
思路:利用quicksort的隨機版本的partition, 將數組分成2部分:
if 左邊部分數目<k, 則在右邊遞歸搜索
else if 左邊的數> k, 則在左邊遞歸搜索
else return a[partition];
算法2: 確定性算法 最壞復雜度O(n)
2. 三個例子
例題1:帶權中位數
n個數x[1], x[2],..., x[n], 各自帶有一個權重, w[1], w[2], ..., w[n], w的總和是1
求x[k]滿足 所有滿足x[i] < x[k]的元素的權重之和< 1/2, 所有滿足x[i] > x[k]的元素的權重之和 >=1/2;
算法1: 先排序O(NlgN), 從前往后遍歷數組,找到第一個x[k], 使得前k個元素的權重之和>=1/2, return x[k]
算法2: 分治: 用中位數算法,將問題的規模減半
思路: 其實這個題並不需要排序,我們僅僅需要找到 n個數中較小的K(未知)個數的集合,使得它的和<1/2, 其他元素的和>=1/2, 具體這兩個集合中的數並不需要排序。考慮用中位數算法來找這個集合。
偽代碼: WeightedMid(a, i, j, w)
mid = Select(a, i, j) //中位數算法
left_sum = w[i]+w[i+1]+...+w[mid-1]; //左半部分數組的權重和
right_sum = w[mid+1]+w[mid+1]+...w[j];
if left_sum >=w, return WeightedMid(a, i, mid-1, w);
elseif right_sum >w, return WeightedMid(a, mid, j, w-left_sum);
else return x[mid];
T(n) = T(n/2) + O(n)
例題2: 部分背包問題:
一個竊賊去一家商店偷竊,有n件商品: 第i件物品值vi元,重wi榜(vi, wi都是整數),他的背包最多只能裝下W榜物品,
每件商品他可以選擇一部分帶走,而不像0-1背包問題。問他最多能帶走多貴的物品?
分析: 由於部分背包問題允許僅拿走物品的一部分,物件更像是金粉,可證明其具有貪心的性質。
算法1: 貪心
按照每榜的價值進行排序,然后由價值的大小依次往包里裝,直到重量為W。
算法復雜度是 O(nlgn).
能不能將其復雜度降低到線性呢?
注意到,無論是動態規划還是貪心,其實都具有問題可分(decomposed)的性質, 也就是可以考慮用分治(divide-and-conquer)。
要構造O(n)的算法,首先想到 T(n) = T(n/2) + O(n),
--------------- 在O(n)的時間內把問題的規模降為一半,那么就得到了一個O(N)的算法。
分析:
貪心算法時間復雜度主要是排序,可以不排序嗎?
可以。排序只是為了找出那些單價高的物品集合,所以我們並不需要把所有的單價進行排序。
我們只需要找到背包所能裝得下的那部分單價較高的物品即可。這類似於在數組中找前k大的k個數(復雜度是O(N)).
因此使用排序我們其實做了多余的比較。
目標:一分為二,而且要找的是前k大的k個數(k未知),
Bingo! 先找出中位數!
算法2:
n個物品的單價數組: A[1:n]
找出其中位數,將數組分成3個部分: A{單價高於中位數} B{單價等於中位數} C{單價小於中位數}
注意到 {A}, {A+B}, {C}的規模<=n/2
下面分三種情況:
1. 若集合A中的物品總質量 >= W, 遞歸在A中解部分背包問題
2. 若集合A中的物品總質量 < W, 且集合{A+B}中的物品總質量 >=W, 將A中物品全部裝入包中,剩余的從B中隨便取即可
3. 若集合{A+B}中的物品總質量 < W, 將A和B中的物品全部放入背包中,剩余的質量遞歸地在集合C中求解
復雜度分析:
求中位數復雜度是 O(N), 上述三種情況中除了子問題外,也頂多花O(N)時間,
T(n) <= T(n/2) + O(n)
所以 T(n) = O(n)
例3:
N個數中有一個數出現的次數大於1/2
算法1: 先排序,再掃描一次數組,記錄出現的次數 O(nlgn)
算法2: 這一問題其實也不需要排序。中位數即為所求。O(n)