本文主要介绍几种初级排序,选择排序、插入排序及希尔排序,这些排序的实现均采用Go语言
简介
作为对排序算法领域的第一次探索,我们将学习两种初级的排序算法以及其中一种的一个变体。深入学习这些相对简单的算法的原因在于:第一,我们将通过它们熟悉一些术语和简单的技巧;第二,这些简单的算法在某些情况下比我们之后将会讨论的复杂算法更有效;第三,以后你会发现,它们有助于我们改进复杂算法的效率。
游戏规则
我们关注的主要对象是重新排列数组元素的算法,其中每个元素都有一个主键。排序算法的目标就是将所有元素的主键按照某种方式排列(通常是按照大小或是字母顺序)。排序后索引较大的主键大于等于索引较小的主键。元素和主键的具体性质在不同的应用中千差万别。
我们将会写一个辅助程序help.go,包含Less、Exch、Show及IsSorted方法。
- Less: 比大小,第一个数字小于返回true,反之为false
- Exch: 交换元素
- Show: 打印整个数组
- IsSorted: 测试数组元素是否有序
具体程序如下:
go
package help
import "fmt"
/**
* @Description 一些辅助方法
* @Author zyz
* @Date 2024/4/18 10:16
**/
func Less(v int, w int) bool {
return v < w
}
func Exch(nums []int, i int, j int) {
nums[i], nums[j] = nums[j], nums[i]
}
func Show(nums []int) {
//在单行中打印数组
for i := 0; i < len(nums); i++ {
str := fmt.Sprintf("%d", nums[i])
fmt.Print(str + " ")
}
fmt.Println()
}
func IsSorted(nums []int) bool {
//测试数组元素是否有序
for i := 1; i < len(nums); i++ {
if Less(nums[i], nums[i-1]) {
return false
}
}
return true
}
选择排序
一种最简单的排序算法是这样的:首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素那么它就和自己交换)。再次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复,直到将整个数组排序。这种方法叫做选择排序,因为它在不断地选择剩余元素之中的最小者。
程序实现:
css
func SelectSort(nums []int) {
N := len(nums)
fmt.Println("选择排序")
//遍历数组,外层循环
for i := 0; i < N; i++ {
minIndex := i
//内层循环,找到数组中最小的元素,然后与数组的当前元素交换位置
for j := i + 1; j < N; j++ {
if help.Less(nums[j], nums[minIndex]) {
minIndex = j
}
}
help.Exch(nums, i, minIndex)
}
}
测试:
go
package main
import (
"fmt"
"github.com/zyz880615/Algorithms/ch2/sortMethod/help"
"github.com/zyz880615/Algorithms/ch2/sortMethod/sort"
)
/**
* @Description 排序模板
* @Author zyz
* @Date 2024/4/17 17:11
**/
func mySort(nums []int) {
//选择排序
sort.SelectSort(nums)
}
func main() {
nums := []int{15, 10, 8, 11, 5}
mySort(nums)
if help.IsSorted(nums) {
fmt.Println("排序成功")
} else {
panic("排序失败,请检查")
}
help.Show(nums)
}
插入排序
通常人们整理桥牌的方法是一张一张的来,将每一张牌插入到其他已经有序的牌中的适当位置。在计算机的实现中,为了给要插入的元素腾出空间,我们需要将其余所有元素在插入之前都向右移动一位。这种算法叫做插入排序,实现请见算法2.2。
与选择排序一样,当前索引左边的所有元素都是有序的,但它们的最终位置还不确定,为了给更小的元素腾出空间,它们可能会被移动。但是当索引到达数组的右端时,数组排序就完成了。
和选择排序不同的是,插入排序所需的时间取决于输入中元素的初始顺序。例如,对一个很大且其中的元素已经有序(或接近有序)的数组进行排序将会比对随机顺序的数组或是逆序数组进行排序要快得多。
程序实现:
css
func InsertSort(nums []int) {
N := len(nums)
fmt.Println("插入排序")
for i := 1; i < N; i++ {
for j := i; j > 0 && help.Less(nums[j], nums[j-1]); j-- {
help.Exch(nums, j, j-1)
}
}
}
希尔排序
为了展示初级排序算法性质的价值,接下来我们将学习一种基于插入排序的快速的排序算法。对于大规模乱序数组插入排序很慢,因为它只会交换相邻的元素,因此元素只能一点一点地从数组的一端移动到另一端。例如,如果主键最小的元素正好在数组的尽头,要将它挪到正确的位置就需要N-1次移动。希尔排序为了加快速度简单地改进了插入排序,交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。
希尔排序的思想是使数组中任意间隔为h的元素都是有序的。这样的数组被称为h有序数组。换句话说,一个h有序数组就是h个互相独立的有序数组编织在一起组成的一个数组。在进行排序时,如果h很大,我们就能将元素移动到很远的地方,为实现更小的h有序创造方便。用这种方式,对于任意以1结尾的h序列,我们都能够将数组排序。这就是希尔排序。
实现希尔排序的一种方法是对于每个h,用插入排序将h个子数组独立地排序。但因为子数组是相互独立的,一个更简单的方法是在h-子数组中将每个元素交换到比它大的元素之前去(将比它大的元素向右移动一格)。只需要在插入排序的代码中将移动元素的距离由1改为h即可。这样,希尔排序的实现就转化为了一个类似于插入排序但使用不同增量的过程。
程序实现:
css
func ShellSort(nums []int) {
N := len(nums)
fmt.Println("希尔排序")
h := 1
for h < N/3 {
h = 3*h + 1
}
for h >= 1 {
for i := h; i < N; i++ {
for j := i; j >= h && help.Less(nums[j], nums[j-h]); j -= h {
help.Exch(nums, j, j-h)
}
}
h = h / 3
}
}
小结
本文介绍了三种初级排序,并用Go语言实现,全部思路来自《算法第四版》,算是一个读书笔记,后续会陆续记录读书历程。