【算法】十大排序算法的方法与C语言+Python实现

本文总结一下我在算法课程中学习到的十大排序算法思路和C语言+Python实现。部分图片来自 https://www.hello-algo.com

冒泡排序

冒泡排序(bubble sort)通过连续地比较与交换相邻元素实现排序。这个过程就像气泡从底部升到顶部一样,因此得名冒泡排序。

算法步骤:

  1. 比较相邻的元素:若第一个比第二个大,则交换;
  2. 遍历开始第一对到结尾最后一对,执行步骤1;
  3. 重复步骤1~2,直到排序完成。

可改进的冒泡排序:第一趟排序之后最后一个元素是最大的,因此下一趟遍历只需执行到倒数第二对。

图片[1] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本
图片[2] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本
def bubble_sort(nums: list[int]):
    """冒泡排序"""
    n = len(nums)
    # 外循环:未排序区间为 [0, i]
    for i in range(n - 1, 0, -1):
        # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端
        for j in range(i):
            if nums[j] > nums[j + 1]:
                # 交换 nums[j] 与 nums[j + 1]
                nums[j], nums[j + 1] = nums[j + 1], nums[j]
/* 冒泡排序 */
void bubbleSort(int nums[], int size) {
    // 外循环:未排序区间为 [0, i]
    for (int i = size - 1; i > 0; i--) {
        // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端
        for (int j = 0; j < i; j++) {
            if (nums[j] > nums[j + 1]) {
                int temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
            }
        }
    }
}
图片[3] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本

选择排序

选择排序(selection sort)的工作原理非常简单:开启一个循环,每轮从未排序区间选择最小的元素,将其放到已排序区间的末尾。

算法步骤:

  1. 初始状态:无序序列为R[0,n−1],长度n,有序区为空;
  2. 第i=1,..,n−1趟排序从当前无序区R[i−1,n−1]中选出最小的元素R[k],并将它与无序区的第1个记录R[i−1]交换,则R[0,i−1]变为元素个数增加1的新有序区,R[i,n−1]变为元素个数减少1的新无序区;
  3. n−1趟选择交换后结束。
图片[4] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本
def selection_sort(nums: list[int]):
    """选择排序"""
    n = len(nums)
    # 外循环:未排序区间为 [i, n-1]
    for i in range(n - 1):
        # 内循环:找到未排序区间内的最小元素
        k = i
        for j in range(i + 1, n):
            if nums[j] < nums[k]:
                k = j  # 记录最小元素的索引
        # 将该最小元素与未排序区间的首个元素交换
        nums[i], nums[k] = nums[k], nums[i]
/* 选择排序 */
void selectionSort(int nums[], int n) {
    // 外循环:未排序区间为 [i, n-1]
    for (int i = 0; i < n - 1; i++) {
        // 内循环:找到未排序区间内的最小元素
        int k = i;
        for (int j = i + 1; j < n; j++) {
            if (nums[j] < nums[k])
                k = j; // 记录最小元素的索引
        }
        // 将该最小元素与未排序区间的首个元素交换
        int temp = nums[i];
        nums[i] = nums[k];
        nums[k] = temp;
    }
}
图片[5] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本

插入排序

插入排序(insertion sort)是一种简单的排序算法,它的工作原理与手动整理一副牌的过程非常相似。

具体来说,我们在未排序区间选择一个基准元素,将该元素与其左侧已排序区间的元素逐一比较大小,并将该元素插入到正确的位置。

算法步骤:

  1. 从第一个元素开始,该元素认为已经被排序;
  2. 取下一个元素,在已经排序的元素序列中从后向前扫描;
  3. 如果已排序元素大于新元素,将已排序元素移到下一位置;
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  5. 将新元素插入到该位置后;
  6. 重复步骤2~5。
图片[6] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本
def insertion_sort(nums: list[int]):
    """插入排序"""
    # 外循环:已排序区间为 [0, i-1]
    for i in range(1, len(nums)):
        base = nums[i]
        j = i - 1
        # 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置
        while j >= 0 and nums[j] > base:
            nums[j + 1] = nums[j]  # 将 nums[j] 向右移动一位
            j -= 1
        nums[j + 1] = base  # 将 base 赋值到正确位置
