当前位置: 首页 > news >正文

C 语言排序算法:从基础到进阶的全面解析一、引言

一、引言

在 C 语言编程领域,排序算法是一项基础且核心的技能。无论是处理海量数据,还是优化程序性能,选择合适的排序算法都至关重要。本文将深入剖析 C 语言中常见的几种排序算法,包括冒泡排序、选择排序、插入排序、希尔排序、归并排序和快速排序,通过详细的原理介绍、代码示例和过程演示,帮助读者全面掌握这些算法。

二、冒泡排序:简单却直观的交换排序

冒泡排序是一种基础的交换排序算法。它通过多次遍历数列,比较相邻元素的大小,若顺序错误则交换位置,每一轮遍历都会将最大(或最小)的元素 “浮” 到数列末尾。

#include <stdio.h>

// 函数声明
void bubble_sort(int arr[], int len);

int main() {
    int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = sizeof(arr) / sizeof(arr[0]);  // 计算数组长度

    bubble_sort(arr, len);  // 调用冒泡排序函数

    // 打印排序后的数组
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

// 冒泡排序函数
void bubble_sort(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 temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

冒泡排序的时间复杂度为 O (n²),空间复杂度为 O (1),适用于小规模数据的排序。

三、选择排序:逐个挑选最小(大)元素

选择排序的核心思想是在未排序序列中找到最小(大)元素,将其放置到已排序序列的起始位置,重复该过程直至所有元素排序完成。

#include <stdio.h>

// 函数声明
void selection_sort(int a[], int len);

int main() {
    int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = sizeof(arr) / sizeof(arr[0]);  // 计算数组长度

    selection_sort(arr, len);  // 调用选择排序函数

    // 打印排序后的数组
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

// 选择排序函数
void selection_sort(int a[], int len) {
    for (int i = 0; i < len - 1; i++) {
        int min = i;  // 记录最小值的位置,第一个元素默认最小
        for (int j = i + 1; j < len; j++) {
            if (a[j] < a[min]) {  // 找到目前最小值
                min = j;  // 记录最小值的位置
            }
        }
        // 交换两个变量
        if (min != i) {
            int temp = a[min];
            a[min] = a[i];
            a[i] = temp;
        }
    }
}

选择排序的时间复杂度同样为 O (n²),空间复杂度为 O (1),也是一种适用于小规模数据的排序算法。

四、插入排序:构建有序序列的逐步插入

插入排序通过构建有序序列,对未排序数据在已排序序列中从后向前扫描,找到合适位置并插入。

#include <stdio.h>

// 函数声明
void insertion_sort(int arr[], int len);

int main() {
    int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = sizeof(arr) / sizeof(arr[0]);  // 计算数组长度

    insertion_sort(arr, len);  // 调用插入排序函数

    // 打印排序后的数组
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

// 插入排序函数
void insertion_sort(int arr[], int len) {
    for (int i = 1; i < len; i++) {
        int temp = arr[i];  // 当前待插入的元素
        int j = i;
        // 向右移动大于temp的元素
        while (j > 0 && arr[j - 1] > temp) {
            arr[j] = arr[j - 1];
            j--;
        }
        arr[j] = temp;  // 插入元素到正确位置
    }
}

插入排序在数据基本有序时效率较高,时间复杂度为 O (n²),空间复杂度为 O (1)。

五、希尔排序:插入排序的高效改进版

希尔排序基于插入排序的性质,通过将数据分成若干组,对每组进行插入排序,逐步缩小分组间隔,最终完成排序。

#include <stdio.h>

// 函数声明
void shell_sort(int arr[], int len);

int main() {
    int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = sizeof(arr) / sizeof(arr[0]);  // 计算数组长度

    shell_sort(arr, len);  // 调用希尔排序函数

    // 打印排序后的数组
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

// 希尔排序函数
void shell_sort(int arr[], int len) {
    // 计算初始间隔
    for (int gap = len / 2; gap > 0; gap /= 2) {
        // 对每个间隔进行插入排序
        for (int i = gap; i < len; i++) {
            int temp = arr[i];  // 当前待插入的元素
            int j = i;
            // 移动大于temp的元素
            while (j >= gap && arr[j - gap] > temp) {
                arr[j] = arr[j - gap];
                j -= gap;
            }
            arr[j] = temp;  // 插入元素到正确位置
        }
    }
}

希尔排序的时间复杂度与间隔序列的选择有关,一般优于 O (n²),空间复杂度为 O (1)。

六、归并排序:分而治之的排序策略

归并排序采用分治思想,将数据分为两段,从两段中逐个选最小的元素移入新数据段的末尾,可通过迭代或递归实现。

// 迭代法
#include <stdio.h>
#include <stdlib.h>

// 函数声明
int min(int x, int y);
void merge_sort(int arr[], int len);

int main() {
    int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = sizeof(arr) / sizeof(arr[0]);  // 计算数组长度

    merge_sort(arr, len);  // 调用归并排序函数

    // 打印排序后的数组
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

// 返回两个数中的最小值
int min(int x, int y) {
    return x < y ? x : y;
}

// 归并排序函数
void merge_sort(int arr[], int len) {
    int* a = arr;
    int* b = (int*) malloc(len * sizeof(int));

    if (b == NULL) {  // 检查内存分配是否成功
        fprintf(stderr, "Memory allocation failed\n");
        exit(EXIT_FAILURE);
    }

    for (int seg = 1; seg < len; seg += seg) {
        for (int start = 0; start < len; start += seg + seg) {
            int low = start;
            int mid = min(start + seg, len);
            int high = min(start + seg + seg, len);
            int k = low;
            int start1 = low, end1 = mid;
            int start2 = mid, end2 = high;

            // 合并两个子数组
            while (start1 < end1 && start2 < end2) {
                b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
            }
            while (start1 < end1) {
                b[k++] = a[start1++];
            }
            while (start2 < end2) {
                b[k++] = a[start2++];
            }
        }

        // 交换数组指针
        int* temp = a;
        a = b;
        b = temp;
    }

    // 如果a和arr不相同,则将a的内容复制回arr
    if (a != arr) {
        for (int i = 0; i < len; i++) {
            b[i] = a[i];
        }
        b = a;
    }

    free(b);  // 释放内存
}
// 递归法
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 函数声明
void merge_sort_recursive(int arr[], int reg[], int start, int end);
void merge_sort(int arr[], const int len);

int main() {
    int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = sizeof(arr) / sizeof(arr[0]);  // 计算数组长度

    merge_sort(arr, len);  // 调用归并排序函数

    // 打印排序后的数组
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

// 递归实现归并排序
void merge_sort_recursive(int arr[], int reg[], int start, int end) {
    if (start >= end)
        return;

    int mid = start + (end - start) / 2;
    int start1 = start, end1 = mid;
    int start2 = mid + 1, end2 = end;

    merge_sort_recursive(arr, reg, start1, end1);
    merge_sort_recursive(arr, reg, start2, end2);

    int k = start;
    while (start1 <= end1 && start2 <= end2) {
        reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
    }
    while (start1 <= end1) {
        reg[k++] = arr[start1++];
    }
    while (start2 <= end2) {
        reg[k++] = arr[start2++];
    }

    // 使用memcpy进行数组复制,提高效率
    memcpy(arr + start, reg + start, (end - start + 1) * sizeof(int));
}

// 归并排序入口函数
void merge_sort(int arr[], const int len) {
    int* reg = (int*)malloc(len * sizeof(int));
    if (reg == NULL) {  // 检查内存分配是否成功
        fprintf(stderr, "Memory allocation failed\n");
        exit(EXIT_FAILURE);
    }
    merge_sort_recursive(arr, reg, 0, len - 1);
    free(reg);  // 释放内存
}

归并排序的时间复杂度为 O (n log n),空间复杂度为 O (n),是一种稳定的排序算法,适用于大规模数据的排序。

七、快速排序:高效的分治排序算法

快速排序通过选择基准元素,将数据分为小于基准和大于基准的两部分,分别对两部分进行排序。

// 迭代法
#include <stdio.h>

// 范围结构体
typedef struct _Range {
    int start, end;
} Range;

// 创建新的范围
Range new_Range(int s, int e) {
    Range r;
    r.start = s;
    r.end = e;
    return r;
}

// 交换两个整数
void swap(int *x, int *y) {
    int t = *x;
    *x = *y;
    *y = t;
}

// 快速排序函数
void quick_sort(int arr[], const int len) {
    if (len <= 0)
        return; // 避免 len 等于负值时引发段错误(Segment Fault)

    Range r[len];
    int p = 0;
    r[p++] = new_Range(0, len - 1);

    while (p > 0) {
        Range range = r[--p];
        if (range.start >= range.end)
            continue;

        int mid = arr[(range.start + range.end) / 2]; // 选取中间点为基准点
        int left = range.start, right = range.end;

        do {
            while (arr[left] < mid) ++left;   // 检测基准点左侧是否符合要求
            while (arr[right] > mid) --right; // 检测基准点右侧是否符合要求

            if (left <= right) {
                swap(&arr[left], &arr[right]);
                left++;
                right--; // 移动指针以继续
            }
        } while (left <= right);

        if (range.start < right) r[p++] = new_Range(range.start, right);
        if (range.end > left) r[p++] = new_Range(left, range.end);
    }
}

int main() {
    int arr[] = {22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70};
    int len = sizeof(arr) / sizeof(arr[0]); // 计算数组长度

    quick_sort(arr, len); // 调用快速排序函数

    // 打印排序后的数组
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}
// 递归法
#include <stdio.h>

// 交换两个整数
void swap(int *x, int *y) {
    int t = *x;
    *x = *y;
    *y = t;
}

// 递归实现快速排序
void quick_sort_recursive(int arr[], int start, int end) {
    if (start >= end)
        return;

    int mid = arr[end];
    int left = start, right = end - 1;

    while (left < right) {
        while (left < right && arr[left] < mid)
            left++;
        while (left < right && arr[right] >= mid)
            right--;
        swap(&arr[left], &arr[right]);
    }

    if (arr[left] >= arr[end])
        swap(&arr[left], &arr[end]);
    else
        left++;

    quick_sort_recursive(arr, start, left - 1);
    quick_sort_recursive(arr, left + 1, end);
}

// 快速排序入口函数
void quick_sort(int arr[], int len) {
    quick_sort_recursive(arr, 0, len - 1);
}

int main() {
    int arr[] = {22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70};
    int len = sizeof(arr) / sizeof(arr[0]); // 计算数组长度

    quick_sort(arr, len); // 调用快速排序函数

    // 打印排序后的数组
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

快速排序的平均时间复杂度为 O (n log n),空间复杂度在最坏情况下为 O (n),是一种高效的排序算法,但不稳定。

八、总结

本文详细介绍了 C 语言中多种常见的排序算法,每种算法都有其独特的原理、适用场景和性能特点。在实际应用中,我们应根据数据规模、数据特点和性能要求等因素,合理选择合适的排序

 

相关文章:

  • Deep Reinforcement Learning for Robotics翻译解读
  • 【Python使用】嘿马云课堂web完整实战项目第3篇:增加数据,修改数据【附代码文档】
  • Python菜鸟教程(小程序)
  • UE5把动画导出为视频格式
  • CentOS 7上配置SQL Server链接其他SQL Server服务器
  • 【HTML】纯前端网页小游戏-戳破彩泡
  • 算法刷题记录——LeetCode篇(2.3) [第121~130题](持续更新)
  • 【嵌入式系统设计师】知识点:第2章 嵌入式系统硬件基础知识
  • Latex语法入门之数学公式
  • 【群晖CPU异常占用原因及解决办法】synoscgi_SYNO.Core.System.ProcessGroup_1_list
  • risc-V学习日记(3):编译与链接
  • MySQL 安全与权限管理:数据库的城堡守卫系统
  • DPDK核心优化技术总结
  • 如何在 Linux 上安装 Python
  • leetcode104 二叉树的最大深度
  • Spring Boot中自定义注解的创建与使用
  • C语言编译和链接错题
  • IDEA/WebStrom操作之commit前批量清除console.log()与debugger
  • Java基础 4.5
  • Fortran 中读取 MATLAB 生成的数据文件