C语言算法专题总结(一)排序

C 语言排序算法专题总结

一、冒泡排序(Bubble Sort)

基本思想

比较相邻两个数的大小,每一趟将最大数"冒"至数组末尾。

优化策略

若某一趟没有进行交换,则说明已经有序,可以设置 flag 提前停止。

代码实现

c 复制代码
void bubbleSort(int a[], int n){
    for(int i = 0; i < n - 1; i++){
        for(int j = 0; j < n - i - 1; j++){
            if(a[j] > a[j + 1]){
                int tmp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = tmp;
            }
        }
    }
}

时间复杂度

  • 最好情况:O(n) - 已经有序
  • 平均情况:O(n²)
  • 最坏情况:O(n²)

空间复杂度

O(1)

稳定性

稳定排序


二、选择排序(Selection Sort)

基本思想

每一趟在未排序部分中选择最小的数,放在已排序部分的末尾。

代码实现

c 复制代码
void selectionSort(int a[], int n){
    for(int i = 0; i < n; i++){
        int minIndex = i;
        // 找到最小值的索引
        for(int j = i + 1; j < n; j++){
            if(a[minIndex] > a[j]){
                minIndex = j;
            }
        }
        // 交换
        int tmp = a[i];
        a[i] = a[minIndex];
        a[minIndex] = tmp;
    }
}

时间复杂度

  • 最好、平均、最坏情况:O(n²)

空间复杂度

O(1)

稳定性

不稳定排序


三、插入排序(Insertion Sort)

基本思想

前 i 个数字是已排序的部分,将第 i+1 个数字插入到前面已排序序列的适当位置。

代码实现

c 复制代码
void insertionSort(int a[], int n){
    for(int i = 1; i < n; i++){
        int key = a[i], j = i;
        // 比 key 大的元素向后移动
        while(j > 0 && key < a[j - 1]){
            a[j] = a[j - 1];
            j--;
        }
        a[j] = key;
    }
}

时间复杂度

  • 最好情况:O(n) - 已经有序
  • 平均情况:O(n²)
  • 最坏情况:O(n²)

空间复杂度

O(1)

稳定性

稳定排序


四、快速排序(Quick Sort)

基本思想

采用分治法,选定一个基准元素(pivot),将数组分为两部分:左侧都比基准小,右侧都比基准大,然后递归排序两部分。

代码实现

c 复制代码
// 分区函数
int partition(int a[], int low, int high){
    int key = a[low];  // 选择第一个元素作为基准
    while(low < high){
        // 从右向左找小于基准的元素
        while(a[high] >= key && low < high) high--;
        a[low] = a[high];
        // 从左向右找大于基准的元素
        while(a[low] <= key && low < high) low++;
        a[high] = a[low];
    }
    a[low] = key;
    return low;
}

// 快速排序主函数
void quickSort(int a[], int n, int low, int high){
    if(low < high){
        int pivotkey = partition(a, low, high);
        quickSort(a, n, low, pivotkey - 1);   // 左半部分
        quickSort(a, n, pivotkey + 1, high);  // 右半部分
    }
}

使用库函数 qsort

c 复制代码
#include <stdlib.h>

// 比较函数
int compare(const void *a, const void *b){
    int num1 = *((int*)a);
    int num2 = *((int*)b);
    return num1 - num2;  // 升序
}

// 调用示例
qsort(arr, n, sizeof(int), compare);

自定义排序规则示例

c 复制代码
// 西电考题:按各位数字之和降序排列,和相等时按数字本身升序
int sumOfDigits(int num){
    int sum = 0;
    while(num > 0){
        sum += num % 10;
        num /= 10;
    }
    return sum;
}

int compare(const void *a, const void *b){
    int num1 = *((int*)a);
    int num2 = *((int*)b);
    int sum1 = sumOfDigits(num1);
    int sum2 = sumOfDigits(num2);
    
    if(sum1 == sum2){
        return num1 - num2;  // 和相等时按数字升序
    } else {
        return sum2 - sum1;  // 按数字和降序
    }
}

时间复杂度

  • 最好情况:O(n log n)
  • 平均情况:O(n log n)
  • 最坏情况:O(n²) - 已经有序时

空间复杂度

O(log n) - 递归栈

稳定性

不稳定排序


五、归并排序(Merge Sort)

基本思想

采用分治法,将数组分为两部分分别排序,然后合并两个有序序列。

代码实现

c 复制代码
// 合并两个有序子序列
void merge(int a[], int low, int mid, int high){
    int i = low, j = mid + 1, k = 0;
    int *temp = (int*)malloc((high - low + 1) * sizeof(int));
    
    // 比较两个子序列的元素,较小的放入临时数组
    while(i <= mid && j <= high){
        if(a[i] <= a[j]){
            temp[k++] = a[i++];
        } else {
            temp[k++] = a[j++];
        }
    }
    
    // 复制剩余元素
    while(i <= mid) temp[k++] = a[i++];
    while(j <= high) temp[k++] = a[j++];
    
    // 将临时数组复制回原数组
    for(i = 0; i < high - low + 1; i++){
        a[low + i] = temp[i];
    }
    
    free(temp);
}

