十大排序算法梳理及Python,Golang语言实现【动态图】

阅读 5

2022-02-16

〇、汇总

排序算法原理平均最坏空间稳定性
冒泡排序相邻元素交换,大的交换到后面【你行你上】O(n²)O(n²)O(1)稳定
选择排序未排序选择最大的到后面 【最菜的前面去】----
插入排序一个记录插入已排好序的表中 【扑克牌理牌】----
快速排序分治法,找基准 【大化小,根据标准明确自己位置】O(nlogn)O(n²)O(nlogn)不稳定
归并排序分治法,两个有序组合并后是有序【小到大在于合】O(nlogn)O(nlogn)O(n)稳定
堆排序利用最大堆,依次弹出到末尾 【堆的性质】O(nlogn)O(nlogn)O(1)不稳定
希尔排序分组的插入排序【缩小增量排序】O(n^1.3)O(1)不稳定
计数排序数作为数组的下标 【空间换时间】一个数一个桶O(n)稳定
桶排序计数排序改进,一段范围一个桶
基数排序计数排序改进,一个键值数字一个桶

平均 快速排序效率最高 但是最坏下不如堆排序和归并排序

A. 冒泡排序(你行你上,不行别xxx)

重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。重复地进行直到没有再需要交换。大数会像“泡泡”一样慢慢的浮到后面

图解

分阶段状态

loop: 0  list:  [5, 6, 8, 2, 7, 3, 4, 1, **9**]
loop: 1  list:  [5, 6, 2, 7, 3, 4, 1, **8, 9**]
loop: 2  list:  [5, 2, 6, 3, 4, 1, **7, 8, 9**]
loop: 3  list:  [2, 5, 3, 4, 1, **6, 7, 8, 9**]
loop: 4  list:  [2, 3, 4, 1, **5, 6, 7, 8, 9**]
loop: 5  list:  [2, 3, 1, **4, 5, 6, 7, 8, 9**]
loop: 6  list:  [2, 1, **3, 4, 5, 6, 7, 8, 9**]
loop: 7  list:  [1, **2, 3, 4, 5, 6, 7, 8, 9**]

Python语言实现

def BubbleSort(list):
    for i in range(len(list)-1):              # 外圈两两对比要对比n-1次
        for j in range(len(list)-i-1):        # 两两对比
            if list[j]>list[j+1]:             # 顺序不对则交换
                list[j],list[j+1] = list[j+1],list[j]
    return list

Golang 语言实现

func BubbleSort(list []int) []int {
  for i := 0; i < len(list)-1; i++ {             // 外循环n-1次
    for j := 0; j < len(list)-1-i; j++ {
      if list[j] > list[j+1] {                   // 两两对比
        list[j], list[j+1] = list[j+1], list[j]  // 交换
      }
    }
  }
  return list
}

B. 选择排序 (最菜的前面去)

在未排序的序列中找出最小(大)元素与第一个位置的元素交换位置。

图解

分阶段状态

loop: 0  list:  [**1**, 9, 6, 8, 2, 7, 3, 4, 5]
loop: 1  list:  [**1, 2**, 6, 8, 9, 7, 3, 4, 5]
loop: 2  list:  [**1, 2, 3**, 8, 9, 7, 6, 4, 5]
loop: 3  list:  [**1, 2, 3, 4**, 9, 7, 6, 8, 5]
loop: 4  list:  [**1, 2, 3, 4, 5**, 7, 6, 8, 9]
loop: 5  list:  [**1, 2, 3, 4, 5, 6,** 7, 8, 9]
loop: 6  list:  [**1, 2, 3, 4, 5, 6, 7,** 8, 9]
loop: 7  list:  [**1, 2, 3, 4, 5, 6, 7, 8**, 9]

Python语言实现

def SelectionSort(list):        
    for i in range(len(list)-1):              # 外圈两两对比要对比n-1次
        minIndex=i
        for j in range(i+1,len(list)):        # 记住最小一个的位置
            if list[j]<list[minIndex]:
                minIndex=j
        list[i],list[minIndex] = list[minIndex],list[i] #交换
    return list

Golang语言实现

func SelectionSort(list []int) []int {
  for i := 0; i < len(list)-1; i++ {                  // 外循环n-1次
    minIndex := i
    for j := i + 1; j < len(list); j++ {              // 找出最小的
      if list[minIndex] > list[j] {
        minIndex = j
      }
    }
    list[minIndex], list[i] = list[i], list[minIndex] //交换
  }
  return list
}

