排序算法-堆排序

发布于:2025-08-03 ⋅ 阅读:(16) ⋅ 点赞:(0)

算法排序-堆排序

简介

堆排序(Heap Sort)是利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。

堆排序由J. W. J. Williams在1964年发明,是一种选择排序的改进版本。它的时间复杂度为O(n log n),且是原地排序算法。

算法原理

堆排序的基本思想是:

  1. 构建最大堆: 将待排序数组构造成一个最大堆
  2. 交换和调整: 将堆顶元素(最大值)与末尾元素交换,然后调整剩余元素重新构成最大堆
  3. 重复过程: 重复步骤2,直到所有元素都被排序

堆的性质

  • 最大堆: 父节点的值大于或等于子节点的值
  • 最小堆: 父节点的值小于或等于子节点的值
  • 完全二叉树: 除了最后一层,其他层都是满的,最后一层从左到右填充

Go语言实现

基础版本

package main

import "fmt"

// HeapSort 堆排序主函数
func HeapSort(arr []int) {
    n := len(arr)
    
    // 构建最大堆(从最后一个非叶子节点开始)
    for i := n/2 - 1; i >= 0; i-- {
        heapify(arr, n, i)
    }
    
    // 一个个从堆顶取出元素
    for i := n - 1; i > 0; i-- {
        // 将当前最大元素移到数组末尾
        arr[0], arr[i] = arr[i], arr[0]
        
        // 调整剩余元素重新构成最大堆
        heapify(arr, i, 0)
    }
}

// heapify 调整堆,使其满足最大堆性质
func heapify(arr []int, n, i int) {
    largest := i    // 初始化最大值为根节点
    left := 2*i + 1 // 左子节点
    right := 2*i + 2 // 右子节点
    
    // 如果左子节点大于根节点
    if left < n && arr[left] > arr[largest] {
        largest = left
    }
    
    // 如果右子节点大于当前最大值
    if right < n && arr[right] > arr[largest] {
        largest = right
    }
    
    // 如果最大值不是根节点
    if largest != i {
        arr[i], arr[largest] = arr[largest], arr[i]
        
        // 递归调整受影响的子树
        heapify(arr, n, largest)
    }
}

func main() {
    arr := []int{64, 34, 25, 12, 22, 11, 90}
    fmt.Println("排序前:", arr)
    
    HeapSort(arr)
    fmt.Println("排序后:", arr)
}

带步骤显示的版本

package main

import "fmt"

var step int

// HeapSortWithSteps 带步骤显示的堆排序
func HeapSortWithSteps(arr []int) {
    n := len(arr)
    step = 0
    
    fmt.Printf("初始数组: %v\n", arr)
    fmt.Println("\n=== 构建最大堆阶段 ===")
    
    // 构建最大堆
    for i := n/2 - 1; i >= 0; i-- {
        step++
        fmt.Printf("步骤 %d: 调整节点 %d\n", step, i)
        heapifyWithSteps(arr, n, i, "构建")
        fmt.Printf("当前状态: %v\n", arr)
        printHeapStructure(arr, n)
        fmt.Println()
    }
    
    fmt.Println("=== 排序阶段 ===")
    
    // 排序过程
    for i := n - 1; i > 0; i-- {
        step++
        fmt.Printf("步骤 %d: 交换堆顶 %d 和末尾元素 %d\n", 
                   step, arr[0], arr[i])
        
        // 交换堆顶和末尾元素
        arr[0], arr[i] = arr[i], arr[0]
        fmt.Printf("交换后: %v\n", arr)
        
        // 调整剩余元素
        fmt.Printf("调整前 %d 个元素重新构成最大堆:\n", i)
        heapifyWithSteps(arr, i, 0, "排序")
        fmt.Printf("调整后: %v\n", arr)
        printHeapStructure(arr, i)
        fmt.Println()
    }
}

// heapifyWithSteps 带步骤显示的堆调整
func heapifyWithSteps(arr []int, n, i int, phase string) {
    largest := i
    left := 2*i + 1
    right := 2*i + 2
    
    fmt.Printf("  检查节点 %d (值: %d)", i, arr[i])
    if left < n {
        fmt.Printf(", 左子节点 %d (值: %d)", left, arr[left])
    }
    if right < n {
        fmt.Printf(", 右子节点 %d (值: %d)", right, arr[right])
    }
    fmt.Println()
    
    // 找到最大值
    if left < n && arr[left] > arr[largest] {
        largest = left
        fmt.Printf("  左子节点 %d 更大\n", left)
    }
    
    if right < n && arr[right] > arr[largest] {
        largest = right
        fmt.Printf("  右子节点 %d 更大\n", right)
    }
    
    // 如果需要交换
    if largest != i {
        fmt.Printf("  交换节点 %d (值: %d) 和节点 %d (值: %d)\n", 
                   i, arr[i], largest, arr[largest])
        arr[i], arr[largest] = arr[largest], arr[i]
        
        // 递归调整
        heapifyWithSteps(arr, n, largest, phase)
    } else {
        fmt.Printf("  节点 %d 已满足堆性质\n", i)
    }
}

