数据结构之Go实现排序


直接插入排序


(1)任意目录下创建名为 InsertSort 的项目,在该目录下编写一个名为 insertsort.go 的排序程序,该程序的具体代码如下所示。

package InsertSort

import "fmt"

func DisplayList(list []int) {
    
    
        for _, v := range list {
    
    
                fmt.Printf("%d ", v)
        }
        fmt.Println()
}

func InsertSort(list []int) {
    
    
        for i := 1; i < len(list); i++ {
    
    
                fmt.Printf("i = %d , 插入 %d ,插入结果:  ", i, list[i])
                // tmp 为哨兵
                tmp := list[i]
                j := i - 1
                // 逆序时,list[i] 关键码小于其前驱,将 list[i] 插入有序表
                if tmp < list[j] {
    
    
                		// 从后往前查找待插入位置
                        for ; j >= 0 && tmp < list[j]; j-- {
    
    
                        		// 将关键码大于 list[i] 的元素向后移位
                                list[j+1] = list[j]
                        }
                }
                // 在 j+1 处复制插入元素
                list[j+1] = tmp
                DisplayList(list)
        }
}

(2)创建一个名为 insertsort_test.go 的文件用于编写测试函数检测排序的逻辑是否正确,此时的目录结构如下所示。

InsertSort
├── insertsort.go
└── insertsort_test.go

(3)编辑 insertsort_test.go 文件,编写如下内容的程序检测排序的逻辑是否正确。

package InsertSort

import (
        "fmt"
        "testing"
)

func TestInsertSort(t *testing.T) {
    
    
        list := []int{
    
    6, 5, 4, 3, 2, 1}
        fmt.Println("排序前: ", list)
        InsertSort(list)
        fmt.Println("排序后: ", list)
}

(4)在当前目录下执行 go test 命令测试程序,运行的结果如下:

排序前:  [6 5 4 3 2 1]
i = 1 ,插入 5 ,插入结果:  5 6 4 3 2 1 
i = 2 ,插入 4 ,插入结果:  4 5 6 3 2 1 
i = 3 ,插入 3 ,插入结果:  3 4 5 6 2 1 
i = 4 ,插入 2 ,插入结果:  2 3 4 5 6 1 
i = 5 ,插入 1 ,插入结果:  1 2 3 4 5 6 
排序后:  [1 2 3 4 5 6]

折半插入排序


(1)任意目录下创建名为 BinInsertSort 的项目 ,在该目录下编写一个名为 bininsertsort.go 的排序程序,该程序的具体代码如下所示。

package BinInsertSort

import "fmt"

func DisplayList(list []int) {
    
    
        for _, v := range list {
    
    
                fmt.Printf("%d ", v)
        }
        fmt.Println()
}

func BinInsertSort(list []int) {
    
    
        for i := 1; i < len(list); i++ {
    
    
                if list[i] < list[i-1] {
    
    
                        fmt.Printf("i = %d ,插入 %d ,插入结果:  ", i, list[i])
                        // 将 list[i] 暂存到 tmp 中
                        tmp := list[i]
                        // 设置折半查找的范围,在 list[low...high] 中查找待插入的位置
                        low, high := 0, i-1
                        // 折半查找(默认递增有序)
                        for low <= high {
    
    
                        		// 取中间位置
                                mid := (low + high) / 2
                                if tmp < list[mid] {
    
    
                                		// 查找左半表
                                        high = mid - 1
                                } else {
    
    
                                		// 查找右半表
                                        low = mid + 1
                                }
                        }
						// 统一将元素后移,空出插入位置
                        for j := i - 1; j >= high+1; j-- {
    
    
                                list[j+1] = list[j]
                        }
                        // 插入 tmp
                        list[high+1] = tmp
                }
                DisplayList(list)
        }
}

(2)创建一个名为 bininsertsort_test.go 的文件用于编写测试函数检测排序的逻辑是否正确,此时的目录结构如下所示。

