前言
這個問題作用的數據集是這樣的,假設有一個數組,其裏面的每一個數據都不是重複的。然後在這樣的個數組裏,去尋找第k小的元素。這樣的方法可以先排序,然後獲取。這樣的算法的時間複雜度爲。那能否在線性時間獲取到第k小的元素呢?本文給出了兩種方法來解決這個問題。
第一種方法其實是快速排序partition函數移植過來的,它的期望是,但是最壞的情況時,其時間複雜度爲等差數列的和;
第二種方法,針對方法一中的最壞的情況,提出了一種選擇分割元素的方法,來解決這個問題,其時間複雜度爲線性,由於這個c很大,所以這個算法應用並不是很廣泛。
Selection in expected linear time
這裏直接給出算法的僞代碼:
Randomized-Select(A,p,r,i)
if p==r
return A[p]
q = Randomized-Partition(A,p,r)
k = q-p+1
if i == k
return A[q]
elseif i<k
return Randomized-Select(A,p,q-1,i)
else return Randomized-Select(A,q+1,r,i-k)
下面給出go實現的完整版的代碼
func partition(A []int, left, right int) int{
flagNum := A[left]
partPosit := left
for i:=left+1; i<=right;i++{
if A[i] < flagNum {
partPosit++
A[partPosit], A[i] = A[i], A[partPosit]
}
}
A[partPosit], A[left] = A[left], A[partPosit]
return partPosit
}
func getRandNum(down, up int) int {
return int(rand.Int63n(int64(up-down)))+down
}
func RandomizedPartition(A []int, left, right int) int{
i:=getRandNum(left, right)
A[left], A[i] = A[i], A[left]
return partition(A, left, right)
}
func RandomizedSelect(A []int, left, right, i int) int {
if left == right {return A[left]}
flagNum := RandomizedPartition(A, left, right)
k := flagNum-left+1
if k == i{
return A[flagNum]
}else if k > i{
return RandomizedSelect(A, left, flagNum-1, i)
}else {
return RandomizedSelect(A, flagNum+1, right, i-k)
}
}
如上這是go語言實現的完整版的代碼。下面對其時間複雜度進行分析:
證明我就不在這裏證明了,其中用到了indicate random variable, 多項疊加,用cn的替換法但是這個的最壞的情況是怎樣的呢?如果每次對長度爲n的數組,分割的都是1,n-1這樣的,那其時間複雜度會達到。
Selection in worst-case linear time
考慮到上述算法,在最壞的情況下,其時間複雜度爲。在很久以前幾位大佬提出了這樣的一種分割方法。用代碼實現起來還是很複雜的(不過本文會給出具體實現的代碼)。
考慮到時間複雜度的不確定性,都是來自於分割位置的不確定性造成的。因此提出了這樣的一種算法來選取分割元素。
首先將數組中的元素按照5個一組,劃分爲,其中最後一組元素個數爲。
我們對每一組的5個元素選取中位數,也就是圖中的白球,一共爲個,然後再在這些元素中選擇中位數。這樣選擇元素有什麼好處呢?
我們在圖上看到,通過劃分獲取的元素x,那麼x在這些元素中所處的位置就是這樣的。有一個象限的元素是大於x的,有一個象限的元素是小於x的。還有兩個象限比較模糊。但是這個元素在數據中所處的位置,肯定不會是最糟糕的情況。然後我們就開始調用之前的算法解決問題。具體實現的代碼如下(鐵子,我覺得有點小複雜,如果有更合適的方法,可以告訴我):
var midNumMap map[int]int
var midNumArray []int
func RandomizedSelectForWorst(A []int, left, right, i int) int{
if left == right {return A[left]}
flagNum := RandomizedPartionForWorst(A, left, right)
k := flagNum-left+1
if k == i{
return A[flagNum]
}else if k > i{
return RandomizedSelect(A, left, flagNum-1, i)
}else {
return RandomizedSelect(A, flagNum+1, right, i-k)
}
}
func RandomizedPartionForWorst(A []int, left, right int) int {
i := SelectMidNum(A, left, right)
A[left], A[i] = A[i], A[left]
return partition(A, left, right)
}
func SelectMidNum(A []int, left, right int) int {
midNumMap = make(map[int]int)
midNumArray = make([]int, (right-left+1)/5)
fiveCnt := 0
for ; fiveCnt < (right-left+1)/5; fiveCnt++{
down := left+5*fiveCnt
insertSort(A, down, down+4)
midNumMap[A[down+2]] = down+2
midNumArray[fiveCnt] = A[down+2]
}
if num := (right-left+1)%5; num != 0{
down := right-num+1
insertSort(A, down, right)
midNumMap[A[(down+right)/2]] = (down+right)/2
midNumArray = append(midNumArray, A[(down+right)/2])
}
ithNum := 0
if len(midNumArray)%2 == 0{
ithNum = len(midNumArray)/2
}else {
ithNum = len(midNumArray)/2+1
}
midOfMids := RandomizedSelect(midNumArray,0, len(midNumArray)-1, ithNum)
return midNumMap[midOfMids]
}
func insertSort(A []int, left, right int){
len := right -left + 1
if len < 2 {
return
}
for i := left+1; i <= right; i++{
key := A[i]
j := i-1
for j >= left && A[j] > key{
A[j+1] = A[j]
j--
}
A[j+1] = key
}
}