常用排序算法面试准备

1.冒泡排序

算法思想:顾名思义,升序排序,冒泡排序每一趟会把最小的值排到最前面,像泡泡一样浮上来,代码如下(golang),算法复杂度为o(n2)

func bubbleSort(l []int) {

    for i := 0; i < len(l); i++ {

        for j := len(l) - 1; j > i; j-- {

            if l[j] < l[j-1] {
                tmp := l[j]
                l[j] = l[j-1]
                l[j-1] = tmp 
            }
        }

    }   

}

2.快速排序

算法思想:快排是运用了分治的思想,选择一个基准值(通常为数组的第一个值),将数组分成两部分,左边部分是由值比基准值小的数组成,右边部分是由值比基准值大得数组成,这样大的颗粒度上数组是有序的,然后最左右部分再使用之间的分组方法,直到最终分组的长度为1,层层返回组成有序的。代码如下(golang),算法复杂度平均为O(nlog2n)

func quickSort(l []int, low, high int) {

    if low >= high {
        return
    }   

    start := low 
    end := high
    ref := l[low]

    for low < high {

        for low < high && l[high] > ref {
            high--
        }

        if low < high {
            l[low] = l[high]
        }

        for low < high && l[low] <= ref {
            low++
        }

        if low < high {
            l[high] = l[low]
        }

    }   

    l[low] = ref 
    quickSort(l, start, low-1)
    quickSort(l, low + 1, end)
}

3.插入排序

算法思想:插入排序通常设想将一个数插入到一个已经排好序的数组中,只需要把这个数与排好序的元素相比,如果小于数组元素就交换,代码如下(golang),插入排序通常适合数组有序,数据量不大的场景

func insertSort(l []int) {
    for i := 1; i< len(l); i++ {
        for j := i; j > 0; j-- {
            if l[j] < l[j-1] {
                tmp := l[j]
                l[j] = l[j-1]
                l[j-1] = tmp 
            }
        }
    }   
}

4.希尔排序

算法思想:希尔排序是插入排序的升级,希尔排序是将数组按照一定增量进行分组,每个分组使用插入排序,知道分组增量为1结束

func shellSort(l []int) {
    for gap := len(l)/2; gap > 0; gap /= 2 { 
        for i := gap; i < len(l); i++ {
            for j := i; j >= gap; j -= gap {
                if l[j] < l[j-gap] {
                    tmp := l[j]
                    l[j] = l[j-gap]
                    l[j-gap] = tmp 
                }
            }
        }
    }   
}

5.选择排序

算法思想:选择排序是每次遍历找到最i小的数放在当前第i的位置

func selectSort(l []int) {
    for i := 0; i < len(l); i++ {
        min := i
        for j := i; j < len(l); j++ {
            if l[j] < l[min] {
                min = j 
            }
        }
        tmp := l[i]
        l[i] = l[min]
        l[min] = tmp 
    }   
}

6.堆排序

算法思想:堆排序是利用了堆的结构特点,堆是一个完全二叉树,有大顶堆和小顶堆两种堆,大顶堆是指所有父节点都不小于其左右子节点,小顶堆是指父节点不大于其左右子节点。以大顶堆为例,根据堆的这种特性可以知道,大顶堆的根节点是这些节点当中数值最大的。堆可以使用一维数组标识,数组下标为i的左右子节点下标left=2i+1,right=2i+2。有了这些基础知识,使用堆排序,首先先把数组组建成一个二叉树,让二叉树满足堆的特性,从下至上不断调整节点位置,形成堆,组成的堆的顶点是这些节点中最大的,把该值与最后的元素交换,将剩下的节点重复组成堆,重复之前的步骤,知道节点个数为1。

func heapSort(l []int) {
    n := len(l)
    for i := n; i > 0; i-- {
        for j := i/2; j > 0; j-- {
            top := j - 1
            left := 2*j - 1
            right := 2*j
            if left < i && l[top] < l[left] {
                top = left
            }
            if right < i && l[top] < l[right] {
                top = right
            }

            fmt.Println(top, left, right, j-1)

            if top != j - 1 {
                tmp := l[j-1]
                l[j-1] = l[top]
                l[top] = tmp
            }
            fmt.Println(l)
        }
        tmp := l[i-1]
        l[i-1] = l[0]
        l[0] = tmp
    }
}

