十大经典排序算法详解(附C语言实现+复杂度分析)

排序算法是数据结构与算法中的基础核心内容,广泛应用于日常开发、数据分析、系统优化等场景。本文将详细拆解十大经典排序算法,包括冒泡排序、选择排序、插入排序等基础算法,以及希尔排序、快速排序、归并排序等进阶算法,从算法原理、核心思路、Java实现代码、时间复杂度、空间复杂度、稳定性等维度进行全面解析,帮助大家快速理解并掌握各类排序算法的适用场景与实现逻辑。

本文所有代码均基于C语言实现,经过编译验证可直接运行,默认排序方向为升序,若需降序只需调整比较逻辑即可。建议结合代码示例手动敲写编译,加深对算法细节的理解。

{{{打印代码}}}

cpp 复制代码
void PrintArray(int arr[], int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

一、基础排序算法(时间复杂度O(n²))

基础排序算法实现简单,适合小规模数据排序,核心思路多为通过嵌套循环进行元素比较与交换,时间复杂度均为O(n²)。

1. 冒泡排序(Bubble Sort)

1.1 算法原理

冒泡排序的核心思路是"相邻元素两两比较,将较大的元素逐步'冒泡'到数组末端"。每一轮遍历都会将未排序区间内的最大元素移动到正确位置,经过n-1轮遍历后,数组即可完成排序。

优化点:若某一轮遍历中未发生任何元素交换,说明数组已完全有序,可直接提前结束排序,避免无效遍历。

cpp 复制代码
void BubbleSort(int arr[], int n)
{ // 外层循环:控制排序轮数,共需n-1轮(最后一个元素天然有序)
    for (int i = 0; i < n - 1; i++)
    {
        int swapped = 0; // 标记本轮是否发生交换
        // 内层循环:遍历未排序区间,相邻元素比较交换 - 未排序区间范围:[0, n-1-i)(末尾i个元素已排好序)
        for (int j = 0; j < n - 1 - i; 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; // 无交换,数组已有序,提前退出
        }
    }
}

void TestBubbleSort()
{
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr) / sizeof(arr[0]); // 计算数组长度
    printf("冒泡排序前:");
    PrintArray(arr, n);
    BubbleSort(arr, n);
    printf("冒泡排序后:");
    PrintArray(arr, n);
}
1.3 复杂度与稳定性分析

时间复杂度:最佳情况O(n)(数组已有序,仅需1轮遍历),最坏情况O(n²)(数组逆序),平均情况O(n²)。

空间复杂度:O(1),仅使用常数个临时变量,属于原地排序。

稳定性:稳定。因为当两个相邻元素相等时,不会进行交换,保持原有相对顺序。

2. 选择排序(Selection Sort)

2.1 算法原理

选择排序的核心思路是"每一轮从待排序区间中找到最小(或最大)元素的索引,然后将其与待排序区间的第一个元素交换位置"。经过n-1轮选择与交换后,数组完成排序。

与冒泡排序的区别:选择排序每轮仅进行一次元素交换,而冒泡排序每轮可能进行多次交换,在实际场景中选择排序的交换开销更小。

2.2 C语言实现代码
objectivec 复制代码
void SelectionSort(int arr[], int n)
{ // 外层循环:控制已排序区间的扩大(i为已排序区间末尾下标)
    for (int i = 0; i < n - 1; i++)
    {
        int MinIndex = i; // 记录未排序区间的最小元素下标
        // 内层循环:遍历未排序区间,找到最小值下标
        for (int j = i + 1; j < n; j++)
        {
            if (arr[j] < arr[MinIndex])
            {
                MinIndex = j; // 更新最小元素下标
            }
        }
        // 交换:将最小元素与未排序区间第一个元素交换
        int temp = arr[i];
        arr[i] = arr[MinIndex];
        arr[MinIndex] = temp;
    }
}

