排序算法是数据结构与算法中的基础核心内容,广泛应用于日常开发、数据分析、系统优化等场景。本文将详细拆解十大经典排序算法,包括冒泡排序、选择排序、插入排序等基础算法,以及希尔排序、快速排序、归并排序等进阶算法,从算法原理、核心思路、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)小规模或几乎有序数据选插入排序。
以上代码均经过编译验证,大家可直接复制到编译器中运行测试,也可根据实际需求调整排序方向(升序/降序)或优化细节。如果有疑问,欢迎在评论区交流讨论!