算法 学习 排序 2025年6月16日10:25:37

比较排序

简单排序

冒泡排序

重点 :相邻元素两两比较,大的往后移动
使用场景 :教学使用,实际应用较少(效率低)
时间复杂度:O(n²)(最坏和平均),O(n)(最好,已排序时)

复制代码
void bubbleSort(int arr[], int n) {
    for (int i = 0; i < n-1; i++) {       // 外层循环控制轮数
        int swapped = 0;                  // 优化:标记是否发生交换
        for (int j = 0; j < n-i-1; j++) { // 内层循环控制比较次数
            if (arr[j] > arr[j+1]) {      // 前一个比后一个大
                // 交换相邻元素
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
                swapped = 1;
            }
        }
        if (!swapped) break; // 本轮无交换,说明已有序
    }
}
冒泡排序调用示例(直观展示)
复制代码
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr)/sizeof(arr[0]);

printf("原始数组: ");
printArray(arr, n); // 输出: 64 34 25 12 22 11 90

bubbleSort(arr, n);

printf("排序后数组: ");
printArray(arr, n); // 输出: 11 12 22 25 34 64 90

//流程变化
第1轮: 34 25 12 22 11 64 90 (最大的90已到位)
第2轮: 25 12 22 11 34 64 90 
第3轮: 12 22 11 25 34 64 90
...

选择排序

重点 :每次选择最小元素放到已排序序列末尾
使用场景 :小规模数据或内存受限环境
时间复杂度:O(n²)

复制代码
void selectionSort(int arr[], int n) {
    for (int i = 0; i < n-1; i++) {
        int min_idx = i; // 记录最小元素位置
        for (int j = i+1; j < n; j++) {
            if (arr[j] < arr[min_idx]) {
                min_idx = j; // 更新最小元素位置
            }
        }
        // 将最小元素交换到已排序序列末尾
        int temp = arr[i];
        arr[i] = arr[min_idx];
        arr[min_idx] = temp;
    }
}
选择排序调用示例(直观展示)
复制代码
  int arr[] = {64, 25, 12, 22, 11};
    int n = sizeof(arr)/sizeof(arr[0]);
    
    printf("原始数组: ");
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("\n\n");
    
    selectionSort(arr, n);
    
    printf("\n最终结果: ");
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);

//流程
原始数组: 64 25 12 22 11 

第1轮选择: [11] 25 12 22 64  // 最小11交换到首位
第2轮选择: 11 [12] 25 22 64  // 最小12交换到第2位
第3轮选择: 11 12 [22] 25 64  // 最小22交换到第3位
第4轮选择: 11 12 22 [25] 64  // 最小25交换到第4位

最终结果: 11 12 22 25 64

插入排序

重点 :将未排序元素插入到已排序序列的合适位置
使用场景 :小规模或基本有序数据
时间复杂度:O(n²)(最坏和平均),O(n)(最好)

复制代码
void insertionSort(int arr[], int n) {
    for (int i = 1; i < n; i++) {  // 从第二个元素开始
        int key = arr[i];          // 当前待插入元素
        int j = i - 1;
        // 将大于key的元素后移
        while (j >= 0 && arr[j] > key) {
            arr[j+1] = arr[j];
            j--;
        }
        arr[j+1] = key; // 插入到正确位置
    }
}
插入排序调用示例(直观展示)
复制代码
  int arr[] = {12, 11, 13, 5, 6};
    int n = sizeof(arr)/sizeof(arr[0]);
    
    printf("原始数组: ");
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("\n\n");
    
    insertionSort(arr, n);
    
    printf("\n最终结果: ");
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);

//流程
原始数组: 12 11 13 5 6 

第1轮插入: |11| 12 13 5 6  // 11插入到12前
第2轮插入: 11 12 |13| 5 6  // 13保持位置
第3轮插入: |5| 11 12 13 6  // 5插入到最前
第4轮插入: 5 |6| 11 12 13  // 6插入到5和11之间

最终结果: 5 6 11 12 13

进阶排序

归并排序

重点 :分治法,先分后合
使用场景 :需要稳定排序且不关心额外空间时
时间复杂度:O(nlogn)