C. 插入排序(扑克牌理牌)

通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

图解

分阶段状态

loop: 1  list:  [5, **9**, 6, 8, 2, 7, 3, 4, 1]
loop: 2  list:  [5, **6**, 9, 8, 2, 7, 3, 4, 1]
loop: 3  list:  [5, 6, **8**, 9, 2, 7, 3, 4, 1]
loop: 4  list:  [2, **5**, 6, 8, 9, 7, 3, 4, 1]
loop: 5  list:  [2, 5, 6, **7**, 8, 9, 3, 4, 1]
loop: 6  list:  [2, **3**, 5, 6, 7, 8, 9, 4, 1]
loop: 7  list:  [2, 3, **4**, 5, 6, 7, 8, 9, 1]
loop: 8  list:  [**1**, 2, 3, 4, 5, 6, 7, 8, 9]

Python语言实现

def InsertionSort(list):
    for i in range(1,len(list)):                # 外圈未排序n-1次
        j=i
        peakOne = list[i]
        while peakOne<list[j-1] and j>0:        # 寻找插入位置并后移
            list[j] = list[j-1]
            j-=1
        list[j]=peakOne                         # 插入
    return list

Golang语言实现

func InsertionSort(list []int) []int {

  for i := 1; i < len(list); i++ {         // 外循环n-1次
    j := i
    peakValue := list[i]
    for j > 0 && peakValue < list[j-1] {  //寻找插入位置并后移
      list[j] = list[j-1]
      j--
    }
    list[j] = peakValue                   //插入
  }

  return list
}

D. 快速排序 (大化小,按照基准站位)

  1. 从数列中挑出一个元素,称为 “基准”(pivot);
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

图解

Python语言实现

def QuickSort(list):
    def swap(list,i,j):
        list[i],list[j]=list[j],list[i]

    def partition(list,left,right):            # 分治法  划分子串
        base = list[left]                      # 基准取左边第一个
        swapIndex = left+1                     # 用于交换的位置

        for i in range(left,right+1):          # 遍历列表 找到小的放在前面
            if list[i]<base:
                swap(list,swapIndex,i)
                swapIndex += 1
        
        swap(list,left,swapIndex-1)            # 将基准插入中间    
        return swapIndex-1 
    
    def quicksort(list,left,right):            # 递归函数
        if left >= right: return               # 退出条件
        mid=partition(list,left,right)         # 自己先排序一次
        quicksort(list,left,mid-1)             # 排序左边
        quicksort(list,mid+1,right)            # 排序右边

    quicksort(list,0,len(list)-1)              # 调用递归函数
    return list
        

Golang 语言实现

func swap(list []int, i, j int) {
  list[i], list[j] = list[j], list[i]
}

func partion(list []int, left, right int) int {
  base := list[left]                            // 以最左边一个为基准
  sindex := left + 1                            // 交换插入的位置

  for i := left; i <= right; i++ {              // 遍历一遍 将小的转换到前面
    if list[i] < base {
      swap(list, sindex, i)
      sindex++
    }
  }

  swap(list, left, sindex-1)                    //将基准插入中间
  return sindex - 1
}

func quicksort(list []int, left, right int) {   // 递归函数
  if left >= right {
    return                                      // 退出条件
  }
  mid := partion(list, left, right)             // 划分成两个
  quicksort(list, left, mid-1)                  // 排序左边
  quicksort(list, mid+1, right)                 // 排序右边
}

func QuickSort(list []int) []int {
  quicksort(list, 0, len(list)-1)               //开始递归
  return list
}

E.归并排序 (小并大,保持有序)

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
  4. 重复步骤 3 直到某一指针达到序列尾;
  5. 将另一序列剩下的所有元素直接复制到合并序列尾。

归并排序vs快速排序

在这里插入图片描述

  • 归并排序:重在,将数列递归分解成只有一个元素。核心的算法是合并函数 merge:将两个有序数组合并后仍然有序。merge 函数决定了归并排序的空间复杂度和稳定性。
  • 快读排序:重在,任意选择一个元素作为分区占,分为小于,等于,大于三部分,然后依次对小于和大于部分递归排序。核心的算法是分区函数 partition:将数列分为左中右三部分。partition 函数同样决定了快速排序的空间复杂度和稳定性。

图解

分阶段状态