// 示例测试函数
void TestSelectionSort()
{
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr) / sizeof(arr[0]);
    printf("选择排序前:");
    PrintArray(arr, n);
    SelectionSort(arr, n);
    printf("选择排序后:");
    PrintArray(arr, n);
}
2.3 复杂度与稳定性分析

时间复杂度:O(n²)。无论数组是否有序,每轮都需要遍历待排序区间找最小值,比较次数固定为n(n-1)/2。

空间复杂度:O(1),原地排序,仅使用常数个临时变量。

稳定性:不稳定。例如数组[2, 2, 1],第一轮会将1与第一个2交换,导致两个2的相对顺序发生改变。

3. 插入排序(Insertion Sort)

3.1 算法原理

插入排序的核心思路是"将数组分为已排序区间和待排序区间,每轮从待排序区间取第一个元素,插入到已排序区间的合适位置"。初始时已排序区间只有第一个元素,待排序区间从第二个元素开始,逐步扩大已排序区间,直至整个数组有序。

插入排序的过程类似整理扑克牌:拿到一张新牌后,与手中已整理好的牌从后往前比较,找到合适的位置插入。

3.2 C语言实现代码
objectivec 复制代码
// 插入排序
void InsertionSort(int arr[], int n)
{ // 外层循环:i从1开始(第一个元素天然属于已排序区间)
    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; // 将key插入到正确位置
    }
}

// 示例测试函数
void TestInsertionSort()
{
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr) / sizeof(arr[0]);
    printf("插入排序前:");
    PrintArray(arr, n);

    InsertionSort(arr, n);

    printf("插入排序后:");
    PrintArray(arr, n);
}
3.3 复杂度与稳定性分析

时间复杂度:最佳情况O(n)(数组已有序,每轮仅需比较1次,无需移动元素),最坏情况O(n²)(数组逆序),平均情况O(n²)。

空间复杂度:O(1),原地排序,仅使用临时变量存储待插入元素。

稳定性:稳定。当待插入元素与已排序区间元素相等时,会插入到相等元素的后面,保持原有相对顺序。

二、进阶排序算法(时间复杂度O(nlogn))

进阶排序算法通过分治、堆化等优化策略,将时间复杂度降低到O(nlogn),适合大规模数据排序,是实际开发中应用更广泛的排序方案。

4. 希尔排序(Shell Sort)

4.1 算法原理

希尔排序是插入排序的优化版,核心思路是"分组插入排序"。通过设定一个增量序列(如n/2、n/4...1),将数组按增量划分为多个子数组,对每个子数组执行插入排序;逐步减小增量,重复分组与排序操作,直至增量为1,此时整个数组已基本有序,最后执行一次普通插入排序即可完成最终排序。

增量的选择直接影响希尔排序的效率,常用的增量序列为希尔增量(n/2^k),也有更优的Hibbard增量、Sedgewick增量等。

4.2 C语言实现代码
cpp 复制代码
// 希尔排序
void ShellSort(int arr[], int n)
{ // 外层循环:逐步缩小增量gap(从n/2开始,每次减半)
    for (int gap = n / 2; gap > 0; gap /= 2)
    {
        // 内层循环:对每组执行插入排序
        for (int i = gap; i < n; i++)
        {
            int key = arr[i]; // 待插入元素
            int j = i;
            // 同组内(下标差为gap)从后往前比较,移动元素
            while (j >= gap && arr[j - gap] > key)
            {
                arr[j] = arr[j - gap];
                j -= gap;
            }
            arr[j] = key; // 插入到组内正确位置
        }
    }
}

