经典排序算法 | 青训营笔记

70 阅读2分钟

经典排序算法

本文简单记录一下后端课程中讲到的几种经典排序算法,包括插入排序、快速排序、堆排序三种算法。

Insertion Sort 插入排序

插入排序简单来说就是将元素插入到已经排序好的数组或者序列当中。这是一种简单直观的排序算法,对于未排序的数据,在已经排序好的序列中从后往前扫描,找到相应的位置并将其插入。

算法复杂度分析:

  • 平均时间复杂度:O(n^2)
  • 最坏时间复杂度:O(n^2)
  • 最优时间复杂度:O(n)
  • 空间复杂度:总共O(n),需要辅助空间O(1)

从一个例子分析:打扑克牌时从牌桌上逐一拿起一张牌,在手上排序

  • 输入:{5,2,4,6,1,3}
  • 拿起第一张牌:手上有{5}
  • 拿起第二张牌,将2插入到手里的牌中,得到{2,5}
  • 拿起第三张牌,将4插入到手里的牌中,得到{2,4,5}
  • 以此类推……

C语言实现例子:

void insertion_sort(int arr[], int len){
    int i,j,key;
    for(i=1;i!=len;i++){
        key=arr[i];
        while(j>=0 && arr[j]>key){
            arr[j+1] = arr[j];
            j--
        }
        arr[j+1] = key;
    }
}

go语言实现例子:

func insertionSort(arr []int) []int {
    for i := 1; i < len(arr); i++ {
        for j := i; j > 0 && arr[j] < arr[j-1]; j-- {
            arr[j], arr[j-1] = arr[j-1], arr[j]
        }
    }
    return arr
}

Quick Sort 快速排序

快速排序也叫分区交换排序,是一种基于分治思想的算法,不断分割序列直到序列整体有序。整体来说包括下列的两个步骤:

  • 选定一个轴点povit
  • 使用povit分割序列,分成元素比povit大和元素比povit小的两个序列
  • 递归排序子序列:递归的将小于povit元素的子序列和大于povit元素的子序列进行排序

算法复杂度分析:

  • 平均时间复杂度:O(nlogn)
  • 最坏时间复杂度:O(n^2)
  • 最优时间复杂度:O(nlogn)
  • 空间复杂度:根据实现的方式的不同而不同

以下是一个在原地进行序列分割的快速排序的例子:

img

C语言实现:

void quick_sort(int *arr, int left, int right) {
    if (left >= right) {
        return;
    }
    int i = left;
    int j = right;
    int pivot = arr[left];
    while (i < j) {
        while (i < j && arr[j] >= pivot) {
            j--;
        }
        arr[i] = arr[j];
        while (i < j && arr[i] <= pivot) {
            i++;
        }
        arr[j] = arr[i];
    }
    arr[i] = pivot;
    quick_sort(arr, left, i - 1);
    quick_sort(arr, i + 1, right);
}

go语言实现:

func quickSort(arr []int) []int {
    if len(arr) <= 1 {
        return arr
    }
    pivot := arr[0]
    var left, right []int
    for i := 1; i < len(arr); i++ {
        if arr[i] < pivot {
            left = append(left, arr[i])
        } else {
            right = append(right, arr[i])
        }
    }
    left = quickSort(left)
    right = quickSort(right)
    return append(append(left, pivot), right...)
}

Heap Sort 堆排序

堆排序即是利用堆的性质实现的一个排序算法,在介绍堆排序之前先简单介绍一下堆这种数据结构。

堆是一种特殊的数据结构,是最高效的优先级队列。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。

算法复杂度分析:

  • 平均时间复杂度:O(nlogn)
  • 最坏时间复杂度:O(nlogn)
  • 最优时间复杂度:O(nlogn)
  • 空间复杂度:总共O(n),需要辅助空间O(1)

go语言实现:

func heapify(arr []int, n, i int) {     // 构建最大堆(大根堆)
    largest := i
    left := 2*i + 1
    right := 2*i + 2
​
    if left < n && arr[left] > arr[largest] {
        largest = left
    }
​
    if right < n && arr[right] > arr[largest] {
        largest = right
    }
​
    if largest != i {
        arr[i], arr[largest] = arr[largest], arr[i]
        heapify(arr, n, largest)
    }
}
​
func heapSort(arr []int) {
    n := len(arr)
​
    for i := n/2 - 1; i >= 0; i-- {
        heapify(arr, n, i)
    }
​
    for i := n - 1; i > 0; i-- {
        arr[0], arr[i] = arr[i], arr[0]
        heapify(arr, i, 0)
    }
}
​

C语言实现:

void heapify(int arr[], int n, int i) {
    int largest = i;
    int left = 2 * i + 1;
    int right = 2 * i + 2;
​
    if (left < n && arr[left] > arr[largest]) {
        largest = left;
    }
​
    if (right < n && arr[right] > arr[largest]) {
        largest = right;
    }
​
    if (largest != i) {
        int temp = arr[i];
        arr[i] = arr[largest];
        arr[largest] = temp;
        heapify(arr, n, largest);
    }
}
​
void heapSort(int arr[], int n) {
    for (int i = n / 2 - 1; i >= 0; i--) {
        heapify(arr, n, i);
    }
​
    for (int i = n - 1; i > 0; i--) {
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;
        heapify(arr, i, 0);
    }
}