BinInsertSort
├──bin insertsort.go
└── bininsertsort_test.go

(3)编辑 bininsertsort_test.go 文件,编写如下内容的程序检测排序的逻辑是否正确。

package BinInsertSort

import (
        "fmt"
        "testing"
)

func TestBinInsertSort(t *testing.T) {
    
    
		list := []int{
    
    6, 5, 4, 3, 2, 1}
        fmt.Println("排序前: ", list)
        BinInsertSort(list)
        fmt.Println("排序后: ", list)
}

程序运行的结果如下:

i = 1 ,插入 5 ,插入结果:  5 6 4 3 2 1 
i = 2 ,插入 4 ,插入结果:  4 5 6 3 2 1 
i = 3 ,插入 3 ,插入结果:  3 4 5 6 2 1 
i = 4 ,插入 2 ,插入结果:  2 3 4 5 6 1 
i = 5 ,插入 1 ,插入结果:  1 2 3 4 5 6 
排序后:  [1 2 3 4 5 6]

希尔排序


(1)任意目录下创建名为 ShellSort 的项目,在该目录下编写一个名为 shellsort.go 的排序程序,该程序的具体代码如下所示。

package ShellSort

import "fmt"

func DisplayList(list []int) {
    
    
        for _, v := range list {
    
    
                fmt.Printf("%d ", v)
        }
        fmt.Println()
}

func ShellSort(list []int) {
    
    
        n := len(list)
        // 设置增量步长值,对所有组采用直接插入排序,按步长变化
        for step := n / 2; step > 0; step /= 2 {
    
    
                for i := step; i < len(list); i += step {
    
    
                        // 暂存 list[i]
                        tmp := list[i]
                        // 相邻 step 个位置为一组采用直接插入排序
                        j := i - step
                        for j >= 0 && list[j] > tmp {
    
    
                        		// 元素后移,查找待插入的位置
                                list[j+step] = list[j]
                                j -= step
                        }
                        // 复制 tmp 插入到 j+step 处
                        list[j+step] = tmp
                }
                fmt.Printf("step = %d ,排序结果: ", step)
                DisplayList(list)
        }
}

(2)创建一个名为 shellsort_test.go 的文件用于编写测试函数检测排序的逻辑是否正确,此时的目录结构如下所示。

ShellSort
├── shellsort.go
└── shellsort_test.go

(3)编辑 shellsort_test.go 文件,编写如下内容的程序检测排序的逻辑是否正确。

package ShellSort

import (
        "fmt"
        "testing"
)

func TestShellSort(t *testing.T) {
    
    
		list := []int{
    
    6, 5, 4, 3, 2, 1}
        fmt.Println("排序前: ", list)
        ShellSort(list)
        fmt.Println("排序后: ", list)
}

程序运行的结果如下:

排序前:  [6 5 4 3 2 1]
step = 3 ,排序结果: 3 5 4 6 2 1 
step = 1 ,排序结果: 1 2 3 4 5 6 
排序后:  [1 2 3 4 5 6]

冒泡排序


(1)任意目录下创建名为 BubbleSort 的项目,在该目录下编写一个名为 bubblesort.go 的排序程序,该程序的具体代码如下所示。

package BubbleSort

import "fmt"

func DisplayList(list []int) {
    
    
        for _, value := range list {
    
    
                fmt.Printf("%d ", value)
        }
        fmt.Println()
}

func BubbleSort(list []int) {
    
    
        var flag bool
        n := len(list)
        for i := 0; i < n-1; i++ {
    
    
                // 标记本次冒泡是否发生交换
                flag = false
                for j := n - 1; j > i; j-- {
    
    
                		// 当相邻两个元素反序时,交换这两个元素
                        if list[j] < list[j-1] {
    
    
                                list[j], list[j-1] = list[j-1], list[j]
                                // 一旦有交换,将 flag 置为 true
                                flag = true
                        }
                        fmt.Printf("i = %d ,归位元素 %d ,排序结果: ", i, list[i])
                        DisplayList(list)
                        // 若本次冒泡没有发生交换,中途结束排序,表明已经有序
                        if !flag {
    
    
                                return
                        }
                }
        }
}

