几种简单的排序算法(C语言)

目录

[1 简介](#1 简介)

[2 冒泡排序](#2 冒泡排序)

[2.1 基本思路](#2.1 基本思路)

[2.2 代码实现](#2.2 代码实现)

[3 选择排序](#3 选择排序)

[3.1 基本思路](#3.1 基本思路)

[3.2 代码实现](#3.2 代码实现)

[4 插入排序](#4 插入排序)

[4.1 基本思路](#4.1 基本思路)

[4.2 代码实现](#4.2 代码实现)

[5 快速排序](#5 快速排序)

[5.1 基本思路](#5.1 基本思路)

[5.2 代码实现](#5.2 代码实现)

[6 归并排序](#6 归并排序)

[6.1 基本思路](#6.1 基本思路)

[6.2 代码实现](#6.2 代码实现)

[7 基数排序](#7 基数排序)

[7.1 基本思路](#7.1 基本思路)

[7.2 代码实现](#7.2 代码实现)

[8 希尔排序](#8 希尔排序)

[8.1 基本思路](#8.1 基本思路)

[8.2 代码实现](#8.2 代码实现)

[9 堆排序](#9 堆排序)

[9.1 基本思路](#9.1 基本思路)

[9.2 代码实现](#9.2 代码实现)

[10 总结](#10 总结)


1 简介

在编程实践中,排序算法是基础却极其重要的一类算法,广泛应用于数据组织、查找优化和算法竞赛等多个领域。本文将系统地介绍几种常见的排序算法,包括冒泡排序、选择排序、插入排序、归并排序、快速排序、希尔排序、堆排序和基数排序,结合 C 语言代码实现,帮助读者深入理解每种排序的原理、适用场景及其性能差异。无论是初学者打基础,还是备战算法面试,本篇内容都具有较高的参考价值。

2 冒泡排序

冒泡排序(Bubble Sort)是一种简单直观的排序算法,通过不断地交换相邻逆序的元素,使较大的元素像气泡一样逐渐"冒"到序列的末尾。尽管它的性能不如高级排序算法,但因其实现简单,常用于算法入门教学或对小规模数据进行排序。

2.1 基本思路

冒泡排序通过双层循环遍历数组,外层控制排序轮数,内层逐个比较相邻元素,如果前一个元素大于后一个,则交换位置。每一轮结束后,当前未排序部分中最大的元素被移到末尾。重复该过程,直到所有元素有序。

冒泡排序

2.2 代码实现

cpp 复制代码
// 冒泡排序
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 temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }
    }
}

3 选择排序

选择排序(Selection Sort)是一种原地、稳定的排序算法。它通过在未排序区间中反复选择最小(或最大)元素,并将其放到已排序序列的末尾,逐步构建整个有序序列。该算法结构简单、易于实现,但在大数据量下效率较低。

3.1 基本思路

选择排序每一轮从未排序部分中选出最小的元素,将其与当前轮起始位置的元素交换。外层循环控制已排序与未排序区域的分界,内层循环寻找最小值的位置。随着每一轮结束,最小值被"选出"并放入正确的位置,最终实现整体有序。

选择排序

3.2 代码实现

cpp 复制代码
// 选择排序
void sel(int *a, int n) {
    for (int i = 0; i < n - 1; i++) {
        int minIndex = i;
        for (int j = i + 1; j < n; j++) {
            if (a[j] < a[minIndex]) {
                minIndex = j;
            }
        }
        if (minIndex != i) {
            int temp = a[i];
            a[i] = a[minIndex];
            a[minIndex] = temp;
        }
    }
}

4 插入排序

插入排序(Insertion Sort)是一种简单直观的排序算法,适用于小规模数据排序或基本有序的数据集合。它通过构建有序序列,将待排序元素逐个插入到已排序部分的适当位置,从而实现整体有序。插入排序是一种稳定排序,空间复杂度为 O(1)。

4.1 基本思路

从第二个元素开始,逐个将当前元素与它前面的元素进行比较,如果当前元素更小,就将前面的元素后移,直到找到合适位置后插入该元素。这个过程相当于模拟打扑克牌时整理牌的方式,不断向有序部分"插入"新的元素,直到所有元素排序完成。

插入排序

4.2 代码实现

cpp 复制代码
// 插入排序
void insert(int *a, int n) {
    for (int i = 1; i < n; i++) {
        int key = a[i];
        int j = i - 1;
        while (j >= 0 && a[j] > key) {
            a[j + 1] = a[j];
            j--;
        }
        a[j + 1] = key;
    }
}

5 快速排序

快速排序是一种采用分治法思想的高效排序算法,它通过选取一个基准元素,将待排序序列划分为左右两个子序列,其中左子序列的所有元素都不大于基准,右子序列的所有元素都不小于基准,然后递归地对这两个子序列进行快速排序。由于其在多数情况下表现优异,因此是实际应用中最常用的排序算法之一。

5.1 基本思路

快速排序的核心思路是选择一个基准元素,通过一趟扫描将数组划分为两个部分:左边是小于基准的元素,右边是大于基准的元素,然后对这两个部分分别递归执行同样的操作,最终实现整个数组的有序排列。排序过程中通常采用交换的方式在原数组上进行划分,从而实现原地排序,减少额外空间开销。

快速排序

5.2 代码实现

cpp 复制代码
// 交换函数
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 分区函数
int partition(int *a, int low, int high) {
    int pivot = a[high]; // 选择最后一个元素作为基准
    int i = low - 1;      // i 用于追踪小于基准的区间

    for (int j = low; j < high; j++) {
        if (a[j] < pivot) {
            i++;
            swap(&a[i], &a[j]);
        }
    }

    swap(&a[i + 1], &a[high]);
    return i + 1; // 返回基准的最终位置
}

// 快速排序主函数
void quickSort(int *a, int low, int high) {
    if (low < high) {
        int pi = partition(a, low, high); // pi 为基准的最终位置

        quickSort(a, low, pi - 1);  // 递归排序左子数组
        quickSort(a, pi + 1, high); // 递归排序右子数组
    }
}

6 归并排序

归并排序是一种基于分治思想 的稳定排序算法,它将一个数组不断二分为更小的子数组,直到每个子数组只包含一个元素,然后再将这些有序子数组两两合并 为一个有序数组,最终得到排序结果。归并排序在最坏、平均和最好情况下的时间复杂度都为 O(n log n),适用于需要稳定排序且数据量较大的场景。

6.1 基本思路

归并排序首先将整个数组划分成两半,递归地对每一半进行归并排序,直到子数组长度为 1。随后,通过一个"合并过程"将两个有序子数组合并成一个新的有序数组。这个合并过程是归并排序的关键步骤,它通过比较两个子数组的元素大小并顺序地放入新数组中,最终形成排序好的数组。

归并排序

6.2 代码实现

cpp 复制代码
// 合并两个有序数组
void merge(int *a, int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;

    int *L = (int *)malloc(n1 * sizeof(int));
    int *R = (int *)malloc(n2 * sizeof(int));

    for (int i = 0; i < n1; i++) L[i] = a[left + i];
    for (int j = 0; j < n2; j++) R[j] = a[mid + 1 + j];

    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) a[k++] = L[i++];
        else a[k++] = R[j++];
    }

    while (i < n1) a[k++] = L[i++];
    while (j < n2) a[k++] = R[j++];

    free(L);
    free(R);
}

// 归并排序
void mergeSort(int *a, int left, int right) {
    if (left < right) {
        int mid = (left + right) / 2;
        mergeSort(a, left, mid);
        mergeSort(a, mid + 1, right);
        merge(a, left, mid, right);
    }
}

7 基数排序

基数排序(Radix Sort)是一种非比较型排序算法,适用于对整数或字符串进行排序。它通过按位分组和排序来实现整体排序,通常从最低有效位(LSD)到最高有效位(MSD)逐位排序。基数排序的时间复杂度可达到 O(k·n),其中 n 是元素个数,k 是最大元素的位数(或长度)。它适合大规模、位数不多的正整数排序。

7.1 基本思路

基数排序将所有元素按"位"进行排序,从最低位到最高位。每一轮排序都使用一个稳定的排序算法(如计数排序)对当前位进行排序。通过多轮"按位排序"后,整个数组就变得有序。因为是逐位稳定排序,所以可以避免破坏已排序好的低位顺序,从而达到全局有序。

基数排序

7.2 代码实现

cpp 复制代码
// 基数排序
void countSort(int *a, int n, int exp) {
    int output[n];
    int count[10] = {0};

    // 统计每个桶中元素个数
    for (int i = 0; i < n; i++) {
        int digit = (a[i] / exp) % 10;
        count[digit]++;
    }

    // 累加,确定各桶位置
    for (int i = 1; i < 10; i++) {
        count[i] += count[i - 1];
    }

    // 从后向前遍历,稳定排序
    for (int i = n - 1; i >= 0; i--) {
        int digit = (a[i] / exp) % 10;
        output[count[digit] - 1] = a[i];
        count[digit]--;
    }

    // 拷贝结果回原数组
    for (int i = 0; i < n; i++) {
        a[i] = output[i];
    }
}

int getMax(int *a, int n) {
    int max = a[0];
    for (int i = 1; i < n; i++) {
        if (a[i] > max)
            max = a[i];
    }
    return max;
}

void radixSort(int *a, int n) {
    int max = getMax(a, n);
    for (int exp = 1; max / exp > 0; exp *= 10) {
        countSort(a, n, exp);
    }
}

8 希尔排序

希尔排序是插入排序的一种优化形式,也被称为"缩小增量排序"。它通过将整个数组按照一定的间隔(gap)分组,对每组分别进行插入排序,从而减少整体移动次数。随着排序的进行,gap 逐渐缩小,最终当 gap = 1 时,整个数组基本接近有序,再进行一次插入排序即可完成排序。希尔排序的效率高于普通插入排序,尤其在处理大量数据时性能更优。

8.1 基本思路

希尔排序通过定义一个初始间隔(如 n/2),将数组分为若干个子序列,对每个子序列进行插入排序。然后不断缩小间隔(通常减半),重复上述过程,直到间隔为 1 时,执行最后一次插入排序。由于前期大间隔排序已经基本理顺了元素位置,最后的插入排序效率很高。这种逐步逼近有序的策略显著提高了整体排序性能。

8.2 代码实现

cpp 复制代码
// 希尔排序
void shell(int *a, int n) {
    for (int gap = n / 2; gap > 0; gap /= 2) {
        for (int i = gap; i < n; i++) {
            int temp = a[i];
            int j;
            for (j = i; j >= gap && a[j - gap] > temp; j -= gap) {
                a[j] = a[j - gap];
            }
            a[j] = temp;
        }
    }
}

9 堆排序

堆排序(Heap Sort)是一种基于堆这种数据结构的比较型排序算法。它将待排序数组构建成一个大根堆(或小根堆),使得堆顶元素为最大值(或最小值),然后将堆顶元素与堆的最后一个元素交换,缩小堆的范围,并重新调整堆结构。通过反复取出堆顶元素,最终形成有序序列。堆排序的时间复杂度为 O(n log n),不需要额外的空间,适合对大量数据进行原地排序。

9.1 基本思路

堆排序的核心在于两步操作:建堆和调整堆。首先,通过"上滤"或"下滤"方法将整个数组构建成一个最大堆(即每个父节点都大于等于其子节点);接着重复执行:将堆顶元素与末尾元素交换,然后对前 n-1 个元素重新调整为最大堆。每轮都能将当前未排序部分的最大值"沉"到末尾,最终实现整体排序。

9.2 代码实现

cpp 复制代码
// 交换两个元素
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 调整为最大堆
void heapify(int *a, int n, int i) {
    int largest = i;          // 假设父节点最大
    int left = 2 * i + 1;     // 左子节点下标
    int right = 2 * i + 2;    // 右子节点下标

    // 如果左子节点比父节点大
    if (left < n && a[left] > a[largest])
        largest = left;

    // 如果右子节点比当前最大值还大
    if (right < n && a[right] > a[largest])
        largest = right;

    // 如果最大值不是当前节点,交换并继续调整
    if (largest != i) {
        swap(&a[i], &a[largest]);
        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--) {
        swap(&a[0], &a[i]);    // 把堆顶元素放到末尾
        heapify(a, i, 0);      // 重新对剩余元素调整为最大堆
    }
}

10 总结

本文系统地介绍了几种经典排序算法,包括冒泡、选择、插入、快速、归并、希尔、堆、基数排序等,结合 C 语言实现,从原理到代码细节进行了深入解析。通过对每种算法的基本思路、适用场景和时间复杂度的比较,读者可以全面了解各类排序算法的优势与局限,为日后编程实践、算法竞赛或技术面试打下坚实基础。这不仅有助于掌握排序的核心思想,也提升了解决实际问题的能力。

本文的可视化均来自于以下网址:VisuAlgo

感兴趣的可以看一下。

相关推荐
芜湖xin10 分钟前
【题解-洛谷】P1706 全排列问题
算法·dfs
学习噢学个屁1 小时前
基于STM32语音识别柔光台灯
c语言·stm32·单片机·嵌入式硬件·语音识别
曦月逸霜2 小时前
第34次CCF-CSP认证真题解析(目标300分做法)
数据结构·c++·算法
开开心心就好3 小时前
高效Excel合并拆分软件
开发语言·javascript·c#·ocr·排序算法·excel·最小二乘法
海的诗篇_3 小时前
移除元素-JavaScript【算法学习day.04】
javascript·学习·算法
自动驾驶小卡3 小时前
A*算法实现原理以及实现步骤(C++)
算法
Unpredictable2223 小时前
【VINS-Mono算法深度解析:边缘化策略、初始化与关键技术】
c++·笔记·算法·ubuntu·计算机视觉
编程绿豆侠3 小时前
力扣HOT100之多维动态规划:1143. 最长公共子序列
算法·leetcode·动态规划
珂朵莉MM3 小时前
2021 RoboCom 世界机器人开发者大赛-高职组(初赛)解题报告 | 珂学家
java·开发语言·人工智能·算法·职场和发展·机器人