Go语言常见排序算法| 青训营笔记

67 阅读3分钟

一、冒泡排序

冒泡排序是最简单直观的排序方式,通过比较前后两个元素的大小,然后交换位置来实现排序。
每次比较相邻两个数的大小,如果前面的数大于后面的数,则交换两个数的位置(否则不变),向后移动。

// 冒泡排序
func BubbleSort(arr []int){
	n := len(arr)
	for i:=0; i<n-1; i++{
		for j:=i+1; j<n; j++{
			if arr[i] > arr[j]{
				arr[i], arr[j] = arr[j], arr[i]
			}
		}
	}
	fmt.Println(arr)
}

改进冒泡排序: 冒泡排序第1次遍历后会将最大值放到最右边,这个最大值也是全局最大值。同理,当前轮的最大值也都会放在最后,每轮结束后,最大值、次大值都会固定,但是普通版冒泡排序每次都会比较全部元素。可以记录每轮比较后最后一个位置,也可以逆序遍历。

// 改进的冒泡排序
func BubbleSort2(arr []int){
	n := len(arr)
	for i:=n-1; i>0; i--{    // 逆序遍历
		for j:=0; j<i; j++{
			if arr[j] > arr[j+1]{
				arr[j], arr[j+1] = arr[j+1], arr[j]
			}
		}
	}
	fmt.Println(arr)
}

二、选择排序

选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。
算法步骤:

  1. 初始序列arr,无序。分成有序区和无序区,有序区初始为0,不断变大;无序区初始为len(arr),不断变小。
  2. 遍历无序找到最小值,与无序区最左边交换。有序区长度+1。
  3. 重复第二步。
// 选择排序
func SelectionSort(arr []int){
	n := len(arr)
	for i:=0; i<n-1; i++{
		minNumIndex := i    // 无序区第一个
		for j:=i+1; j<n; j++{
			if arr[j] < arr[minNumIndex]{
				minNumIndex = j
			}
		}
		arr[i], arr[minNumIndex] = arr[minNumIndex], arr[i]
	}
	fmt.Println(arr)
}

三、 插入排序

插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

// 插入排序
func InsertionSort(arr []int){
	for i := range arr{
		preIndex := i-1
		current := arr[i]

		for preIndex >= 0 && arr[preIndex] > current{  // 移动
			arr[preIndex+1] = arr[preIndex]
			preIndex--
		}

		arr[preIndex+1] = current
	}
	fmt.Println(arr)
}

// 插入排序1
func InsertionSort1(arr []int){
	n := len(arr)
	for i:=1; i<n; i++{
		tmp := arr[i]
		j:=i-1
		for ; j>=0 && tmp<arr[j];j--{
			arr[j+1] = arr[j]
		}
		arr[j+1] = tmp
	}
	fmt.Println(arr)
}

改进插入排序:  查找插入位置时使用二分查找的方式

// 改进版插入排序
func InsertionSort2(arr []int){
	n := len(arr)
	for i:=1; i<n; i++{   // 无序区
		tmp := arr[i]
		left, right := 0, i-1
		for left<=right{
			mid := (left+right)/2
			if arr[mid] > tmp{
				right = mid-1
			}else{
				left = mid+1
			}
		}
		j:=i-1
		for ; j>=left; j--{   // 有序区
			arr[j+1] = arr[j]
		}
		arr[left] = tmp
	}
	fmt.Println(arr)
}

四、希尔排序

希尔排序又称递减增量排序、缩小增量排序,是简单插入排序的改进版,但是是非稳定算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入算法在对几乎已经排好序的数据操作时,效率高,即可达线性排序效率
  • 但插入排序一般来说是低效的,因为每次只能将数据移动一位
    希尔排序的基本思想是:先将整个待排序列分割成若干个子序列,对若个子序列分别进行插入排序,待整个待排序列基本有序时,对整体进行插入排序。
    算法步骤:
  • 选择一个增量序列t1,t2,…,tk,其中ti>ti+1,tk=1;
  • 按增量序列个数k,对序列进行k 趟排序;
  • 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
    其实就是两个两个换位置,将整个序列变成基本排好序的
// 希尔排序
func ShellSort(arr []int){
	n := len(arr)
	for gap:=n/2; gap>=1; gap=gap/2{   // 缩小增量序列,希尔建议每次缩小一半
		for i:=gap; i<n; i++{		// 子序列
			tmp := arr[i]
			j:=i-gap
			for ; j>=0 && tmp<arr[j]; j=j-gap{
				arr[j+gap] = arr[j]
			}
			arr[j+gap] = tmp
		}
	}
	fmt.Println(arr)
}