// 示例测试函数
void TestShellSort()
{
    int arr[] = {64, 34, 25, 12, 22, 11, 90, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    printf("希尔排序前:");
    PrintArray(arr, n);

    ShellSort(arr, n);

    printf("希尔排序后:");
    PrintArray(arr, n);
}
4.3 复杂度与稳定性分析

时间复杂度:与增量序列相关,希尔增量下最坏情况O(n²),平均情况O(n^1.3);使用更优增量序列可接近O(nlogn)。

空间复杂度:O(1),原地排序,仅使用常数个临时变量。

稳定性:不稳定。分组排序时,相等元素可能被分到不同子数组,导致相对顺序改变(如数组[3, 2, 2],增量为2时,3与第一个2交换,破坏稳定性)。

5. 快速排序(Quick Sort)

5.1 算法原理

快速排序基于分治思想,核心思路是"选基准、分区间、递归排序"。步骤如下:1)从数组中选择一个元素作为基准(pivot);2)将数组划分为两部分,左半部分元素均小于基准,右半部分元素均大于基准(分区操作);3)对左右两个子数组分别递归执行上述步骤,直至子数组长度为1(天然有序)。

优化点:基准选择影响效率,可采用"三数取中"(取数组首、尾、中间元素的中位数作为基准)避免最坏情况;对小规模子数组(如长度≤10)可改用插入排序,减少递归开销。

objectivec 复制代码
void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 分区函数:返回基准元素的最终位置
int partition(int arr[], int low, int high)
{
    int pivot = arr[high]; // 选数组末尾元素作为基准
    int i = (low - 1);     // 小于基准区间的右边界

    for (int j = low; j < high; j++)
    {
        // 若当前元素小于等于基准,加入小于基准区间
        if (arr[j] <= pivot)
        {
            i++;
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[high]); // 将基准放到最终位置
    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);
    }
}

// 示例测试函数
void TestQuickSort()
{
    int arr[] = {64, 34, 25, 12, 22, 11, 90, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    printf("快速排序前:");
    PrintArray(arr, n);

    QuickSort(arr, 0, n - 1);

    printf("快速排序后:");
    PrintArray(arr, n);
}
5.3 复杂度与稳定性分析

时间复杂度:最佳情况O(nlogn)(基准每次均分数组),最坏情况O(n²)(数组有序或逆序,基准每次选到最值),平均情况O(nlogn)。

空间复杂度:O(logn)~O(n),主要为递归栈空间;最佳情况递归深度logn,最坏情况n(可通过尾递归优化将空间复杂度降为O(logn))。

稳定性:不稳定。分区交换时,相等元素的相对顺序可能被破坏(如数组[2, 2, 1],基准为1,交换后两个2的顺序改变)。

6. 归并排序(Merge Sort)

6.1 算法原理

归并排序同样基于分治思想,核心思路是"分拆、合并"。步骤如下:1)将数组从中间拆分为两个等长的子数组,递归拆分直至子数组长度为1;2)将两个有序子数组合并为一个有序数组(合并操作是核心),逐步向上合并,最终得到完整的有序数组。

归并排序的关键是合并操作,需借助临时数组存储合并后的结果,合并完成后再将临时数组的内容复制回原数组。

6.2 C语言实现代码
objectivec 复制代码
// 合并两个有序子数组:arr[low..mid] 和 arr[mid+1..high]
void merge(int arr[], int low, int mid, int high)
{
    int i, j, k;
    int n1 = mid - low + 1; // 左子数组长度
    int n2 = high - mid;    // 右子数组长度

    // 创建临时数组
    int *L = (int *)malloc(n1 * sizeof(int));
    int *R = (int *)malloc(n2 * sizeof(int));

    // 复制数据到临时数组
    for (i = 0; i < n1; i++)
    {
        L[i] = arr[low + i];
    }
    for (j = 0; j < n2; j++)
    {
        R[j] = arr[mid + 1 + j];
    }

    // 合并临时数组到原数组
    i = 0;
    j = 0;
    k = low;
    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++;
    }

    // 释放临时数组内存
    free(L);
    free(R);
}

// 归并排序核心函数
void MergeSort(int arr[], int low, int high)
{
    if (low < high)
    {
        int mid = low + (high - low) / 2; // 避免溢出,等价于(low+high)/2

        // 递归拆分
        MergeSort(arr, low, mid);
        MergeSort(arr, mid + 1, high);

        // 合并
        merge(arr, low, mid, high);
    }
}

