igozhang

——

    go_sort

    用 Go 语言实现插入排序、希尔排序、冒泡排序、堆排序、选择排序、快速排序、归并排序算法,并注释说明时间复杂度及原理。

    说明

    • 下文函数均对切片 原地排序(归并排序内部使用临时数组)。
    • 时间复杂度默认指 最坏情况;如有差异会另行标注。
    • O(nlogn) 表示 (O(n\log n)),O(n^2) 表示 (O(n^2))。

    1. 插入排序(Insertion Sort)

    // 时间复杂度:平均 / 最坏 O(n^2),最好 O(n)(已基本有序时)
    // 空间复杂度:O(1)
    // 稳定性:稳定
    // 原理:构建有序序列;对待排序元素,在已排序部分从后向前扫描,找到合适位置插入。
    func InsertionSort(arr []int) {
    	for i := 1; i < len(arr); i++ {
    		for j := i; j > 0; j-- {
    			if arr[j] < arr[j-1] {
    				arr[j], arr[j-1] = arr[j-1], arr[j]
    			} else {
    				break // 前面已有序,可提前结束
    			}
    		}
    	}
    }
    

    2. 希尔排序(Shell Sort)

    // 时间复杂度:与增量序列有关;Knuth 增量下约为 O(n^{3/2}),实践中常优于 O(n^2) 的简单排序
    // 空间复杂度:O(1)
    // 稳定性:不稳定
    // 原理:按一定增量将记录分组,对每组做插入排序;增量逐步减小,直至增量为 1 时对整组做插入排序并结束。
    func ShellSort(arr []int) {
    	n := len(arr)
    	h := 1
    	// Knuth 增量序列:1, 4, 13, 40, ...
    	for h < n/3 {
    		h = 3*h + 1
    	}
    	for h >= 1 {
    		for i := h; i < n; i++ {
    			for j := i; j >= h && arr[j] < arr[j-h]; j -= h {
    				arr[j], arr[j-h] = arr[j-h], arr[j]
    			}
    		}
    		h /= 3
    	}
    }
    

    3. 冒泡排序(Bubble Sort)

    // 时间复杂度:平均 / 最坏 O(n^2),最好 O(n)(加入提前退出标志时)
    // 空间复杂度:O(1)
    // 稳定性:稳定
    // 原理:反复遍历待排序列,比较相邻元素,若顺序错误则交换,较大(或较小)元素逐步“冒泡”到一端。
    func BubbleSort(arr []int) {
    	n := len(arr)
    	for i := 0; i < n; i++ {
    		swapped := false
    		for j := 0; j < n-i-1; j++ {
    			if arr[j] > arr[j+1] {
    				arr[j], arr[j+1] = arr[j+1], arr[j]
    				swapped = true
    			}
    		}
    		if !swapped {
    			break // 本轮无交换,说明已有序
    		}
    	}
    }
    

    4. 堆排序(Heap Sort)

    // 时间复杂度:O(nlogn)
    // 空间复杂度:O(1)
    // 稳定性:不稳定
    // 原理:树形选择排序。将序列调整为大顶堆后,反复将堆顶与末尾交换并缩小堆,再下沉调整,直至有序。
    // 堆定义(1-based 下标):对 i = 1..n/2,满足 hi ≥ h(2i) 且 hi ≥ h(2i+1)(大顶堆),
    // 或 hi ≤ h(2i) 且 hi ≤ h(2i+1)(小顶堆)。Go 中常用 0-based:左子 i*2+1,右子 i*2+2。
    func HeapSort(arr []int) {
    	n := len(arr)
    	// 构建大顶堆
    	for i := n/2 - 1; i >= 0; i-- {
    		heapify(arr, i, n)
    	}
    	// 依次将堆顶放到末尾
    	for i := n - 1; i > 0; i-- {
    		arr[0], arr[i] = arr[i], arr[0]
    		heapify(arr, 0, i)
    	}
    }
    
    // heapify:从下标 i 开始向下调整,使以 i 为根的子树满足大顶堆(范围 [0, n))
    func heapify(arr []int, i, n int) {
    	for {
    		maxPos := i
    		left, right := i*2+1, i*2+2
    		if left < n && arr[left] > arr[maxPos] {
    			maxPos = left
    		}
    		if right < n && arr[right] > arr[maxPos] {
    			maxPos = right
    		}
    		if maxPos == i {
    			break
    		}
    		arr[i], arr[maxPos] = arr[maxPos], arr[i]
    		i = maxPos
    	}
    }
    

    5. 选择排序(Selection Sort)

    // 时间复杂度:O(n^2)
    // 空间复杂度:O(1)
    // 稳定性:不稳定
    // 原理:每一趟从待排序区间选出最小(或最大)元素,放到区间起始位置,直到全部排完。
    func SelectionSort(arr []int) {
    	for i := 0; i < len(arr); i++ {
    		minIndex := i
    		for j := i + 1; j < len(arr); j++ {
    			if arr[j] < arr[minIndex] {
    				minIndex = j
    			}
    		}
    		arr[i], arr[minIndex] = arr[minIndex], arr[i]
    	}
    }
    

    6. 快速排序(Quick Sort)

    // 时间复杂度:平均 O(nlogn),最坏 O(n^2)(如已有序且枢轴选得不好)
    // 空间复杂度:平均 O(logn)(递归栈),最坏 O(n)
    // 稳定性:不稳定
    // 原理:分治。一趟划分将序列分成两部分,左侧均不大于枢轴、右侧均不小于枢轴,再对左右两侧递归快速排序。
    func QuickSort(arr []int) {
    	if len(arr) == 0 {
    		return
    	}
    	quickSort(arr, 0, len(arr)-1)
    }
    
    func quickSort(arr []int, left, right int) {
    	if left < right {
    		pivot := partition(arr, left, right)
    		quickSort(arr, left, pivot-1)
    		quickSort(arr, pivot+1, right)
    	}
    }
    
    // partition:以最右侧元素为枢轴,返回枢轴最终下标
    func partition(arr []int, left, right int) int {
    	pivot := arr[right]
    	i := left
    	for j := left; j < right; j++ {
    		if arr[j] < pivot {
    			arr[i], arr[j] = arr[j], arr[i]
    			i++
    		}
    	}
    	arr[i], arr[right] = arr[right], arr[i]
    	return i
    }
    

    7. 归并排序(Merge Sort)

    // 时间复杂度:O(nlogn)(最好 / 平均 / 最坏)
    // 空间复杂度:O(n)
    // 稳定性:稳定
    // 原理:分治。先递归使左右子序列有序,再将两个有序子序列合并(2-路归并)得到整体有序序列。
    func MergeSort(arr []int) {
    	if len(arr) == 0 {
    		return
    	}
    	mergeSort(arr, 0, len(arr)-1)
    }
    
    func mergeSort(arr []int, left, right int) {
    	if left < right {
    		mid := left + (right-left)/2 // 避免 left+right 溢出
    		mergeSort(arr, left, mid)
    		mergeSort(arr, mid+1, right)
    		merge(arr, left, mid, right)
    	}
    }
    
    func merge(arr []int, left, mid, right int) {
    	tmp := make([]int, right-left+1)
    	i, j, k := left, mid+1, 0
    	for i <= mid && j <= right {
    		if arr[i] <= arr[j] { // <= 保证稳定性
    			tmp[k] = arr[i]
    			i++
    		} else {
    			tmp[k] = arr[j]
    			j++
    		}
    		k++
    	}
    	for i <= mid {
    		tmp[k] = arr[i]
    		i++
    		k++
    	}
    	for j <= right {
    		tmp[k] = arr[j]
    		j++
    		k++
    	}
    	copy(arr[left:right+1], tmp)
    }
    

    复杂度与特性对照

    算法 平均时间 最坏时间 空间 稳定性
    插入排序 (O(n^2)) (O(n^2)) (O(1)) 稳定
    希尔排序 取决于增量 取决于增量 (O(1)) 不稳定
    冒泡排序 (O(n^2)) (O(n^2)) (O(1)) 稳定
    堆排序 (O(n\log n)) (O(n\log n)) (O(1)) 不稳定
    选择排序 (O(n^2)) (O(n^2)) (O(1)) 不稳定
    快速排序 (O(n\log n)) (O(n^2)) (O(\log n)) 不稳定
    归并排序 (O(n\log n)) (O(n\log n)) (O(n)) 稳定

    简单调用示例

    package main
    
    import "fmt"
    
    func main() {
    	data := []int{5, 2, 9, 1, 5, 6}
    	arr := append([]int(nil), data...) // 复制一份,避免互相影响
    
    	InsertionSort(arr)
    	fmt.Println("插入排序:", arr)
    
    	arr = append([]int(nil), data...)
    	QuickSort(arr)
    	fmt.Println("快速排序:", arr)
    }
    

    可将上述各排序函数放在同一 package 中,按需调用。

    MP3