[5]                  + [9]                 ---> [5, 9]
[6]                  + [8]                 ---> [6, 8]
[5, 9]               + [6, 8]              ---> [5, 6, 8, 9]
[2]                  + [7]                 ---> [2, 7]
[4]                  + [1]                 ---> [1, 4]
[3]                  + [1, 4]              ---> [1, 3, 4]
[2, 7]               + [1, 3, 4]           ---> [1, 2, 3, 4, 7]
[5, 6, 8, 9]         + [1, 2, 3, 4, 7]     ---> [1, 2, 3, 4, 5, 6, 7, 8, 9]

Python语言实现

def merge(left,right):                     # 合并左右两个
    result=[]

    while len(left)>0 and len(right)>0:    # 按照大小插入
        if left[0]<right[0]:
            result.append(left.pop(0))    
        else:
            result.append(right.pop(0))
    while len(left)>0:
        result.append(left.pop(0)) 
    while len(right)>0:
        result.append(right.pop(0)) 
    return result


def MergeSort(list):                        # 递归函数
    if len(list)<2:
        return list                         # 退出条件
    import math
    mid = math.floor(len(list)/2)           # 分割点
    left = list[0:mid]
    right = list[mid:]
    return merge(MergeSort(left), MergeSort(right))

Golang语言实现

func merge(left, right []int) []int {
  result := make([]int, len(left)+len(right)) //缓存数组

  mergeIndex := 0
  leftIndex := 0
  rightIndex := 0
  //按照大小插入
  for leftIndex < len(left) && rightIndex < len(right) {
    if left[leftIndex] < right[rightIndex] {
      result[mergeIndex] = left[leftIndex]
      leftIndex++
      mergeIndex++
    } else {
      result[mergeIndex] = right[rightIndex]
      rightIndex++
      mergeIndex++
    }
  }

  for leftIndex < len(left) {
    result[mergeIndex] = left[leftIndex]
    leftIndex++
    mergeIndex++
  }

  for rightIndex < len(right) {
    result[mergeIndex] = right[rightIndex]
    rightIndex++
    mergeIndex++
  }

  return result
}

func MergeSort(list []int) []int {
  if len(list) < 2 { 
    return list                              // 退出条件
     }
  var mid int = len(list) / 2                // 划分为一半
  return merge(MergeSort(list[0:mid]), MergeSort(list[mid:]))
}

F. 堆排序(堆的性质)

  • 借助堆来实现的选择排序
  • 建堆——>将根节点与最后一个交换 输出——>重建
  • 升序用大顶堆
  • 降序用小顶堆

图解

分阶段状态

init:     [9, 8, 7, 5, 2, 6, 3, 4, 1]
loop: 8   [8, 5, 7, 4, 2, 6, 3, 1, **9**]
loop: 7   [7, 5, 6, 4, 2, 1, 3, **8, 9**]
loop: 6   [6, 5, 3, 4, 2, 1, **7, 8, 9**]
loop: 5   [5, 4, 3, 1, 2, **6, 7, 8, 9**]
loop: 4   [4, 2, 3, 1, **5, 6, 7, 8, 9**]
loop: 3   [3, 2, 1, **4, 5, 6, 7, 8, 9**]
loop: 2   [2, 1,** 3, 4, 5, 6, 7, 8, 9**]
loop: 1   [1, **2, 3, 4, 5, 6, 7, 8, 9**]

Python 语言实现

def HeapSort(list):
    def down(list,i,len):                          
        while 2*i+1<=len:
            l=2*i+1
            r=2*i+2 if 2*i+2<len else len
            maxChild=max(list[l],list[r])      # 最小的孩子
            if maxChild<list[i]:               # 已经满足最大堆性质
                return 
            k = l if list[l]==maxChild else r  # 最大孩子位置
            list[k],list[i]=list[i],list[k]    # 交换父子节点
            i=k

    #1. 建立初最大堆 
    for i in range(math.ceil(len(list)-1/2),-1,-1): # 将每个父节点下沉
        down(list,i,len(list)-1)
    
    #2. 依次取顶点放入到后面
    for j in range(len(list)-1,0,-1):
        list[j],list[0] =list[0],list[j]            # 交换首尾
        down(list,0,j-1)
        print("loop:",j," ", list)
    return list

Golang实现

// 节点下沉
func heapDown(list []int, i, len int) {
  for 2*i+1 <= len {
    left := 2*i + 1 // 左右子节点
    right := 2*i + 2
    if right > len {
      right = len // 注意溢出
    }

    var maxIndex int // 寻找最大的子节点
    if list[left] > list[right] {
      maxIndex = left
    } else {
      maxIndex = right
    }

    if list[maxIndex] < list[i] {
      return //已经满足最大堆性质
    } else {
      list[i], list[maxIndex] = list[maxIndex], list[i]
      i = maxIndex
    }
  }
}