// printHeapStructure 打印堆的树形结构
func printHeapStructure(arr []int, n int) {
    if n == 0 {
        return
    }
    
    fmt.Println("堆的树形结构:")
    printHeapLevel(arr, n, 0, 0)
}

func printHeapLevel(arr []int, n, index, level int) {
    if index >= n {
        return
    }
    
    // 打印右子树
    printHeapLevel(arr, n, 2*index+2, level+1)
    
    // 打印当前节点
    for i := 0; i < level; i++ {
        fmt.Print("    ")
    }
    fmt.Printf("%d\n", arr[index])
    
    // 打印左子树
    printHeapLevel(arr, n, 2*index+1, level+1)
}

堆数据结构实现

package main

import "fmt"

// MaxHeap 最大堆结构
type MaxHeap struct {
    data []int
}

// NewMaxHeap 创建新的最大堆
func NewMaxHeap() *MaxHeap {
    return &MaxHeap{
        data: make([]int, 0),
    }
}

// Insert 插入元素
func (h *MaxHeap) Insert(val int) {
    h.data = append(h.data, val)
    h.heapifyUp(len(h.data) - 1)
}

// ExtractMax 提取最大元素
func (h *MaxHeap) ExtractMax() (int, bool) {
    if len(h.data) == 0 {
        return 0, false
    }
    
    max := h.data[0]
    lastIndex := len(h.data) - 1
    
    // 将最后一个元素移到根部
    h.data[0] = h.data[lastIndex]
    h.data = h.data[:lastIndex]
    
    // 向下调整
    if len(h.data) > 0 {
        h.heapifyDown(0)
    }
    
    return max, true
}

// heapifyUp 向上调整
func (h *MaxHeap) heapifyUp(index int) {
    for index > 0 {
        parentIndex := (index - 1) / 2
        if h.data[index] <= h.data[parentIndex] {
            break
        }
        h.data[index], h.data[parentIndex] = h.data[parentIndex], h.data[index]
        index = parentIndex
    }
}

// heapifyDown 向下调整
func (h *MaxHeap) heapifyDown(index int) {
    for {
        largest := index
        leftChild := 2*index + 1
        rightChild := 2*index + 2
        
        if leftChild < len(h.data) && h.data[leftChild] > h.data[largest] {
            largest = leftChild
        }
        
        if rightChild < len(h.data) && h.data[rightChild] > h.data[largest] {
            largest = rightChild
        }
        
        if largest == index {
            break
        }
        
        h.data[index], h.data[largest] = h.data[largest], h.data[index]
        index = largest
    }
}

// Size 返回堆大小
func (h *MaxHeap) Size() int {
    return len(h.data)
}

// Peek 查看最大元素
func (h *MaxHeap) Peek() (int, bool) {
    if len(h.data) == 0 {
        return 0, false
    }
    return h.data[0], true
}

// HeapSortUsingHeap 使用堆数据结构进行排序
func HeapSortUsingHeap(arr []int) []int {
    heap := NewMaxHeap()
    
    // 将所有元素插入堆
    for _, val := range arr {
        heap.Insert(val)
    }
    
    // 从堆中依次提取最大元素
    result := make([]int, len(arr))
    for i := len(arr) - 1; i >= 0; i-- {
        max, _ := heap.ExtractMax()
        result[i] = max
    }
    
    return result
}

func main() {
    // 测试基础堆排序
    arr1 := []int{64, 34, 25, 12, 22, 11, 90}
    fmt.Println("=== 基础堆排序 ===")
    fmt.Println("排序前:", arr1)
    HeapSort(arr1)
    fmt.Println("排序后:", arr1)
    
    // 测试带步骤显示的堆排序
    arr2 := []int{4, 10, 3, 5, 1}
    fmt.Println("\n=== 带步骤显示的堆排序 ===")
    HeapSortWithSteps(arr2)
    
    // 测试使用堆数据结构的排序
    arr3 := []int{5, 2, 8, 1, 9, 3}
    fmt.Println("\n=== 使用堆数据结构的排序 ===")
    fmt.Println("排序前:", arr3)
    sorted := HeapSortUsingHeap(arr3)
    fmt.Println("排序后:", sorted)
}