(2)创建一个名为 bubblesort_test.go 的文件用于编写测试函数检测排序的逻辑是否正确,此时的目录结构如下所示。

BubbleSort
├── bubblesort.go
└── bubblesort_test.go

(3)编辑 bubblesort_test.go 文件,编写如下内容的程序检测排序的逻辑是否正确。

package BubbleSort

import (
        "fmt"
        "testing"
)

func TestBubbleSort(t *testing.T) {
    
    
        list := []int{
    
    2, 3, 5, 1, 0}
        fmt.Println("排序前: ", list)
        BubbleSort(list)
        fmt.Println("排序后: ", list)
}

程序运行的结果如下:

排序前:  [2 3 5 1 0]
i = 0 ,归位元素 2 ,排序结果: 2 3 5 0 1 
i = 0 ,归位元素 2 ,排序结果: 2 3 0 5 1 
i = 0 ,归位元素 2 ,排序结果: 2 0 3 5 1 
i = 0 ,归位元素 0 ,排序结果: 0 2 3 5 1 
i = 1 ,归位元素 2 ,排序结果: 0 2 3 1 5 
i = 1 ,归位元素 2 ,排序结果: 0 2 1 3 5 
i = 1 ,归位元素 1 ,排序结果: 0 1 2 3 5 
i = 2 ,归位元素 2 ,排序结果: 0 1 2 3 5 
排序后:  [0 1 2 3 5]

快速排序


(1)任意目录下创建名为 QuickSort 的项目,在该目录下编写一个名为 quicksort.go 的排序程序,该程序的具体代码如下所示。

package QuickSort

import "fmt"

var count int = 1

func DisplayPart(list []int, begin int, end int) {
    
    
        fmt.Printf("第 %d 次划分: ", count)
        for i := 0; i < begin; i++ {
    
    
                fmt.Printf("   ")
        }
        for j := begin; j <= end; j++ {
    
    
                fmt.Printf(" %d ", list[j])
        }
        count++
        fmt.Println()
}

func partition(list []int, begin, end int) (int, int) {
    
    
        lt := begin      // 左下标从第一位开始
        rt := end        // 右下标是数组的最后一位
        i := begin + 1   // 中间下标,从第二位开始
        v := list[begin] // 基准数
        // 以中间坐标为准
        for i <= rt {
    
    
                if list[i] > v {
    
    
                        // 大于基准数,那么交换,右指针左移
                        list[i], list[rt] = list[rt], list[i]
                        rt--
                } else if list[i] < v {
    
    
                        // 小于基准数,那么交换,左指针右移
                        list[i], list[lt] = list[lt], list[i]
                        lt++
                        i++
                } else {
    
    
                        i++
                }
        }
        DisplayPart(list, begin, end)
        return lt, rt
}

func QuickSort(list []int, begin int, end int) {
    
    
        if begin < end {
    
    
                // 三向切分函数,返回左边和右边下标
                lt, rt := partition(list, begin, end)
                // 左边三向快排
                QuickSort(list, begin, lt-1)
                // 右边三向快排
                QuickSort(list, rt+1, end)
        }
}

(2)创建一个名为 quicksort_test.go 的文件用于编写测试函数检测排序的逻辑是否正确,此时的目录结构如下所示。

QuickSort
├── quicksort.go
└── quicksort_test.go

(3)编辑 quicksort_test.go 文件,编写如下内容的程序检测排序的逻辑是否正确。

package QuickSort

import (
        "fmt"
        "testing"
)