/* 插入排序 */
void insertionSort(int nums[], int size) {
    // 外循环:已排序区间为 [0, i-1]
    for (int i = 1; i < size; i++) {
        int base = nums[i], j = i - 1;
        // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置
        while (j >= 0 && nums[j] > base) {
            // 将 nums[j] 向右移动一位
            nums[j + 1] = nums[j];
            j--;
        }
        // 将 base 赋值到正确位置
        nums[j + 1] = base;
    }
}
图片[7] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本

希尔排序

希尔排序由Shell在1959年发明,又叫缩小增量排序,是第一个突破O(n^2)的排序算法,属于简单插入排序的改进版,会优先比较距离较远的元素。

算法步骤:

  1. 选择一个增量序列T1,T2,…,Tk,其中Ti>Tj,Tk=1,i>j;
  2. 每趟排序,根据对应的增量T,将待排序列分割成若干子序列,分别对各子序列进行直接插入排序;
  3. 按增量序列个数k,对序列进行k趟排序。

希尔排序实例: 下图的增量序列为:5,2,1,第一趟排序将增量为5的子序列进行插入排序,第二趟排序将增量为2的子序列进行插入排序,第三趟将增量为1的子序列进行插入排序,最终完成排序。

图片[8] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本

希尔排序的核心在于增量序列的设定:既可以提前设定好增量序列,也可以动态的定义增量序列。例如序列长度为n,则动态增量为:1,4,7,…,3x+1<n/3。

def shellSort(arr): 
    # 希尔排序
    n = len(arr)
    gap = int(n/2)
    while gap > 0: 
        for i in range(gap,n): 
            temp = arr[i] 
            j = i 
            while  j >= gap and arr[j-gap] >temp: 
                arr[j] = arr[j-gap] 
                j -= gap 
            arr[j] = temp 
        gap = int(gap/2)
/* 希尔排序 */
void shell_sort(int* arr, int n) {
	int gap, i, j, temp, count = 0;
	// 指定的增量序列
	int gaps[] = {5, 2, 1};
	int num_gaps = sizeof(gaps) / sizeof(gaps[0]);
	for (int g = 0; g < num_gaps; g++) {
		gap = gaps[g];
		for (i = gap; i < n; i++) {
			temp = arr[i];
			for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
				arr[j] = arr[j - gap];
			}
			arr[j] = temp;
		}
	}
}

归并排序

归并排序(merge sort)是一种基于分治策略的排序算法,包含“划分”和“合并”阶段。

  1. 划分阶段:通过递归不断地将数组从中点处分开,将长数组的排序问题转换为短数组的排序问题。
  2. 合并阶段:当子数组长度为 1 时终止划分,开始合并,持续地将左右两个较短的有序数组合并为一个较长的有序数组,直至结束。
图片[9] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本

算法步骤:

  1. 把长度为n的输入序列分成两个长度为n/2的子序列;
  2. 对这两个子序列分别采用归并排序;
  3. 将两个排序好的子序列合并成一个最终的排序序列。
图片[10] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本
def merge(nums: list[int], left: int, mid: int, right: int):
    """合并左子数组和右子数组"""
    # 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right]
    # 创建一个临时数组 tmp ,用于存放合并后的结果
    tmp = [0] * (right - left + 1)
    # 初始化左子数组和右子数组的起始索引
    i, j, k = left, mid + 1, 0
    # 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中
    while i <= mid and j <= right:
        if nums[i] <= nums[j]:
            tmp[k] = nums[i]
            i += 1
        else:
            tmp[k] = nums[j]
            j += 1
        k += 1
    # 将左子数组和右子数组的剩余元素复制到临时数组中
    while i <= mid:
        tmp[k] = nums[i]
        i += 1
        k += 1
    while j <= right:
        tmp[k] = nums[j]
        j += 1
        k += 1
    # 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间
    for k in range(0, len(tmp)):
        nums[left + k] = tmp[k]

def merge_sort(nums: list[int], left: int, right: int):
    """归并排序"""
    # 终止条件
    if left >= right:
        return  # 当子数组长度为 1 时终止递归
    # 划分阶段
    mid = (left + right) // 2 # 计算中点
    merge_sort(nums, left, mid)  # 递归左子数组
    merge_sort(nums, mid + 1, right)  # 递归右子数组
    # 合并阶段
    merge(nums, left, mid, right)
