首先弄清楚有哪十种:
然后下文尽量使用简单语言来帮助理解,并使用Go语言来实现。 下面给定的数组都默认为[9,2,5,7,3,1,8,1,3,9]含十个元素的数组
1.冒泡排序
冒泡排序应该是大多数人接触的第一种排序算法。 其是一种简单的排序算法,其基本思想是通过重复地交换相邻的元素来将待排序的元素按照顺序逐步"冒泡"到正确的位置。
就按从小到大进行排序
具体步骤如下:
- 从待排序的列表的第一个元素开始,依次比较相邻的两个元素的大小。
- 如果前一个元素大于后一个元素,就交换这两个元素的位置,将较大的元素向后移动。
- 继续对列表中的每一对相邻元素进行比较和交换操作,直到最后一个元素。 重复上述步骤,每次都将待排序的元素"冒泡"到正确的位置,直到整个列表排序完成。
定义数组arr := []int{9, 2, 5, 7, 3, 1, 8, 1, 3, 9}
只实现函数部分
scss
func maopao(arr []int) {
//应该传入参数--数组.返回的参数也是数组
for i := 0; i < len(arr)-1; i++ {
for j := i + 1; j < len(arr); j++ {
if arr[i] > arr[j] {
arr[i],arr[j] = arr[j],arr[i]
}
}
}
}
2. 选择排序
选择排序是一种简单直观的排序算法,其基本思想是每次从待排序的元素中选择最小(或最大)的元素,并将其放置在已排序部分的末尾。
具体步骤如下:
- 首先,在待排序的列表中找到最小(或最大)的元素,记为最小元素。
- 将最小元素与列表的第一个元素进行交换,将最小元素放置在已排序部分的末尾。
- 接下来,在剩余的未排序部分中找到最小(或最大)的元素,再次进行交换,将它放置在已排序部分的末尾。
重复上述步骤,每次从剩余的未排序部分中选择最小(或最大)的元素,并将其放置在已排序部分的末尾,直到整个列表排序完成。
选择排序的核心思想是不断地选择最小(或最大)的元素,逐步构建有序序列。
css
func xuanze(arr []int) {
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
}
}
arr[i], arr[min] = arr[min], arr[i]
}
}
个人感觉写下来比冒泡排序好在,他是把整个数组中的最小值给遍历出来以后,在进行位置的变换。不需要像冒泡排序那样频繁的进行交换。
但是在稳定性方面,选择排序是不稳定的排序算法,即相同元素的相对位置可能会发生变化。而冒泡排序是稳定的排序算法,相同元素的相对位置不会改变。如果需要保持相同元素的相对顺序,冒泡排序可能更适合。
3.插入排序
插入排序是一种简单直观的排序算法,其基本思想是将待排序元素依次插入到已排序序列中的合适位置,最终得到一个有序序列。
具体步骤如下:
- 将待排序元素分成已排序部分和未排序部分。
- 从未排序部分依次取出一个元素,将它插入到已排序部分的合适位置,使已排序部分仍然保持有序。
- 重复第二步,直到未排序部分为空,所有元素都被插入到已排序部分中。
插入排序的核心思想是:将每个元素插入到已排序序列中的合适位置,逐渐构建有序序列。在执行过程中,插入排序可以通过比较和移动元素位置来实现插入操作。
插入排序的时间复杂度为O(n^2),其中n是待排序列表的长度。在实际应用中,对于小规模数据的排序任务,插入排序的性能通常比较高效,因为它只需要常数级的额外空间,并且对于几乎有序的列表,插入排序的表现非常出色。
css
func charu(arr []int) {
for i := 1; i < len(arr); i++ {
tmp := arr[i]
j := i - 1
//将比tmp大的往右移动
for j >= 0 && arr[j] > tmp {
arr[j+1] = arr[j]
j = j - 1
}
arr[j+1] = tmp //将tmp插入正确的位置
}
}
大概思路就是,先把初始的i设为1,然后对i之前的数进行比较选择其合适大小的地方插入。i进行累加。时间复杂度还是挺高的是n^2
4.希尔排序
希尔排序(Shell Sort)是插入排序的一种改进版本,也被称为"缩小增量排序"。它的主要思想是通过比较距离较远的元素,逐步减小这个距离,从而使得数组中的元素能够更快地接近它们最终的排序位置。
希尔排序的基本步骤如下:
- 选择一个增量序列,通常为 n/2,n/4,n/8,... 直到增量为1。
- 对每个增量,使用插入排序对子数组进行排序。
- 逐渐减小增量,重复步骤2,直到增量为1。
希尔排序的关键在于选择适当的增量序列,不同的增量序列可能导致不同的性能。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
- 插入算法在对几乎已经排好序的数据操作时,效率高,即可达线性排序效率
- 但插入排序一般来说是低效的,因为每次只能将数据移动一位
一句话总结就是:两个两个换位置,将整个序列变成基本排好序的(可以有效改变插入排序只能一个一个更改位置的问题)
css
func xier(arr []int) {
//选择增量序列
for gap := len(arr) / 2; gap > 0; gap /= 2 {
for i := gap; i < len(arr); i++ {
tmp := arr[i]
j := i
//将比tmp大的往右移动
for j >= gap && arr[j-gap] > tmp {
arr[j] = arr[j-gap]
j = j - gap
}
arr[j] = tmp //将tmp插入正确的位置
}
}
}
5.归并排序
归并排序是一种经典的排序算法,它采用分治的思想来进行排序。它的基本思想是将待排序的序列不断地拆分成较小的子序列,直到每个子序列只有一个元素,然后再将这些子序列两两合并并排序,最终得到完全排序的序列。
归并排序的实现步骤如下:
- 将待排序的序列不断拆分成两个较小的子序列,直到每个子序列只有一个元素。
- 逐层合并相邻的子序列,并按照从小到大的顺序进行排序。合并时需要额外的辅助空间来存储合并后的结果。
- 重复步骤2,直到所有的子序列合并成一个完整的有序序列。
sql
func guibing(arr []int) []int {
if len(arr) <= 1 {
return arr
}
mid := len(arr) / 2
left := guibing(arr[:mid])
right := guibing(arr[mid:])
return merge(left, right)
}
func merge(left, right []int) []int {
result := make([]int, 0)
i, j := 0, 0
for i < len(left) && j < len(right) {
if left[i] < right[j] {
result = append(result, left[i])
i++
} else {
result = append(result, right[j])
j++
}
}
result = append(result, left[i:]...)
result = append(result, right[j:]...)
return result
}
result = append(result, left[i:]...)
是 Go 语言中的切片操作语法。它的作用是将切片 left[i:] 中的所有元素追加到切片 result 的末尾。
在归并排序的 merge 函数中,我们使用这个语法来将剩余未合并的元素直接添加到结果切片中。left[i:] 表示从索引 i 开始到切片末尾的所有元素,... 表示将切片展开成单个元素。通过将这些元素追加到 result 切片中,我们可以保持合并后的序列的有序性。
例如,假设 left 切片为 [1, 3, 5],right 切片为 [2, 4, 6],在合并过程中,当比较完 [1, 3, 5] 和 [2, 4, 6] 的元素后,left 中还剩下一个元素 5。为了保证有序性,我们需要将 5 追加到 result 中,这就可以通过 result = append(result, left[i:]...) 实现。使用俩个append是因为不确定左右两边最大的值在哪。
这种切片操作语法可以方便地处理切片之间的拼接和追加操作,使代码更加简洁和易读。
6.快速排序
快速排序的关键在于分区操作,通过不断地将元素移到基准值的左右两侧,最终能够将序列分割成若干个子序列,使得基准值左边的元素都小于基准值,右边的元素都大于基准值。这样,在递归排序时,就可以保证基准值的位置是正确的,最终得到完全有序的序列。
快速排序是一种常用的排序算法,其可以总结为以下几步:
-
选择一个基准值(pivot):从待排序序列中选择一个元素作为基准值。一般设第一个的多
-
分区(Partition):将序列中比基准值小的元素放到基准值的左边,比基准值大的元素放到右边。经过这一步操作之后,基准值将位于最终排序序列的正确位置。
-
递归排序:对基准值左右两侧的子序列分别重复上述步骤,直到每个子序列只剩下一个元素为止。
-
合并结果:将所有子序列的结果合并起来,即得到最终排好序的序列。
css
func kuaipai(arr []int, left, right int) int { //先实现分区
private := arr[left] //先选第一个为特定元素
//然后分成俩部分,一部分大一部分小
for left < right {
for left < right && arr[right] <= private {
right--
}
arr[left] = arr[right]
for left < right && arr[left] < private {
left++
}
arr[right] = arr[left]
}
arr[left] = private
return left
}
func Quick(arr []int, left, right int) {
if left < right {
private := sort(arr, left, right)
Quick(arr, left, private-1)
Quick(arr, private+1, right)
}
}
7.堆排序
堆排序是一种基于二叉堆数据结构的排序算法,其基本思想可以总结为以下几步:
-
构建最大堆(Max Heap):将待排序序列构建成一个最大堆,即满足父节点的值大于等于其子节点的值。
-
堆排序:不断地将堆顶元素(最大值)与堆的最后一个元素交换,并对剩余元素重新进行最大堆调整。这样,每次交换后,最大值就会被放置在正确的位置上。
-
重复执行步骤2,直到所有元素都被排序完毕。
代码实现如下
scss
// 调整堆,将以rootIndex为根节点的子树调整为最大堆
func maxHeapify(arr []int, n int, rootIndex int) {
largest := rootIndex // 假设根节点最大
left := 2*rootIndex + 1 // 左子节点
right := 2*rootIndex + 2 // 右子节点
// 找出左右子节点和根节点中的最大值
if left < n && arr[left] > arr[largest] {
largest = left
}
if right < n && arr[right] > arr[largest] {
largest = right
}
// 如果最大值不是根节点,则交换根节点和最大值,并继续调整子树
if largest != rootIndex {
arr[rootIndex], arr[largest] = arr[largest], arr[rootIndex]
maxHeapify(arr, n, largest)
}
}
// 堆排序
func dui(arr []int) {
n := len(arr)
// 构建最大堆
for i := n/2 - 1; i >= 0; i-- {
maxHeapify(arr, n, i)
}
// 交换堆顶元素和最后一个元素,然后重新调整堆
for i := n - 1; i > 0; i-- {
arr[0], arr[i] = arr[i], arr[0]
maxHeapify(arr, i, 0)
}
}
8.计数排序
计数排序是一种非比较性的排序算法,适用于待排序元素的取值范围比较小的情况。其基本思想可以概括如下:
- 找出待排序序列中的最大值,以确定计数数组的长度。
- 遍历待排序序列,统计每个元素出现的次数,将统计结果存储在计数数组中。
- 根据计数数组中的统计信息,重新生成有序的序列。
go
func jishu(arr []int, maxValue int) []int {
n := len(arr)
output := make([]int, n)
// 初始化计数数组并统计每个元素出现的次数
count := make([]int, maxValue+1)
for i := 0; i < n; i++ {
count[arr[i]]++
}
// 对计数数组进行累加,得到每个元素在排序后的序列中的位置
for i := 1; i <= maxValue; i++ {
count[i] += count[i-1]
}
// 根据计数数组的信息,将元素放置到输出数组中
for i := n - 1; i >= 0; i-- {
output[count[arr[i]]-1] = arr[i]
count[arr[i]]--
}
return output
}
func getMax(arr []int) (max int) {
max = arr[0]
for _, v := range arr {
if max < v {
max = v
}
}
return
}
8.桶排序
桶排序是一种分布式排序算法,它将待排序的元素分到不同的桶中,每个桶分别进行排序,然后将桶中的元素按顺序合并得到最终的有序序列。桶排序的基本思想如下:
- 确定桶的数量和范围,将待排序的元素均匀地分到这些桶中。
- 对每个桶中的元素进行排序,可以使用其他排序算法如插入排序、快速排序等。
- 将各个桶中的元素按顺序依次合并,得到最终的有序序列。
go
func tong(arr []int) {
n := len(arr)
if n <= 1 {
return
}
// 找到最大值
maxVal := arr[0]
for i := 1; i < n; i++ {
if arr[i] > maxVal {
maxVal = arr[i]
}
}
// 确定桶的数量
numOfBuckets := maxVal + 1
buckets := make([][]int, numOfBuckets)
// 将元素分配到桶中
for i := 0; i < n; i++ {
index := arr[i]
buckets[index] = append(buckets[index], arr[i])
}
// 合并桶中的元素
index := 0
for i := 0; i < numOfBuckets; i++ {
for j := 0; j < len(buckets[i]); j++ {
arr[index] = buckets[i][j]
index++
}
}
}
10.基数排序
基数排序是一种非比较型整数排序算法,它的基本思想是将待排序的整数按照位数进行分解,然后依次对每一位进行桶排序。这个过程需要使用多轮桶排序,从最低位到最高位,直到所有位都排好序。
css
func radixSort(arr []int) {
n := len(arr)
if n <= 1 {
return
}
// 获取最大值
max := arr[0]
for _, val := range arr {
if val > max {
max = val
}
}
// 对每一位进行计数排序
for exp := 1; max/exp > 0; exp *= 10 {
output := make([]int, n)
count := [10]int{}
// 统计每个数字出现的次数
for i := 0; i < n; i++ {
count[(arr[i]/exp)%10]++
}
// 将count[i]更新为包含小于等于i的元素个数
for i := 1; i < 10; i++ {
count[i] += count[i-1]
}
// 根据count数组将元素放到输出数组中
for i := n - 1; i >= 0; i-- {
output[count[(arr[i]/exp)%10]-1] = arr[i]
count[(arr[i]/exp)%10]--
}
// 将输出数组复制到原始数组
for i := 0; i < n; i++ {
arr[i] = output[i]
}
}
}