Files
leetcode/sort_test.go
T
2026-07-18 23:06:46 +08:00

288 lines
7.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()
}
}