/* 合并左子数组和右子数组 */
void merge(int *nums, int left, int mid, int right) {
    // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right]
    // 创建一个临时数组 tmp ,用于存放合并后的结果
    int tmpSize = right - left + 1;
    int *tmp = (int *)malloc(tmpSize * sizeof(int));
    // 初始化左子数组和右子数组的起始索引
    int i = left, j = mid + 1, k = 0;
    // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中
    while (i <= mid && j <= right) {
        if (nums[i] <= nums[j]) {
            tmp[k++] = nums[i++];
        } else {
            tmp[k++] = nums[j++];
        }
    }
    // 将左子数组和右子数组的剩余元素复制到临时数组中
    while (i <= mid) {
        tmp[k++] = nums[i++];
    }
    while (j <= right) {
        tmp[k++] = nums[j++];
    }
    // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间
    for (k = 0; k < tmpSize; ++k) {
        nums[left + k] = tmp[k];
    }
    // 释放内存
    free(tmp);
}

/* 归并排序 */
void mergeSort(int *nums, int left, int right) {
    // 终止条件
    if (left >= right)
        return; // 当子数组长度为 1 时终止递归
    // 划分阶段
    int mid = left + (right - left) / 2;    // 计算中点
    mergeSort(nums, left, mid);      // 递归左子数组
    mergeSort(nums, mid + 1, right); // 递归右子数组
    // 合并阶段
    merge(nums, left, mid, right);
}
图片[11] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本

快速排序

快速排序(quick sort)是一种基于分治策略的排序算法,运行高效,应用广泛。

快速排序的核心操作是“哨兵划分”,其目标是:选择数组中的某个元素作为“基准数”,将所有小于基准数的元素移到其左侧,而大于基准数的元素移到其右侧。

图片[12] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本

算法步骤:

  1. 从数列中挑出一个元素,称为基准pivot;
  2. 分区partition操作:比基准值小的元素放在左边,比基准值大的元素放在右边;
  3. 递归recursive:把小于基准值元素的子数列和大于基准值元素的子数列分别递归排序。
图片[13] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本
def partition(self, nums: list[int], left: int, right: int) -> int:
    """哨兵划分"""
    # 以 nums[left] 为基准数
    i, j = left, right
    while i < j:
        while i < j and nums[j] >= nums[left]:
            j -= 1  # 从右向左找首个小于基准数的元素
        while i < j and nums[i] <= nums[left]:
            i += 1  # 从左向右找首个大于基准数的元素
        # 元素交换
        nums[i], nums[j] = nums[j], nums[i]
    # 将基准数交换至两子数组的分界线
    nums[i], nums[left] = nums[left], nums[i]
    return i  # 返回基准数的索引
/* 元素交换 */
void swap(int nums[], int i, int j) {
    int tmp = nums[i];
    nums[i] = nums[j];
    nums[j] = tmp;
}

/* 哨兵划分 */
int partition(int nums[], int left, int right) {
    // 以 nums[left] 为基准数
    int i = left, j = right;
    while (i < j) {
        while (i < j && nums[j] >= nums[left]) {
            j--; // 从右向左找首个小于基准数的元素
        }
        while (i < j && nums[i] <= nums[left]) {
            i++; // 从左向右找首个大于基准数的元素
        }
        // 交换这两个元素
        swap(nums, i, j);
    }
    // 将基准数交换至两子数组的分界线
    swap(nums, i, left);
    // 返回基准数的索引
    return i;
}
图片[14] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本

堆排序

堆排序(heap sort)是一种基于堆数据结构实现的高效排序算法。我们可以利用已经学过的“建堆操作”和“元素出堆操作”实现堆排序。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

算法步骤:

  1. 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
  2. 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
  3. 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
图片[15] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本
def sift_down(nums: list[int], n: int, i: int):
    """堆的长度为 n ,从节点 i 开始,从顶至底堆化"""
    while True:
        # 判断节点 i, l, r 中值最大的节点,记为 ma
        l = 2 * i + 1
        r = 2 * i + 2
        ma = i
        if l < n and nums[l] > nums[ma]:
            ma = l
        if r < n and nums[r] > nums[ma]:
            ma = r
        # 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出
        if ma == i:
            break
        # 交换两节点
        nums[i], nums[ma] = nums[ma], nums[i]
        # 循环向下堆化
        i = ma