void TestMergeSort()
{
    int arr[] = {64, 34, 25, 12, 22, 11, 90, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    printf("归并排序前:");
    PrintArray(arr, n);

    MergeSort(arr, 0, n - 1);

    printf("归并排序后:");
    PrintArray(arr, n);
}
6.3 复杂度与稳定性分析

时间复杂度:O(nlogn)。无论数组是否有序,分拆过程需logn层,每层合并操作需O(n)时间,总时间固定为O(nlogn)。

空间复杂度:O(n)。需额外开辟临时数组存储合并结果,递归栈空间为O(logn),整体空间复杂度由临时数组决定。

稳定性:稳定。合并过程中,当两个子数组元素相等时,优先选择左子数组的元素,保持原有相对顺序。

7. 堆排序(Heap Sort)

7.1 算法原理

堆排序基于堆(完全二叉树)的数据结构,核心思路是"建堆、排序"。堆分为大顶堆(父节点值≥子节点值)和小顶堆(父节点值≤子节点值),升序排序使用大顶堆。步骤如下:1)将无序数组构建为大顶堆;2)将堆顶元素(最大值)与堆尾元素交换,此时堆尾为有序区;3)调整剩余元素为新的大顶堆,重复步骤2,直至有序区覆盖整个数组。

核心操作是"堆调整"(heapify):当某个节点的左右子树均为大顶堆,而该节点值小于子节点时,将该节点与较大子节点交换,再递归调整受影响的子树。

7.2 C语言实现代码
cpp 复制代码
void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 堆调整:将以root为根的子树调整为大顶堆
void heapify(int arr[], int n, int root)
{
    int largest = root;       // 初始化最大值为根节点
    int left = 2 * root + 1;  // 左子节点索引
    int right = 2 * root + 2; // 右子节点索引

    // 若左子节点大于根节点,更新最大值
    if (left < n && arr[left] > arr[largest])
    {
        largest = left;
    }

    // 若右子节点大于当前最大值,更新最大值
    if (right < n && arr[right] > arr[largest])
    {
        largest = right;
    }

    // 若最大值不是根节点,交换并递归调整子树
    if (largest != root)
    {
        swap(&arr[root], &arr[largest]);
        heapify(arr, n, largest);
    }
}

// 堆排序
void HeapSort(int arr[], int n)
{
    // 1. 构建大顶堆(从最后一个非叶子节点开始调整)
    for (int i = n / 2 - 1; i >= 0; i--)
    {
        heapify(arr, n, i);
    }

    // 2. 排序:逐步提取堆顶元素
    for (int i = n - 1; i > 0; i--)
    {
        swap(&arr[0], &arr[i]); // 堆顶(最大值)与堆尾交换
        heapify(arr, i, 0);     // 调整剩余元素为大顶堆
    }
}

void TestHeapSort()
{
    int arr[] = {64, 34, 25, 12, 22, 11, 90, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    printf("堆排序排序前:");
    PrintArray(arr, n);

    HeapSort(arr, n);

    printf("堆排序排序后:");
    PrintArray(arr, n);
}
7.3 复杂度与稳定性分析

时间复杂度:O(nlogn)。建堆过程时间复杂度O(n),排序过程需执行n-1次堆调整,每次堆调整时间O(logn),总时间O(nlogn)。

空间复杂度:O(1),原地排序,仅使用常数个临时变量(递归实现的堆调整空间复杂度为O(logn),此处为迭代实现,空间O(1))。

稳定性:不稳定。堆调整过程中,相等元素的相对顺序可能被破坏(如数组[2, 2, 3],建堆后交换堆顶与堆尾,两个2的顺序改变)。

三、线性时间排序算法(时间复杂度O(n))

线性时间排序算法不基于元素比较,而是利用元素的数值特征(如范围、位数)实现排序,仅适用于特定场景(如元素为整数、数值范围已知等),时间复杂度可达O(n)。

8. 桶排序(Bucket Sort)

8.1 算法原理

桶排序的核心思路是"分桶、排序、合并"。步骤如下:1)根据元素范围将数组划分为若干个有序的桶(如元素范围0~99,可划分为10个桶,每个桶存储10个数值区间的元素);2)对每个桶内的元素执行排序(可选用插入排序、快速排序等);3)将所有桶内的有序元素依次合并,得到最终的有序数组。

