冒泡排序

冒泡排序(Bubble Sort)是一种简单直观的排序算法。

这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

时间复杂度:$O(n^2)$

算法步骤

假设一个序列长度为n,m(m≤n)是已排序完成的在末尾的数。

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。

  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。对比结束后,最后的元素会是最大的数。

  3. 对接下来n-m个未排序的数重复步骤1和2,直到没有任何一对数字需要比较。

    第一趟对序列中所有n个数进行比对,第二趟对序列中n-1个未排序完成的数进行比对,以此类推。每次比对的数为n-m。

动画演示:

代码实现

Python实现

def bubbleSort(arr):
    for i in range(len(arr)-1):
        for j in range(len(arr)-1-i):
            if arr[j] > arr[j+1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
    return arr

if __name__ == '__main__':
    arr = [ 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 ]
    bubbleSort(arr)
    print(arr)

C实现

#include <stdio.h>

void bubbleSort(int arr[], int len)
{
    for (int i = 0; i < len - 1; i++)
        for (int j = 0; j < len - 1 - i; j++)
            if (arr[j] > arr[j + 1])
            {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
}

int main(void)
{
    int arr[] = { 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = (int)sizeof(arr) / sizeof(*arr);
    bubbleSort(arr, len);
    for (int i = 0; i < len; i++)
        printf("%d ", arr[i]);
    return 0;
}

优化算法

有序标志

冒泡排序还有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来说并没有什么太大作用。

  • Python实现:

    def bubbleSort(arr):
        for i in range(len(arr)-1):
            # 有序flag,初始为True
            isSorted = True
            for j in range(len(arr)-1-i):
                if arr[j] > arr[j+1]:
                    arr[j], arr[j + 1] = arr[j + 1], arr[j]
                    # 发生交换,序列定为无序
                    isSorted = False
            # 序列有序,停止循环
            if isSorted:
                break
        return arr
    
    if __name__ == '__main__':
        arr = [ 
            22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 ]
        bubbleSort(arr)
        print(arr)
    
  • C实现:

    #include <stdio.h>
    #include <stdbool.h>
    
    void bubbleSort(int arr[], int len)
    {
        for (int i = 0; i < len - 1; i++)
        {
            // 有序flag,初始为True
            bool isSorted = true;
            for (int j = 0; j < len - 1 - i; j++)
                if (arr[j] > arr[j + 1])
                {
                    arr[j] = arr[j] ^ arr[j+1];
                    arr[j+1] = arr[j] ^ arr[j+1];
                    arr[j] = arr[j] ^ arr[j+1];
                    // 发生交换,序列定为无序
                    isSorted = false;
                }
            // 序列有序,停止循环
            if (isSorted)
                break;
        }
    }
    
    int main(void)
    {
        int arr[] = { 
            22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
        int len = (int)sizeof(arr) / sizeof(*arr);
        bubbleSort(arr, len);
        for (int i = 0; i < len; i++)
            printf("%d ", arr[i]);
        return 0;
    }
    

数列有序区

通常,有序区的长度和排序的轮数是相等的。如,第一轮排序后的有序区长度是1,第二轮排序后是2 ……

但实际上,数列的有序区长度可能会大于这个长度,并且每轮增加的有序区长度也不一定仅为1。

该优化算法解决了这个问题,通过在每一轮排序的最后,记录下最后一次元素交换的位置(该位置也就是无序数列的边界,再往后就是有序区),而每轮的交换只要交换到无序区边界。

  • Python实现:

    def bubbleSort(arr):
        # 无序数列的边界
        sortBorder = len(arr) - 1
        for i in range(len(arr)-1):
            # 有序flag,初始为True
            isSorted = True
            # 每次仅需交换到无序区边界
            for j in range(sortBorder):
                if arr[j] > arr[j+1]:
                    arr[j], arr[j + 1] = arr[j + 1], arr[j]
                    # 把无序数列的边界更新为最后一次交换元素的位置
                    sortBorder = j
                    # 发生交换,序列定为无序
                    isSorted = False
            # 序列有序,停止循环
            if isSorted:
                break
        return arr
    
    if __name__ == '__main__':
        arr = [ 
            22, 5, 50, 3, 32, 37, 34, 35, 9, 55, 64, 70, 82, 89 ]
        bubbleSort(arr)
        print(arr)
    
  • C实现:

    #include <stdio.h>
    #include <stdbool.h>
    
    void bubbleSort(int arr[], int len)
    {
        int sortBorder = len - 1;
        for (int i = 0; i < len - 1; i++)
        {
            // 有序flag,初始为True
            bool isSorted = true;
            for (int j = 0; j < len - 1 - i; j++)
                if (arr[j] > arr[j + 1])
                {
                    arr[j] = arr[j] ^ arr[j+1];
                    arr[j+1] = arr[j] ^ arr[j+1];
                    arr[j] = arr[j] ^ arr[j+1];
                    // 把无序数列的边界更新为最后一次交换元素的位置
                    sortBorder = j;
                    // 发生交换,序列定为无序
                    isSorted = false;
                }
            // 序列有序,停止循环
            if (isSorted)
                break;
        }
    }
    
    int main(void)
    {
        int arr[] = { 
            22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
        int len = (int)sizeof(arr) / sizeof(*arr);
        bubbleSort(arr, len);
        for (int i = 0; i < len; i++)
            printf("%d ", arr[i]);
        return 0;
    }
    

选择排序

选择排序(Selection Sort)是一种简单直观的排序算法,无论什么数据进去都是 $O(n²)$ 的时间复杂度。

算法步骤

  1. 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
  2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
  3. 重复步骤2,直到所有元素均排序完毕。

动画演示:

代码实现

Python实现

def selectionSort(arr):
    for i in range(len(arr) - 1):
        # 记录最小数的索引
        minIndex = i
        for j in range(i + 1, len(arr)):
            if arr[j] < arr[minIndex]:
                minIndex = j
        # i 不是最小数时,将 i 和最小数进行交换
        if i != minIndex:
            arr[i], arr[minIndex] = arr[minIndex], arr[i]
    return arr

if __name__ == '__main__':
    arr = [ 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 ]
    arr = selectionSort(arr)
    print(arr)

C实现

#include <stdio.h>

void selectionSort(int arr[], int len)
{
    for (int i = 0 ; i < len - 1 ; i++)
    {
        int min = i;
        for (int j = i + 1; j < len; j++)     // 走访未排序的元素
            // 找到最小值
            if (arr[j] < arr[min])
                min = j;
        // i 不是最小数时,将 i 和最小数进行交换
        if (i != min)
        {
            int tmp = arr[i];
            arr[i] = arr[min];
            arr[min] = tmp; 
        }
    }
}

int main(void)
{
    int arr[] = { 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = (int)sizeof(arr) / sizeof(*arr);
    selectionSort(arr, len);
    for (int i = 0; i < len; i++)
        printf("%d ", arr[i]);
    return 0;
}

插入排序

插入排序(Insertion Sort)是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

插入排序的有一种优化算法,叫做拆半插入。

算法步骤

假设序列的长度为$n$,其待排序序列第一个元素的位置为$m$($1 \le m \le n$,元素位置从0开始)。

  1. 将元素$m$与已排序序列中的每个元素进行比较。如果已排序元素比元素$m$大,将元素$m$中比已排序序列大的元素往后移,直到前面没有比元素$m$大的元素(或前面已经没有元素)。将元素m插入。

    如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。

  2. 从头到尾依次扫描未排序序列,直到没有未排序元素。

动画演示:

代码实现

Python实现

def insertionSort(arr):
    for i in range(len(arr) - 1):
        preIndex = i
        current = arr[i + 1]
        # 找出要插入的位置
        while preIndex >= 0 and arr[preIndex] > current:
            # 将比current大的元素往后移
            arr[preIndex + 1] = arr[preIndex]
            preIndex -= 1
        # 将current插入到适当的位置
        arr[preIndex + 1] = current
    return arr

if __name__ == '__main__':
    arr = [ 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 ]
    insertionSort(arr)
    print(arr)

C实现

#include <stdio.h>

void insertionSort(int arr[], int len)
{
    for (int i = 0 ; i < len - 1 ; i++)
    {
        int current = arr[i + 1];
        int j = i;
        while (j >= 0 && arr[j] > current)
        {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = current;
    }
}

int main(void)
{
    int arr[] = { 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = (int)sizeof(arr) / sizeof(*arr);
    insertionSort(arr, len);
    for (int i = 0; i < len; i++)
        printf("%d ", arr[i]);
    return 0;
}

希尔排序

算法步骤

希尔排序(Shell Sort),也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。

希尔排序的基本思想是:先将整个待排序的增量序列根据增量分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

算法步骤

  1. 选择一个增量序列$t_1,t_2,…,t_k$,其中$t_i < t_{i-1}(1 \le i \le k),t_k = 1$;
  2. 按增量序列个数$k$,对序列进行$k$趟排序;
  3. 每趟排序,根据对应的增量$t_i$,将待排序列分割成若干长度为$t_i$的子序列,分别对各子表进行直接插入排序。仅增量因子为$1$时,整个序列作为一个表来处理,表长度即为整个序列的长度。

动画演示:

详细过程(引用自博客:一个很懒的人):

代码实现

Python实现

def shellSort(arr):
    # 将增量d初始化为len(arr)//2
    d = len(arr) // 2
    while d > 0:
        # 分别用插入排序排序每个以d为增量的分组
        for i in range(d, len(arr)):
            tmp = arr[i]
            j = i - d
            while j >= 0 and tmp < arr[j]:
                arr[j + d] = arr[j]
                j -= d
            arr[j + d] = tmp
        # 每次将增量d缩小1/2
        d //= 2

    return arr

if __name__ == '__main__':
    arr = [ 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 ]
    shellSort(arr)
    print(arr)

C实现

#include <stdio.h>

void shellSort(int arr[], int len)
{
    // 将增量d初始化为len/2,每次将增量d缩小1/2
    for (int d = len >> 1; d > 0; d >>= 1)
    {
        // 分别用插入排序排序每个以d为增量的分组
        for (int i = d; i < len; i++)
        {
            int tmp = arr[i];
            int j;
            for (j = i - d; j >= 0 && tmp < arr[j]; j -= d)
                arr[j + d] = arr[j];
            arr[j + d] = tmp;
        }
    }
}

int main(void)
{
    int arr[] = { 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = (int)sizeof(arr) / sizeof(*arr);
    shellSort(arr, len);
    for (int i = 0; i < len; i++)
        printf("%d ", arr[i]);
    return 0;
}

归并排序

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:

  1. 自上而下的递归;
  2. 自下而上的迭代。

算法步骤

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

动画演示:

图片演示:

  1. 递归演示:

  2. 合并演示:

代码实现

Python 实现

def mergeSort(arr):
    # 结束递归
    if(len(arr) < 2):
        return arr
    # 计算中间位置下标
    middle = len(arr) // 2
    # 将序列切分为两半
    left, right = arr[:middle], arr[middle:]
    # 进行归并排序
    return merge(mergeSort(left), mergeSort(right))

def merge(left, right):
    result = []
    while left and right:
          # 将较小的元素放在前面
        if left[0] <= right[0]:
            result.append(left.pop(0))
        else:
            result.append(right.pop(0))
    # 将某一序列中剩下的元素全部放入
    while left:
        result.append(left.pop(0))
    while right:
        result.append(right.pop(0))
    return result

if __name__ == '__main__':
    arr = [ 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 ]
    arr = mergeSort(arr)
    print(arr)

C实现

非递归实现:

#include <stdio.h>
#include <stdlib.h>

int min(int x, int y)
{
    return x < y ? x : y;
}

void mergeSort(int arr[], int len)
{
    int* a = arr;
    int* b = (int*)malloc(len * sizeof(int));
    if (!b)
        return;
    // 分为约log_2(len)次
    for (int seg = 1; seg < len; seg += seg)
    {
        // 每次对下标区间为[low,high)的子序列进行归并排序
        for (int start = 0; start < len; start += seg * 2)
        {
            // 当前排序区间:[low,high)
            int low = start, mid = min(start + seg, len), 
                high = min(start + seg * 2, len);
            // 第1段:[low,mid)
            int start1 = low, end1 = mid;
            // 第2段:[mid,high)
            int start2 = mid, end2 = high;
            // 对子序列进行归并排序
            int i = low;
            // 将两个序列中较小的放在前面
            while (start1 < end1 && start2 < end2)
                b[i++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
            // 将某一序列中剩下的元素全部放入
            while (start1 < end1)
                b[i++] = a[start1++];
            while (start2 < end2)
                b[i++] = a[start2++];
        }
        int* tmp = a;
        a = b;
        b = tmp;
    }
    if (a != arr)
    {
        for (int i = 0; i < len; i++)
            b[i] = a[i];
        b = a;
    }
    free(b);
}

int main(void)
{
    int arr[] = { 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = (int) sizeof(arr) / sizeof(*arr);
    mergeSort(arr, len);
    for (int i = 0; i < len; i++)
        printf("%d ", arr[i]);
    return 0;
}

递归实现:

#include <stdio.h>
#include <stdlib.h>

void merge_sort_recursive(int arr[], int reg[], int start, int end)
{
    // 结束递归
    if (start >= end)
        return;
    // 当前排序区间:[start,end]
    int len = end - start, mid = (len >> 1) + start;
    // 第1段:[start,mid]
    int start1 = start, end1 = mid;
    // 第2段:[mid+1,end]
    int start2 = mid + 1, end2 = end;
    // 对第1段进行递归地排序
    merge_sort_recursive(arr, reg, start1, end1);
    // 对第2段进行递归地排序
    merge_sort_recursive(arr, reg, start2, end2);
    int i = start;
    while (start1 <= end1 && start2 <= end2)
        reg[i++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
    while (start1 <= end1)
        reg[i++] = arr[start1++];
    while (start2 <= end2)
        reg[i++] = arr[start2++];
    for (int i = start; i <= end; i++)
        arr[i] = reg[i];
}

void mergeSort(int arr[], int len)
{
    int* reg = (int*)malloc(len * sizeof(int));
    merge_sort_recursive(arr, reg, 0, len - 1);
}

int main(void)
{
    int arr[] = { 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = (int) sizeof(arr) / sizeof(*arr);
    mergeSort(arr, len);
    for (int i = 0; i < len; i++)
        printf("%d ", arr[i]);
    return 0;
}

快速排序

快速排序在平均状况下,排序$n$个项目是$O(n\log {n})$。最坏运行情况是$O(n^2)$,但这种状况并不常见,比如说数列为顺序数列的情况下。一般,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。

事实上,快速排序通常明显比其他$O(n\log {n})$算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。

本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。

算法步骤

  1. 从序列中选择一个元素作为“基准”(pivot)。
  2. 将所有比基准数小的放在基准左边,所有比基准数大的放在基准右边(相同的数可以在任一边)。这个称为分区(partition)操作。
  3. 分区完成后,该基准就会归到序列中的相应位置,该位置是排序完成后的位置。
  4. 分别递归地把小于基准数的子序列(左边)和大于基准数的子序列(右边)执行1~3操作。

动画演示:

代码实现

Python实现

def swap(arr, i, j):
    arr[i], arr[j] = arr[j], arr[i]

def partition(arr, left, right):
    # 设置基准
    pivot = left
    # 索引从基准的下一个元素开始
    index = pivot + 1
    # 遍历:[index,right]
    for i in range(index, right + 1):
        # 将小于基准值的元素全部放到左边
        if arr[i] < arr[pivot]:
            swap(arr, i, index)
            index += 1
        i += 1
    # 将基准归位
    # 此时index位置上的是比基准值大的元素
    # 或者等于right+1
    # 即1<=index<=right+1
    # 只有index-1才是基准真正的位置
    swap(arr, pivot, index - 1)
    return index - 1

def quickSort(arr, left=None, right=None):
    # 设定参数值
    left = 0 if not isinstance(left, (int, float)) else left
    right = len(arr) - 1 if not \
        isinstance(right, (int, float)) else right
    if left < right:
        # 先进行“治”操作并取得分区索引
        partitionIndex = partition(arr, left, right)
        # 分别对左右两个分区递归地进行快速排序
        quickSort(arr, left, partitionIndex - 1)
        quickSort(arr, partitionIndex + 1, right)
    return arr

if __name__ == '__main__':
    arr = [ 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 ]
    quickSort(arr)
    print(arr)

C实现

递归实现:

#include <stdio.h>

void quick_sort_recursive(int arr[], int start, int end)
{
    if (start >= end)
        return;
    // 设置基准
    int pivot = arr[start];
    int left = start, right = end;
    while (left < right)
    {
        // 先从右边起找出比基准小的
        while (arr[right] >= pivot && left < right)
            right--;
        // 把比基准小的放到基准左边
        arr[left] = arr[right];
        // 再从左边起找出比基准大的
        while (arr[left] <= pivot && left < right)
            left++;
        // 把比基准大的放到基准右边
        arr[right] = arr[left];
    }
    // 将基准归位
    arr[left] = pivot;
    // 分别递归地排序左右两个分区
    quick_sort_recursive(arr, start, left - 1);
    quick_sort_recursive(arr, left + 1, end);
}

void quickSort(int arr[], const int len)
{
    quick_sort_recursive(arr, 0, len - 1);
}

int main(void)
{
    int arr[] = { 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = (int)sizeof(arr) / sizeof(*arr);
    quickSort(arr, len);
    for (int i = 0; i < len; i++)
        printf("%d ", arr[i]);
    return 0;
}

非递归实现:

#include <stdio.h>
#include <stdlib.h>

// 范围
typedef struct _stack
{
    int start, end;
} Range;

// 申请一个新的栈元素
Range new_Range(int start, int end)
{
    Range s = {
        .start = start,
        .end = end
    };
    return s;
}

void quickSort(int arr[], const int len)
{
    if (len <= 1)
        return;
    // s模拟栈,p为数量,r[p++]为push,r[--p]为pop0
    Range* s = (Range*)malloc(len * sizeof(Range));
    int p = 0;
    // 范围为[0,len-1]
    s[p++] = new_Range(0, len - 1);
    while (p)
    {
        // pop出当前要排序的范围
        Range range = s[--p];
        if (range.start >= range.end)
            continue;
        // 设置基准
        int pivot = arr[range.start];
        int left = range.start, right = range.end;
        while (left < right)
        {
            // 先从右边起找出比基准小的
            while (arr[right] >= pivot && left < right)
                right--;
            // 把比基准小的放到基准左边
            arr[left] = arr[right];
            // 再从左边起找出比基准大的
            while (arr[left] <= pivot && left < right)
                left++;
            // 把比基准大的放到基准右边
            arr[right] = arr[left];
        }
        // 将基准归位
        arr[left] = pivot;
        // 分别设置左右两个分区的范围
        if (range.start < left)
            s[p++] = new_Range(range.start, left - 1);
        if (range.end > left)
            s[p++] = new_Range(left + 1, range.end);
    }
    free(s);
}

int main(void)
{
    int arr[] = { 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = (int)sizeof(arr) / sizeof(*arr);
    quickSort(arr, len);
    for (int i = 0; i < len; i++)
        printf("%d ", arr[i]);
    return 0;
}

堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。

堆积是一种近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

  1. 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
  2. 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

堆排序的平均时间复杂度为 Ο(nlogn)。

堆排序是不稳定的。

算法步骤

  1. 将待排序的数组构造出一个堆 H[0……n-1]

  2. 把堆首(堆顶结点,即最大值)和堆尾(堆的最下层最右边的结点)互换;

    此时不再对原堆顶(最大值)进行操作,即原堆顶已经被“移出”,堆的长度缩小1。

  3. 把新的数组顶端数据调整到相应位置;

  4. 重复步骤 2~3,直到堆的尺寸为 1。

动画演示:

代码实现

Python实现

def swap(arr, i, j):
    arr[i], arr[j] = arr[j], arr[i]

def heapify(arr, len, i):
    # 子结点
    son = 2*i + 1
    # 最大值元素位置
    largest = i
    # 将最大值置为堆顶结点
    if son < len:
        if son + 1 < len and arr[son + 1] > arr[son]:
            son += 1
        if arr[son] > arr[largest]:
            largest = son
            swap(arr, i, largest)
            # 重新构造子堆
            heapify(arr, len, largest)

def buildMaxHeap(arr):
    # int(len(arr)/2)递减至0
    for i in range(int(len(arr)/2) - 1, -1, -1):
        heapify(arr, len(arr), i)

def heapSort(arr, left=None, right=None):
    global arrLen
    arrLen = len(arr)
    # 构造堆
    buildMaxHeap(arr)
    # len(arr)-1递减至1
    for i in range(len(arr)-1, 0, -1):
        # 交换堆顶和最下层最右元素
        swap(arr, 0, i)
        # 将原堆顶移出并重新调整堆
        heapify(arr, i, 0)
    return arr

if __name__ == '__main__':
    arr = [ 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 ]
    heapSort(arr)
    print(arr)

C实现

#include <stdio.h>
#include <stdlib.h>

void swap(int* x, int* y)
{
    int tmp = *x;
    *x = *y;
    *y = tmp;
}

void heapify(int arr[], int len, int i)
{
    // 子节点
    int son = i*2 + 1;
    // 最大值元素位置
    int largest = i;
    // 将最大值置为堆顶结点
    if (son < len)
    {
        if (son + 1 < len && arr[son + 1] > arr[son])
            son++;
        if (arr[son] > arr[largest])
        {
            largest = son;
            swap(&arr[i], &arr[largest]);
            // 重新构造子堆
            heapify(arr, len, largest);
        }
    }
}

void buildMaxHeap(int arr[], int len)
{
    for (int i = len / 2 - 1; i >= 0; i--)
        heapify(arr, len, i);
}

void heapSort(int arr[], int len)
{
    // 构造堆
    buildMaxHeap(arr, len);
    for (int i = len - 1; i > 0; i--)
    {
        // 交换堆顶和最下层最右元素
        swap(&arr[0], &arr[i]);
        // 将原堆顶移出并重新调整堆
        heapify(arr, i, 0);
    }
}

int main(void)
{
    int arr[] = { 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = (int)sizeof(arr) / sizeof(*arr);
    heapSort(arr, len);
    for (int i = 0; i < len; i++)
        printf("%d ", arr[i]);
    return 0;
}

计数排序

计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是$Θ(n + k)$。计数排序不是比较排序,排序的速度快于任何比较排序算法。

由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。

算法步骤

  1. 找出待排序的数组中最大和最小的元素。
  2. 统计数组中每个值为 i的元素出现的次数,存入数组 C的第 i项。
  3. 对所有的计数累加(从 C中的第一个元素开始,每一项和前一项相加)。
  4. 反向填充目标数组:将每个元素 i放在新数组的第 C(i) 项,每放一个元素就将 C(i) 减去 1

动画演示:

代码实现

Python实现

def countingSort(arr):
    # 找出最大值
    max = arr[0]
    for i in range(1, len(arr)):
        if arr[i] > max:
            max = arr[i]
    # 构建一个长度为max+1的数组
    bucket = [0] * (max + 1)
    # 计数
    for i in range(len(arr)):
        if not bucket[arr[i]]:
            bucket[arr[i]] = 0
        bucket[arr[i]] += 1
    # 反向填充
    index = 0
    for i in range(len(bucket)):
        while bucket[i] > 0:
            arr[index] = i
            bucket[i] -= 1
            index += 1
    return arr

if __name__ == '__main__':
    arr = [ 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 ]
    countingSort(arr)
    print(arr)

C实现

#include <stdio.h>
#include <stdlib.h>

void countingSort(int arr[], int len)
{
    // 找出最大值
    int max = arr[0];
    for (int i = 0; i < len; i++)
        if (arr[i] > max)
            max = arr[i];
    // 构造和初始化
    int bucketLen = max + 1;
    int* bucket = (int*)malloc(bucketLen * sizeof(int));
    for (int i = 0; i < bucketLen; i++)
        bucket[i] = 0;
  
    // 计数
    for (int i = 0; i < len; i++)
        if (arr[i] < bucketLen)
            bucket[arr[i]]++;
    // 反向填充
    for (int i = 0, j = 0; i < bucketLen; i++)
        while (bucket[i] > 0)
        {
            arr[j++] = i;
            bucket[i]--;
        }
}

int main(void)
{
    int arr[] = { 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = (int)sizeof(arr) / sizeof(*arr);
    countingSort(arr, len);
    for (int i = 0; i < len; i++)
        printf("%d ", arr[i]);
    return 0;
}

桶排序

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,需要做到两点:

  1. 在额外空间充足的情况下,尽量增大桶的数量
  2. 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中

同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。

  • 最快的情况:当输入的数据可以均匀的分配到每一个桶中。
  • 最慢的情况:当输入的数据被分配到了同一个桶中。

算法步骤

  1. 将数列中的数均匀地分布到每个桶中(有时候并不是均匀分布)。

  2. 将每个桶中的数进行排序。

    这里的排序可以使用桶排序也可以使用其它方法排序。

  3. 按照顺序将所有桶中的数据取出。

图片演示:

将元素分布在桶中:

元素在每个桶中排序:

代码实现

Python实现

from quick_sort import quickSort

def bucketSort(arr, bucketsize):
    if len(arr) == 0 or bucketsize <= 0:
        return arr
    # 确定最大最小值
    maxValue = minValue = arr[0]
    for i in arr:
        if i < minValue:
            minValue = i
        elif i > maxValue:
            maxValue = i
    # 桶数量
    count = (maxValue - minValue) // bucketsize + 1
    # 对应的桶
    buckets = [[] for i in range(count + 1)]
  
    # 把数据放入相应的桶
    for i in arr:
        index = (i - minValue) // bucketsize
        buckets[index].append(i)
  
    # 桶内排序并合并数据
    arr.clear()
    for j in buckets:
        if len(j) != 0:
            # 桶排序
            # bucketSort(j, bucketsize-1)
            # 快速排序
            quickSort(j)
            # 还可以换其它方法排序
            arr.extend(j)
    return arr

if __name__ == "__main__" :
    arr = [ 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 ]
    bucketSort(arr, 5)
    print(arr)

C实现

#include <stdio.h>
#include <stdlib.h>

extern void quickSort(int arr[], const int len);

typedef struct _list
{
    int* bucket;
    int len;
} List;

void append(List* pl, int num)
{
    if (!pl->len)
    {
        pl->bucket = (int*)malloc(sizeof(int));
        pl->bucket[pl->len++] = num;
        return;
    }
    int* tmp = (int*)malloc((pl->len + 1) * sizeof(int));
    for (int i = 0; i < pl->len; i++)
        tmp[i] = pl->bucket[i];
    tmp[pl->len++] = num;
    free(pl->bucket);
    pl->bucket = tmp;
}

void bucketSort(int arr[], int len, int bucketsize)
{
    // 确定最大最小值
    int max = arr[0], min = max;
    for (int i = 0; i < len; i++)
        if (arr[i] < min)
            min = arr[i];
        else if (arr[i] > max)
            max = arr[i];
  
    // 桶数量
    int count = (max - min) / bucketsize + 1;
    // 对应的桶
    List* buckets = (List*)malloc(count * sizeof(List));
    for (int i = 0; i < count; i++)
    {
        buckets[i].bucket = NULL;
        buckets[i].len = 0;
    }

    // 方案一:动态数组
    // 把数据放入相应的桶
    for (int i = 0; i < len; i++)
    {
        int index = (arr[i] - min) / bucketsize;
        append(&buckets[index], arr[i]);
    }
    // 方案二:二维数组
    // int* size = (int*)malloc(count * sizeof(int));
    // // 计算最大宽度
    // for (int i = 0; i < count; i++)
    //     size[i] = 0;
    // for (int i = 0; i < len; i++)
    //     size[(arr[i] - min) / bucketsize]++;
    // int maxSize = 0;
    // for (int i = 0; i < count; i++)
    //     if (size[i] > maxSize)
    //         maxSize = size[i];
    // // 把数据放入相应的桶
    // for (int i = 0; i < len; i++)
    // {
    //     int index = (arr[i] - min) / bucketsize;
    //     if (!buckets[index].len)
    //         buckets[index].bucket = (int*)malloc(maxSize * sizeof(int));
    //     buckets[index].bucket[buckets[index].len++] = arr[i];
    // }
    // free(size);

    // 桶内快排并合并数据
    int index = 0;
    for (int i = 0; i < count; i++)
        if (buckets[i].len)
        {
            quickSort(buckets[i].bucket, buckets[i].len);
            for (int j = 0; j < buckets[i].len; j++)
                arr[index + j] = buckets[i].bucket[j];
            index += buckets[i].len;
            free(buckets[i].bucket);
            buckets[i].bucket = NULL;
            buckets[i].len = 0;
        }
  
    free(buckets);
}

int main(void)
{
    int arr[] = { 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = (int)sizeof(arr) / sizeof(*arr);
    bucketSort(arr, len, 5);
    for (int i = 0; i < len; i++)
        printf("%d ", arr[i]);
    return 0;
}

基数排序

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。基数排序还可以用于其它数据类型的排序(但其本质上还是整型,如字符型)。

基数排序用到了桶的概念,是桶排序的扩展,它是根据键值的每位数字来分配桶。

算法步骤

有两类基数排序:

  1. 最低位优先法,简称LSD法:先从最低位开始排序,再对次低位排序,直到对最高位排序后得到一个有序序列;
  2. 最高位优先法,简称MSD法:先从最高位开始排序,再逐个对各分组按次高位进行子排序,循环直到最低位。

📌如果位没有数的话,补0。

动画演示:

LSD基数排序演示

代码实现

Python实现

def getBit(num, i):
    return (num // i) % 10

def getMax(arr):
    max = arr[0]
    for i in range(len(arr)):
        if arr[i] > max:
            max = arr[i]
    return max

def radixSort(arr):
    if len(arr) <= 1:
        return arr
    # 获取最大值
    max = getMax(arr)
    # 根据最大位数排序
    index = 1
    while max // index:
        # 桶排序
        buckets = [[] for i in range(10)]
        for x in arr:
            bit_num = getBit(x, index)
            buckets[bit_num].append(x)
        arr.clear()
        for x in buckets:
            arr.extend(x)
        index *= 10

    return arr

if __name__ == "__main__" :
    arr = [ 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 ]
    radixSort(arr)
    print(arr)

C实现

#include <stdio.h>
#include <stdlib.h>

typedef struct _list
{
    int* bucket;
    int len;
} List;

void append(List* pl, int num)
{
    if (!pl->len)
    {
        pl->bucket = (int*)malloc(sizeof(int));
        pl->bucket[pl->len++] = num;
        return;
    }
    int* tmp = (int*)malloc((pl->len + 1) * sizeof(int));
    for (int i = 0; i < pl->len; i++)
        tmp[i] = pl->bucket[i];
    tmp[pl->len++] = num;
    free(pl->bucket);
    pl->bucket = tmp;
}

int getBit(int num, int i)
{
    return (num / i) % 10;
}

void radixSort(int arr[], int len)
{
    if (len <= 1)
        return;
    // 获取最大值
    int max = arr[0];
    for (int i = 1; i < len; i++)
        if (arr[i] > max)
            max = arr[i];
    // 根据最大位数排序
    // 方案一:动态数组
    for (int i = 1; max / i; i *= 10)
    {
        // 桶排序
        List buckets[10] = { {NULL, 0} };
        for (int j = 0; j < len; j++)
            append(&buckets[getBit(arr[j], i)], arr[j]);
        int index = 0;
        for (int j = 0; j < 10; j++)
            if (buckets[j].len)
            {
                for (int k = 0; k < buckets[j].len; k++)
                    arr[index + k] = buckets[j].bucket[k];
                index += buckets[j].len;
                free(buckets[j].bucket);
                buckets[j].bucket = NULL;
                buckets[j].len = 0;
            }
    }
    // 方案二:二维数组
    // for (int i = 1; max / i; i *= 10)
    // {
    //     // 计算最大宽度
    //     int size[10] = { 0 };
    //     for (int j = 0; j < len; j++)
    //         size[getBit(arr[j], i)]++;
    //     int maxSize = size[0];
    //     for (int j = 0; j < 10; j++)
    //         if (size[j] > maxSize)
    //             maxSize = size[j];
    //     // 桶排序
    //     List buckets[10] = { {NULL, 0} };
    //     for (int j = 0; j < len; j++)
    //     {
    //         int index = getBit(arr[j], i);
    //         if (!buckets[index].len)
    //             buckets[index].bucket = (int*)malloc(maxSize*sizeof(int));
    //         buckets[index].bucket[buckets[index].len++] = arr[j];
    //     }
    //     int index = 0;
    //     for (int j = 0; j < 10; j++)
    //         if (buckets[j].len)
    //         {
    //             for (int k = 0; k < buckets[j].len; k++)
    //                 arr[index + k] = buckets[j].bucket[k];
    //             index += buckets[j].len;
    //             free(buckets[j].bucket);
    //             buckets[j].bucket = NULL;
    //             buckets[j].len = 0;
    //         }
    // }
}

int main(void)
{
    int arr[] = { 
        22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = (int)sizeof(arr) / sizeof(*arr);
    radixSort(arr, len);
    for (int i = 0; i < len; i++)
        printf("%d ", arr[i]);
    return 0;
}