Bootstrap

Go语言学习笔记【13】 排序算法之插入、二分查找插入、希尔排序

【声明】

非完全原创,部分内容来自于学习其他人的理论和B站视频。如果有侵权,请联系我,可以立即删除掉。

一、插入排序

1、方法和复杂度

1.1、核心思想

基于某序列已经有序排列的情况下,通过一次插入一个元素的方式按照原有排序方式增加元素。每次将1个待排序的记录按其关键字大小插入到前面已经排好序的子序列中,寻找最适当的位置,直至全部记录插入完毕。

1.2、主要方法

从该有序序列的最末端开始执行,即要插入序列中的元素最先和有序序列中最大的元素比较,若其大于该最大元素,则可直接插入最大元素的后面即可,否则再向前一位比较查找直至找到应该插入的位置为止。

1.3、稳定性

当待插入元素和插入元素相等的位置,则将待插入的元素放在该相等元素的后面,从而保证了排序的稳定性

1.4、时间复杂度

最优情况:当待排序数组是有序时,只需当前数跟前一个数比较一下就可以了,数组无需移动,这时一共需要比较N-1次,时间复杂度为 O ( N ) O(N) O(N)
最坏情况:当待排序数组是逆序时,N-1次循环需要移动:1+2+3+..+(N-1) = N*(N-1)/2,时间复杂度为 O ( N 2 ) O(N^2) O(N2)
综合来看,时间复杂度 O ( N 2 ) O(N^2) O(N2)

1.5、空间复杂度

在整个算法中,只用到了三个辅助变量:i控制外层循环、j控制内层循环、tmp记录待插入的元素。辅助的空间与问题规模无关,因此空间复杂度 O ( 1 ) O(1) O(1)

2、排序的过程

  • 第一趟排序。数组的有序区间(只有第一个元素)下标为[0, 0],此时需要将第二个元素插入到有序区间中,即,将第二个元素和前面的元素进行比较,小于则插入到有序区间的前面(即第一个元素后移一位,第二个元素替换掉第一个元素);大于或等于,则不进行操作。此时有序区间为[0, 1]
  • 第二趟排序。有序区间下标[0, 1],此时待插入元素为:第三个元素。即,待插入元素按照从后往前的顺序,与有序区间的元素进行比较,小于有序区间的元素则继续往前查找。在有序区间中,找到大于或等于待插入元素的索引k,此时区间[k, 1]整体往后移动1位,然后索引k处的元素用待插入元素替换。
  • 依此类推

3、改进思路:有序区间查找用二分查找法

由于每一趟排序,都需要在有序区间中查找待插入元素的位置,因此可以使用二分查找法来提高查找的效率。
该思路只能提高查找效率,并不能减少比较的趟数,也不能减少元素右移的次数,因此时间复杂度不会改变。由于二分查找只需要多引入三个变量:low记录查找区间最小元素索引,high记录查找区间最大元素索引,mid记录查找区间中间元素的索引,因此空间复杂度也不会改变

4、代码实现

package main

import "fmt"

const size = 10

func Insert(arr *[size]int) {
   
	var i, j int  //用于两层循环
	cnt := 0      //记录循环/元素比较的次数
	swap_cnt := 0 //记录发生交换的次数
	tmp := 0      //临时变量,记录待插入的元素
	fmt.Println("原数组序列:  ", *arr)
	for i = 1; i < len(arr); i++ {
   
		//arr数组[0, i-1]是有序,寻找arr[i]的位置
		tmp = arr[i]
		for j = i; j > 0 && arr[j-1] > tmp; j-- {
   
			arr[j] = arr[j-1] //整体往后移动
			swap_cnt++
		}
		arr[j] = tmp
		cnt += i - j
		fmt.Printf("第[%02d]趟: %v\n", i, *arr)
	}
	fmt.Printf("【Insert】排序的趟数: %d, 排
;