适用场景:元素分布均匀的大规模数据(如学生成绩、用户年龄等),桶的数量选择直接影响效率,一般建议桶数量与元素数量相当。

8.2 C语言实现代码
cpp 复制代码
void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

#define BUCKET_NUM 10 // 桶的数量
// 链表节点(用于存储桶内元素)
typedef struct Node
{
    int data;
    struct Node *next;
} Node;

// 初始化桶(创建空链表)
Node *InitBucket()
{
    Node *head = (Node *)malloc(sizeof(Node));
    head->next = NULL;
    return head;
}

// 插入元素到桶(按升序插入)
void InsertIntoBucket(Node *head, int data)
{
    Node *NewNode = (Node *)malloc(sizeof(Node));
    NewNode->data = data;
    NewNode->next = NULL;

    Node *p = head;
    // 找到插入位置
    while (p->next != NULL && p->next->data < data)
    {
        p = p->next;
    }
    NewNode->next = p->next;
    p->next = NewNode;
}

// 桶排序
void BucketSort(int arr[], int n)
{
    // 1. 初始化所有桶
    Node *buckets[BUCKET_NUM];
    for (int i = 0; i < BUCKET_NUM; i++)
    {
        buckets[i] = InitBucket();
    }
    // 2. 将元素分配到对应桶(假设元素范围0~99,桶i存储10*i ~10*(i+1)-1的元素)
    for (int i = 0; i < n; i++)
    {
        int BucketIdx = arr[i] / 10;
        InsertIntoBucket(buckets[BucketIdx], arr[i]);
    }

    // 3. 合并所有桶的元素到原数组
    int idx = 0;
    for (int i = 0; i < BUCKET_NUM; i++)
    {
        Node *p = buckets[i]->next;
        while (p != NULL)
        {
            arr[idx++] = p->data;
            Node *temp = p;
            p = p->next;
            free(temp); // 释放节点内存
        }
        free(buckets[i]); // 释放桶的头节点
    }
}

void TestBucketSort()
{
    int arr[] = {64, 34, 25, 12, 22, 11, 90, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    printf("桶排序排序前:");
    PrintArray(arr, n);

    BucketSort(arr, n);

    printf("桶排序排序后:");
    PrintArray(arr, n);
}
8.3 复杂度与稳定性分析

时间复杂度:最佳情况O(n)(元素均匀分布,每个桶内元素数量为1,无需排序),最坏情况O(n²)(所有元素分到同一个桶,桶内排序为O(n²)),平均情况O(n + k)(k为桶内排序总时间,均匀分布时k≈n)。

空间复杂度:O(n + k),k为桶的数量,需额外存储n个元素和k个桶的头节点。

稳定性:稳定。桶内排序选用稳定排序(如插入排序),且元素分配到桶时保持相对顺序,整体排序稳定。

9. 计数排序(Counting Sort)

9.1 算法原理

计数排序是一种非比较排序,核心思路是"统计频率、计算位置、放置元素"。适用于元素为非负整数且数值范围较小的场景(如0~100的成绩)。步骤如下:1)找出数组中的最大值max,创建大小为max+1的计数数组,统计每个元素的出现频率;2)将计数数组转换为前缀和数组,前缀和表示对应元素在最终有序数组中的最后一个位置;3)从原数组末尾开始遍历,根据前缀和数组确定元素的位置,放置后更新前缀和(保证稳定性)。