func TestQuickSort(t *testing.T) {
    
    
        list := []int{
    
    6, 7, 1, 3, 2, 4, 5}
        fmt.Println("排序前: ", list)
        QuickSort(list)
        fmt.Println("排序后: ", list)
}

程序运行的结果如下:

排序前:  [6 7 1 3 2 4 5]
第 1 次划分:  5  1  3  2  4  6  7 
第 2 次划分:  1  3  2  4  5 
第 3 次划分:  1  2  4  3 
第 4 次划分:     2  3  4 
第 5 次划分:        3  4 
排序后:  [1 2 3 4 5 6 7]

简单选择排序


(1)任意目录下创建名为 SelectSort 的项目,在该目录下编写一个名为 selectsort.go 的排序程序,该程序的具体代码如下所示。

package SelectSort

import "fmt"

func DisplayList(list []int) {
    
    
        for _, value := range list {
    
    
                fmt.Printf("%d ", value)
        }
        fmt.Println()
}

func SelectSort(list []int) {
    
    
        var j int
        n := len(list)
        // 一共进行 n-1 次排序
        for i := 0; i < n-1 ; i++ {
    
    
                // 记录最小元素的位置
                MinIndex := i
                // 在 list[i...n-1] 中选择最小的元素
                for j = i + 1; j < n; j++ {
    
    
                        if list[j] < list[MinIndex] {
    
    
                                // 记录目前找到的最小元素所在的位置
                                MinIndex = j
                        }
                }
                if MinIndex != j {
    
    
                		// 交换元素值
                        list[i], list[MinIndex] = list[MinIndex], list[i]
                }
                fmt.Printf("i = %d ,选择关键字: %d ,排序结果: ", i, list[i])
                DisplayList(list)
        }
}

(2)创建一个名为 selectsort_test.go 的文件用于编写测试函数检测排序的逻辑是否正确,此时的目录结构如下所示。

SelectSort
├── selectsort.go
└── selectsort_test.go

(3)编辑 selectsort_test.go 文件,编写如下内容的程序检测排序的逻辑是否正确。

package SelectSort

import (
        "fmt"
        "testing"
)

func TestSelectSort(t *testing.T) {
    
    
        list := []int{
    
    6, 7, 1, 3, 2, 4, 5}
        fmt.Println("排序前: ", list)
        SelectSort(list)
        fmt.Println("排序后: ", list)
}

程序运行的结果如下:

排序前:  [6 7 1 3 2 4 5]
i = 0 ,选择关键字: 1 ,排序结果: 1 7 6 3 2 4 5 
i = 1 ,选择关键字: 2 ,排序结果: 1 2 6 3 7 4 5 
i = 2 ,选择关键字: 3 ,排序结果: 1 2 3 6 7 4 5 
i = 3 ,选择关键字: 4 ,排序结果: 1 2 3 4 7 6 5 
i = 4 ,选择关键字: 5 ,排序结果: 1 2 3 4 5 6 7 
i = 5 ,选择关键字: 6 ,排序结果: 1 2 3 4 5 6 7 
i = 6 ,选择关键字: 7 ,排序结果: 1 2 3 4 5 6 7 
排序后:  [1 2 3 4 5 6 7]

归并排序


  • 自顶向下

(1)任意目录下创建名为 MergeSort 的项目,在该目录下编写一个名为 mergesort.go 的排序程序,该程序的具体代码如下所示。

package MergeSort

// 自顶向下归并排序,排序范围在 [begin,end) 的数组
func MergeSort(list []int, begin int, end int) {
    
    
        // 元素数量大于 1 时才进入递归
        if end-begin > 1 {
    
    
                // 将数组一分为二,分为 list[begin,mid) 和 list[mid,high)
                mid := begin + (end-begin+1)/2
                // 先将左边排序好
                MergeSort(list, begin, mid)
                // 再将右边排序好
                MergeSort(list, mid, end)
                // 两个有序数组进行合并
                merge(list, begin, mid, end)
        }
}