func HeapSort(list []int) []int {

  //1. 建立初始的最大堆
  for i := len(list) / 2; i >= 0; i-- { //最后一个父节点开始
    heapDown(list, i, len(list)-1)
  }

  //2. 依次取堆顶到后面
  for i := len(list) - 1; i > 0; i-- {
    list[i], list[0] = list[0], list[i] //放在最后
    heapDown(list, 0, i-1)              //调整堆
  }

  return list
}

G. 希尔排序 (插入排序改进版)

  • 也叫缩小增量排序
  • 分割成若干子序列进行直接插入排序 相隔某个增量
  • 基本有序后对整体进行插入排序

图解

分阶段状态

gap:  4   [**1**, 7, 3, 4, **2**, 9, 6, 8, **5**]
gap:  2   [1, 4, 2, 7, 3, 8, 5, 9, 6]
gap:  1   [1, 2, 3, 4, 5, 6, 7, 8, 9]

Python语言实现

def ShellSort(list):
    #希尔插入
    def shellInsert(list,s,gap):
        for i in range(s,len(list),gap):
            temp = list[i]
            j=i-gap
            while j>=0 and list[j]>temp:               #以gap为间隔插入
                list[j],list[j+gap]=list[j+gap],list[j]
                j-=gap
            list[j+gap]=temp

    gap=int(math.floor(len(list)/2))                  #逐步缩小gap

    while gap>0:                 
        for i in range(gap):
            shellInsert(list,i,gap)
        gap=int(gap/2) 
    return list

Golang语言实现

// 希尔插入 以gap为间隔,i为起点
func shellInsert(list []int, i, gap int) {
  for ; i < len(list); i += gap {
    temp := list[i]
    j := i - gap
    for j >= 0 && temp < list[j] {
      swap(list, j, j+gap)
      j -= gap
    }
    list[j+gap] = temp
  }
}

// 希尔排序
func ShellSort(list []int) []int {

  for gap := len(list) / 2; gap > 0; gap /= 2 {
    for i := 0; i < gap; i++ {
      shellInsert(list, i, gap)
    }
  }
  return list

}
*//一次排序 增量为d*voidShellInsert(int* a, int d,int len){
    int i;
    for(int i=d;i<len-1;i++){
        int j=i-d;
        int temp=a[i];*//移动的量*while(j>=0&&a[j]>temp){
            a[j+d]=a[j];*//后移 重复*
            j-=d;
        }
        
        if(j!=i-d){
            a[j+d]=temp;*//交换*
        }
    }
}

*//希尔排序*voidShellSort(int* a,int len){
    
    int d=len/2;*//一开始 间隔为len/2**//间隔缩小*while(d>=1){
        ShellInsert(a,d,len);
        d/=2;
    }
}

H. 计数排序 (空间换时间)

  • O(n)

  • 前提条件 带排序的数满足一定的范围

  • 需要空间

  • 数作为数组的下标

图解

Python语言实现

def CountSort(list):
    maxValue=max(list)                                # 最大的数

    Counter =[0 for _ in range(maxValue+1) ]          # 计数器

    for i in list:                                   # 遍历一次计数 
        Counter[i]+=1
    
    result=[]                                        # 根据计数输出
    for i in range(len(Counter)): 
        for j in range(Counter[i]):
            result.append(i)
    return result

Golang语言实现

// 计数排序
func CountSort(list []int) []int {

  // 获得最大值
  max := list[0]
  for i := range list {
    if list[i] > max {
      max = list[i]
    }
  }

  // 容器
  counter := make([]int, max+1)

  // 遍历一次统计
  for i := range list {
    value := list[i]
    counter[value]++
  }

  // 取出排序
  index := 0
  for i := range counter {
    for j := 0; j < counter[i]; j++ {
      list[index] = i
      index++
    }
  }

  return list

}

拓展:桶排序

桶排序是计数排序的扩展版本,计数排序可以看成每个桶只存储相同元素,而桶排序每个桶存储一定范围的元素,通过映射函数,将待排序数组中的元素映射到各个对应的桶中,对每个桶中的元素进行排序,最后将非空桶中的元素逐个放入原序列中。

拓展:基数排序

将整数按位数切割成不同的数字,然后按每个位数分别比较。

精彩评论(0)

0 0 举报