7.归并排序

算法思想:归并排序采用了分治的算法,将数组二分,对每个分组再次使用归并排序,然后每层将排好序的两组合并为一个有序数组返回,这种是递归实现;迭代实现思路:先将数组按最小粒度分组,即一个元素一组,相邻组来两两合并,所以初始跨度应该为2,往后依次乘2,需要额外处理剩下跨度不足的部分,代码如下

func mergeSort(l []int) {
    for gap := 2; gap < len(l); gap *= 2 { 
        i := 0;
        for ; i + gap -1 < len(l); i++ {
            merge(l, i, i+gap/2-1, i + gap -1) 
        }

        if i + gap/2 -1 < len(l) {
            merge(l, i, i+gap/2-1, len(l)-1)
        }
    }   
}

func merge(l []int, low, mid, high int) {
    sortL := make([]int, high - low + 1)
    len1 := mid - low + 1 
    len2 := high - mid 
    var i, j int 

    for i < len1 && j < len2 {
        if l[low + i] < l[mid + j + 1] {
            sortL[i + j] = l[low + i]
            i++
        } else {
            sortL[i + j] = l[mid + j + 1]
            j++
        }

    }

    if i < len1 {
        for i < len1 {
            sortL[i+j] = l[low + i]
            i++
        }
    }

    if j < len2 {
        for j < len2 {
            sortL[i+j] = l[mid+j + 1]
            j++
        }
    }

    for k, v := range sortL {
        l[low + k] = v
    }
}

                                                                                                                                                      212,1         98%

8.计数排序

算法思想:对于数值正在一定范围内的数列,可以将数值映射到一个新的数组上,这个新的数组下标与数组值一一mapping,新的数组的值代表的是这个该位置出现在原数组的次数,这样的话,只需要遍历新的数组按照顺序输出的就是有序的数组,次数代表重复填充的个数;可以在前面的基础上再进行优化,新数组的值不是简单的统计次数,而是累计他前面所有的次数,这样的话,按照原数组的顺序遍历,找的该位置的值就是排序的次序,每次找到一次需要次值减去一,表示拿出一个数,代码如下

func countSort(l []int) []int {
    var min int = l[0]
    var max int = l[0]
    for _, v := range l { 
        if min > v { 
            min = v 
        }

        if max < v { 
            max = v 
        }
    }   

    rangeL := make([]int, max-min+1)

    for _, v := range l { 
        rangeL[v-min]++
    }


    var sum int 
    for k, v := range rangeL {
        sum += v
        rangeL[k] = sum 
    }

    l2 := make([]int, len(l))
    for _, v := range l { 
        l2[rangeL[v-min]-1] = v 
        rangeL[v-min]--
    }   
    
    return l2
}

 

9.桶排序

算法思想:桶排序是计数排序的升级,计数排序将需要排序的数列值一一映射到一个数组空间上,而桶排序是将需要排序的数列指的某一范围映射到一个桶,即一个桶对应的可能是多个待排序数,这样在大的力度上是有序的,对每个桶可以采用排序算法进行排序,这个算法可以是其他排序

10.基数排序

 算法思想:基数排序也有痛的概念,基数排序分LSD(低位优先算法)和MSD(高位优先算法);举例:LSD是从低的位数开始比较,10进制,会划分10个桶,范围0-9,先按个位数进行分组,继续在按十位数进行分组,直到所有高位都是0,集合都在桶为0的组,代码如下

func radixSort(l []int) {
    mod := 10
    level := 1
    bucketList := make([][]int, mod)
    for len(bucketList[0]) < len(l) {
        bucketList = make([][]int, mod)
        for _, v := range l {
            index := (v/level)%mod
            bucketList[index] = append(bucketList[index], v)
        }
        level *= 10
        //mod *= 10
        var i int
        for _, bucket := range bucketList {
            for _, v := range bucket {
                l[i] = v
                i++
            }
        }
    }
}

 

posted @ 2020-09-21 19:11  新手小博  阅读(145)  评论(0)    收藏  举报