This commit is contained in:
2026-07-18 23:06:46 +08:00
parent 3bd18ddaf2
commit 4fa78d4c00
5 changed files with 719 additions and 0 deletions
+287
View File
@@ -0,0 +1,287 @@
package main
import (
"fmt"
"testing"
)
// QuickSortHoare 使用 Hoare 双指针分区:l 主动向右找 >= pivotr 主动向左找 <= 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 // 全相等,无需排
}
// 桶数取 nidx = (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()
}
}