Go
package main
import "fmt"
func main() {
Bubble_Sort()
Select_Sort()
Insert_Sort()
Shell_Sort()
Heap_Sort()
Merge_Sort()
Quick_Sort()
}
一、
1、冒泡排序
冒泡排序的基本思想是:
- 从左到右不断交换相邻逆序的元素
- 一轮下来,最大元素会交换移到右端
- 对除最后元素外的子序列,重复步骤 1
- 若没有发生交换,说明序列已排序完成
内层循环处理交换,外层循环控制轮数。
Go
// 冒泡排序
func Bubble_Sort() {
str := []int{9, 1, 5, 8, 3, 7, 4, 6, 2}
// 反向冒泡
for i := 0; i < len(str)-1; i++ {
for j := 1; j <= len(str)-i; j++ {
if str[j-1] > str[j] {
str[j-1], str[j] = str[j], str[j-1]
}
}
}
// 正向冒泡
for i := 0; i < len(str)-1; i++ {
for j := len(str) - 1; j > i; j-- {
if str[j-1] > str[j] {
str[j-1], str[j] = str[j], str[j-1]
}
}
}
//改进
//如果在某一轮中没有发生交换,说明数组已经有序,可以提前退出循环。
for i := 0; i < len(str)-1; i++ {
flag := false
for j := 1; j <= len(str)-i; j++ {
if str[j-1] > str[j] {
str[j-1], str[j] = str[j], str[j-1]
flag = true
}
}
if !flag {
break
}
}
fmt.Println(str)
}
2、快速排序
其核心思想是:
- 选择一个基准值。
- 将数组分为两部分,左边小于基准值,右边大于或等于基准值。
- 对左右两部分递归排序,最终合并。
Go
func Quick_Sort() {
str := []int{9, 1, 5, 8, 3, 7, 4, 6, 2}
Quicking_Sort(str, 0, 8)
fmt.Println(str)
}
func Quicking_Sort(str []int, low, high int) {
if low < high {
pivot := Partition_1(str, low, high)
Quicking_Sort(str, low, pivot-1)
Quicking_Sort(str, pivot+1, high)
}
}
func Partition_0(str []int, low, high int) int {
//选取基准点。也可以从三个数或者九个数中选取大小为中间值的数作为基准
pivotkey := str[low]
for low < high { //从表的两端交替向中间扫描
for low < high && str[high] > pivotkey {
high--
}
//1、交换法
//str[low], str[high] = str[high], str[low]
for low < high && str[low] < pivotkey {
low++
}
//1、交换法
//str[low], str[high] = str[high], str[low]
//2、统一交换
str[low], str[high] = str[high], str[low]
}
return low
}
func Partition_1(str []int, low, high int) int {
//选取基准点。也可以从三个数或者九个数中选取大小为中间值的数作为基准
pivotkey := str[low]
temp := pivotkey
for low < high { //从表的两端交替向中间扫描
for low < high && str[high] > pivotkey {
high--
}
//替换法
str[low] = str[high]
for low < high && str[low] < pivotkey {
low++
}
//替换法
str[high] = str[low]
}
str[low] = temp
return low
}
func Partition_2(str []int, low, high int) int {
pivotkey := str[high] // 选择最后一个元素作为基准值
i := low
for j := low; j < high; j++ {
// 将比 pivot 小的数丢到 [l...i-1] 中,剩下的 [i...j] 区间都是比 pivot 大的
if str[j] < pivotkey {
// 互换 i、j 下标对应数据
str[i], str[j] = str[j], str[i]
// 将 i 下标后移一位
i++
}
}
// 由于for循环之后i++,所以[l...i-1] 中的值都比pivotkey小,剩下的 [i...j] 区间中的值都是比 pivot 大的
// 最后将 pivot 与 i 下标对应数据值互换
// 这样一来,pivot 就位于当前数据序列中间,i 也就是 pivot 值对应的下标
str[i], str[high] = pivotkey, str[i]
// 返回 i 作为 pivot 分区位置
return i
}
二、
1、选择排序
Go
// 选择排序
func Select_Sort() {
str := []int{9, 1, 5, 8, 3, 7, 4, 6, 2}
for i := 0; i < len(str)-1; i++ {
// 未排序区间最小值初始化为第一个元素
min := i
// 从未排序区间第二个元素开始遍历,直到找到最小值
for j := i + 1; j <= len(str)-1; j++ {
if str[min] > str[j] {
min = j
}
}
// 将最小值与未排序区间第一个元素互换位置(等价于放到已排序区间最后一个位置)
if min != i {
str[min], str[i] = str[i], str[min]
}
}
fmt.Println(str)
}
2、堆排序
算法思路:
堆其实是一颗父节点大于/小于左右子节点的完全二叉树,父节点大于子节点的堆称为大顶堆,父节点小于子节点的堆称为小顶堆。以大顶堆为例,在建堆和调整堆的过程中,我们发现子节点大于父节点时,需要交换父节点与子节点,由于交换后,父节点并不能保证比子节点的子节点更大,因此还需要递归向下比较。当建堆/调整堆完成后,堆顶元素(二叉树的根节点)即为堆中的最大值,我们把最大值取出并重新调整出元素数量为n-1的新大顶堆,再次取出最大值,n轮过后,所有元素都被依次取出,排序完毕
Go
//堆排序
func Heap_Sort() {
str := []int{9, 1, 5, 8, 3, 7, 4, 6, 2}
//构建一个大顶堆
for i := len(str) / 2; i >= 0; i-- {
Heap_Adjust_2(str, len(str), i)
}
for i := len(str) - 1; i > 0; i-- {
str[0], str[i] = str[i], str[0]
Heap_Adjust_2(str, i, 0) //将剩余的重新调整为大顶堆,此处的 i 就是len(str) - 1
}
fmt.Println(str)
}
//函数0和函数1是同一种思路,只是写法不同
//堆调整函数0
func Heap_Adjust_0(str []int, length, index int) {
temp := str[index]
for j := 2*index + 1; j < length-1; j *= 2 { //沿关键字较大的孩子节点向下筛选
if j < length-1 && str[j] < str[j+1] {
j++ //j记录孩子节点较大的下标
}
if temp > str[j] { //父节点大于孩子节点
break
}
str[index] = str[j] //给节点赋值
index = j //index此时已代表孩子节点下标
}
str[index] = temp //给孩子节点赋值,到此代表节点的值跟孩子节点中的较大值进行互换完成
}
//堆调整函数1
func Heap_Adjust_1(str []int, length, index int) {
parent := index
child := parent*2 + 1
for ; child < length-1; child *= 2 {
if child < length-1 && str[child+1] > str[child] {
child++
}
if str[child] > str[parent] {
str[child], str[parent] = str[parent], str[child]
parent = child //最后一个元素和堆顶元素交换后需要向下调整
}
}
}
//堆调整函数2
func Heap_Adjust_2(str []int, length, index int) {
largest := index // 初始化最大元素为根节点
left := 2*index + 1 // 左子节点索引
right := 2*index + 2 // 右子节点索引
// 如果左子节点大于根节点
if left < length && str[left] > str[largest] {
largest = left
}
// 如果右子节点大于最大元素
if right < length && str[right] > str[largest] {
largest = right
}
// 如果最大元素不是根节点
if largest != index {
str[index], str[largest] = str[largest], str[index]
// 递归调整受影响的子树
Heap_Adjust_2(str, length, largest)
}
}
func Heap_Adjust_3(str []int, length, index int) {
for {
largest := index // 初始化最大元素为根节点
left := 2*index + 1 // 左子节点索引
right := 2*index + 2 // 右子节点索引
// 如果左子节点大于根节点
if left < length && str[left] > str[largest] {
largest = left
}
// 如果右子节点大于最大元素
if right < length && str[right] > str[largest] {
largest = right
}
if largest != index {
str[index], str[largest] = str[largest], str[index]
index = largest
}else {
return
}
}
}
三、
1、插入排序
**要点:**设立哨兵,作为临时存储和判断数组边界之用
具体算法描述如下:
- 从第一个元素开始,该元素可以认为已经被排序 取出下一个元素
- 在已经排序的元素序列中从后向前扫描
- 如果该元素(已排序)大于新元素,将该元素移到下一位置
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置 将新元素插入到该位置后
- 将新元素插入到该位置后
- 重复步骤2~5
循环细节:
外层循环
在外层循环中, for i := 1; i < len(arr); i++ ,循环变量 i(下标) 从 1 开始,直到切片 arr 的最后一个元素。这是因为第一个元素 (arr[0]) 默认是已排序的,所以我们从第二个元素 (arr[1]) 开始考虑如何将它插入到前面已排序的序列中
内层循环
内层循环的目的是为了找到新元素 (temp) 在已排序序列中的正确位置,并将这个位置及之后的所有元素向后移动一位,为新元素腾出空间。内层循环的条件是 j >= 0 && arr[j] > temp ,这意味着只要 j 没有越界,并且 arr[j] (已排序序列中的一个元素) 大于新元素 temp,就执行元素的移动操作 (arr[j+1] = arr[j]) 和 j 的递减操作 (j = j - 1)。
当内层循环结束时,j + 1 就是新元素 key 应该插入的位置,因为此时 j 指向的是第一个不大于 temp 的元素,或者 j 已经是 -1 (即已排序序列为空,或者 key 应该插入到序列的最前面)。然后,将 temp 插入到这个位置 (arr[j+1] = temp)
通过外层循环的不断迭代,整个切片 arr 将逐渐变成有序状态
Go
//插入排序
func Insert_Sort() {
str := []int{9, 1, 5, 8, 3, 7, 4, 6, 2}
for i := 1; i <= len(str)-1; i++ {
//要点:设立哨兵,作为临时存储和判断数组边界之用
temp := str[i]
j := i - 1
for ; j >= 0 && str[j] > temp; j-- {
str[j+1] = str[j] // 元素后移
}
str[j+1] = temp //插入到正确位置
}
fmt.Println(str)
}
2、希尔排序
Go
//希尔排序 与插入排序比较,把原来按顺序变成了相隔增量
func Shell_Sort() {
str := []int{9, 1, 5, 8, 3, 7, 4, 6, 2}
//increment相隔数量
// for increment := len(str) / 3; increment > 0; increment /= 3 {
increment := len(str)
for increment > 0 {
increment = increment / 3
//此过程类似于插入排序的过程
for i := increment; i <= len(str)-1; i++ {
key := str[i]
j := i - increment
//按照increment,数组从j到0进行交换比较
for ; j >= 0 && str[j] > key; j -= increment {
str[j+increment] = str[j]
}
//如果是从for循环走到这里,此时j<0,因为for循环走完时j-=increment ,所以要加回来
//走到这里时,j已经减掉increment 了,所以要加回来
str[j+increment] = key
}
}
fmt.Println(str)
}
四、归并排序
Go
// 归并排序
func Merge_Sort() {
str := []int{9, 1, 5, 8, 3, 7, 4, 6, 2}
str = Merging_Sort(str)
fmt.Println(str)
}
func Merging_Sort(str []int) []int {
// str := []int{9, 1, 5, 8, 3, 7, 4, 6, 2}
if len(str) <= 1 {
return str
}
mid := len(str) / 2 //获取分区位置
//进行递归分区
left := Merging_Sort(str[:mid])
right := Merging_Sort(str[mid:])
res := Merge_0(left, right) //函数将两个有序数组合并成一个有序数组
return res
}
func Merge_0(left, right []int) []int {
// 用于存放结果集
result := make([]int, len(left)+len(right))
i, j, k := 0, 0, 0
for ; k < len(result); k++ {
// 任何一个区间遍历完,则退出
if i >= len(left) || j >= len(right) {
break
}
// 对所有区间数据进行排序
if left[i] < right[j] {
result[k] = left[i]
i++
} else {
result[k] = right[j]
j++
}
}
// 如果左侧区间还没有遍历完,将剩余数据放到结果集
if i <= len(left) {
for l := 0; l < len(left)-i; l++ {
result[k+l] = left[i+l]
}
}
// 如果右侧区间还没有遍历完,将剩余数据放到结果集
if j <= len(right) {
for l := 0; l < len(right)-j; l++ {
result[k+l] = right[j+l]
}
}
return result
}
func Merge_1(left, right []int) []int {
result := make([]int, len(left)+len(right))
i, j := 0, 0
for k := 0; k < len(result); k++ {
if i >= len(left) {
result[k] = right[j]
j++
} else if j >= len(right) {
result[k] = left[i]
i++
} else if left[i] < right[j] {
result[k] = left[i]
i++
} else {
result[k] = right[j]
j++
}
}
return result
}
func Merge_2(left, right []int) []int {
// result := make([]int, 0, len(left)+len(right))
//此处不能用make初始化[]int切片时长度必须是0,
//因为make初始化后,result=[]int{0,0,0},再用append,result=[]int{0,0,0,5},
//相当于直接在result后面添加数据,会导致结果多了很多0
result := make([]int, 0, len(left)+len(right))
//var result []int
i, j := 0, 0
for {
if i >= len(left) {
result = append(result, right[j:]...)
break
} else if j >= len(right) {
result = append(result, left[i:]...)
break
} else if left[i] < right[j] {
result = append(result, left[i])
i++
} else if right[j] < left[i] {
result = append(result, right[j])
j++
}
}
return result
}