Classic Sorting Algorithms

3 minute read

Published:

This is my personal notes for classic sorting algorithms, including BubbleSort, SelectionSort, InsertionSort, MergeSort, QuickSort.

Overall Introduction

经典排序算法十分重要,也是基础,笔者整理并用Python实现了几大经典排序算法,包括冒泡排序,选择排序,插入排序,归并排序,快速排序, 计数排序, 基数排序, 堆排序。

本篇博客所有排序实现均默认从小到大。

BubbleSort

  • 介绍:
    冒泡排序的原理非常简单,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。

  • 步骤:
    • 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
    • 对第0个到第n-1个数据做同样的工作。这时,最大的数就“浮”到了数组最后的位置上。
    • 针对所有的元素重复以上的步骤,除了最后一个。
    • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
  • python实现
def bubble_sort(array):
    flag=0
    for i in range(len(array)-1):
        for j in range(len(array)-i-1):
            if array[j]>array[j+1]:
                array[j],array[j+1]=array[j+1],array[j]
                flag=1
        if flag==0:
            break
    return array

注: flag==0表示某一趟已经没有进行交换操作,数组全部有序,调出循环。

SelectionSort

  • 介绍:
    选择排序的原理非常简单,每一趟将未排序序列中的最小(大)值放到已排序序列的末尾。

  • 步骤:
    • 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
    • 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
    • 以此类推,直到所有元素均排序完毕。
  • python实现
def select_sort(array):
    for i in range(len(array)):
        min=i
        for j in range(i+1,len(array)):
            if array[j]<array[min]:
                min=j
        array[i],array[min]=array[min],array[i]
    return array

InsertionSort

  • 介绍:
    插入排序的工作原理是,对于每个未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

  • 步骤:
    • 从第一个元素开始,该元素可以认为已经被排序
    • 取出下一个元素,在已经排序的元素序列中从后向前扫描
    • 如果被扫描的元素(已排序)大于新元素,将该元素后移一位
    • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
    • 将新元素插入到该位置后
    • 重复步骤2~5
  • python实现
def insert_sort(array):
    for i in range(1,len(array)):
        if array[i]<array[i-1]:
            temp=array[i]
            for j in range(i-1,-1,-1):
                if array[j]>temp:
                    array[j+1]=array[j]
                    index=j
                else:
                    break
            array[index]=temp
    return array

MergeSort

  • 介绍:
    归并排序是采用分治法的一个非常典型的应用。归并排序的思想就是先递归分解数组,再合并数组。

  • 步骤:
    先考虑合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。
    再考虑递归分解,基本思路是将数组分解成left和right,如果这两个数组内部数据是有序的,那么就可以用上面合并数组的方法将这两个数组合并排序。如何让这两个数组内部是有序的?可以再二分,直至分解出的小组只含有一个元素时为止,此时认为该小组内部已有序。然后合并排序相邻两个小组即可。

  • python实现

def merge(array1,array2):
    new_array=[]
    i,j=0,0
    while i<len(array1) and j<len(array2):
        if array1[i]<array2[j]:
            new_array.append(array1[i])
            i+=1
        else:
            new_array.append(array2[j])
            j+=1
    new_array+=array1[i:]
    new_array+=array2[j:]
    return new_array
def mergsort(array):
    if len(array)<2:
        return array
    mid=len(array)//2
    left=mergsort(array[:mid])
    right=mergsort(array[mid:])
    return merge(left,right)

QuickSort

  • 介绍:
    快速排序通常明显比同为Ο(n log n)的其他算法更快,因此常被采用,而且快排采用了分治法的思想,所以在很多笔试面试中能经常看到快排的影子。可见掌握快排的重要性。

  • 步骤:
    • 从数列中挑出一个元素作为基准数(最左边或右边)。
    • 分区过程,将比基准数大的放到右边,小于或等于它的数都放到左边。
    • 再对左右区间递归执行第二步,直至各区间只有一个数。
  • python实现

# (1) 左右两个指针相向而走

def partition(array,low, high):
    left=low
    key=array[low]
    while low<high:
        while low<high and array[high]>=key:
            high-=1
        while low<high and array[low]<=key:
            low+=1
        array[low], array[high] = array[high], array[low]
    array[low],array[left]=array[left],array[low]
    return low

def quick_sort(array,low,high):
    if low<high:
        key=partition(array,low,high)
        quick_sort(array,low,key-1)
        quick_sort(array,key+1,high)

 # (2) 两个指针同时从左边走 (算法导论)

