Go语言实现十大排序算法,讲解超详细
大家好,我是王不错。(欢迎大家关注我的公众号:程序员王不错)
今天为大家介绍排序算法。
排序算法是算法领域的基础,可以说是每个程序员都必须熟练掌握的知识。
本文将最常见的十大排序算法进行了完整的总结,相信看完这篇文章,你对排序算法的掌握将会更进一步。
要说明的是,本文的算法实现都是由Go语言实现的,你可以根据自己的习惯用不同语言实现。
01 冒泡排序
冒泡排序是一种比较简单的排序算法,它的原理是:
从左到右遍历数组,相邻元素两两进行比较。每次比较完一轮,就会找到数组中最大的一个或最小的一个。这个数就会从序列的最右边冒出来。
以从小到大排序为例,第一轮比较后,所有数中最大的那个数就会浮到最右边;第二轮比较后,所有数中第二大的那个数就会浮到倒数第二个位置……
就这样一轮一轮地比较,最后实现从小到大排序。由于这个排序算法较为简单,就不需要再赘述了。
直接看代码:
这个算法的时间复杂度是O(n^2),空间复杂度是O(1)。
02 选择排序
选择排序的原理是,首先在开始的时候遍历整个数组,找到序列中的最小元素,然后将这个元素与第一个元素交换,这样最小的元素就放到了它的最终位置上了。
然后,从第二个元素开始遍历,找到剩下的n-1个元素中的最小元素,再与第二个元素进行交换。
以此类推,直到最后一个元素放到了最终位置。
同样,选择排序也不难理解,还是直接看代码:
这个算法的时间复杂度也是O(n^2)。
03 插入排序
**插入排序,也叫直接插入排序。
它的原理是,将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。
在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面的有序表进行待插入位置查找,并进行移动。
插入排序的代码如下:
这个算法的时间复杂度仍是O(n^2),空间复杂度是O(1)。
下面介绍几个加快排序的算法。
04 快速排序
快速排序要求我们选择一个基准,根据这个基准为原数组分组,比基准大的一组,比基准小的一组,再重复递归地进行快速排序,重新合并每组排序后的序列,即为排好序的序列。
快速排序需要我们先理解递归的思想,代码如下:
这个算法的时间复杂度是O(nlogn),空间复杂度为O(logn) 。
05 归并排序
归并排序是采用分治法的一个典型的排序算法,将已有序的子序列合并为一个完全有序的序列。
归并排序的过程是:首先将序列拆分成子序列,然后对子序列进行排序,最后将排序好的子序列合并,就得到了一个有序的序列。
时间复杂度是O(nlogn),空间复杂度是O(1)
06 堆排序
先来介绍一下什么是堆?堆是一种近似完全二叉树的数据结构,可以分为大根堆,小根堆。
大根堆:每个节点的值都大于或等于他左右孩子节点的值。
小根堆:每个节点的值都小于或等于他左右孩子节点的值。
堆排序就是基于这种结构而产生的一种排序算法。
接着我们来看一下堆排序的过程:
假设我们想得到一个升序序列,那么就需要借助大根堆这种数据结构。
1.将待排序序列构造成一个大根堆,此时,整个数组的最大值就是堆结构顶端的根节点。
2.将堆根节点的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序序列个数为n-1。
3.将剩余的n-1个数再构造成一个大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组。
最后,我们还需要了解如何构造一个大根堆?
构造大根堆,要从堆的最后一个叶子结点起,从后往前调整,每次调整,将叶子结点和根节点中的最大值作为根节点,调整到最后,便构造成了一个大根堆。
下面看上述过程的代码。
维护堆性质(heapify)的时间复杂度是O(logN),因为一个二叉树的高度为O(logN),建堆过程的时间复杂度是O(N)。
综上,算法的时间复杂度为O(NlogN) 。
07 希尔排序
希尔排序是插入排序的一种,也叫缩小增量排序,是直接插入排序算法的一种更高效的改进版本。
希尔排序的原理是:希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
希尔排序的过程如下:
增量表示整个数组中的元素以它为跨度被分在一组中,增量为几数组就会被分成几组。
来看代码:
希尔排序的时间复杂度有点不同。它不像其他时间复杂度为O(nlogn)的排序算法那么快,但是要比选择排序和插入排序这种时间复杂度为O(n^2)的排序算法快得多。
08 计数排序
计数排序是一种通过计数而不是比较进行排序的算法,适用于范围较小的整数序列。
它的优势在于在对一定范围内的整数排序时,时间复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。
当然这是一种牺牲空间换取时间的做法。
下面通过排序过程了解计数排序的原理:
计数排序代码:
这个算法的时间复杂度为O(n+m),空间复杂度为O(m) ,m为数组最大值。
09 桶排序
桶排序类似于计数排序,其思想近乎彻底的分治思想。
原理是将数组按照元素所属范围分到有限数量的桶里,再单独对每个桶排序(可以使用别的排序算法或是以递归方式继续使用桶排序进行排序),最后依次把各个桶中的记录列出来记得到有序序列。
代码如下:
func BucketSort(nums []int, bucketNum int) []int {
// bucketNum:桶数量
numsLen := len(nums)
// 确定数组元素范围
min, max := nums[0], nums[0]
for i := 0; i < numsLen; i++ {
if min > nums[i] {
min = nums[i]
}
if max < nums[i] {
max = nums[i]
}
}
bucket := make([][]int, bucketNum)
for j := 0; j < numsLen; j++ {
// 分桶:这里比较复杂,重点是地板除
n := int(math.Floor(float64(nums[j] - min) / (float64(max - min + 1) / float64(bucketNum))))
bucket[n] = append(bucket[n], nums[j])
k := len(bucket[n]) - 2
for k >= 0 && nums[j] < bucket[n][k] {
bucket[n][k+1] = bucket[n][k]
k--
}
bucket[n][k+1] = nums[j]
}
i := 0
for p, q := range bucket {
for t := 0; t < len(q); t++ {
nums[i] = bucket[p][t]
i++
}
}
return nums
}
桶排序的时间复杂度为O(n) ,是一种线性复杂度的排序算法。
10 基数排序
先来举个例子看一下该算法的排序思路。
假设待排序数组为:
1、我们按照个位将待排序数组分组。
然后按照索引大小取出得到新的数组。
2、我们按照十位将待排序数组分组。
然后按照索引大小取出得到新的数组。
3、我们按照百位将待排序数组分组。
然后按照索引大小取出得到新的数组。
总结一下,基数排序的原理是:依次按照个位、十位、百位...的顺序对待排序数组分组,然后将每一次的分组按照索引大小重新组成新的数组。最后一轮的新数组即为排好序的数组。
基数排序需要注意的是,
1)要先知道数组最大值的位数,假设为n,那么就需要n次分组;
2)下面的算法只能对全为正整数的数组排序。
func BaseSort(nums []int) []int {
numsLen := len(nums)
if numsLen < 2 {
return nums
}
// 找最大值
max := nums[0]
for i := 1; i < numsLen; i++ {
if max < nums[i] {
max = nums[i]
}
}
// 计算最大值的位数
maxDigit := 0
for max > 0 {
max /= 10
maxDigit++
}
// 定义一个桶,分别存储每一位对应的数组中的数
bucket := [10][]int{}
for i := 0; i < 10; i++ {
// 为了防止每一位都一样所以将每个桶的长度设为最大,与原数组大小相同
bucket[i] = make([]int, numsLen)
}
// 定义一个计数数组,计算当前每一位对应有多少个数
count := [10]int{0}
// 定义一个除数
divisor := 1
// 定义一个位数
var digit int
for i := 1; i <= maxDigit; i++ {
// 分组
for j := 0; j < numsLen; j++ {
temp := nums[j]
digit = (temp / divisor) % 10
bucket[digit][count[digit]] = temp
count[digit]++
}
// 重排数组
k := 0
for m := 0; m < 10; m++ {
if count[m] == 0 {
continue
}
for n := 0; n < count[m]; n++ {
nums[k] = bucket[m][n]
k++
}
count[m] = 0
}
divisor *= 10
}
return nums
}
该算法的时间复杂度为O(m*n) ,m为数组长度,n为数组最大值位数。
以上就是对十大常见的排序算法的总结。
除了仔细阅读本文,最重要的是,一边看文章,一边也要动手把代码敲起来;如果你对排序算法不是那么熟悉,最好每天都拿出一点时间敲一遍,毕竟这也算是程序员的一件趁手兵器。
好啦,今天的分享就到这里。
我是程序员王不错,如果文章内容有帮助到你,就点个“关注”吧~
转载自:https://juejin.cn/post/7205839782381944892