【声明】
非完全原创,部分内容来自于学习其他人的理论和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 //记录循环/元素比较的次数
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语言中冒泡排序和选择排序的原理与实现。包括冒泡排序的核心思想、改进方法,以及选择排序的详细步骤和优化策略。文章通过实例分析了两种排序算法的时间复杂度和应用场景。

6729

被折叠的 条评论
为什么被折叠?



