Go语言学习笔记【12】 排序算法之冒泡、选择排序

【声明】

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

一、排序算法

所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。排序算法,就是如何使得记录按照要求排列的方法。

1、相关术语

  • 稳定性:当两个相同的元素同时出现于某个序列之中,则经过一定的排序算法之后,两者在排序前后的相对位置不发生变化。如果 a 原本在 b 的前面,且 a == b,排序之后 a 仍然在 b 的前面,则为稳定排序;否则就是不稳定排序。稳定排序:插入排序,基数排序,归并排序,冒泡排序,计数排序;不稳定排序:快速排序,希尔排序,简单选择排序,堆排序
  • 原地排序:排序过程中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。如:希尔排序、冒泡排序、插入排序、选择排序、堆排序、快速排序
  • 时间复杂度:描述该算法的运行时间。通常使用算法的最坏情况的时间复杂度,记为T(n),定义为任何大小的输入n所需的最大运行时间。如果T(n)的上界与输入大小无关,则称其具有常数时间,记作O(1)时间;若T(n) = O( (logn)^k ),则称其具有对数时间;若T(n) = O(k*n) = O(n),则称其具有线性时间;若T(n) = O(M^n) M = O(T(n)) , 则称其具有指数时间;若T(n) = O(n^k),则称其具有k次方时间
  • 空间复杂度:描述算法所耗费的存储空间。

2、主要的排序算法

主要分为:冒泡排序,选择排序,插入排序,希尔排序,归并排序,快速排序,基数排序,堆排序,计数排序,桶排序

二、冒泡排序

1、核心思想和主要方法

  • 核心思想是:把较小的元素往前调或者把较大的元素往后调。
  • 主要方法是:通过对相邻两个元素进行大小的比较,根据比较结果和算法规则对该二元素的位置进行交换,这样逐个依次进行比较和交换,就能达到排序目的。

2、排序的过程

  • 第一趟排序:将第1个和第2个记录的关键字比较大小,如果是逆序的,就将这两个记录进行交换(大的元素往后移),再对第2个和第3个记录的关键字进行比较,依次类推,重复进行上述计算,直至完成第(n-1)个和第n个记录的关键字之间的比较,这样第一趟就可以将最大的元素放在数组的最后面
  • 第二趟排序:将第1个和第2个记录的关键字比较大小,如果是逆序的,就将这两个记录进行交换(大的元素往后移),依次类推,重复进行上述计算,直至完成第(n-2)个和第(n-1)个记录的关键字之间的比较,这样第二趟就可以将第二大的元素放在数组的倒数第二位
  • 重复以上操作,直到第(n-1)趟排序

3、改进的思路(减少趟数)

上述排序过程,固定需要对数组遍历(n-1)次,所耗费的时间较多。
如果在数组中的某一次遍历时,遍历的数组范围就已经有序了,那就说明整个数组已经是有序的,无需继续进行遍历。
按照这个思路,可以进行以下操作:
(1)在数组的每一趟遍历开始时设置一个标志位,初始值为true,假定当前数组是有序的
(2)在数组中元素两两比较中,如果发生了数据交换,则代表不是有序,将标志位设为false;如果没发生交换,则代表当前比较的数据范围是有序的,而剩余的数据则是已排好序的,因此整个数组此时已经是有序的
(3)当前遍历完成之后,根据标志位判断当前数组是否有序,有序则退出遍历

注意:该方法可能可以减少比较的趟数

4、针对数组部分有序的改进(减少比较次数)

分析:假如冒泡排序按照 将较大的元素往后调 的思路
(1)第m趟排序后当前数组正好后面的数据都已排好序(假设是[k+1, n],k < n-m),则按照上一个方法,第m+1趟排序的数组范围是[0, n-m]
(2)如果设置一个索引记录上一次最后发生交换的元素索引k,则第m+1趟排序的数组范围是[0, k],则该范围比[0, n-m]更小,因而可以减少第二层循环的次数,从而减少程序的运行时间

  • 示意图如下:
    Go语言学习笔记【12】 排序算法之冒泡、选择排序_第1张图片
    说明:
    (1)图中,红色的数字表示需要和下一位进行比较,即BubblePlus需要比较5*(9+5)/2 = 35次;BubbleOrdered需要比较9+3+2+1 = 15
    (2)BubbleOrdered排序的说明:
  • 第1趟排序时,[2 1 4 3 0 5 6 7 8 9] --> [1 2 4 3 0 5 6 7 8 9] --> [1 2 3 4 0 5 6 7 8 9] --> [1 2 3 (0) 4 5 6 7 8 9]。注意:由于arr[j]arr[j+1]进行比较,因此0对应的索引3就是第一趟确定的k
  • 第2趟排序时,待排序的数据范围变为了[0, 3],但是由于但是由于数据是两两比较的,因此,4个数据需要比较3次。其他趟依此类推。

