06算法-查找第k小的數(order statistic)

前言

這個問題作用的數據集是這樣的,假設有一個數組,其裏面的每一個數據都不是重複的。然後在這樣的個數組裏,去尋找第k小的元素。這樣的方法可以先排序,然後獲取。這樣的算法的時間複雜度爲Θ(nlgn)\Theta(nlgn)。那能否在線性時間獲取到第k小的元素呢?本文給出了兩種方法來解決這個問題。
第一種方法其實是快速排序partition函數移植過來的,它的期望是Θ(n)\Theta(n),但是最壞的情況時,其時間複雜度爲等差數列的和Θ(n2)\Theta(n^2)
第二種方法,針對方法一中的最壞的情況,提出了一種選擇分割元素的方法,來解決這個問題,其時間複雜度爲線性Θ(cn)\Theta(cn),由於這個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這樣的,那其時間複雜度會達到n2n^2

Selection in worst-case linear time

考慮到上述算法,在最壞的情況下,其時間複雜度爲n2n^2。在很久以前幾位大佬提出了這樣的一種分割方法。用代碼實現起來還是很複雜的(不過本文會給出具體實現的代碼)。
考慮到時間複雜度的不確定性,都是來自於分割位置的不確定性造成的。因此提出了這樣的一種算法來選取分割元素。
在這裏插入圖片描述
首先將數組中的元素按照5個一組,劃分爲[n/5][n/5],其中最後一組元素個數爲n%5n\%5
我們對每一組的5個元素選取中位數,也就是圖中的白球,一共爲[n/5][n/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
	}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章