// 归并操作
func merge(list []int, begin int, mid int, end int) {
    
    
        // 申请额外的空间来合并两个有序数组,这两个数组是 list[begin,mid),list[mid,end)
        leftSize := mid - begin         // 左边数组的长度
        rightSize := end - mid          // 右边数组的长度
        newSize := leftSize + rightSize // 辅助数组的长度
        result := make([]int, 0, newSize)
        l, r := 0, 0
        for l < leftSize && r < rightSize {
    
    
                lValue := list[begin+l] // 左边数组的元素
                rValue := list[mid+r]   // 右边数组的元素
                // 将小的元素先放进辅助数组里
                if lValue < rValue {
    
    
                        result = append(result, lValue)
                        l++
                } else {
    
    
                        result = append(result, rValue)
                        r++
                }
        }
        // 将剩下的元素追加到辅助数组后面
        result = append(result, list[begin+l:mid]...)
        result = append(result, list[mid+r:end]...)
        // 将辅助数组的元素复制回原数组,这样该辅助空间就可以被释放掉
        for i := 0; i < newSize; i++ {
    
    
                list[begin+i] = result[i]
        }
        return
}

(2)创建一个名为 mergesort_test.go 的文件用于编写测试函数检测排序的逻辑是否正确,此时的目录结构如下所示。

MergeSort
├── mergesort.go
└── mergesort_test.go

(3)编辑 mergesort_test.go 文件,编写如下内容的程序检测排序的逻辑是否正确。

package MergeSort

import (
        "fmt"
        "testing"
)

func TestMergoSort(t *testing.T) {
    
    
        list1 := []int{
    
    3, 1, 3, 5, 7, 8, 33, 0, 12, 22}
        fmt.Println("list1 归并前: \n", list1)
        MergeSort(list1, 0, len(list1))
        fmt.Println("list1 归并后: \n", list1)
        list2 := []int{
    
    1, 0, 2, 4, 9, 2, 4, 9, 6, 3}
        fmt.Println("list2 归并前: \n", list2)
        MergeSort(list2, 0, len(list2))
        fmt.Println("list2 归并后: \n", list2)
}

(4)在当前目录下执行 go test 命令测试程序,运行的结果如下:

list1 归并前: 
 [3 1 3 5 7 8 33 0 12 22]
list1 归并后: 
 [0 1 3 3 5 7 8 12 22 33]
list2 归并前: 
 [1 0 2 4 9 2 4 9 6 3]
list2 归并后: 
 [0 1 2 2 3 4 4 6 9 9]
  • 自底向上

(1)任意目录下创建名为 MergeSort 的项目,在该目录下编写一个名为 mergesort.go 的排序程序,该程序的具体代码如下所示。

package MergeSort

// 自底向上归并排序
func MergeSort(list []int, begin, end int) {
    
    
        // 步数从 1 开始,step 长度的数组表示一个有序的数组
        step := 1
        // 范围大于 step 的数组才可以进入归并
        for end-begin > step {
    
    
                // 从头到尾对数组进行归并操作
                // step << 1 = 2 * step 表示偏移到后两个有序数组将它们进行归并
                for i := begin; i < end; i += step << 1 {
    
    
                        var lo = i                // 第一个有序数组的上界
                        var mid = lo + step       // 第一个有序数组的下界,第二个有序数组的上界
                        var hi = lo + (step << 1) // 第二个有序数组的下界
                        // 不存在第二个数组,直接返回
                        if mid > end {
    
    
                                return
                        }
                        // 第二个数组长度不够
                        if hi > end {
    
    
                                hi = end
                        }
                        // 两个有序数组进行合并
                        merge(list, lo, mid, hi)
                }
                // 上面的 step 长度的两个数组都归并成一个数组了,现在步长翻倍
                step <<= 1
        }
}