5、代码实现

package main

import "fmt"

const size = 10

func Bubble(arr *[size]int) {
	var i, j int  //用于两层循环
	cnt := 0      //记录循环/元素比较的次数
	swap_cnt := 0 //记录发生交换的次数
	fmt.Println("原数组序列:  ", *arr)
	for i = 0; i < len(arr)-1; i++ {
		/*
			//每次都将较小的元素往前移
			for j = len(arr) - 1; j > i; j-- {
				if arr[j] < arr[j-1] {
					arr[j], arr[j-1] = arr[j-1], arr[j]
					flag = false
				}
			}
		*/
		//每次都将较大的元素往后移
		for j = 0; j < len(arr)-i-1; j++ {
			if arr[j] > arr[j+1] {
				arr[j], arr[j+1] = arr[j+1], arr[j]
				swap_cnt++
			}
		}
		fmt.Printf("第[%02d]趟: %v\n", i+1, *arr)
		cnt += j
	}
	fmt.Printf("【Bubble】排序的趟数: %d, 排序元素比较的次数: %d, 发生元素交换的次数: %d\n", i, cnt, swap_cnt)
}

func BubblePlus(arr *[size]int) {
	var i, j int  //用于两层循环
	var flag bool //设置标志位
	cnt := 0      //记录循环/元素比较的次数
	swap_cnt := 0 //记录发生交换的次数
	fmt.Println("原数组序列:  ", *arr)
	for i = 0; i < len(arr)-1; i++ {
		flag = true //假设当前数组是有序的
		//每次都将较大的元素往后移
		for j = 0; j < len(arr)-i-1; j++ {
			if arr[j] > arr[j+1] {
				arr[j], arr[j+1] = arr[j+1], arr[j]
				swap_cnt++
				flag = false //有元素交换,则表示当前数组无序
			}
		}
		fmt.Printf("第[%02d]趟: %v\n", i+1, *arr)
		cnt += j
		if flag {
			break
		}
	}
	fmt.Printf("【BubblePlus】排序的趟数: %d, 排序元素比较的次数: %d, 发生元素交换的次数: %d\n", i+1, cnt, swap_cnt)
}

func BubbleOrdered(arr *[size]int) {
	var i, j int                 //用于两层循环
	var flag bool                //设置标志位
	var index int = len(arr) - 1 //记录上一趟排序中最后发生交换的索引
	var tmp int                  //中间变量,记录每一次发生交换时的索引
	cnt := 0                     //记录循环/元素比较的次数
	swap_cnt := 0                //记录发生交换的次数
	fmt.Println("原数组序列:  ", *arr)
	for i = 0; i < len(arr)-1; i++ {
		flag = true //假设当前数组是有序的
		//每次都将较大的元素往后移
		for j = 0; j < index; j++ {
			if arr[j] > arr[j+1] {
				arr[j], arr[j+1] = arr[j+1], arr[j]
				swap_cnt++
				flag = false //有元素交换,则表示当前数组无序
				tmp = j
			}
		}
		index = tmp
		fmt.Printf("第[%02d]趟: %v\n", i+1, *arr)
		cnt += j
		if flag {
			break
		}
	}
	fmt.Printf("【BubbleOrdered】排序的趟数: %d, 排序元素比较的次数: %d, 发生元素交换的次数: %d\n", i+1, cnt, swap_cnt)
}

func main() {
	arr := [size]int{5, 0, 4, 3, 9, 1, 7, 8, 2, 6}
	var arr_tmp [size]int = arr

	Bubble(&arr_tmp)
	arr_tmp = arr
	BubblePlus(&arr_tmp)
	fmt.Printf("\n\n")

	arr = [size]int{2, 1, 4, 3, 0, 5, 6, 7, 8, 9}
	arr_tmp = arr
	BubblePlus(&arr_tmp)
	arr_tmp = arr
	BubbleOrdered(&arr_tmp)
}

6、结果