9.2 C语言实现代码
cpp 复制代码
void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 计数排序(稳定版)
void CountingSort(int arr[], int n)
{
    if (n <= 1)
    {
        return;
    }

    // 1. 找出数组中的最大值
    int max = arr[0];
    for (int i = 1; i < n; i++)
    {
        if (arr[i] > max)
        {
            max = arr[i];
        }
    }

    // 2. 初始化计数数组,统计元素频率
    int *count = (int *)calloc(max + 1, sizeof(int));
    for (int i = 0; i < n; i++)
    {
        count[arr[i]]++;
    }

    // 3. 转换为前缀和数组,确定元素最终位置
    for (int i = 1; i <= max; i++)
    {
        count[i] += count[i - 1];
    }

    // 4. 创建临时数组存储排序结果(从后往前遍历,保证稳定性)
    int *temp = (int *)malloc(n * sizeof(int));
    for (int i = n - 1; i >= 0; i--)
    {
        int pos = count[arr[i]] - 1; // 计算位置(前缀和减1为索引)
        temp[pos] = arr[i];
        count[arr[i]]--; // 更新前缀和
    }

    // 5. 将临时数组结果复制回原数组
    for (int i = 0; i < n; i++)
    {
        arr[i] = temp[i];
    }

    // 释放内存
    free(count);
    free(temp);
}

void TestCountingSort()
{
    int arr[] = {64, 34, 25, 12, 22, 11, 90, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    printf("计数排序前:");
    PrintArray(arr, n);

    CountingSort(arr, n);

    printf("计数排序后:");
    PrintArray(arr, n);
}
9.3 复杂度与稳定性分析

时间复杂度:O(n + max),n为元素个数,max为元素最大值。统计频率、计算前缀和、放置元素均为线性时间。

空间复杂度:O(n + max),需额外存储计数数组(大小max+1)和临时数组(大小n)。

稳定性:稳定。从原数组末尾开始遍历放置元素,相等元素的相对顺序不会改变。

10. 基数排序(Radix Sort)

1.1 算法原理

基数排序是一种非比较排序,核心思路是"按位排序"。通过对元素的每一位(个位、十位、百位...)依次进行排序(从低位到高位或从高位到低位),最终得到有序数组。每一位的排序通常选用计数排序或桶排序(保证稳定性),因为若某一位排序不稳定,会破坏前一位排序的结果。

步骤如下:1)找出数组中元素的最大位数d;2)从低位(个位,d=1)到高位(第d位),对每一位执行计数排序;3)完成所有位的排序后,数组有序。

10.2 C语言实现代码
cpp 复制代码
void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 获取x的第d位数字(d=1为个位,d=2为十位...)
int GetDigit(int x, int d)
{
    int divisor = 1;
    for (int i = 1; i < d; i++)
    {
        divisor *= 10;
    }
    return (x / divisor) % 10;
}

// 对数组按第d位进行计数排序(基数排序的子操作)
void RadixCountSort(int arr[], int n, int d)
{
    int count[10] = {0}; // 0~9共10个数字,计数数组大小为10
    int *temp = (int *)malloc(n * sizeof(int));

    // 1. 统计第d位数字的频率
    for (int i = 0; i < n; i++)
    {
        int digit = GetDigit(arr[i], d);
        count[digit]++;
    }

    // 2. 计算前缀和,确定位置
    for (int i = 1; i < 10; i++)
    {
        count[i] += count[i - 1];
    }

    // 3. 从后往前放置元素,保证稳定性
    for (int i = n - 1; i >= 0; i--)
    {
        int digit = GetDigit(arr[i], d);
        int pos = count[digit] - 1;
        temp[pos] = arr[i];
        count[digit]--;
    }

    // 4. 复制回原数组
    for (int i = 0; i < n; i++)
    {
        arr[i] = temp[i];
    }

    free(temp);
}

