快速排序 quicksort

本文深入讲解PHP中快速排序算法的实现原理与过程,通过具体示例解析如何利用递归进行数组排序,避免死循环,达到理论最优效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

快速排序是比较常用的排序算法,比如Go1.23.4标准库中的slices.Sort()的实现,实际上很多编程语言提供的默认排序算法就是quicksort,应该说在绝大部分场景下这都是最优解。

src\slices\zsortordered.go

// pdqsortOrdered sorts data[a:b].
// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort.
// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf
// C++ implementation: https://github.com/orlp/pdqsort
// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/
// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort.
func pdqsortOrdered[E cmp.Ordered](data []E, a, b, limit int) {
    ......
}
快速排序

其时间复杂度可为O(nlogn),对应的空间复杂度可为O(n)。快速排序可实现理论最优效率。

快速排序(QuickSort )是一个分治算法(Divide and Conquer)。它选择一个元素作为枢轴元素(pivot),并围绕选定的主元素对给定数组进行分区(partition)。快速排序有很多不同的版本,它们以不同的方式选择枢轴。

  1. 总是选择第一个元素作为 pivot。
  2. 总是选择最后一个元素作为 pivot。
  3. 随机选一个元素作为 pivot。
  4. 选择中值作为 pivot。

根据基准F,将序列拆分为大于,等于,小于三块,然后再对左右两块也执行同样的拆分,拆到最后每组只有一个或两个数,比较一下就好了,然后再倒回来把各个小块合并起来就是排好序的了,当然这需要用到递归,所以在实现的时候一定注意避免死循环。

根据以上原理,以下是一个基础的实现

func QuickSort1(data []int) []int {
	if len(data) == 1 {
		return data
	}
	if len(data) == 2 {
		if data[0] > data[1] {
			data[0], data[1] = data[1], data[0]
		}
		return data
	}

	pivot := data[0]
	left := make([]int, 0)
	middle := make([]int, 0)
	right := make([]int, 0)
	for _, x := range data {
		if x > pivot {
			right = append(right, x)
		} else if x == pivot {
			middle = append(middle, x)
		} else {
			left = append(left, x)
		}
	}

	var rst []int
	if len(left) > 0 && len(right) > 0 {
		rst = slices.Concat(QuickSort1(left), middle, QuickSort1(right))
	} else if len(left) > 0 {
		rst = slices.Concat(QuickSort1(left), middle)
	} else if len(right) > 0 {
		rst = slices.Concat(middle, QuickSort1(right))
	}

	return rst
}

func main() {
	series := []int{6, 0, 1, 7, 9, 4, 3, 8, 2, 5}
	series = QuickSort1(series)
	fmt.Println(series)
}
// 输出为:[0 1 2 3 4 5 6 7 8 9]

这种实现需要大量的创建切片并合并切片,性能肯定是有问题的。golang的切片是引用传递,我们完全可以使用swap操作来将切片一分为二然后分而治之。

// 参考:https://www.geeksforgeeks.org/quick-sort-algorithm/
func QuickSort2(data []int, low, high int) {
	if low < high {
		pivot := data[high]
		i := low - 1
		for j := low; j <= high; j++ {
			if data[j] < pivot {
				i++
				data[i], data[j] = data[j], data[i]
			}
		}
		data[i+1], data[high] = data[high], data[i+1]

		pi := i + 1

		QuickSort2(data, low, pi-1)
		QuickSort2(data, pi+1, high)
	}
}
func main() {
	series := []int{6, 0, 1, 7, 9, 4, 3, 8, 2, 5}
	QuickSort2(series, 0, len(series)-1)
	fmt.Println(series)
}
// 输出为:[0 1 2 3 4 5 6 7 8 9]

三个操作数 pivot, i, j,其中 i 的初始值为 -1,j 从左往右走,将小于 pivot 的元素与 i 互换,最后将 pivot 换到 i 的右边,这样就实现了按照基准分割。

基准测试
var num = 10000
func BenchmarkSlicesSort(b *testing.B) {
	slices.Sort(RandSeries(num))
}
func BenchmarkQuickSort1(b *testing.B) {
	QuickSort1(RandSeries(num))
}
func BenchmarkQuickSort2(b *testing.B) {
	series := RandSeries(num)
	QuickSort2(series, 0, len(series)-1)
}
> go test -v -bench=.
goos: windows
goarch: amd64
pkg: go-demo/demos/sort
cpu: Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz
BenchmarkSlicesSort
BenchmarkSlicesSort-8           1000000000               0.0007569 ns/op
BenchmarkQuickSort1
BenchmarkQuickSort1-8           1000000000               0.003849 ns/op
BenchmarkQuickSort2
BenchmarkQuickSort2-8           1000000000               0.0006694 ns/op

从输出可以看到,QuickSort2与Go官方的实现性能差不多,都比QuickSort1快了五倍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值