原数组序列:   [5 0 4 3 9 1 7 8 2 6][01]: [0 4 3 5 1 7 8 2 6 9][02]: [0 3 4 1 5 7 2 6 8 9][03]: [0 3 1 4 5 2 6 7 8 9][04]: [0 1 3 4 2 5 6 7 8 9][05]: [0 1 3 2 4 5 6 7 8 9][06]: [0 1 2 3 4 5 6 7 8 9][07]: [0 1 2 3 4 5 6 7 8 9][08]: [0 1 2 3 4 5 6 7 8 9][09]: [0 1 2 3 4 5 6 7 8 9]
【Bubble】排序的趟数: 9, 排序元素比较的次数: 45, 发生元素交换的次数: 19
原数组序列:   [5 0 4 3 9 1 7 8 2 6][01]: [0 4 3 5 1 7 8 2 6 9][02]: [0 3 4 1 5 7 2 6 8 9][03]: [0 3 1 4 5 2 6 7 8 9][04]: [0 1 3 4 2 5 6 7 8 9][05]: [0 1 3 2 4 5 6 7 8 9][06]: [0 1 2 3 4 5 6 7 8 9][07]: [0 1 2 3 4 5 6 7 8 9]
【BubblePlus】排序的趟数: 7, 排序元素比较的次数: 42, 发生元素交换的次数: 19


原数组序列:   [2 1 4 3 0 5 6 7 8 9][01]: [1 2 3 0 4 5 6 7 8 9][02]: [1 2 0 3 4 5 6 7 8 9][03]: [1 0 2 3 4 5 6 7 8 9][04]: [0 1 2 3 4 5 6 7 8 9][05]: [0 1 2 3 4 5 6 7 8 9]
【BubblePlus】排序的趟数: 5, 排序元素比较的次数: 35, 发生元素交换的次数: 6
原数组序列:   [2 1 4 3 0 5 6 7 8 9][01]: [1 2 3 0 4 5 6 7 8 9][02]: [1 2 0 3 4 5 6 7 8 9][03]: [1 0 2 3 4 5 6 7 8 9][04]: [0 1 2 3 4 5 6 7 8 9][05]: [0 1 2 3 4 5 6 7 8 9]
【BubbleOrdered】排序的趟数: 5, 排序元素比较的次数: 15, 发生元素交换的次数: 6

三、选择排序

1、核心思想和主要方法

  • 核心思想是:每一个位置选择当前最小的元素。
  • 主要方法是:先确定当前位置,再扫描后面的所有元素,选出比当前位置中小的元素给该位置。比如,数组第一个位置需要最小的元素,则对全部元素进行选择,选出全部元素中最小的给第一个位置

2、排序的过程

  • 第一趟排序:扫描整个数组的n个元素,将最小的元素与第一个元素进行交换
  • 第二趟排序:扫描后面n-1个元素,将最小的元素与第二个元素进行交换
  • 依此类推

3、改进思路1:首尾同时选择(趟数减半)

主要的思想,就是直接让每一趟确认两个位置的元素:

  • 第一趟排序确定第一个和最后一个元素的位置;
  • 第二趟排序确定第二个和倒数第二个元素的位置;
  • 依此类推

4、改进思路2:记录有序元素个数

前面的方法,排序的总趟数是固定的:假设数组长度为N,原始简单排序的趟数是n-1,改进思路1的趟数是n/2
但是,如果选择排序中,中间某一次排完序之后,剩下来的元素已经是有序的,那么通过前面的方法,多余的趟数显得没有必要。

由于剩下来的元素是有序的(设其长度为k),因此可以假设最小元素的下标是最后一个元素,从后往前扫描并与最小元素比较,那么最小元素的下标将随着扫描不断往前移动,即移动的次数=剩余数组中有序元素个数=k,此时移动的次数与当前趟数之和就等于数组的长度-1,于是可以跳出循环

5、代码实现

package main

import "fmt"

const size = 10

func Select(arr *[size]int) {
	var i, j int  //用于两层循环
	var min int   //记录最小元素的索引
	cnt := 0      //记录循环/元素比较的次数
	swap_cnt := 0 //记录发生交换的次数
	fmt.Println("原数组序列:  ", *arr)
	for i = 0; i < len(arr)-1; i++ {
		min = i
		//查找更小的元素
		for j = i + 1; j < len(arr); j++ {
			if arr[j] < arr[min] {
				min = j
			}
		}
		if min != i {
			arr[i], arr[min] = arr[min], arr[i]
			swap_cnt++
		}
		fmt.Printf("第[%02d]趟:  %v\n", i+1, *arr)
		cnt += j
	}
	fmt.Printf("【Select】排序的趟数: %d, 排序元素比较的次数: %d, 发生元素交换的次数: %d\n", i, cnt, swap_cnt)
}