// 归并操作
func merge(list []int, begin int, mid int, end int) {
    
    
        // 申请额外的空间来合并两个有序数组,这两个数组是 list[begin,mid),list[mid,end)
        leftSize := mid - begin         // 左边数组的长度
        rightSize := end - mid          // 右边数组的长度
        newSize := leftSize + rightSize // 辅助数组的长度
        result := make([]int, 0, newSize)
        l, r := 0, 0
        for l < leftSize && r < rightSize {
    
    
                lValue := list[begin+l] // 左边数组的元素
                rValue := list[mid+r]   // 右边数组的元素
                // 小的元素先放进辅助数组里
                if lValue < rValue {
    
    
                        result = append(result, lValue)
                        l++
                } else {
    
    
                        result = append(result, rValue)
                        r++
                }
        }
        // 将剩下的元素追加到辅助数组后面
        result = append(result, list[begin+l:mid]...)
        result = append(result, list[mid+r:end]...)
        // 将辅助数组的元素复制回原数组,这样该辅助空间就可以被释放掉
        for i := 0; i < newSize; i++ {
    
    
                list[begin+i] = result[i]
        }
        return
}

(2)创建一个名为 mergesort_test.go 的文件用于编写测试函数检测排序的逻辑是否正确,此时的目录结构如下所示。

MergeSort
├── mergesort.go
└── mergesort_test.go

(3)编辑 mergesort_test.go 文件,编写如下内容的程序检测排序的逻辑是否正确。

package MergeSort

import (
        "fmt"
        "testing"
)

func TestMergoSort(t *testing.T) {
    
    
        list1 := []int{
    
    3, 1, 3, 5, 7, 8, 33, 0, 12, 22}
        fmt.Println("list1 归并前: \n", list1)
        MergeSort(list1, 0, len(list1))
        fmt.Println("list1 归并后: \n", list1)
        list2 := []int{
    
    1, 0, 2, 4, 9, 2, 4, 9, 6, 3}
        fmt.Println("list2 归并前: \n", list2)
        MergeSort(list2, 0, len(list2))
        fmt.Println("list2 归并后: \n", list2)
}

(4)在当前目录下执行 go test 命令测试程序,运行的结果如下:

list1 归并前: 
 [3 1 3 5 7 8 33 0 12 22]
list1 归并后: 
 [0 1 3 3 5 7 8 12 22 33]
list2 归并前: 
 [1 0 2 4 9 2 4 9 6 3]
list2 归并后: 
 [0 1 2 2 3 4 4 6 9 9]

排序算法的性能总结


  • 直接插入排序
时间复杂度 性能
最好情况 O(n)
平均情况 O(n^2)
最坏情况 O(n^2)
空间复杂度 稳定性
O(1) 稳定
  • 冒泡排序
时间复杂度 性能
最好情况 O(n)
平均情况 O(n^2)
最坏情况 O(n^2)
空间复杂度 稳定性
O(1) 稳定
  • 简单选择排序
时间复杂度 性能
最好情况 O(n^2)
平均情况 O(n^2)
最坏情况 O(n^2)
空间复杂度 稳定性
O(1) 不稳定
  • 希尔排序
时间复杂度 性能
最好情况
平均情况
最坏情况
空间复杂度 稳定性
O(1) 不稳定
  • 快速排序
时间复杂度 性能
最好情况 O(nlog2n)
平均情况 O(nlog2n)
最坏情况 O(n^2)
空间复杂度 稳定性
O(nlog2n) 不稳定
  • 二路归并排序
时间复杂度 性能
最好情况 O(nlog2n)
平均情况 O(nlog2n)
最坏情况 O(nlog2n)
空间复杂度 稳定性
O(n) 稳定

  • 参考书籍:《数据结构教程 第6版》(李春葆 主编)

  • 参考书籍:《数据结构 C语言版》(严蔚敏、李冬梅、吴伟民著)

猜你喜欢

转载自blog.csdn.net/qq_46457076/article/details/129848368