10种常用排序算法
0——序言
以下是常用的排序算法及其对应的时间复杂度:
- 冒泡排序(Bubble Sort):
- 最好情况时间复杂度:O(n)
- 平均情况时间复杂度:O(n^2)
- 最坏情况时间复杂度:O(n^2)
- 选择排序(Selection Sort):
- 最好情况时间复杂度:O(n^2)
- 平均情况时间复杂度:O(n^2)
- 最坏情况时间复杂度:O(n^2)
- 插入排序(Insertion Sort):
- 最好情况时间复杂度:O(n)
- 平均情况时间复杂度:O(n^2)
- 最坏情况时间复杂度:O(n^2)
- 希尔排序(Shell Sort):
- 最好情况时间复杂度:O(n log n)
- 平均情况时间复杂度:取决于步长序列的选择
- 最坏情况时间复杂度:O(n^2)
- 归并排序(Merge Sort):
- 最好情况时间复杂度:O(n log n)
- 平均情况时间复杂度:O(n log n)
- 最坏情况时间复杂度:O(n log n)
- 快速排序(Quick Sort):
- 最好情况时间复杂度:O(n log n)
- 平均情况时间复杂度:O(n log n)
- 最坏情况时间复杂度:O(n^2)
- 堆排序(Heap Sort):
- 最好情况时间复杂度:O(n log n)
- 平均情况时间复杂度:O(n log n)
- 最坏情况时间复杂度:O(n log n)
- 计数排序(Counting Sort):
- 最好情况时间复杂度:O(n + k),其中 k 为数据范围
- 平均情况时间复杂度:O(n + k)
- 最坏情况时间复杂度:O(n + k)
- 桶排序(Bucket Sort):
- 最好情况时间复杂度:O(n)
- 平均情况时间复杂度:O(n)
- 最坏情况时间复杂度:O(n^2)
- 基数排序(Radix Sort):
- 最好情况时间复杂度:O(n * k),其中 k 为数字的位数
- 平均情况时间复杂度:O(n * k)
- 最坏情况时间复杂度:O(n * k)
以上是常见的排序算法及其对应的时间复杂度。请注意,这些时间复杂度是基于最坏情况或平均情况下的分析,实际运行时间可能会受到多种因素的影响。
1.冒泡排序
冒泡排序是一种简单且直观的排序算法。它的基本原理是通过相邻元素之间的比较和交换来逐步将最大(或最小)的元素“冒泡”到数组的末尾,从而实现排序。
下面是冒泡排序的实现步骤:
- 从数组的第一个元素开始,比较相邻的两个元素。如果前一个元素大于后一个元素,则交换它们的位置。
- 继续向后遍历,重复以上比较和交换的步骤,直到最后一个元素。此时,最大的元素已经“冒泡”到了数组的末尾。
- 重复上述步骤,但是这次只需要比较和交换前 n-1 个元素,其中 n 是数组的长度,因为最大的元素已经在上一步被放置在了正确的位置。
- 重复进行以上步骤,直到整个数组排序完成。
以下是使用C语言实现冒泡排序的示例代码:
#include <stdio.h>
// 冒泡排序函数
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
// 如果前一个元素大于后一个元素,则交换它们的位置
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main() {
int arr[] = {5, 2, 8, 12, 1, 6};
int n = sizeof(arr) / sizeof(arr[0]);
printf("原始数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
bubbleSort(arr, n);
printf("\n排序后的数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
运行以上代码,输出结果为:
复制代码原始数组:5 2 8 12 1 6
排序后的数组:1 2 5 6 8 12
2.选择排序
选择排序是一种简单直观的排序算法。它的基本原理是每次从未排序的部分中选择最小(或最大)的元素,并将其放置在已排序部分的末尾,从而逐步构建有序序列。
下面是选择排序的实现步骤:
- 遍历数组,将第一个元素设为当前最小值。
- 从当前最小值的下一个位置开始,遍历数组,找到比当前最小值更小的元素,并更新最小值的索引。
- 在一次完整遍历后,将找到的最小值与当前位置的元素进行交换。
- 重复以上步骤,每次遍历将最小值放置在已排序部分的末尾,直到整个数组排序完成。
以下是使用C语言实现选择排序的示例代码:
c复制代码#include <stdio.h>
// 选择排序函数
void selectionSort(int arr[], int n) {
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[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}
}
int main() {
int arr[] = {5, 2, 8, 12, 1, 6};
int n = sizeof(arr) / sizeof(arr[0]);
printf("原始数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
selectionSort(arr, n);
printf("\n排序后的数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
运行以上代码,输出结果为:
原始数组:5 2 8 12 1 6
排序后的数组:1 2 5 6 8 12
3.插入排序
插入排序是一种简单直观的排序算法。它的基本原理是将数组分为已排序和未排序两部分,每次从未排序部分取出一个元素,将其插入到已排序部分的正确位置,从而逐步构建有序序列。
下面是插入排序的实现步骤:
- 从数组的第二个元素开始,将其视为已排序部分。
- 从已排序部分的最后一个元素开始,将当前元素与前面的元素逐个比较,直到找到合适的位置插入。
- 将当前元素插入到合适的位置后,已排序部分的长度加一。
- 重复以上步骤,直到整个数组排序完成。
以下是使用C语言实现插入排序的示例代码:
#include <stdio.h>
// 插入排序函数
void insertionSort(int arr[], int n) {
for (int i = 1; i < n; i++) {
int key = arr[i]; // 当前要插入的元素
int j = i - 1;
// 将当前元素与已排序部分的元素逐个比较并移动
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
// 将当前元素插入到合适的位置
arr[j + 1] = key;
}
}
int main() {
int arr[] = {5, 2, 8, 12, 1, 6};
int n = sizeof(arr) / sizeof(arr[0]);
printf("原始数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
insertionSort(arr, n);
printf("\n排序后的数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
运行以上代码,输出结果为:
原始数组:5 2 8 12 1 6
排序后的数组:1 2 5 6 8 12
4.希尔排序
希尔排序是一种改进版的插入排序算法,也被称为缩小增量排序。它的基本原理是将数组按照一定的间隔分组,对每个分组进行插入排序,然后逐步缩小间隔,直到间隔为1,完成最后一次插入排序,从而实现整个数组的排序。
下面是希尔排序的实现步骤:
- 选择一个间隔序列,通常为原数组长度的一半,并将其记为 gap。
- 将数组按照 gap 进行分组,对每个分组进行插入排序。
- 缩小 gap 的值,通常将 gap 减半,重复步骤2,直到 gap 的值为1。
- 使用 gap 为1进行最后一次插入排序,完成整个数组的排序。
以下是使用C语言实现希尔排序的示例代码:
#include <stdio.h>
// 希尔排序函数
void shellSort(int arr[], int n) {
int gap = n / 2; // 初始间隔
while (gap > 0) {
for (int i = gap; i < n; i++) {
int key = arr[i];
int j = i;
// 对当前分组进行插入排序
while (j >= gap && arr[j - gap] > key) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = key;
}
gap /= 2; // 缩小间隔
}
}
int main() {
int arr[] = {5, 2, 8, 12, 1, 6};
int n = sizeof(arr) / sizeof(arr[0]);
printf("原始数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
shellSort(arr, n);
printf("\n排序后的数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
运行以上代码,输出结果为:
原始数组:5 2 8 12 1 6
排序后的数组:1 2 5 6 8 12
5.归并排序
归并排序是一种分治算法,它的基本原理是将数组分成两个子数组,分别对子数组进行排序,然后将两个已排序的子数组合并成一个有序数组,从而实现整个数组的排序。
下面是归并排序的实现步骤:
- 将数组分成两个子数组,递归地对每个子数组进行归并排序。
- 将两个已排序的子数组合并成一个有序数组。
- 重复以上步骤,直到整个数组排序完成。
以下是使用C语言实现归并排序的示例代码:
#include <stdio.h>
// 合并两个子数组
void merge(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1; // 左子数组的长度
int n2 = right - mid; // 右子数组的长度
// 创建临时数组存储左右子数组
int L[n1], R[n2];
// 将数据复制到临时数组
for (int i = 0; i < n1; i++) {
L[i] = arr[left + i];
}
for (int j = 0; j < n2; j++) {
R[j] = arr[mid + 1 + j];
}
// 合并临时数组中的元素到原数组
int i = 0, j = 0, k = left;
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++;
}
}
// 归并排序函数
void mergeSort(int arr[], int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
// 递归地对左右子数组进行归并排序
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// 合并两个已排序的子数组
merge(arr, left, mid, right);
}
}
int main() {
int arr[] = {5, 2, 8, 12, 1, 6};
int n = sizeof(arr) / sizeof(arr[0]);
printf("原始数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
mergeSort(arr, 0, n - 1);
printf("\n排序后的数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
运行以上代码,输出结果为:
原始数组:5 2 8 12 1 6
排序后的数组:1 2 5 6 8 12
6.快速排序
快速排序(Quick Sort)是一种常用的排序算法,其原理和实现步骤如下:
原理:
- 选择一个基准元素(pivot)。
- 将数组分割成两个子数组,其中一个子数组的所有元素小于等于基准元素,另一个子数组的所有元素大于基准元素。
- 对这两个子数组分别递归地进行快速排序。
- 最后将两个子数组合并起来即可得到有序数组。
实现步骤:
- 选择基准元素。通常选择数组的第一个元素作为基准元素,也可以选择随机位置的元素。
- 进行分区操作。定义两个指针,一个指向数组的起始位置(low),一个指向数组的结束位置(high)。将基准元素放在正确的位置上,使得基准元素左边的元素都小于等于它,右边的元素都大于它。可以使用两个指针从数组的两端开始遍历,分别找到一个大于基准元素和一个小于基准元素的元素,然后交换它们的位置,直到两个指针相遇。
- 递归地对分割后的子数组进行快速排序。对基准元素左边的子数组和右边的子数组分别进行递归排序,直到子数组的长度为1或0。
- 合并子数组。将左边子数组、基准元素和右边子数组合并起来,即可得到有序数组。
以下是使用C语言实现快速排序的示例代码:
#include <stdio.h>
// 交换两个元素的值
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 分区操作,返回基准元素的索引
int partition(int arr[], int low, int high) {
int pivot = arr[low]; // 选择第一个元素作为基准元素
int i = low, j = high;
while (i < j) {
// 从右向左找到第一个小于基准元素的元素
while (i < j && arr[j] >= pivot) {
j--;
}
// 从左向右找到第一个大于基准元素的元素
while (i < j && arr[i] <= pivot) {
i++;
}
// 交换这两个元素的位置
if (i < j) {
swap(&arr[i], &arr[j]);
}
}
// 将基准元素放到正确的位置上
arr[low] = arr[i];
arr[i] = pivot;
return i;
}
// 快速排序
void quickSort(int arr[], int low, int high) {
if (low < high) {
int pivotIndex = partition(arr, low, high); // 分区操作,获取基准元素的索引
quickSort(arr, low, pivotIndex - 1); // 递归排序左边子数组
quickSort(arr, pivotIndex + 1, high); // 递归排序右边子数组
}
}
// 打印数组
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {9, 4, 2, 7, 5, 1, 8, 3, 6};
int size = sizeof(arr) / sizeof(arr[0]);
printf("Original array: ");
printArray(arr, size);
quickSort(arr, 0, size - 1);
printf("Sorted array: ");
printArray(arr, size);
return 0;
}
以上代码实现了快速排序算法,通过选择基准元素和分区操作,对数组进行递归排序,最后得到有序数组。
7.堆排序
堆排序是一种基于二叉堆的排序算法,它的基本原理是通过构建一个最大堆(或最小堆),然后将堆顶元素与堆的最后一个元素交换,并将堆的大小减少1,再对新的堆顶元素进行调整,重复这个过程直到堆为空,从而实现整个数组的排序。
下面是堆排序的实现步骤:
- 构建一个最大堆(或最小堆)。
- 将堆顶元素与堆的最后一个元素交换。
- 将堆的大小减少1,即排除已排序的元素。
- 对新的堆顶元素进行调整,使其满足堆的性质。
- 重复步骤2至4,直到堆为空。
以下是使用C语言实现堆排序的示例代码:
#include <stdio.h>
// 调整堆
void heapify(int arr[], int n, int i) {
int largest = i; // 初始化最大元素为根节点
int left = 2 * i + 1; // 左子节点索引
int right = 2 * i + 2; // 右子节点索引
// 如果左子节点大于根节点,则更新最大元素索引
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
// 如果右子节点大于根节点,则更新最大元素索引
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
// 如果最大元素索引不是根节点,则交换根节点和最大元素
if (largest != i) {
int temp = arr[i];
arr[i] = arr[largest];
arr[largest] = temp;
// 递归地调整交换后的子树
heapify(arr, n, largest);
}
}
// 堆排序函数
void heapSort(int arr[], int n) {
// 构建最大堆
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
// 交换堆顶元素与堆的最后一个元素,并调整堆
for (int i = n - 1; i > 0; i--) {
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
heapify(arr, i, 0);
}
}
int main() {
int arr[] = {5, 2, 8, 12, 1, 6};
int n = sizeof(arr) / sizeof(arr[0]);
printf("原始数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
heapSort(arr, n);
printf("\n排序后的数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
运行以上代码,输出结果为:
原始数组:5 2 8 12 1 6
排序后的数组:1 2 5 6 8 12
8.计数排序
计数排序是一种非比较排序算法,它的基本原理是通过确定每个元素在排序后的序列中的位置,从而实现整个数组的排序。计数排序假设输入的元素都是非负整数,并且已知待排序数组的最大值,根据最大值创建一个计数数组,统计每个元素的出现次数,然后根据计数数组构建有序序列。
下面是计数排序的实现步骤:
- 找出待排序数组的最大值,并根据最大值创建一个计数数组,数组长度为最大值加1。
- 遍历待排序数组,统计每个元素的出现次数,并将统计结果存储在计数数组中。
- 对计数数组进行累加操作,得到每个元素在排序后的序列中的位置。
- 创建一个与待排序数组长度相同的临时数组。
- 逆序遍历待排序数组,根据计数数组的值将元素放入临时数组中,并将计数数组的值减1。
- 将临时数组的元素复制回待排序数组,完成排序。
以下是使用C语言实现计数排序的示例代码:
#include <stdio.h>
// 计数排序函数
void countingSort(int arr[], int n) {
int max = arr[0];
// 找出最大值
for (int i = 1; i < n; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
// 创建计数数组并初始化为0
int count[max + 1];
for (int i = 0; i <= max; i++) {
count[i] = 0;
}
// 统计每个元素的出现次数
for (int i = 0; i < n; i++) {
count[arr[i]]++;
}
// 对计数数组进行累加操作
for (int i = 1; i <= max; i++) {
count[i] += count[i - 1];
}
// 创建临时数组存储排序结果
int output[n];
// 逆序遍历待排序数组,根据计数数组的值将元素放入临时数组中
for (int i = n - 1; i >= 0; i--) {
output[count[arr[i]] - 1] = arr[i];
count[arr[i]]--;
}
// 将临时数组的元素复制回待排序数组
for (int i = 0; i < n; i++) {
arr[i] = output[i];
}
}
int main() {
int arr[] = {5, 2, 8, 12, 1, 6};
int n = sizeof(arr) / sizeof(arr[0]);
printf("原始数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
countingSort(arr, n);
printf("\n排序后的数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
运行以上代码,输出结果为:
原始数组:5 2 8 12 1 6
排序后的数组:1 2 5 6 8 12
9.桶排序
桶排序是一种排序算法,它的基本原理是将待排序的元素划分到不同的桶中,每个桶内部使用其他排序算法(如插入排序或快速排序)进行排序,然后按照桶的顺序将各个桶中的元素依次取出,从而实现整个数组的排序。桶排序适用于待排序的元素均匀分布在一个范围内的情况。
下面是桶排序的实现步骤:
- 确定桶的数量和范围。根据待排序数组的最大值和最小值,确定桶的数量,并计算出每个桶的范围。
- 创建桶,并将待排序的元素分配到各个桶中。根据元素的值,将其放入对应的桶中。
- 对每个桶内部的元素使用其他排序算法进行排序。可以选择插入排序、快速排序等。
- 按照桶的顺序将各个桶中的元素依次取出,放入结果数组中,完成排序。
以下是使用C语言实现桶排序的示例代码:
#include <stdio.h>
#include <stdlib.h>
// 桶排序函数
void bucketSort(int arr[], int n, int bucketSize) {
int max = arr[0];
int min = arr[0];
// 找出最大值和最小值
for (int i = 1; i < n; i++) {
if (arr[i] > max) {
max = arr[i];
}
if (arr[i] < min) {
min = arr[i];
}
}
// 计算桶的数量
int bucketCount = (max - min) / bucketSize + 1;
// 创建桶
int** buckets = (int**)malloc(bucketCount * sizeof(int*));
for (int i = 0; i < bucketCount; i++) {
buckets[i] = (int*)malloc(bucketSize * sizeof(int));
}
// 将元素分配到桶中
for (int i = 0; i < n; i++) {
int bucketIndex = (arr[i] - min) / bucketSize;
for (int j = 0; j < bucketSize; j++) {
if (buckets[bucketIndex][j] == 0) {
buckets[bucketIndex][j] = arr[i];
break;
}
}
}
// 对每个桶内部的元素进行排序
for (int i = 0; i < bucketCount; i++) {
// 使用插入排序对桶内的元素进行排序
for (int j = 1; j < bucketSize; j++) {
int temp = buckets[i][j];
int k = j - 1;
while (k >= 0 && buckets[i][k] > temp) {
buckets[i][k + 1] = buckets[i][k];
k--;
}
buckets[i][k + 1] = temp;
}
}
// 将各个桶中的元素依次取出,放入结果数组中
int index = 0;
for (int i = 0; i < bucketCount; i++) {
for (int j = 0; j < bucketSize; j++) {
if (buckets[i][j] != 0) {
arr[index++] = buckets[i][j];
}
}
}
// 释放桶的内存空间
for (int i = 0; i < bucketCount; i++) {
free(buckets[i]);
}
free(buckets);
}
int main() {
int arr[] = {5, 2, 8, 12, 1, 6};
int n = sizeof(arr) / sizeof(arr[0]);
int bucketSize = 3;
printf("原始数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
bucketSort(arr, n, bucketSize);
printf("\n排序后的数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
运行以上代码,输出结果为:
原始数组:5 2 8 12 1 6
排序后的数组:1 2 5 6 8 12
10.基数排序
基数排序是一种非比较排序算法,它的基本原理是将待排序的元素按照低位到高位的顺序依次进行排序。基数排序适用于待排序的元素是非负整数的情况。
下面是基数排序的实现步骤:
- 确定排序的轮数。根据待排序元素的最大位数,确定需要进行多少轮排序。
- 创建10个桶,用于存放每个位上的元素。每个桶的范围是0到9。
- 从低位到高位,依次对每个位上的元素进行排序。
- 根据当前位的值,将元素放入对应的桶中。
- 按照桶的顺序将各个桶中的元素依次取出,放入结果数组中。
- 重复步骤3到步骤5,直到对所有位都进行了排序。
以下是使用C语言实现基数排序的示例代码:
#include <stdio.h>
// 获取数组中的最大值
int getMax(int arr[], int n) {
int max = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
// 对数组按照指定位数进行计数排序
void countSort(int arr[], int n, int exp) {
int output[n];
int 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 = getMax(arr, n);
// 对每个位进行排序
for (int exp = 1; max / exp > 0; exp *= 10) {
countSort(arr, n, exp);
}
}
int main() {
int arr[] = {170, 45, 75, 90, 802, 24, 2, 66};
int n = sizeof(arr) / sizeof(arr[0]);
printf("原始数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
radixSort(arr, n);
printf("\n排序后的数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
运行以上代码,输出结果为:
原始数组:170 45 75 90 802 24 2 66
排序后的数组:2 24 45 66 75 90 170 802