// 归并排序主函数
void mergeSort(int a[], int low, int high){
    if(low < high){
        int mid = low + (high - low) / 2;
        mergeSort(a, low, mid);      // 左半部分
        mergeSort(a, mid + 1, high); // 右半部分
        merge(a, low, mid, high);    // 合并
    }
}

时间复杂度

  • 最好、平均、最坏情况:O(n log n)

空间复杂度

O(n) - 需要额外空间

稳定性

稳定排序

特点

常考稳定性,适合链表排序


六、堆排序(Heap Sort)

基本思想

基于堆数据结构(完全二叉树),构建大根堆/小根堆后进行排序。

代码实现

c 复制代码
// 堆化函数:维护以 i 为根的子树满足大根堆性质
void heapify(int a[], int n, int i){
    int largest = i;
    int left = i * 2 + 1;   // 左孩子
    int right = i * 2 + 2;  // 右孩子
    
    // 找出父节点和两个孩子中的最大值
    if(left < n && a[largest] < a[left]) largest = left;
    if(right < n && a[largest] < a[right]) largest = right;
    
    if(largest != i){
        // 交换
        int tmp = a[i];
        a[i] = a[largest];
        a[largest] = tmp;
        
        // 递归调整被影响的子树
        heapify(a, n, largest);
    }
}

// 堆排序主函数
void heapSort(int a[], int n){
    // 构建初始大根堆
    for(int i = n / 2 - 1; i >= 0; i--){
        heapify(a, n, i);
    }
    
    // 依次将堆顶元素与末尾元素交换
    for(int i = n - 1; i > 0; i--){
        int tmp = a[0];
        a[0] = a[i];
        a[i] = tmp;
        
        // 重新调整堆
        heapify(a, i, 0);
    }
}

时间复杂度

  • 最好、平均、最坏情况:O(n log n)

空间复杂度

O(1)

稳定性

不稳定排序


七、基数排序(Radix Sort)

基本思想

按照低位优先(LSD)或高位优先(MSD)的方式,逐位进行排序。通常配合计数排序使用。

代码实现

c 复制代码
// 计数排序辅助函数:按指定位数排序
void countingSortForRadix(int arr[], int n, int exp){
    int output[n], count[10] = {0};
    
    // 统计当前位的基数
    for(int i = 0; i < n; i++){
        count[(arr[i] / exp) % 10]++;
    }
    
    // 累加基数
    for(int i = 1; i < 10; i++){
        count[i] += count[i - 1];
    }
    
    // 从后往前放置元素(保证稳定性)
    for(int i = n - 1; i >= 0; i--){
        output[count[(arr[i] / exp) % 10] - 1] = arr[i];
        count[(arr[i] / exp) % 10]--;
    }
    
    // 复制回原数组
    for(int i = 0; i < n; i++){
        arr[i] = output[i];
    }
}

// 基数排序主函数
void radixSort(int arr[], int n){
    // 找出最大值确定位数
    int max = arr[0];
    for(int i = 1; i < n; i++){
        if(arr[i] > max) max = arr[i];
    }
    
    // 按每一位进行计数排序
    for(int exp = 1; max / exp > 0; exp *= 10){
        countingSortForRadix(arr, n, exp);
    }
}

时间复杂度

  • O(d × (n + k)),d 为位数,k 为基数(通常为 10)

空间复杂度

O(n + k)

稳定性

稳定排序


八、双向冒泡排序(Cocktail Sort)

基本思想

冒泡排序的改进版,每一轮在两个方向上进行冒泡:先从左到右将最大值移到右端,再从右到左将最小值移到左端。

代码实现

c 复制代码
void cocktailSort(int arr[], int n){
    int start = 0, end = n - 1;
    
    while(start < end){
        // 从左到右,将最大值移到右端
        for(int i = start; i < end; i++){
            if(arr[i] > arr[i + 1]){
                int tmp = arr[i];
                arr[i] = arr[i + 1];
                arr[i + 1] = tmp;
            }
        }
        end--;
        
        // 从右到左,将最小值移到左端
        for(int i = end; i > start; i--){
            if(arr[i] < arr[i - 1]){
                int tmp = arr[i];
                arr[i] = arr[i - 1];
                arr[i - 1] = tmp;
            }
        }
        start++;
    }
}

时间复杂度

  • 最好情况:O(n)
  • 平均情况:O(n²)
  • 最坏情况:O(n²)

空间复杂度

O(1)

稳定性

稳定排序


九、统一测试代码

以下代码可以用来测试上述所有排序算法,只需替换调用的排序函数即可:

c 复制代码
#include<stdio.h>
#include<stdlib.h>

// 函数声明
void bubbleSort(int a[], int n);         // 冒泡排序
void selectionSort(int a[], int n);      // 选择排序
void insertionSort(int a[], int n);      // 插入排序
void quickSort(int a[], int n, int low, int high);     // 快速排序
void mergeSort(int a[], int low, int high);            // 归并排序
void heapSort(int a[], int n);
void radixSort(int arr[], int n);
void cocktailSort(int arr[], int n);

