【声明】
非完全原创,部分内容来自于学习其他人的理论和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]
更小,因而可以减少第二层循环的次数,从而减少程序的运行时间
- 示意图如下:
说明:
(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 //记录循环/元