288 lines
7.4 KiB
Go
288 lines
7.4 KiB
Go
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"testing"
|
||
)
|
||
|
||
// QuickSortHoare 使用 Hoare 双指针分区:l 主动向右找 >= pivot,r 主动向左找 <= pivot,
|
||
// 两指针相遇时分区完成。取中间元素作 pivot 避免已排序/逆序退化;
|
||
// 内层用严格不等号,相等元素也让两指针停下交换,防止全相等数组死循环。
|
||
func QuickSortHoare(nums []int) {
|
||
var helper func(start, end int)
|
||
helper = func(start, end int) {
|
||
if start >= end {
|
||
return
|
||
}
|
||
pivot := nums[start+(end-start)/2] // 取中间元素,避免已排序/逆序退化
|
||
l, r := start, end
|
||
for {
|
||
for nums[l] < pivot { // l 主动向右找 >= pivot 的
|
||
l++
|
||
}
|
||
for nums[r] > pivot { // r 主动向左找 <= pivot 的
|
||
r--
|
||
}
|
||
if l >= r { // 两指针相遇/交叉,分区完成
|
||
break
|
||
}
|
||
nums[l], nums[r] = nums[r], nums[l]
|
||
l++
|
||
r-- // 各进一步,相等元素也会交换,防止全相等数组死循环
|
||
}
|
||
helper(start, r)
|
||
helper(r+1, end)
|
||
}
|
||
helper(0, len(nums)-1)
|
||
}
|
||
|
||
// QuickSortLomuto 使用 Lomuto 单向分区:pivot 放 nums[end],i 维护"<= pivot 区"的右边界+1,
|
||
// j 从左扫到 end-1,遇到 <= pivot 的就与 nums[i] 交换并 i++。
|
||
// 循环结束后 swap(nums[i], nums[end]) 让 pivot 归位到 i,左半 <= pivot、右半 > pivot。
|
||
func QuickSortLomuto(nums []int) {
|
||
var helper func(start, end int)
|
||
helper = func(start, end int) {
|
||
if start >= end {
|
||
return
|
||
}
|
||
pivot := nums[end] // 取最后一个作 pivot
|
||
i := start // i 指向"<= pivot 区"的右边界+1
|
||
for j := start; j < end; j++ {
|
||
if nums[j] <= pivot {
|
||
nums[i], nums[j] = nums[j], nums[i]
|
||
i++
|
||
}
|
||
}
|
||
nums[i], nums[end] = nums[end], nums[i] // 把 pivot 放到最终位置 i
|
||
helper(start, i-1)
|
||
helper(i+1, end)
|
||
}
|
||
helper(0, len(nums)-1)
|
||
}
|
||
|
||
func TestQuickSort(t *testing.T) {
|
||
nums := []int{1, 2, 1, 2, 1, 2, 3, 1, 3, 2}
|
||
QuickSortHoare(nums)
|
||
fmt.Printf("Hoare: %+v\n", nums)
|
||
|
||
nums2 := []int{1, 2, 1, 2, 1, 2, 3, 1, 3, 2}
|
||
QuickSortLomuto(nums2)
|
||
fmt.Printf("Lomuto: %+v\n", nums2)
|
||
}
|
||
|
||
func BubbleSort(nums []int) {
|
||
for i := 0; i < len(nums); i++ {
|
||
swapped := false
|
||
for j := 0; j < len(nums)-1-i; j++ {
|
||
if nums[j] > nums[j+1] {
|
||
nums[j], nums[j+1] = nums[j+1], nums[j]
|
||
swapped = true
|
||
}
|
||
}
|
||
if !swapped {
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestBubbleSort(t *testing.T) {
|
||
nums := []int{1, 2, 1, 2, 1, 2, 3, 1, 3, 2}
|
||
BubbleSort(nums)
|
||
fmt.Printf("TestBubbleSort: %+v\n", nums)
|
||
}
|
||
|
||
// SelectionSort 选择排序:每轮在 [i, n) 找最小值,与 nums[i] 交换。
|
||
// 交换次数少(每轮最多一次),但比较次数固定 O(n²),不稳定。
|
||
func SelectionSort(nums []int) {
|
||
for i := 0; i < len(nums); i++ {
|
||
minIdx := i
|
||
for j := i; j < len(nums); j++ {
|
||
if nums[j] < nums[minIdx] {
|
||
minIdx = j
|
||
}
|
||
}
|
||
nums[i], nums[minIdx] = nums[minIdx], nums[i]
|
||
}
|
||
}
|
||
|
||
// InsertionSort 插入排序:把 nums[i] 往前插到 [0, i) 的正确位置。
|
||
// 近乎有序时接近 O(n);是很多高级排序在小数组时的收尾段。稳定。
|
||
func InsertionSort(nums []int) {
|
||
// for i := 1; i < len(nums); i++ {
|
||
// for j := i; j > 0; j-- {
|
||
// if nums[j] < nums[j-1] {
|
||
// nums[j], nums[j-1] = nums[j-1], nums[j]
|
||
// }
|
||
// }
|
||
// }
|
||
|
||
for i := 1; i < len(nums); i++ {
|
||
key := nums[i]
|
||
j := i
|
||
for j > 0 && nums[j-1] > key {
|
||
nums[j] = nums[j-1] // 往右挪一格
|
||
j--
|
||
}
|
||
nums[j] = key // 落到正确位置
|
||
}
|
||
}
|
||
|
||
// MergeSort 归并排序:分治——左半排好 + 右半排好,再合并。
|
||
// 稳定,最坏 O(n log n),需 O(n) 额外空间。合并用双指针。
|
||
func MergeSort(nums []int) {
|
||
tmp := make([]int, len(nums)) // 全局临时数组,只分配一次
|
||
var helper func(start, end int)
|
||
helper = func(start, end int) {
|
||
if start >= end {
|
||
return
|
||
}
|
||
mid := start + (end-start)/2
|
||
helper(start, mid)
|
||
helper(mid+1, end)
|
||
// merge [start,mid] 和 [mid+1,end] → tmp → 拷回 nums
|
||
p1, p2, k := start, mid+1, start
|
||
for p1 <= mid && p2 <= end {
|
||
if nums[p1] <= nums[p2] {
|
||
tmp[k] = nums[p1]
|
||
p1++
|
||
} else {
|
||
tmp[k] = nums[p2]
|
||
p2++
|
||
}
|
||
k++
|
||
}
|
||
for p1 <= mid {
|
||
tmp[k] = nums[p1]
|
||
p1++
|
||
k++
|
||
}
|
||
for p2 <= end {
|
||
tmp[k] = nums[p2]
|
||
p2++
|
||
k++
|
||
}
|
||
copy(nums[start:end+1], tmp[start:end+1]) // 关键:拷回原切片
|
||
}
|
||
helper(0, len(nums)-1)
|
||
}
|
||
|
||
// HeapSort 堆排序:建大顶堆,反复把堆顶(最大值)与末尾交换并下沉调整。
|
||
// 原地、最坏 O(n log n),不稳定。top-K / 优先队列的基础。
|
||
func HeapSort(nums []int) {
|
||
n := len(nums)
|
||
if n <= 1 {
|
||
return
|
||
}
|
||
// siftDown:在 [0, end] 范围内把 nums[i] 下沉到正确位置
|
||
var siftDown func(i, end int)
|
||
siftDown = func(i, end int) {
|
||
for 2*i+1 <= end {
|
||
l, r := 2*i+1, 2*i+2
|
||
largest := i
|
||
if l <= end && nums[l] > nums[largest] {
|
||
largest = l
|
||
}
|
||
if r <= end && nums[r] > nums[largest] {
|
||
largest = r
|
||
}
|
||
if largest == i {
|
||
break
|
||
}
|
||
nums[i], nums[largest] = nums[largest], nums[i]
|
||
i = largest
|
||
}
|
||
}
|
||
// 阶段 1:建大顶堆,从最后一个父节点往前
|
||
for i := n/2 - 1; i >= 0; i-- {
|
||
siftDown(i, n-1)
|
||
}
|
||
// 阶段 2:反复把堆顶(最大)与末尾交换,堆范围 -1,修复堆
|
||
for end := n - 1; end > 0; end-- {
|
||
nums[0], nums[end] = nums[end], nums[0]
|
||
siftDown(0, end-1) // 注意:堆范围是 [0, end-1],排除已归位的 nums[end]
|
||
}
|
||
}
|
||
|
||
// ShellSort 希尔排序:按递减步长分组做插入排序,最后步长为 1 退化为插入排序。
|
||
// 平均约 O(n^1.3),非稳定。代码短,比插入排序在乱序数据上快很多。
|
||
func ShellSort(nums []int) {
|
||
// TODO: 你来写
|
||
}
|
||
|
||
// CountingSort 计数排序:元素为 [0, k] 整数,统计频次后前缀和定位。
|
||
// O(n+k),k=值域大小。非比较,稳定版本需倒序填充。
|
||
func CountingSort(nums []int) {
|
||
// TODO: 你来写(假设元素均 >= 0)
|
||
}
|
||
|
||
// BucketSort 桶排序:按值把元素分到 n 个桶,每桶用 InsertionSort 排好,再依次写回原数组。
|
||
// 平均 O(n+k),k=桶数;最坏 O(n²)(全挤进一个桶,退化成插入排序)。
|
||
// 稳定(InsertionSort 稳定)。需 min/max 决定桶映射区间。
|
||
func BucketSort(nums []int) {
|
||
n := len(nums)
|
||
if n <= 1 {
|
||
return
|
||
}
|
||
// 找值域
|
||
minVal, maxVal := nums[0], nums[0]
|
||
for _, v := range nums {
|
||
if v < minVal {
|
||
minVal = v
|
||
}
|
||
if v > maxVal {
|
||
maxVal = v
|
||
}
|
||
}
|
||
if minVal == maxVal {
|
||
return // 全相等,无需排
|
||
}
|
||
// 桶数取 n;idx = (v-minVal) * n / (maxVal-minVal+1),分母 +1 保证 maxVal 不越界
|
||
buckets := make([][]int, n)
|
||
for _, v := range nums {
|
||
idx := (v - minVal) * n / (maxVal - minVal + 1)
|
||
buckets[idx] = append(buckets[idx], v)
|
||
}
|
||
// 每桶内部插入排序(稳定),再依次拷回原数组
|
||
k := 0
|
||
for _, b := range buckets {
|
||
InsertionSort(b)
|
||
copy(nums[k:], b)
|
||
k += len(b)
|
||
}
|
||
}
|
||
|
||
func TestSortAlgos(t *testing.T) {
|
||
cases := [][]int{
|
||
{1, 2, 1, 2, 1, 2, 3, 1, 3, 2},
|
||
{3, 2, 1},
|
||
{5, 4, 3, 2, 1},
|
||
{2, 1},
|
||
{1, 3, 2, 3, 1},
|
||
{10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
|
||
{1},
|
||
{},
|
||
{2, 2, 2, 2},
|
||
{3, 3, 3, 1, 1, 1, 2, 2, 2},
|
||
}
|
||
sorters := []struct {
|
||
name string
|
||
fn func([]int)
|
||
}{
|
||
// {"SelectionSort", SelectionSort},
|
||
// {"InsertionSort", InsertionSort},
|
||
// {"MergeSort", MergeSort},
|
||
{"HeapSort", HeapSort},
|
||
{"BucketSort", BucketSort},
|
||
// {"ShellSort", ShellSort},
|
||
// {"CountingSort", CountingSort},
|
||
}
|
||
for _, s := range sorters {
|
||
for _, c := range cases {
|
||
got := append([]int(nil), c...)
|
||
s.fn(got)
|
||
fmt.Printf("%s in=%v got=%v\n", s.name, c, got)
|
||
}
|
||
fmt.Println()
|
||
}
|
||
}
|