bfprt算法——无序数组第k小的数O(N)

本文介绍了三种寻找无序数组中第k小/大的数的算法:线性时间复杂度的bfprt算法、大根堆实现以及改进版快速选择算法。详细阐述了bfprt算法的思路,通过随机选取中位数减少比较次数,达到平均时间复杂度O(N)。同时,对比了快排和bfprt的不同,并提供了代码实现和测试案例。
摘要由CSDN通过智能技术生成
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")
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

metabit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值