Go 语言实现经典排序算法 | 豆包MarsCode AI 刷题

13 阅读4分钟

使用 Go 语言实现排序算法及其性能基准测试

排序算法是计算机科学中最基本且最重要的算法之一。本文将介绍如何使用 Go 语言实现三种常见的排序算法:插入排序、快速排序和堆排序,并通过基准测试(Benchmark)来比较它们的性能。

1. 插入排序

插入排序是一种简单的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上通常使用 in-place 排序(即只需用到 O(1) 的额外空间的排序)。

实现

package sort

import "fmt"

// InsertionSort 插入排序
func InsertionSort(arr []int) {
    for i := 1; i < len(arr); i++ {
        key := arr[i]
        j := i - 1
        for j >= 0 && arr[j] > key {
            arr[j+1] = arr[j]
            j--
        }
        arr[j+1] = key
    }
}

func main() {
    arr := []int{12, 11, 13, 5, 6}
    fmt.Println("Original array:", arr)
    InsertionSort(arr)
    fmt.Println("Sorted array:", arr)
}

2. 快速排序

快速排序是一种高效的排序算法,采用分治法的策略。它的基本思想是通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,然后分别对这两部分记录继续进行排序,以达到整个序列有序。

实现

package sort

import "fmt"

// QuickSort 快速排序
func QuickSort(arr []int) {
    quickSortHelper(arr, 0, len(arr)-1)
}

func quickSortHelper(arr []int, low, high int) {
    if low < high {
        pi := partition(arr, low, high)
        quickSortHelper(arr, low, pi-1)
        quickSortHelper(arr, pi+1, high)
    }
}

func partition(arr []int, low, high int) int {
    pivot := arr[high]
    i := low - 1
    for j := low; j < high; j++ {
        if arr[j] < pivot {
            i++
            arr[i], arr[j] = arr[j], arr[i]
        }
    }
    arr[i+1], arr[high] = arr[high], arr[i+1]
    return i + 1
}

func main() {
    arr := []int{10, 7, 8, 9, 1, 5}
    fmt.Println("Original array:", arr)
    QuickSort(arr)
    fmt.Println("Sorted array:", arr)
}

3. 堆排序

堆排序是一种基于二叉堆的数据结构的排序算法。堆是一个近似完全二叉树的结构,并同时满足堆属性:即子节点的键值或索引总是小于(或大于)它的父节点。

实现

package sort

import "fmt"

// HeapSort 堆排序
func HeapSort(arr []int) {
    n := len(arr)
    // Build heap (rearrange array)
    for i := n/2 - 1; i >= 0; i-- {
        heapify(arr, n, i)
    }
    // One by one extract an element from heap
    for i := n - 1; i > 0; i-- {
        arr[0], arr[i] = arr[i], arr[0] // Move current root to end
        heapify(arr, i, 0)              // call max heapify on the reduced heap
    }
}

func heapify(arr []int, n int, i int) {
    largest := i       // Initialize largest as root
    left := 2*i + 1    // left = 2*i + 1
    right := 2*i + 2   // right = 2*i + 2

    // If left child is larger than root
    if left < n && arr[left] > arr[largest] {
        largest = left
    }

    // If right child is larger than largest so far
    if right < n && arr[right] > arr[largest] {
        largest = right
    }

    // Change root, if needed
    if largest != i {
        arr[i], arr[largest] = arr[largest], arr[i] // swap
        // Recursively heapify the affected sub-tree
        heapify(arr, n, largest)
    }
}

func main() {
    arr := []int{12, 11, 13, 5, 6, 7}
    fmt.Println("Original array:", arr)
    HeapSort(arr)
    fmt.Println("Sorted array:", arr)
}

4. 基准测试

为了比较不同排序算法的性能,我们可以使用 Go 语言的 testing 包来进行基准测试。基准测试可以帮助我们了解每种算法在不同数据规模下的表现。

基准测试代码