int main(){
    int n;
    printf("请输入数字的个数:");
    scanf("%d", &n);
    
    // 动态分配内存
    int *arr = (int *)malloc(n * sizeof(int));
    if(arr == NULL){
        printf("内存分配失败\n");
        return 0;
    }
    
    printf("请输入%d个数字:", n);
    for(int i = 0; i < n; i++){
        scanf("%d", &arr[i]);
    }
    
    // 对数组进行从小到大的排序
    // 可以替换为以下任意排序函数:
    // bubbleSort(arr, n);           // 冒泡排序
    // selectionSort(arr, n);        // 选择排序
    // insertionSort(arr, n);        // 插入排序
    // quickSort(arr, n, 0, n-1);    // 快速排序
    // mergeSort(arr, 0, n-1);       // 归并排序
    // heapSort(arr, n);             // 堆排序
    // radixSort(arr, n);            // 基数排序
    cocktailSort(arr, n);            // 双向冒泡排序
    
    // 输出排序结果
    for(int i = 0; i < n; i++){
        printf("%d ", arr[i]);
    }
    
    // 释放内存
    free(arr);
    return 0;
}

使用说明:

  1. 编译时需要将所有排序函数的实现代码一起编译
  2. 通过注释/取消注释不同的排序函数调用来测试不同算法
  3. 快速排序调用:quickSort(arr, n, 0, n-1)
  4. 归并排序调用:mergeSort(arr, 0, n-1)

十、各种排序算法对比

排序算法 最好时间 平均时间 最坏时间 空间复杂度 稳定性 适用场景
冒泡排序 O(n) O(n²) O(n²) O(1) 稳定 数据量小、基本有序
选择排序 O(n²) O(n²) O(n²) O(1) 不稳定 数据量小、对稳定性无要求
插入排序 O(n) O(n²) O(n²) O(1) 稳定 数据量小、基本有序
快速排序 O(n log n) O(n log n) O(n²) O(log n) 不稳定 大规模数据、一般情况首选
归并排序 O(n log n) O(n log n) O(n log n) O(n) 稳定 要求稳定性、链表排序
堆排序 O(n log n) O(n log n) O(n log n) O(1) 不稳定 要求最坏情况也是 O(n log n)
基数排序 O(d×n) O(d×n) O(d×n) O(n+k) 稳定 整数排序、位数固定
双向冒泡 O(n) O(n²) O(n²) O(1) 稳定 数据量小、基本有序

十一、排序算法选择建议

1. 小规模数据(n ≤ 50)

  • 推荐:插入排序、冒泡排序
  • 理由:简单易懂,常数因子小

2. 大规模数据

  • 一般情况:快速排序(效率最高)
  • 要求稳定性:归并排序
  • 要求最坏情况好:堆排序

3. 特殊场景

  • 基本有序:插入排序、冒泡排序
  • 整数排序且范围已知:基数排序、计数排序
  • 链表排序:归并排序
  • 内存受限:堆排序、原地排序

4. 考试重点

  • 稳定性判断:归并、插入、冒泡、基数是稳定的
  • 时间复杂度分析:重点掌握快排、归并、堆排序
  • 实际应用:qsort 库函数的使用

十二、常见问题

1. 如何判断排序算法的稳定性?

:如果排序后相同元素的相对位置不变,则为稳定排序。例如:[3a, 1, 3b, 2] 排序后为 [1, 2, 3a, 3b],3a 仍在 3b 前面。

2. 快速排序为什么不稳定?

:因为在分区过程中,可能会跨越多个位置交换元素,导致相同元素的相对位置改变。

3. 归并排序为什么稳定?

:因为在合并时,当两个元素相等时,优先选择左边子序列的元素,保持了原有顺序。

4. 如何选择基准元素(快速排序)?

  • 固定位置(如第一个、最后一个)
  • 随机选择
  • 三数取中(首、中、尾的中位数)

5. 堆排序中建堆的时间复杂度?

:O(n),不是 O(n log n)。因为大部分节点的高度较小。

相关推荐
故事和你9110 分钟前
洛谷-算法2-3-分治与倍增5
开发语言·数据结构·c++·算法·动态规划·图论
北顾笙98027 分钟前
day37-数据结构力扣
数据结构·算法·leetcode
啦啦啦_999932 分钟前
1. 逻辑回归
算法·机器学习·逻辑回归
handler0140 分钟前
Git 核心指令速查
linux·c语言·c++·笔记·git·学习
学会去珍惜1 小时前
学会C语言可以做什么
c语言·网络编程·游戏开发·嵌入式系统·系统编程
fengenrong1 小时前
20260429
c++·算法
南宫萧幕1 小时前
Python与Simulink联合仿真:基于DQN的HEV能量管理策略建模与全链路排雷实战
开发语言·人工智能·python·算法·机器学习·matlab·控制
『昊纸』℃1 小时前
Mac上编译C语言的简易方法
c语言·mac·教程·xcode·编译
apollowing1 小时前
启发式算法WebApp实验室:从搜索策略到群体智能的能力进阶(优)
算法·启发式算法·web app
代码中介商2 小时前
C语言核心知识完全回顾:从数据类型到动态内存管理
c语言·开发语言