Go语言实现几种常见的排序算法

前面已经介绍golang基本的语法和容器了,这一篇文章用golang实现四种排序算法,快速排序,插入排序,选择排序,冒泡排序。既可以总结前文的基础知识,又可以熟悉下golang如何实现这四种排序。

快速排序

算法介绍:
假设用户输入了如下数组:

下标 | 0 | 1 | 2 | 3 | 4 | 5 |
数值 | 6 | 2 | 7 | 3 | 8 | 9 |

创建变量i=0(指向第一个数据), j=5(指向最后一个数据), k=6(赋值为第一个数据的值)
我们要把所有比k小的数移动到k的左面,所以我们可以开始寻找比6小的数,从j开始,从右往左找,不断递减变量j的值,我们找到第一个下标3的数据比6小,于是把数据3移到下标0的位置,把下标0的数据6移到下标3,完成第一次比较

下标 | 0 | 1 | 2 | 3 | 4 | 5 |
数值 | 3 | 2 | 7 | 6 | 8 | 9 |

i=0 j=3 k=6
接着,开始第二次比较,这次要变成找比k大的了,而且要从前往后找了。递加变量i,发现下标2的数据是第一个比k大的,于是用下标2的数据7和j指向的下标3的数据的6做交换,数据状态变成下表:

下标 | 0 | 1 | 2 | 3 | 4 | 5 |
数值 | 3 | 2 | 6 | 7 | 8 | 9 |

i=2,j=3,k=6
接下来继续重复上边的步骤,从j开始,从右往左找比k小的,此时i=2,j=3,j再移动一位就和i相等了,此时就完成了k=6的排序,此时k=6,i和j相等都为2,6右边的数都比6大,6左边的数比6小。
接下来分别比较6左边的序列(下标从0到1)和6右边(下标从3到5)的序列,同样采用上述办法,直到所有序列都比较完成。

算法实现:

//思路分析:
/*
快速排序算法采用的分治算法,因此对一个子数组A[p…r]进行快速排序的三个步骤为:

  (1)分解:数组A[p...r]被划分为两个(可能为空)子数组A[p...q-1]和A[q+1...r],给定一个枢轴,使得A[p...q-1]中的每个元素小于等于A[q],A[q+1...r]中的每个元素大于等于A[q],q下标是在划分过程中计算得出的。

  (2)解决:通过递归调用快速排序,对子数组A[p...q-1]和A[q+1...r]进行排序。

  (3)合并:因为两个子数组是就地排序,不需要合并操作,整个数组A[p…r]排序完成。

  快速排序关键过程是对数组进行划分,划分过程需要选择一个主元素(pivot element)作为参照,围绕着这个主元素进划分子数组。举个列说明如何划分数组,现有子数组A={3, 7, 9, 8, 38, 93, 12, 222, 45, 93, 23, 84, 65, 2},以最后一个元素为主元素进行划分,划分过程如图所示:
*/
package main

import "fmt"

func quickSort(arr []int, start, end int) {
    if start < end {
        i, j := start, end
        key := arr[(start+end)/2]
        for i <= j {
            for arr[i] < key {
                i++
            }
            for arr[j] > key {
                j--
            }
            if i <= j {
                arr[i], arr[j] = arr[j], arr[i]
                i++
                j--
            }
        }

        if start < j {
            quickSort(arr, start, j)
        }
        if end > i {
            quickSort(arr, i, end)
        }
    }
}

func main() {
    arr := []int{3, 7, 9, 8, 38, 93, 12, 222, 45, 93, 23, 84, 65, 2}
    quickSort(arr, 0, len(arr)-1)
    fmt.Println(arr)
}

上述算法时间复杂度达到O(nlogn)

插入排序

算法描述
假设用户输入了如下数组

下标 | 0 | 1 | 2 | 3 | 4 | 5 |
数值 | 6 | 2 | 7 | 3 | 8 | 9 |

假设从小到大排序
插入排序先从下标为1的元素2开始,比较前边下标为0的元素6,2比6小,则将6移动到2的位置,2放到6的位置