package sort

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

func BenchmarkInsertionSort(b *testing.B) {
    for i := 0; i < b.N; i++ {
        arr := generateRandomArray(1000)
        InsertionSort(arr)
    }
}

func BenchmarkQuickSort(b *testing.B) {
    for i := 0; i < b.N; i++ {
        arr := generateRandomArray(1000)
        QuickSort(arr)
    }
}

func BenchmarkHeapSort(b *testing.B) {
    for i := 0; i < b.N; i++ {
        arr := generateRandomArray(1000)
        HeapSort(arr)
    }
}

func generateRandomArray(size int) []int {
    rand.Seed(time.Now().UnixNano())
    arr := make([]int, size)
    for i := range arr {
        arr[i] = rand.Intn(10000)
    }
    return arr
}

运行基准测试

在终端中运行以下命令来执行基准测试:

go test -bench . 

基准测试结果

基准测试的结果可能会因系统配置和随机数据的不同而有所差异。以下是一个示例输出:

image.png

使用代码:

//sort_do_test.go
package sort

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

func BenchmarkInsertionSort(b *testing.B) {
	for i := 0; i < b.N; i++ {
		arr := generateRandomArray(1000)
		InsertionSort(arr)
	}
}

func BenchmarkQuickSort(b *testing.B) {
	for i := 0; i < b.N; i++ {
		arr := generateRandomArray(1000)
		QuickSort(arr)
	}
}

func BenchmarkHeapSort(b *testing.B) {
	for i := 0; i < b.N; i++ {
		arr := generateRandomArray(1000)
		HeapSort(arr)
	}
}

func generateRandomArray(size int) []int {
	rand.Seed(time.Now().UnixNano())
	arr := make([]int, size)
	for i := range arr {
		arr[i] = rand.Intn(10000)
	}
	return arr
}

package sort
//sort_do.go
// InsertionSort 插入排序
func InsertionSort(arr []int) {
	for i := 1; i < len(arr); i++ {
		key := arr[i]
		j := i - 1
		for j >= 0 && arr[j] > key {
			arr[j+1] = arr[j]
			j--
		}
		arr[j+1] = key
	}
}

// QuickSort 快速排序
func QuickSort(arr []int) {
	quickSortHelper(arr, 0, len(arr)-1)
}

func quickSortHelper(arr []int, low, high int) {
	if low < high {
		pi := partition(arr, low, high)
		quickSortHelper(arr, low, pi-1)
		quickSortHelper(arr, pi+1, high)
	}
}

func partition(arr []int, low, high int) int {
	pivot := arr[high]
	i := low - 1
	for j := low; j < high; j++ {
		if arr[j] < pivot {
			i++
			arr[i], arr[j] = arr[j], arr[i]
		}
	}
	arr[i+1], arr[high] = arr[high], arr[i+1]
	return i + 1
}

// HeapSort 堆排序
func HeapSort(arr []int) {
	n := len(arr)
	// Build heap (rearrange array)
	for i := n/2 - 1; i >= 0; i-- {
		heapify(arr, n, i)
	}
	// One by one extract an element from heap
	for i := n - 1; i > 0; i-- {
		arr[0], arr[i] = arr[i], arr[0] // Move current root to end
		heapify(arr, i, 0)              // call max heapify on the reduced heap
	}
}

func heapify(arr []int, n int, i int) {
	largest := i     // Initialize largest as root
	left := 2*i + 1  // left = 2*i + 1
	right := 2*i + 2 // right = 2*i + 2

	// If left child is larger than root
	if left < n && arr[left] > arr[largest] {
		largest = left
	}

	// If right child is larger than largest so far
	if right < n && arr[right] > arr[largest] {
		largest = right
	}

	// Change root, if needed
	if largest != i {
		arr[i], arr[largest] = arr[largest], arr[i] // swap
		// Recursively heapify the affected sub-tree
		heapify(arr, n, largest)
	}
}