def partition(List,begin,end):
    i=begin-1
    for j in range(begin,end):
        if List[j]<List[end]:
            i=i+1
            List[i],List[j]=List[j],List[i]
    List[i+1],List[end]=List[end],List[i+1]
    return i+1

def quick_sort(List,begin,end):
    if begin<end:
        begin_next=partition(List,begin,end)
        quick_sort(List,begin,begin_next-1)
        quick_sort(List,begin_next+1,end)

CoutingSort

  • 介绍:
    计数排序非comparison based sort, 可以做到O(n+k),其中n是数字个数,k是数据的范围(maxN),其适用条件是输入数字不明显大于数字个数. 比如[10, 5, 10k, 5k] 便不能用coutingsort。空间复杂度为O(n+k)。

  • 步骤:
    • 构造count数组,长度为maxN+1,统计nums中各个数字的个数。
    • 将count进行累加, 得到sumCount。
    • 对于nums中的每个数,其位置即为sumCount中的个数,赋值完后将sumCount减1。
  • python实现
def countingsort(nums):
    maxN=max(nums)
    count=[0]*(maxN+1)
    ans=[0]*len(nums)
    for n in nums:
        count[n]+=1
    for i in range(1,maxN+1):
        count[i]+=count[i-1]
    for j in range(len(nums)):
        ans[count[nums[j]]-1]=nums[j]
        count[nums[j]]-=1
    return ans

RadixSort

  • 介绍:
    基数排序非comparison based sort, 时间复杂度可以做到O(d(n+k)),其中d趟数,n是数字个数 (d=b/r, b=log(base)(range),r=log(base)(size),k=2^r)。空间复杂度和计数排序一样,为O(n+k)。基数排序对每位数进行排序时是调用的counting sort,所以b==r时,基数排序就是计数排序。

  • 步骤:
    • 求最长数字的位数d,即为外层循环次数。
    • 在每次循环中,根据当前位的数字大小(0-9)将各个数字放到不同的桶中,相同的保持原来顺序不变。
    • 从桶中数字按顺序取出,重构nums。
  • python实现
def radixsort(nums):
    N=len(nums)
    d=len(str(max(nums)))
    for i in range(d):
        new=[[] for _ in range(10)]
        for j in range(N):
            new[(nums[j]//(10**i))%10].append(nums[j])
        nums=[n for m in new for n in m]
    return nums

HeapSort

  • 介绍:
    堆排序的关键是创建最大/最小堆的过程,时间复杂度为 O(nlogn),其中创建堆的过程为 O(logn)。

  • 步骤:
    • 对数组创建堆。
    • 将堆顶元素和最后一个交换,将剩余元素heapify。
    • 直至只剩下一个元素。
  • python实现
def heapify(arr, n, i):
    largest = i  # Initialize largest as root
    l = 2 * i + 1  # left = 2*i + 1
    r = 2 * i + 2  # right = 2*i + 2

    # See if left child of root exists and is
    # greater than root
    if l < n and arr[i] < arr[l]:
        largest = l

    # See if right child of root exists and is
    # greater than root
    if r < n and arr[largest] < arr[r]:
        largest = r

        # Change root, if needed
    if largest != i:
        arr[i], arr[largest] = arr[largest], arr[i]  # swap

        # Heapify the root.
        heapify(arr, n, largest)

    # The main function to sort an array of given size


def heapSort(arr):
    n = len(arr)

    # Build a maxheap.
    for i in range(n, -1, -1):
        heapify(arr, n, i)

        # One by one extract elements
    for i in range(n - 1, 0, -1):
        arr[i], arr[0] = arr[0], arr[i]  # swap
        heapify(arr, i, 0)

Comparison

MethodOverallBestWrostSpaceStability
BubbleO(n^2)O(n)O(n^2)O(1)Stable
SelectionO(n^2)O(n^2)O(n^2)O(1)Unstable
InsertionO(n^2)O(n)O(n^2)O(1)Stable
MergeO(nlogn)O(nlogn)O(nlogn)O(n)Stable
QuickO(nlogn)O(nlogn)O(n^2)O(nlogn)~O(n)Unstable
CountingO(n+k)O(n+k)O(n+k)O(n+k)stable
RadixO(d(n+k))O(d(n+k))O(d(n+k))O(n+k)stable
HeapO(nlogn)O(nlogn)O(nlogn)O(1)Unstable