下标 | 0 | 1 | 2 | 3 | 4 | 5 |
数值 | 2 | 6 | 7 | 3 | 8 | 9 |

记下来比较下标为2的元素7,和前边0~1下标的元素对比,从后往前找,如果找到比7大的元素,则将该元素后边的序列依次后移,将7插入该元素位置
目前7不需要移动。
接下来寻找下标为3 的元素3,从下标3往前找,由于下标1,下标2的元素都比3大,所以依次后移,将3放倒下标1的位置。

下标 | 0 | 1 | 2 | 3 | 4 | 5 |
数值 | 2 | 3 | 6 | 7 | 8 | 9 |

以此类推,进行比较。

算法实现

该算法时间复杂度为O(n*n)

冒泡排序

算法描述
假设用户输入了如下数组

下标 | 0 | 1 | 2 | 3 | 4 | 5 |
数值 | 6 | 2 | 7 | 3 | 8 | 9 |

冒泡排序依次比较相邻的两个元素 ,将大的元素后移即可。
先比较下标为0和下标为1的元素,6比2大,所以6和2交换位置。

下标 | 0 | 1 | 2 | 3 | 4 | 5 |
数值 | 2 | 6 | 7 | 3 | 8 | 9 |

接下来比较下标为1和下标为2的元素,6比7小所以不做交换。然后比较7和3,7比3大,7和三交换位置,以此类推,直到比较到最后一个元素

下标 | 0 | 1 | 2 | 3 | 4 | 5 |
数值 | 2 | 6 | 3 | 7 | 8 | 9 |

经过这一轮相邻元素的比较,将最大的元素9冒泡到最后的位置。
接下来重复上述步骤,从下标0开始到下标4两两比较,将第二大元素放到下标4的位置,因为下标5已经是最大元素,所以不参与比较。

算法实现



package main

import (
    "fmt"
)

/*
插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,比如量级小于千,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)
*/
// 插入排序
func insert_sort(a []int){
    for i:=1;i< len(a);i++{// 类似抓扑克牌排序
        j := i-1  // 拿在左手上的牌总是排序好的,i-1对应的数据是已排序的最大值
        get := a[i] // 右手抓到一张扑克牌

        for j >= 0 && a[j] > get{// 将抓到的牌与左手牌从右向左进行比较
            a[j+1] = a[j] //右移大的牌
            j--
        }
        a[j+1] = get// 直到该手牌比抓到的牌小(或二者相等),将抓到的牌插入到该手牌右边
                    //(相等元素的相对次序未变,所以插入排序是稳定的)
    }

}



/*
当n较大时,二分插入排序的比较次数比直接插入排序的最差情况好得多,但比直接插入排序的最好情况要差,所当以元素初始序列已经接近升序时,直接插入排序比二分插入排序比较次数少。二分插入排序元素移动次数与直接插入排序相同,依赖于元素初始序列。
*/

//采用二分查找法来减少比较操作的次数,我们称为二分插入排序
func half_insert_sort(a []int){
    for i:=1;i < len(a);i++{
        get := a[i]
        left := 0
        right := i - 1

        for left <= right {// 采用二分法定位新牌的位置
            mid := (left + right)/2
            if a[mid] > get {
                right = mid - 1
            }else{
                left = mid + 1//left的位置就是新牌要插入的位置
            }
        }
        for j := i-1;j>=left;j--{
            a[j+1] = a[j]
        }
        a[left] = get// 将抓到的牌插入手牌
    }
}
// 希尔排序
func shell_sort(a []int){
    //step := len(a)/3 + 1

    step := 0           // 生成初始增量,这里寻找初始增量很重要
    for step <= len(a){
        step = 3*step +1
    }

    for step > 0{
        for i:=step;i< len(a);i++{// 类似抓扑克牌排序

            j := i-step  // 拿在左手上的牌总是排序好的,i-1对应的数据是已排序的最大值
            get := a[i] // 右手抓到一张扑克牌
            for j >= 0 && a[j] > get{// 将抓到的牌与左手牌从右向左进行比较
                a[j+step] = a[j] //右移大的牌
                j = j - step
            }
            a[j+step] = get// 直到该手牌比抓到的牌小(或二者相等),将抓到的牌插入到该手牌右边
                        //(相等元素的相对次序未变,所以插入排序是稳定的)
        }
        fmt.Println(step)
        step = (step-1)/3 // 递减增量

    }
}