复制代码
// 合并两个有序数组
void merge(int arr[], int l, int m, int r) {
    int n1 = m - l + 1;
    int n2 = r - m;
    
    // 创建临时数组
    int L[n1], R[n2];
    
    // 拷贝数据到临时数组
    for (int i = 0; i < n1; i++)
        L[i] = arr[l + i];
    for (int j = 0; j < n2; j++)
        R[j] = arr[m + 1 + j];
    
    // 合并临时数组
    int i = 0, j = 0, k = l;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            arr[k] = L[i];
            i++;
        } else {
            arr[k] = R[j];
            j++;
        }
        k++;
    }
    
    // 拷贝剩余元素
    while (i < n1) {
        arr[k] = L[i];
        i++;
        k++;
    }
    while (j < n2) {
        arr[k] = R[j];
        j++;
        k++;
    }
}

void mergeSort(int arr[], int l, int r) {
    if (l < r) {
        int m = l + (r - l) / 2; // 防止溢出
        mergeSort(arr, l, m);     // 排序左半部分
        mergeSort(arr, m+1, r);   // 排序右半部分
        merge(arr, l, m, r);      // 合并
    }
}
归并排序调用示例(直观展示)
复制代码
int arr[] = {38, 27, 43, 3, 9, 82, 10};
int n = sizeof(arr)/sizeof(arr[0]);

printf("原始数组: ");
printArray(arr, n); // 输出: 38 27 43 3 9 82 10

mergeSort(arr, 0, n-1);

printf("排序后数组: ");
printArray(arr, n); // 输出: 3 9 10 27 38 43 82

//合并过程
分割到最小单元: [38] [27] → 合并为[27,38]
[43] [3] → 合并为[3,43]
[27,38]和[3,43] → 合并为[3,27,38,43]

快速排序

重点 :分治法,选取基准值分区
使用场景 :大规模数据排序,性能要求高
时间复杂度:O(nlogn)(平均),O(n²)(最坏)

复制代码
// 分区函数
int partition(int arr[], int low, int high) {
    int pivot = arr[high]; // 选取最后一个元素作为基准
    int i = (low - 1);     // 小于基准的元素的索引
    
    for (int j = low; j <= high-1; j++) {
        if (arr[j] < pivot) { // 当前元素小于基准
            i++;
            // 交换arr[i]和arr[j]
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    // 将基准放到正确位置
    int temp = arr[i+1];
    arr[i+1] = arr[high];
    arr[high] = temp;
    return (i + 1);
}

void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high); // 分区索引
        quickSort(arr, low, pi - 1);  // 排序左子数组
        quickSort(arr, pi + 1, high); // 排序右子数组
    }
}
快速排序调用示例(直观展示)
复制代码
int arr[] = {10, 7, 8, 9, 1, 5};
int n = sizeof(arr)/sizeof(arr[0]);

printf("原始数组: ");
printArray(arr, n); // 输出: 10 7 8 9 1 5

quickSort(arr, 0, n-1);

printf("排序后数组: ");
printArray(arr, n); // 输出: 1 5 7 8 9 10

//分区过程
基准选择5: [1] 5 [10,7,8,9]  // 第一次分区结果
左子数组[10,7,8,9]选基准9: [7,8] 9 [10]  
继续处理[7,8]...

希尔排序

重点 :改进的插入排序,按增量分组
使用场景 :中等规模数据,需要比O(n²)更好的性能
时间复杂度:O(n^(3/2))(取决于增量序列

复制代码
void shellSort(int arr[], int n) {
    // 初始增量gap为数组长度的一半,逐步缩小
    for (int gap = n/2; gap > 0; gap /= 2) {
        // 对每个子数组进行插入排序
        for (int i = gap; i < n; i++) {
            int temp = arr[i];
            int j;
            for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
                arr[j] = arr[j - gap];
            }
            arr[j] = temp;
        }
    }
}
希尔排序调用示例(直观展示)
复制代码
int arr[] = {35, 33, 42, 10, 14, 19, 27, 44};
    int n = sizeof(arr)/sizeof(arr[0]);
    
    printf("原始数组: ");
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("\n\n");
    
    shellSort(arr, n);
    
    printf("\n最终结果: ");
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);