// 基数排序(从低位到高位)
void RadixSort(int arr[], int n)
{
    if (n <= 1)
        return;

    // 1. 找出数组中元素的最大位数
    int max = arr[0];
    for (int i = 1; i < n; i++)
    {
        if (arr[i] > max)
        {
            max = arr[i];
        }
    }
    int d = 0;
    while (max > 0)
    {
        d++;
        max /= 10;
    }

    // 2. 对每一位执行计数排序
    for (int i = 1; i <= d; i++)
    {
        RadixCountSort(arr, n, i);
    }
}

void TestRadixSort()
{
    int arr[] = {64, 34, 25, 12, 22, 11, 90, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    printf("基数排序排序前:");
    PrintArray(arr, n);

    RadixSort(arr, n);

    printf("基数排序排序后:");
    PrintArray(arr, n);
}
10.3 复杂度与稳定性分析

时间复杂度:O(d*(n + k)),d为最大元素的位数,k为基数(此处k=10,0~9的数字)。每一位排序时间为O(n + k),共执行d次。

空间复杂度:O(n + k),需额外存储临时数组(大小n)和计数数组(大小k)。

稳定性:稳定。每一位的排序选用稳定的计数排序,保证相等元素的相对顺序在每一轮排序中不改变。

四、十大排序算法总结

为方便大家快速对比选择,整理十大排序算法的核心特性如下:

排序算法 时间复杂度(平均) 空间复杂度 稳定性 适用场景
冒泡排序 O(n²) O(1) 稳定 小规模数据、几乎有序的数据
选择排序 O(n²) O(1) 不稳定 小规模数据、交换开销较大的场景
插入排序 O(n²) O(1) 稳定 小规模数据、几乎有序的数据(实际应用中常用作子数组排序)
希尔排序 O(n^1.3) O(1) 不稳定 中等规模数据,对插入排序的优化
快速排序 O(nlogn) O(logn) 不稳定 大规模数据,通用排序首选(实际开发中应用最广)
归并排序 O(nlogn) O(n) 稳定 大规模数据、需要稳定排序的场景(如对象排序)
堆排序 O(nlogn) O(1) 不稳定 大规模数据、内存紧张的场景(原地排序,无额外空间开销)
桶排序 O(n) O(n + k) 稳定 大规模、分布均匀的数据(如成绩、年龄)
计数排序 O(n + max) O(n + max) 稳定 非负整数、数值范围较小的数据(如0~100的分数)
基数排序 O(d*(n + k)) O(n + k) 稳定 大规模整数数据(如身份证号、手机号)

核心选择建议:1)通用场景优先选快速排序;2)需要稳定排序选归并排序;3)内存有限选堆排序;4)数据范围已知且较小选计数排序;5)小规模或几乎有序数据选插入排序。

以上代码均经过编译验证,大家可直接复制到编译器中运行测试,也可根据实际需求调整排序方向(升序/降序)或优化细节。如果有疑问,欢迎在评论区交流讨论!

相关推荐
霑潇雨12 小时前
题解 | 分析每个商品在不同时间段的销售情况
数据库·sql·算法·笔试
金枪不摆鳍12 小时前
算法-动态规划
算法·动态规划
季明洵12 小时前
Java中哈希
java·算法·哈希
jaysee-sjc12 小时前
【练习十】Java 面向对象实战:智能家居控制系统
java·开发语言·算法·智能家居
我是大咖12 小时前
C 语言笔记: const 指针 + 堆内存申请
c语言·开发语言
cici1587413 小时前
基于MATLAB实现eFAST全局敏感性分析
算法·matlab
gihigo199813 小时前
MATLAB实现K-SVD算法
数据结构·算法·matlab
dyyx11113 小时前
C++编译期数据结构
开发语言·c++·算法
Swift社区13 小时前
LeetCode 384 打乱数组
算法·leetcode·职场和发展
running up that hill13 小时前
日常刷题记录
java·数据结构·算法