直接插入排序
(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语言版》(严蔚敏、李冬梅、吴伟民著)