//流程
原始数组: 35 33 42 10 14 19 27 44 

增量gap=4时: 14 19 27 10 35 33 42 44  // 分组[35,14], [33,19], [42,27], [10,44]排序
增量gap=2时: 14 10 27 19 35 33 42 44  // 分组[14,27,35,42], [10,19,33,44]排序
增量gap=1时: 10 14 19 27 33 35 42 44  // 标准插入排序

最终结果: 10 14 19 27 33 35 42 44

非比较排序

基数排序

重点 :按位数从低到高分别排序
使用场景 :整数或字符串排序,位数不多
时间复杂度:O(nk)(k是最大数字的位数)

复制代码
// 获取数组中最大数的位数
int getMaxDigits(int arr[], int n) {
    int max = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] > max) {
            max = arr[i];
        }
    }
    int digits = 0;
    while (max != 0) {
        digits++;
        max /= 10;
    }
    return digits;
}

// 基数排序
void radixSort(int arr[], int n) {
    int max_digits = getMaxDigits(arr, n);
    int exp = 1; // 当前位数(1表示个位,10表示十位...)
    
    for (int d = 0; d < max_digits; d++) {
        // 10个桶(0-9)
        int buckets[10][n];
        int bucket_sizes[10] = {0};
        
        // 分配到桶中
        for (int i = 0; i < n; i++) {
            int digit = (arr[i] / exp) % 10;
            buckets[digit][bucket_sizes[digit]++] = arr[i];
        }
        
        // 从桶中收集回数组
        int index = 0;
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < bucket_sizes[i]; j++) {
                arr[index++] = buckets[i][j];
            }
        }
        
        exp *= 10; // 处理下一位
    }
}
基数排序 调用示例(直观展示)
复制代码
int arr[] = {170, 45, 75, 90, 802, 24, 2, 66};
int n = sizeof(arr)/sizeof(arr[0]);

printf("原始数组: ");
printArray(arr, n); 

radixSort(arr, n);

printf("排序后数组: ");
printArray(arr, n); // 输出: 2 24 45 66 75 90 170 802

//流程
个位排序: 170 90 802 2 24 45 75 66
十位排序: 802 2 24 45 66 170 75 90
百位排序: 2 24 45 66 75 90 170 802

计数排序

重点 :统计元素出现次数
使用场景 :数据范围不大的非负整数
时间复杂度:O(n+k)(k是数据范围)

复制代码
void countingSort(int arr[], int n) {
    // 找出数组中的最大值
    int max = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] > max) {
            max = arr[i];
        }
    }
    
    // 创建计数数组并初始化
    int count[max+1];
    for (int i = 0; i <= max; i++) {
        count[i] = 0;
    }
    
    // 统计每个元素出现次数
    for (int i = 0; i < n; i++) {
        count[arr[i]]++;
    }
    
    // 根据计数数组重构原数组
    int index = 0;
    for (int i = 0; i <= max; i++) {
        while (count[i] > 0) {
            arr[index++] = i;
            count[i]--;
        }
    }
}
计数排序调用示例(直观展示)
复制代码
int arr[] = {4, 2, 2, 8, 3, 3, 1};
int n = sizeof(arr)/sizeof(arr[0]);

printf("原始数组: ");
printArray(arr, n); // 输出: 4 2 2 8 3 3 1

countingSort(arr, n);

printf("排序后数组: ");
printArray(arr, n); // 输出: 1 2 2 3 3 4 8

//流程
count数组: [0,1,2,2,1,0,0,0,1] 
(表示1出现1次,2出现2次...8出现1次)

桶排序

重点 :将数据分到有限数量的桶中,每个桶单独排序
使用场景 :数据均匀分布在一定范围内
时间复杂度:O(n+k)(k是桶的数量)