def heap_sort(nums: list[int]):
    """堆排序"""
    # 建堆操作:堆化除叶节点以外的其他所有节点
    for i in range(len(nums) // 2 - 1, -1, -1):
        sift_down(nums, len(nums), i)
    # 从堆中提取最大元素,循环 n-1 轮
    for i in range(len(nums) - 1, 0, -1):
        # 交换根节点与最右叶节点(交换首元素与尾元素)
        nums[0], nums[i] = nums[i], nums[0]
        # 以根节点为起点,从顶至底进行堆化
        sift_down(nums, i, 0)
/* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */
void siftDown(int nums[], int n, int i) {
    while (1) {
        // 判断节点 i, l, r 中值最大的节点,记为 ma
        int l = 2 * i + 1;
        int r = 2 * i + 2;
        int ma = i;
        if (l < n && nums[l] > nums[ma])
            ma = l;
        if (r < n && nums[r] > nums[ma])
            ma = r;
        // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出
        if (ma == i) {
            break;
        }
        // 交换两节点
        int temp = nums[i];
        nums[i] = nums[ma];
        nums[ma] = temp;
        // 循环向下堆化
        i = ma;
    }
}

/* 堆排序 */
void heapSort(int nums[], int n) {
    // 建堆操作:堆化除叶节点以外的其他所有节点
    for (int i = n / 2 - 1; i >= 0; --i) {
        siftDown(nums, n, i);
    }
    // 从堆中提取最大元素,循环 n-1 轮
    for (int i = n - 1; i > 0; --i) {
        // 交换根节点与最右叶节点(交换首元素与尾元素)
        int tmp = nums[0];
        nums[0] = nums[i];
        nums[i] = tmp;
        // 以根节点为起点,从顶至底进行堆化
        siftDown(nums, i, 0);
    }
}
图片[16] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本

计数排序

计数排序(counting sort)通过统计元素数量来实现排序,通常应用于整数数组。

图片[17] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本

算法步骤:

  1. 找出待排序的数组中最大和最小的元素;
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
图片[18] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本
def counting_sort_naive(nums: list[int]):
    """计数排序"""
    # 简单实现,无法用于排序对象
    # 1. 统计数组最大元素 m
    m = 0
    for num in nums:
        m = max(m, num)
    # 2. 统计各数字的出现次数
    # counter[num] 代表 num 的出现次数
    counter = [0] * (m + 1)
    for num in nums:
        counter[num] += 1
    # 3. 遍历 counter ,将各元素填入原数组 nums
    i = 0
    for num in range(m + 1):
        for _ in range(counter[num]):
            nums[i] = num
            i += 1
/* 计数排序 */
// 简单实现,无法用于排序对象
void countingSortNaive(int nums[], int size) {
    // 1. 统计数组最大元素 m
    int m = 0;
    for (int i = 0; i < size; i++) {
        if (nums[i] > m) {
            m = nums[i];
        }
    }
    // 2. 统计各数字的出现次数
    // counter[num] 代表 num 的出现次数
    int *counter = calloc(m + 1, sizeof(int));
    for (int i = 0; i < size; i++) {
        counter[nums[i]]++;
    }
    // 3. 遍历 counter ,将各元素填入原数组 nums
    int i = 0;
    for (int num = 0; num < m + 1; num++) {
        for (int j = 0; j < counter[num]; j++, i++) {
            nums[i] = num;
        }
    }
    // 4. 释放内存
    free(counter);
}
def counting_sort(nums: list[int]):
    """计数排序"""
    # 完整实现,可排序对象,并且是稳定排序
    # 1. 统计数组最大元素 m
    m = max(nums)
    # 2. 统计各数字的出现次数
    # counter[num] 代表 num 的出现次数
    counter = [0] * (m + 1)
    for num in nums:
        counter[num] += 1
    # 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引”
    # 即 counter[num]-1 是 num 在 res 中最后一次出现的索引
    for i in range(m):
        counter[i + 1] += counter[i]
    # 4. 倒序遍历 nums ,将各元素填入结果数组 res
    # 初始化数组 res 用于记录结果
    n = len(nums)
    res = [0] * n
    for i in range(n - 1, -1, -1):
        num = nums[i]
        res[counter[num] - 1] = num  # 将 num 放置到对应索引处
        counter[num] -= 1  # 令前缀和自减 1 ,得到下次放置 num 的索引
    # 使用结果数组 res 覆盖原数组 nums
    for i in range(n):
        nums[i] = res[i]
/* 计数排序 */
// 完整实现,可排序对象,并且是稳定排序
void countingSort(int nums[], int size) {
    // 1. 统计数组最大元素 m
    int m = 0;
    for (int i = 0; i < size; i++) {
        if (nums[i] > m) {
            m = nums[i];
        }
    }
    // 2. 统计各数字的出现次数
    // counter[num] 代表 num 的出现次数
    int *counter = calloc(m, sizeof(int));
    for (int i = 0; i < size; i++) {
        counter[nums[i]]++;
    }
    // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引”
    // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引
    for (int i = 0; i < m; i++) {
        counter[i + 1] += counter[i];
    }
    // 4. 倒序遍历 nums ,将各元素填入结果数组 res
    // 初始化数组 res 用于记录结果
    int *res = malloc(sizeof(int) * size);
    for (int i = size - 1; i >= 0; i--) {
        int num = nums[i];
        res[counter[num] - 1] = num; // 将 num 放置到对应索引处
        counter[num]--;              // 令前缀和自减 1 ,得到下次放置 num 的索引
    }
    // 使用结果数组 res 覆盖原数组 nums
    memcpy(nums, res, size * sizeof(int));
    // 5. 释放内存
    free(res);
    free(counter);
}
图片[19] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本

桶排序

桶排序(bucket sort)是分治策略的一个典型应用。它通过设置一些具有大小顺序的桶,每个桶对应一个数据范围,将数据平均分配到各个桶中;然后,在每个桶内部分别执行排序;最终按照桶的顺序将所有数据合并。

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。

图片[20] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本

算法步骤:

  1. 设置一个定量的数组当作空桶;
  2. 遍历输入数据,并且把数据一个一个放到对应的桶里去;
  3. 对每个不是空的桶进行排序;
  4. 从不是空的桶里把排好序的数据拼接起来。
def bucket_sort(nums: list[float]):
    """桶排序"""
    # 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素
    k = len(nums) // 2
    buckets = [[] for _ in range(k)]
    # 1. 将数组元素分配到各个桶中
    for num in nums:
        # 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1]
        i = int(num * k)
        # 将 num 添加进桶 i
        buckets[i].append(num)
    # 2. 对各个桶执行排序
    for bucket in buckets:
        # 使用内置排序函数,也可以替换成其他排序算法
        bucket.sort()
    # 3. 遍历桶合并结果
    i = 0
    for bucket in buckets:
        for num in bucket:
            nums[i] = num
            i += 1
/* 桶排序 */
void bucketSort(float nums[], int n) {
    int k = n / 2;                                 // 初始化 k = n/2 个桶
    int *sizes = malloc(k * sizeof(int));          // 记录每个桶的大小
    float **buckets = malloc(k * sizeof(float *)); // 动态数组的数组(桶)
    // 为每个桶预分配足够的空间
    for (int i = 0; i < k; ++i) {
        buckets[i] = (float *)malloc(n * sizeof(float));
        sizes[i] = 0;
    }
    // 1. 将数组元素分配到各个桶中
    for (int i = 0; i < n; ++i) {
        int idx = (int)(nums[i] * k);
        buckets[idx][sizes[idx]++] = nums[i];
    }
    // 2. 对各个桶执行排序
    for (int i = 0; i < k; ++i) {
        qsort(buckets[i], sizes[i], sizeof(float), compare);
    }
    // 3. 合并排序后的桶
    int idx = 0;
    for (int i = 0; i < k; ++i) {
        for (int j = 0; j < sizes[i]; ++j) {
            nums[idx++] = buckets[i][j];
        }
        // 释放内存
        free(buckets[i]);
    }
}
图片[21] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本

基数排序

基数排序(radix sort)的核心思想与计数排序一致,也通过统计个数来实现排序。在此基础上,基数排序利用数字各位之间的递进关系,依次对每一位进行排序,从而得到最终的排序结果。

基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

图片[22] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本

算法步骤:

  1. 取得数组中的最大数,并取得位数;
  2. arr为原始数组,从最低位开始取每个位组成radix数组;
  3. 对radix进行计数排序(利用计数排序适用于小范围数的特点);
图片[23] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本
def digit(num: int, exp: int) -> int:
    """获取元素 num 的第 k 位,其中 exp = 10^(k-1)"""
    # 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算
    return (num // exp) % 10

def counting_sort_digit(nums: list[int], exp: int):
    """计数排序(根据 nums 第 k 位排序)"""
    # 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组
    counter = [0] * 10
    n = len(nums)
    # 统计 0~9 各数字的出现次数
    for i in range(n):
        d = digit(nums[i], exp)  # 获取 nums[i] 第 k 位,记为 d
        counter[d] += 1  # 统计数字 d 的出现次数
    # 求前缀和,将“出现个数”转换为“数组索引”
    for i in range(1, 10):
        counter[i] += counter[i - 1]
    # 倒序遍历,根据桶内统计结果,将各元素填入 res
    res = [0] * n
    for i in range(n - 1, -1, -1):
        d = digit(nums[i], exp)
        j = counter[d] - 1  # 获取 d 在数组中的索引 j
        res[j] = nums[i]  # 将当前元素填入索引 j
        counter[d] -= 1  # 将 d 的数量减 1
    # 使用结果覆盖原数组 nums
    for i in range(n):
        nums[i] = res[i]

def radix_sort(nums: list[int]):
    """基数排序"""
    # 获取数组的最大元素,用于判断最大位数
    m = max(nums)
    # 按照从低位到高位的顺序遍历
    exp = 1
    while exp <= m:
        # 对数组元素的第 k 位执行计数排序
        # k = 1 -> exp = 1
        # k = 2 -> exp = 10
        # 即 exp = 10^(k-1)
        counting_sort_digit(nums, exp)
        exp *= 10
/* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */
int digit(int num, int exp) {
    // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算
    return (num / exp) % 10;
}

/* 计数排序(根据 nums 第 k 位排序) */
void countingSortDigit(int nums[], int size, int exp) {
    // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组
    int *counter = (int *)malloc((sizeof(int) * 10));
    memset(counter, 0, sizeof(int) * 10); // 初始化为 0 以支持后续内存释放
    // 统计 0~9 各数字的出现次数
    for (int i = 0; i < size; i++) {
        // 获取 nums[i] 第 k 位,记为 d
        int d = digit(nums[i], exp);
        // 统计数字 d 的出现次数
        counter[d]++;
    }
    // 求前缀和,将“出现个数”转换为“数组索引”
    for (int i = 1; i < 10; i++) {
        counter[i] += counter[i - 1];
    }
    // 倒序遍历,根据桶内统计结果,将各元素填入 res
    int *res = (int *)malloc(sizeof(int) * size);
    for (int i = size - 1; i >= 0; i--) {
        int d = digit(nums[i], exp);
        int j = counter[d] - 1; // 获取 d 在数组中的索引 j
        res[j] = nums[i];       // 将当前元素填入索引 j
        counter[d]--;           // 将 d 的数量减 1
    }
    // 使用结果覆盖原数组 nums
    for (int i = 0; i < size; i++) {
        nums[i] = res[i];
    }
    // 释放内存
    free(res);
    free(counter);
}

/* 基数排序 */
void radixSort(int nums[], int size) {
    // 获取数组的最大元素,用于判断最大位数
    int max = INT32_MIN;
    for (int i = 0; i < size; i++) {
        if (nums[i] > max) {
            max = nums[i];
        }
    }
    // 按照从低位到高位的顺序遍历
    for (int exp = 1; max >= exp; exp *= 10)
        // 对数组元素的第 k 位执行计数排序
        // k = 1 -> exp = 1
        // k = 2 -> exp = 10
        // 即 exp = 10^(k-1)
        countingSortDigit(nums, size, exp);
}
图片[24] - AI科研 编程 读书笔记 - 【算法】十大排序算法的方法与C语言+Python实现 - AI科研 编程 读书笔记 - 小竹の笔记本
© 版权声明
THE END
点赞7 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容