1. 程式人生 > >最差情況為線性時間的選擇

最差情況為線性時間的選擇

這個演算法寫了我好久,在這裡記一下。

演算法的原理是利用中位數來作為劃分元素選擇第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方法會影響到切片中不需要重新劃分的部分,但不影響演算法的複雜度,只是多了一點無意義的操作。

程式碼對於所有測試用例都能通過。