func SelectTwo(arr *[size]int) {
	var i, j int      //用于两层循环
	var min, max int  //记录最小元素的索引
	var max_bound int //记录查找范围的最大值
	cnt := 0          //记录循环/元素比较的次数
	swap_cnt := 0     //记录发生交换的次数
	fmt.Println("原数组序列:  ", *arr)
	for i = 0; i < len(arr)/2; i++ {
		min = i
		max_bound = len(arr) - i - 1
		max = max_bound
		//查找更小的元素
		for j = i; j <= max_bound; j++ {
			if arr[j] < arr[min] {
				min = j
			}
			if arr[j] > arr[max] {
				max = j
			}
		}
		if min != i {
			arr[i], arr[min] = arr[min], arr[i]
			swap_cnt++
		}
		//注意:此处很重要!!!
		if max == i { //如果最大值刚好在第i个位置
			max = min //因为min和i已经换位了,所以max应该重新指到min
		}
		if max != max_bound {
			arr[max_bound], arr[max] = arr[max], arr[max_bound]
			swap_cnt++
		}
		fmt.Printf("第[%02d]趟:  %v\n", i+1, *arr)
		cnt += j - i
	}
	fmt.Printf("【SelectTwo】排序的趟数: %d, 排序元素比较的次数: %d, 发生元素交换的次数: %d\n", i, cnt, swap_cnt)
}

func SelectLogOrder(arr *[size]int) {
	var i, j int      //用于两层循环
	var min int       //记录最小元素的索引
	var order_cnt int //从后往前查找,记录后面有序元素的个数
	cnt := 0          //记录循环/元素比较的次数
	swap_cnt := 0     //记录发生交换的次数
	fmt.Println("原数组序列:  ", *arr)
	for i = 0; i < len(arr)-1; i++ {
		min = len(arr) - 1
		order_cnt = 0
		//查找更小的元素
		for j = len(arr) - 2; j >= i; j-- {
			if arr[min] > arr[j] {
				min = j
				order_cnt++
			}
		}
		cnt += len(arr) - 2 - j
		if min != i {
			arr[i], arr[min] = arr[min], arr[i]
			swap_cnt++
		}
		fmt.Printf("第[%02d]趟:  %v\n", i+1, *arr)
		//如果后面元素完全有序,前面元素也是有序,则可以跳出循环
		if order_cnt+i == len(arr)-1 {
			break
		}
	}
	fmt.Printf("【SelectLogOrder】排序的趟数: %d, 排序元素比较的次数: %d, 发生元素交换的次数: %d\n", i+1, cnt, swap_cnt)
}

func main() {
	arr := [size]int{8, 0, 4, 3, 1, 7, 6, 5, 2, 9}
	var arr_tmp [size]int = arr

	Select(&arr_tmp)
	arr_tmp = arr
	SelectTwo(&arr_tmp)
	arr_tmp = arr
	SelectLogOrder(&arr_tmp)
}

6、运行结果

原数组序列:   [8 0 4 3 1 7 6 5 2 9][01]:  [0 8 4 3 1 7 6 5 2 9][02]:  [0 1 4 3 8 7 6 5 2 9][03]:  [0 1 2 3 8 7 6 5 4 9][04]:  [0 1 2 3 8 7 6 5 4 9][05]:  [0 1 2 3 4 7 6 5 8 9][06]:  [0 1 2 3 4 5 6 7 8 9][07]:  [0 1 2 3 4 5 6 7 8 9][08]:  [0 1 2 3 4 5 6 7 8 9][09]:  [0 1 2 3 4 5 6 7 8 9]
【Select】排序的趟数: 9, 排序元素比较的次数: 90, 发生元素交换的次数: 5
原数组序列:   [8 0 4 3 1 7 6 5 2 9][01]:  [0 8 4 3 1 7 6 5 2 9][02]:  [0 1 4 3 2 7 6 5 8 9][03]:  [0 1 2 3 4 5 6 7 8 9][04]:  [0 1 2 3 4 5 6 7 8 9][05]:  [0 1 2 3 4 5 6 7 8 9]
【SelectTwo】排序的趟数: 5, 排序元素比较的次数: 30, 发生元素交换的次数: 5
原数组序列:   [8 0 4 3 1 7 6 5 2 9][01]:  [0 8 4 3 1 7 6 5 2 9][02]:  [0 1 4 3 8 7 6 5 2 9][03]:  [0 1 2 3 8 7 6 5 4 9][04]:  [0 1 2 3 8 7 6 5 4 9][05]:  [0 1 2 3 4 7 6 5 8 9][06]:  [0 1 2 3 4 5 6 7 8 9][07]:  [0 1 2 3 4 5 6 7 8 9]
【SelectLogOrder】排序的趟数: 7, 排序元素比较的次数: 42, 发生元素交换的次数: 5

你可能感兴趣的:(go语言学习之路,排序算法,排序算法,算法,学习,golang,后端)