package BFPRT
import (
"encoding/json"
"math/rand"
"testing"
"time"
)
// bfprt算法 ———— 解决无序数组中第k小的数,第k大的数问题 O(N)拿下
// k 往往从1 开始
// 有一个好理解的线性时间内拿下的方法,就是来折腾快排,和快排又不一样的地方,是快排的话,就是O(N*logN)的复杂度
// 这道题借用了快排的思路 。
//快排,数组中随机选一个数,M 左 < M 中 = M 右 > M
//假如 下标 29~31 是 = M 区域的, 如果求第 7 小的数, 则在左侧 再随机选一个数,
//如果求70小,则在右边继续玩荷兰国旗,直到命中到了,结束
// 这个算法不是快排,快排 = 区域是不用动的, 左侧 快排,右侧 快排
// 该算法,只会进左右 一侧
// 随机选M
// 时间复杂度O(N) 概率累加
// 利用大根堆,时间复杂度O(N*logK)
func minKth1(arr []int, k int) int {
maxHeap := NewMyHeap(func(a, b interface{}) bool {
return a.(int) > b.(int)
})
for i := 0; i < k; i++ {
maxHeap.push(arr[i])
}
for i := k; i < len(arr); i++ {
if arr[i] < maxHeap.peek().(int) {
maxHeap.pop()
maxHeap.push(arr[i])
}
}
return maxHeap.peek().(int)
}
func TestMinKth1(t *testing.T) {
t.Log(minKth1([]int{3,4,2,1,3,5,6,9},5))
}
type MyHeap struct {
heap []interface{}
indexMap map[interface{}]int //任何一个样本,记录在堆上的位置
heapSize int
comparator func(a, b interface{}) bool //比大小
}
func (my *MyHeap)String() string {
byt,_ := json.MarshalIndent(my.heap,"\t"," ")
return string(byt)
}
func (my *MyHeap)peek() interface{} {
return my.heap[0]
}
func NewMyHeap(com func(a, b interface{}) bool ) *MyHeap {
return &MyHeap{
heap: []interface{}{},
indexMap: map[interface{}]int{},
heapSize: 0,
comparator: com,
}
}
func (my *MyHeap) IsEmpty() bool {
return my.heapSize == 0
}
func (my *MyHeap) Size() int {
return my.heapSize
}
func (my *MyHeap)contains(key interface{}) bool {
_, ok := my.indexMap[key]
return ok
}
func (my *MyHeap)push(value interface{}) {
my.heap = append(my.heap, value)
my.indexMap[value] = my.heapSize
my.heapInsert(my.heapSize)
my.heapSize++
}
func (my *MyHeap)pop() interface{} {
ans := my.heap[0]
end := my.heapSize - 1
my.swap(0,end)
my.heap = my.heap[0:end]
delete(my.indexMap,ans)
my.heapSize--
my.heapify(0,my.heapSize)
return ans
}
func (my *MyHeap)heapInsert(index int){
for my.comparator(my.heap[index],my.heap[(index -1) /2]) {
my.swap(index,(index - 1) / 2)
index = (index - 1) / 2
}
}
func (my *MyHeap)resign(val interface{}) {
valueIndex := my.indexMap[val]
my.heapInsert(valueIndex) //上行或下行,两个分支 只会中一个
my.heapify(valueIndex,my.heapSize) //都不中就出去,可以实现重复加入值,按引用传递的话
}
func (my *MyHeap)heapify(index, heapSize int) {
leftChild := 2 * index + 1
for leftChild < heapSize {
best := leftChild //下沉
if leftChild + 1 < heapSize && my.comparator(my.heap[best + 1], my.heap[best]) {
best = leftChild + 1
}
if !my.comparator(my.heap[best],my.heap[index]) {
break
}
my.swap(index,best)
index = best
leftChild = 2 *index + 1
}
}
func (my *MyHeap)swap(i, j int) { //强同步
my.heap[i], my.heap[j] = my.heap[j],my.heap[i]
my.indexMap[my.heap[i]],my.indexMap[my.heap[j]] = my.indexMap[my.heap[j]],my.indexMap[my.heap[i]]
}
// 改写快排,时间复杂度O(N)
func minKth2(array []int,k int) int { // 笔试时用该算法就可以
arr := copyArray(array)
return process2(arr,0,len(arr)-1, k - 1)
}
func copyArray(arr []int) []int {
ans := make([]int,len(arr))
for i := 0; i < len(ans); i++ {
ans[i] = arr[i]
}
return ans
}
func process2(arr []int, L, R, index int) int {
if L == R {
return arr[L]
}
rand.Seed(time.Now().UnixNano())
pivot := arr[L + rand.Int()%(R - L + 1)] // R - L + 1 ?
Range := partition(arr,L,R,pivot)
if index >= Range[0] && index <= Range[1] {
return arr[index]
}else if index < Range[0] {
return process2(arr, L, Range[0] - 1 , index)
}else {
return process2(arr,Range[1] + 1,R,index)
}
}
func partition(arr []int, L, R, pivot int) []int {
less := L - 1
more := R + 1
cur := L
for cur < more {
if arr[cur] < pivot {
less++
Swap(arr,less,cur)
cur++
}else if arr[cur] > pivot {
more--
Swap(arr,cur,more)
}else {
cur++
}
}
return []int{less + 1,more - 1}
}
func TestMinKth2(t *testing.T) {
t.Log(minKth2([]int{3,4,2,1,3,5,6,9},8))
}
func NetherlandsFlag(arr []int, L, R int) []int {
if L > R {
return []int{-1, -1}
}
if L == R {
return []int{L, R}
}
less, more, index := L-1, R, L
for index < more {
if arr[index] == arr[R] {
index++
} else if arr[index] < arr[R] {
less++
Swap(arr, index, less)
index++
} else {
more--
Swap(arr, index, more)
}
}
Swap(arr, more, R)
//fmt.Println(arr)
return []int{less + 1, more}
}
func Swap(arr []int, i, j int) {
arr[i],arr[j] = arr[j],arr[i]
}
// bfprt 不用概率,也能收敛于 O(N), MIT的五个大牛搞出来的算法
// bfprt 随机选一个数 非常讲究,其余的和快排一样
// bfprt 精心挑选数, 让唯一的后序过程尽可能淘汰尽量多的数字
// 快排没有必要这么搞,bfprt 用他有实际意义
// 如何精挑细选 选出M
// 1. 数组中 0 ~ 4 上的数算一组 5 ~ 9 上的数算一组,10 ~ 14... 后面不足5个的单独一组
// 2. 0 到 4 范围上排个序... 每个小组内部都排个序
// 如果Arr N个数, 做完 1.2. 两步, O(N) 复杂度
// 一共 N / 5 组,还是O(N)
// 3. 选出每个组中的每个小组的中位数, 最后一组不是5个 拿上中位数或下中位数
// m0, m1, m2... Mx 中位数组成的数组叫 M数组
// 4.新数组中 找到他的中位数 (递归调用bfprt),返回,他就是选出的种子 第N/10小的数
// 利用bfprt算法,时间复杂度O(N)
func minKth3(array []int, k int) int {
arr := copyArray(array)
return bfprt(arr,0,len(arr)-1,k - 1)
}
// arr[L..R] 如果排序的话,位于index位置的数,是什么,返回
func bfprt(arr []int, L, R, index int) int {
if L == R {
return arr[L]
}
// L...R 每五个数一组
// 每一个小组内部排好序
// 小组的中位数组成新数组
// 这个新数组的中位数返回
pivot := medianOfMedians(arr, L, R)
Range := partition(arr, L, R, pivot)
if index >= Range[0] && index <= Range[1] {
return arr[index]
} else if index < Range[0] {
return bfprt(arr, L, Range[0] - 1, index)
} else {
return bfprt(arr, Range[1] + 1, R, index)
}
}
// arr[L...R] 五个数一组
// 每个小组内部排序
// 每个小组中位数领出来,组成marr
// marr中的中位数,返回
func medianOfMedians(arr []int, L, R int) int {
size := R - L + 1
offset := 1
if size % 5 == 0 {
offset = 0
}
mArr := make([]int,size/5 + offset)
for team := 0; team < len(mArr); team++ {
teamFirst := L + team * 5
// L ... L + 4
// L +5 ... L +9
// L +10....L+14
mArr[team] = getMedian(arr, teamFirst, Min(R, teamFirst + 4))
}
// marr中,找到中位数
// marr(0, marr.len - 1, mArr.length / 2 )
return bfprt(mArr, 0, len(mArr) - 1, len(mArr) / 2)
}
func Min(a, b int) int {
if a > b {
return b
}
return a
}
func getMedian(arr []int, L, R int) int {
insertionSort(arr,L,R)
return arr[(L + R) / 2]
}
func insertionSort(arr []int, L, R int) {
for i := L + 1; i <= R; i++ {
for j := i -1; j >= L && arr[j] > arr[j+1]; j-- {
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
func TestMinKth3(t *testing.T) {
t.Log(minKth3([]int{3,4,2,1,3,5,6,9},7))
}
func GenerateRandomArray(maxSize, maxValue int) []int {
rand.Seed(time.Now().UnixNano())
arr := make([]int,rand.Int() % maxSize + 1 )
for i := 0; i < len(arr); i++ {
arr[i] = rand.Int() % maxValue + 1
}
return arr
}
func TestMinKth(t *testing.T) {
rand.Seed(time.Now().UnixNano())
testTimes := 10000
maxSize := 10000
maxValue := 10000
t.Log("test begin")
for i := 0; i < testTimes; i++ {
arr := GenerateRandomArray(maxSize,maxValue)
k := rand.Int() % len(arr) + 1
//fmt.Println(arr,k)
ans1 := minKth1(arr,k)
ans2 := minKth2(arr,k)
ans3 := minKth3(arr,k)
if ans1 != ans2 || ans2 != ans3 {
t.Log(ans2,ans3,arr,k)
return
}
}
t.Log("test finish")
}
bfprt算法——无序数组第k小的数O(N)
本文介绍了三种寻找无序数组中第k小/大的数的算法:线性时间复杂度的bfprt算法、大根堆实现以及改进版快速选择算法。详细阐述了bfprt算法的思路,通过随机选取中位数减少比较次数,达到平均时间复杂度O(N)。同时,对比了快排和bfprt的不同,并提供了代码实现和测试案例。
摘要由CSDN通过智能技术生成