func main(){

    c := [...]int{8,7,6,1,4,3,2,512365628}
    insert_sort(c[:])
    fmt.Println(c)

    b := [...]int{8,7,6,1,4,3,2,512365628}
    half_insert_sort(b[:])
    fmt.Println(b)

    d := [...]int{8,7,6,1,4,3,2,512365628}
    shell_sort(d[:])
    fmt.Println(d)
}

选择排序

算法描述
假设用户输入了如下数组

下标 | 0 | 1 | 2 | 3 | 4 | 5 |
数值 | 6 | 2 | 7 | 3 | 8 | 9 |

从下标0开始,比较6和其他位置的元素,找到最小的元素2和6交换位置

下标 | 0 | 1 | 2 | 3 | 4 | 5 |
数值 | 2 | 6 | 7 | 3 | 8 | 9 |

接下来从下标1开始,比较6和后边位置的元素,选择最小的和6交换位置。

下标 | 0 | 1 | 2 | 3 | 4 | 5 |
数值 | 2 | 3 | 7 | 6 | 8 | 9 |

以此类推,从下标2开始,比较7和后边的元素,选择最小的6交换位置

下标 | 0 | 1 | 2 | 3 | 4 | 5 |
数值 | 2 | 3 | 6 | 7 | 8 | 9 |

以此类推,直到下标5的位置元素都比较完。

算法实现

该算法时间复杂度为o(n*n)

// 思路分析:
/*选择排序是不稳定的排序算法,不稳定发生在最小元素与A[i]交换的时刻。
比如序列:{ 8,7,6,1,4,3,2,5,12,36,56,28 },一次选择的最小元素是2,然后把2和第一个5进行交换,从而改变了两个元素5的相对次序。*/

package main

import(
    "fmt"
)


//选出最大的排到最后面
func select_sort(a []int){

    index := 0
    for i := 0; i < len(a)-1; i++{//外层循环(冒泡和选择排序)是从0到len-2
        index = len(a) - 1 - i//默认每次排序完最大元素的下标是未排序的最后一个
                            //(要保证最后一次比较是a[0]和a[1]比较,所以外层循环最多到len-2)
                            //len(a) - 1 - i也就是未排序序列的末尾
        for j := 0; j < len(a) - 1 - i; j++{//内层循环针对未排序的序列,默认最后部分是排序的,所以最后一个元素是len-2
            if a[j] > a[index] {//记录未排序序列中最大值的下标
                index = j//更新下标
            }
        }

        if index != len(a) - 1 - i{//将未排序序列中的最大值放到未排序序列最后的位置
            a[index],a[len(a)-1-i] = a[len(a)-1-i],a[index]
        }
    }
}


//选出最小值放到序列前端
func select_sort2(a []int){
    min := 0//最小值下标
    for i := 0; i < len(a) - 1;i++{// i为已排序序列的末尾
        min = i
        for j := i+1;j < len(a);j++{// 未排序序列
            if a[j] < a[min]{// 找出未排序序列中的最小值
                min = j
            }
        } 

        if min != i{// 放到已排序序列的末尾,该操作很有可能把稳定性打乱,所以选择排序是不稳定的排序算法
            a[min], a[i] = a[i],a[min]
        }
    }
}

func main(){
    b := [...]int{8,7,6,1,4,3,2,5}
    select_sort(b[:])
    fmt.Println(b)

    c := [...]int{8,7,6,1,4,3,2,5}
    select_sort2(c[:])
    fmt.Println(c)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值