复制代码
// 简单桶排序实现(假设输入数据在[0,100)范围内)
void bucketSort(int arr[], int n) {
    // 创建10个桶(每个桶负责10个数的范围)
    const int bucket_num = 10;
    int buckets[bucket_num][n]; // 每个桶最多可能存储n个元素
    int bucket_sizes[bucket_num] = {0}; // 记录每个桶当前大小
    
    // 将元素分配到桶中
    for (int i = 0; i < n; i++) {
        int bucket_idx = arr[i] / 10; // 确定桶索引
        buckets[bucket_idx][bucket_sizes[bucket_idx]++] = arr[i];
    }
    
    // 对每个桶进行排序(这里使用插入排序)
    for (int i = 0; i < bucket_num; i++) {
        insertionSort(buckets[i], bucket_sizes[i]);
    }
    
    // 将排序后的桶合并回原数组
    int index = 0;
    for (int i = 0; i < bucket_num; i++) {
        for (int j = 0; j < bucket_sizes[i]; j++) {
            arr[index++] = buckets[i][j];
        }
    }
}
桶排序调用示例(直观展示)
复制代码
 int arr[] = {29, 25, 3, 49, 9, 37, 21, 43};
    int n = sizeof(arr)/sizeof(arr[0]);
    int bucketSize = 5; // 桶数量
    
    printf("原始数组: ");
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("\n\n");
    
    bucketSort(arr, n, bucketSize);
    
    printf("\n最终结果: ");
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    
//流程
原始数组: 29 25 3 49 9 37 21 43 

分桶结果:
桶0: 3 9      // 0-9.8
桶1: 25 21    // 9.8-19.6
桶2: 29       // 19.6-29.4
桶3: 37       // 29.4-39.2
桶4: 49 43    // 39.2-49

最终结果: 3 9 21 25 29 37 43 49

堆排序

重点 :利用堆数据结构进行选择排序
使用场景 :需要原地排序且O(nlogn)时间复杂度
时间复杂度:O(nlogn)

复制代码
// 调整堆(大顶堆)
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);
    }
}
堆排序调用示例(直观展示)
复制代码
int arr[] = {12, 11, 13, 5, 6, 7};
int n = sizeof(arr)/sizeof(arr[0]);

printf("原始数组: ");
printArray(arr, n); // 输出: 12 11 13 5 6 7

heapSort(arr, n);

printf("排序后数组: ");
printArray(arr, n); // 输出: 5 6 7 11 12 13

//建堆过程
初始: 12(0)
     /    \
   11(1)  13(2)
   / \  
 5(3)6(4)

调整后堆顶: 13
交换13和末尾元素7...

使用场景选择

排序算法 最佳场景 时间复杂度(平均) 空间复杂度 稳定性
冒泡排序 教学使用 O(n²) O(1) 稳定
选择排序 小数据量 O(n²) O(1) 不稳定
插入排序 基本有序小数据 O(n²) O(1) 稳定
希尔排序 中等规模数据 O(n^(3/2)) O(1) 不稳定
归并排序 需要稳定排序 O(nlogn) O(n) 稳定
快速排序 通用高效排序 O(nlogn) O(logn) 不稳定
堆排序 需要原地排序 O(nlogn) O(1) 不稳定
计数排序 小范围整数 O(n+k) O(k) 稳定
桶排序 均匀分布数据 O(n+k) O(n+k) 稳定
基数排序 多位数整数 O(nk) O(n+k) 稳定
相关推荐
用户686916134903 小时前
哈希表实现指南:从原理到C++实践
数据结构·c++
Frank_zhou4 小时前
算法-数组实战【合并区间】中等
数据结构
speop7 小时前
【datawhale组队学习】共读AI新圣经
人工智能·学习
羑悻的小杀马特7 小时前
从信息孤岛到智能星云:学习助手编织高校学习生活的全维度互联网络
c++·学习·生活·api
-qOVOp-8 小时前
408第一季 - 数据结构 - 排序II
数据结构·算法·排序算法
黄豆匿zlib8 小时前
Python中的其他数据结构:除了列表和元组,还有哪些?
数据结构·windows·python
Watermelo61711 小时前
内存泄漏到底是个什么东西?如何避免内存泄漏
开发语言·前端·javascript·数据结构·缓存·性能优化·闭包
Frank_zhou1 天前
算法-链表实战【删除链表的倒数第 N 个结点】中等
数据结构
Chef_Chen1 天前
从0开始学习语言模型--Day02-如何最大化利用硬件
人工智能·学习·语言模型