复杂度分析

时间复杂度

  • 构建堆: O(n) - 虽然看起来是O(n log n),但实际分析为O(n)
  • 排序过程: O(n log n) - n次提取操作,每次O(log n)
  • 总体: O(n log n) - 所有情况下都是O(n log n)

空间复杂度

  • 空间复杂度: O(1) - 原地排序,只需要常数级别的额外空间

算法特点

优点

  1. 时间复杂度稳定: 始终为O(n log n)
  2. 原地排序: 只需要O(1)的额外空间
  3. 不依赖输入: 性能不受输入数据分布影响
  4. 实现相对简单: 比快速排序更容易实现

缺点

  1. 不稳定: 相等元素的相对位置可能改变
  2. 缓存性能差: 堆操作的内存访问模式对缓存不友好
  3. 实际性能: 虽然理论复杂度好,但实际运行时间通常比快速排序慢

堆的应用

1. 优先队列

// PriorityQueue 优先队列实现
type PriorityQueue struct {
    heap *MaxHeap
}

func NewPriorityQueue() *PriorityQueue {
    return &PriorityQueue{
        heap: NewMaxHeap(),
    }
}

func (pq *PriorityQueue) Enqueue(val int) {
    pq.heap.Insert(val)
}

func (pq *PriorityQueue) Dequeue() (int, bool) {
    return pq.heap.ExtractMax()
}

2. Top K 问题

// FindTopK 找到数组中最大的K个元素
func FindTopK(arr []int, k int) []int {
    heap := NewMaxHeap()
    
    for _, val := range arr {
        heap.Insert(val)
    }
    
    result := make([]int, 0, k)
    for i := 0; i < k && heap.Size() > 0; i++ {
        max, _ := heap.ExtractMax()
        result = append(result, max)
    }
    
    return result
}

实际应用场景

堆排序广泛应用于:

  1. 系统调度: 操作系统的进程调度
  2. 优先队列: 任务优先级管理
  3. 图算法: Dijkstra算法、Prim算法等
  4. 数据流处理: 实时数据的Top K问题
  5. 内存管理: 堆内存分配算法
  6. 事件驱动系统: 事件优先级处理

性能测试

package main

import (
    "fmt"
    "math/rand"
    "time"
)

// 性能测试函数
func benchmarkHeapSort(size int) {
    // 生成随机数组
    arr := make([]int, size)
    for i := 0; i < size; i++ {
        arr[i] = rand.Intn(10000)
    }
    
    start := time.Now()
    HeapSort(arr)
    duration := time.Since(start)
    
    fmt.Printf("数组大小: %d, 排序时间: %v\n", size, duration)
}

// 比较不同输入情况下的性能
func compareHeapSortPerformance() {
    size := 10000
    
    // 随机数组
    randomArr := make([]int, size)
    for i := 0; i < size; i++ {
        randomArr[i] = rand.Intn(10000)
    }
    
    // 已排序数组
    sortedArr := make([]int, size)
    for i := 0; i < size; i++ {
        sortedArr[i] = i
    }
    
    // 逆序数组
    reverseArr := make([]int, size)
    for i := 0; i < size; i++ {
        reverseArr[i] = size - i
    }
    
    // 测试随机数组
    start := time.Now()
    HeapSort(randomArr)
    fmt.Printf("随机数组排序时间: %v\n", time.Since(start))
    
    // 测试已排序数组
    start = time.Now()
    HeapSort(sortedArr)
    fmt.Printf("已排序数组排序时间: %v\n", time.Since(start))
    
    // 测试逆序数组
    start = time.Now()
    HeapSort(reverseArr)
    fmt.Printf("逆序数组排序时间: %v\n", time.Since(start))
}

func main() {
    fmt.Println("=== 堆排序性能测试 ===")
    sizes := []int{1000, 10000, 100000, 1000000}
    
    for _, size := range sizes {
        benchmarkHeapSort(size)
    }
    
    fmt.Println("\n=== 不同输入情况性能比较 ===")
    compareHeapSortPerformance()
}

总结

堆排序是一个重要的排序算法,它结合了堆数据结构的特性,提供了稳定的O(n log n)时间复杂度和O(1)空间复杂度。虽然在实际应用中可能不如快速排序常用,但其稳定的性能特征使其在某些特定场景中非常有价值。

关键要点:

  • 基于堆数据结构,利用完全二叉树的性质
  • 时间复杂度稳定为O(n log n),空间复杂度为O(1)
  • 是原地排序算法,但不稳定
  • 适合需要稳定性能的场景
  • 堆数据结构在优先队列等应用中非常重要