這個算法寫了我好久,在這裏記一下。
算法的原理是利用中位數來作爲劃分元素選擇第M小的元素,中位數需要遞歸自身來求得。算法的最優,平均,最差時間複雜度都爲O(N)。相對於隨機算法改善了最差時間複雜度。
和快排用了同樣的partition,但是這個算法所使用的pivot是確定的,即中位數。
代碼版本爲golang 1.8.0。
路徑goWorkSpace/algorithms/worseLinearSelect.go
package algorithms
import (
"fmt"
)
func WorseLinearSelect(array []int, startIndex, stopIndex, rank int)(selectedValue int) {
if startIndex + 5 > stopIndex {
insertionSort(array)
return array[rank]
}
midValueArrays := getMidValueArray(array, startIndex, stopIndex)
midValue := WorseLinearSelect(midValueArrays, 0, len(midValueArrays), (len(midValueArrays) - 1)/2)
devideIndex := partitionWithPiovt(array, midValue)
if devideIndex == rank {
return array[devideIndex]
} else if devideIndex > rank {
return WorseLinearSelect(array, startIndex, devideIndex, rank)
} else {
return WorseLinearSelect(array, devideIndex + 1, stopIndex, rank)
}
}
//sort array by groups and return the mid value array
func getMidValueArray(array []int, startIndex, stopIndex int)(midValues []int) {
array = array[startIndex : stopIndex]
arrayGroups := len(array) / 5
if len(array) % 5 == 0 {
midValues = make([]int, arrayGroups)
} else {
midValues = make([]int, arrayGroups + 1)
}
//j := 0
for i, j := 0, 0; i < len(array); i += 5 {
if i + 5 <= len(array) {
b := array[i : i + 5]
insertionSort(b)
midValues[j] = array[i + 2]
} else {
insertionSort(array[i : len(array)])
midValues[j] = array[(i + len(array)) / 2]
}
//(i + 5 <= len(array)) ? (insertionSort(array[i : i + 5])) : (insertionSort(array[i : len(array)]))
j ++
}
return midValues
}
func partitionWithPiovt(array []int, pivotValue int)(firstIndex int) {
firstIndex = -1
for secondIndex := 0; secondIndex != len(array); secondIndex ++ {
if array[secondIndex] < pivotValue {
firstIndex ++
swap(&array[firstIndex], &array[secondIndex])
} else if array[secondIndex] == pivotValue {
firstIndex ++
swap(&array[firstIndex], &array[secondIndex])
swap(&array[0], &array[firstIndex])
}
}
swap(&array[0], &array[firstIndex])
return firstIndex
}
func insertionSort(array []int) {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
for i := 1; i != len(array); i++ {
selectValue := array[i]
for j := i - 1; j >= 0; j -- {
if array[j] > selectValue {
swap(&array[j], &array[j + 1])
} else {
break
}
}
}
}
func swap(a *int, b *int) {
temp := *b
*b = *a
*a = temp
}
func Partition(array []int, pivotValue int) int {
return partitionWithPiovt(array, pivotValue)
}
main包路徑goWorkSpace/golangTest/test.go,測試代碼如下:
package main
import (
"fmt"
"algorithms"
"sort"
"time"
"math/rand"
)
func main() {
//to new a random slice
rand.Seed(time.Now().UnixNano())
arrayLen := rand.Intn(400) + 200
array := make([]int, arrayLen)
for index, _ := range array {
array[index] = rand.Intn(5 * arrayLen)
fmt.Print(",", array[index])
}
rank := rand.Intn(arrayLen)
fmt.Println("array befor select\n", array)
fmt.Println("rank", rank)
fmt.Println("array length", arrayLen)
//run the select function
selectedValue := algorithms.WorseLinearSelect(array[:], 0, len(array), rank)
sort.Ints(array[:])
fmt.Println("\nselectedValue by sort", array[rank])
fmt.Println("\nselectedValue", selectedValue)
}
後來發現代碼還有小缺陷,partition方法會影響到切片中不需要重新劃分的部分,但不影響算法的複雜度,只是多了一點無意義的操作。
代碼對於所有測試用例都能通過。