排序大全
Kingdom_Tears · · 算法·理论
冒泡排序
冒泡排序是一种简单直观的排序算法。它通过重复地遍历要排序的数列,一次比较两个相邻的元素,如果它们的顺序错误就交换它们的位置。这个过程会一直重复,直到没有需要交换的元素为止,从而使整个数列有序
代码示例:
#include <iostream>
using namespace std;
void print(int arr[], int n) {
for (int j = 0; j < n; j++) {
cout << arr[j] << " ";
}
cout << endl;
}
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 s[10] = {8, 1, 9, 7, 2, 4, 5, 6, 10, 3};
cout << "初始序列:";
print(s, 10);
BubbleSort(s, 10);
cout << "排序结果:";
print(s, 10);
return 0;
}
时间复杂度:冒泡排序的时间复杂度为
空间复杂度:冒泡排序的空间复杂度为
稳定性:冒泡排序是稳定的排序算法,因为它不会改变相同元素的相对位置
插入排序
插入排序是一种简单直观的排序算法,其工作原理是通过构建有序序列,对未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。这种算法在数据量小或者部分数据已经有序的情况下效率较高,是稳定的排序方法
代码示例:
#include <iostream>
using namespace std;
void insertSort(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]; // 将大于key的元素后移
j--;
}
a[j + 1] = key; // 将key插入到正确位置
}
}
void printArray(int a[], int n) {
for (int i = 0; i < n; i++) {
cout << a[i] << " ";
}
cout << endl;
}
int main() {
int a[] = {8, 1, 9, 7, 2, 4, 5, 6, 10, 3};
int n = sizeof(a) / sizeof(a[0]);
insertSort(a, n);
printArray(a, n);
return 0;
}
最优时间复杂度:当待排序数组已经是有序的情况下,每个元素只需与前一个元素比较一次,无需移动其他元素。此时,比较次数为
最坏时间复杂度:当待排序数组是完全逆序时,每次插入都需要将当前元素与前面所有已排序的元素进行比较,并将这些元素逐一后移。此时,比较次数和移动次数均为
平均时间复杂度:在一般情况下,插入排序的比较和移动次数介于最优和最坏之间,其平均时间复杂度为
空间复杂度:插入排序只需要常量级的辅助空间(如临时变量),因此空间复杂度为
稳定性:插入排序是一种稳定的排序算法,因为在插入过程中不会改变相同元素的相对顺序
选择排序
选择排序是一种简单直观的排序算法,其工作原理是在未排序序列中找到最小(或最大)元素,将其存放到排序序列的起始位置,然后再从剩余未排序元素中继续寻找最小(或最大)元素,以此类推
代码示例:
#include<iostream>
using namespace std;
void select_sort(int* a, int n) {
for (int i = 0; i < n - 1; i++) {
int min_index = i;
for (int j = i + 1; j < n; j++) {
if (a[j] < a[min_index])
min_index = j;
}
if(min_index != i) {
swap(a[i], a[min_index]);
}
}
}
int main() {
int arr[] = {64, 25, 12, 22, 11};
int n = sizeof(arr)/sizeof(arr[0]);
select_sort(arr, n);
cout << "Sorted array: \n";
for (int i = 0; i < n; i++)
cout << arr[i] << " ";
cout << endl;
}
时间复杂度:选择排序的时间复杂度主要取决于比较和交换操作的次数。无论输入序列的初始状态如何,选择排序的比较次数都是固定的,即:
稳定性:选择排序是不稳定的排序算法。原因在于,在排序过程中,如果一个元素比当前元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。例如,在序列
归并排序
归并排序是一种高效的排序算法,它采用分治法的策略,将问题分解为一些小问题然后递归求解,而后将解决的部分合并成整体的解决方案
代码示例:
#include <iostream>
using namespace std;
// 合并函数,将两个有序数组合并为一个有序数组
void merge(int* a, int low, int mid, int high) {
int* b = new int[high - low + 1];
int i = low, j = mid + 1, k = 0;
while (i <= mid && j <= high) {
if (a[i] <= a[j]) {
b[k++] = a[i++];
} else {
b[k++] = a[j++];
}
}
while (i <= mid) {
b[k++] = a[i++];
}
while (j <= high) {
b[k++] = a[j++];
}
for (int i = low; i <= high; i++) {
a[i] = b[i - low];
}
delete[] b;
}
// 归并排序函数
void mergeSort(int* a, int low, int high) {
if (low < high) {
int mid = (low + high) / 2;
mergeSort(a, low, mid);
mergeSort(a, mid + 1, high);
merge(a, low, mid, high);
}
}
int main() {
int a[] = {42, 15, 20, 6, 8, 38, 50, 12};
int n = sizeof(a) / sizeof(a[0]);
mergeSort(a, 0, n - 1);
for (int i = 0; i < n; i++) {
cout << a[i] << " ";
}
cout << endl;
return 0;
}
时间复杂度:归并排序的时间复杂度主要由两个部分组成:数组分割和数组合并。在分割阶段,数组被递归地分成两半,直到每个子数组只有一个元素,这个过程的时间复杂度是
空间复杂度:归并排序的空间复杂度是
稳定性:归并排序通过递归将序列分割成小段,然后合并成有序序列。在合并过程中,如果两个元素相等,优先选择左侧的元素,因此归并排序是稳定的
快速排序
快速排序是一种高效的排序算法,它使用分治法的策略来对一个数组进行排序。其核心思想是选择一个基准元素,通过一趟排序将待排数组分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据要小,然后再对这两部分数据分别进行快速排序
代码示例:
#include<iostream>
using namespace std;
void quickSort(int a[], int low, int high) {
if (low < high) {
int i = low, j = high;
int pivot = a[low];
while (i < j) {
while (i < j && a[j] >= pivot) j--;
if (i < j) a[i++] = a[j];
while (i < j && a[i] <= pivot) i++;
if (i < j) a[j--] = a[i];
}
a[i] = pivot;
quickSort(a, low, i - 1);
quickSort(a, i + 1, high);
}
}
int main() {
int a[10] = {8, 1, 9, 7, 2, 4, 5, 6, 10, 3};
cout << "初始序列:";
for (int j = 0; j < 10; j++) cout << a[j] << " ";
cout << endl;
quickSort(a, 0, 9);
cout << "排序结果:";
for (int j = 0; j < 10; j++) cout << a[j] << " ";
cout << endl;
system("pause");
}
最优时间复杂度: 在理想情况下,每次划分都能将数组均匀分成两部分。此时递归深度为
最差时间复杂度: 如果每次选择的基准值是当前数组的最大值或最小值,划分会极不均匀,导致递归深度为
平均时间复杂度: 在随机分布的情况下,快速排序的平均时间复杂度为
稳定性:在分区时,枢轴元素的交换可能导致相等元素的顺序被打乱,因此是不稳定的
希尔排序
希尔排序,也称为缩小增量排序,是一种基于插入排序的排序算法。它通过将整个序列分割成若干子序列分别进行插入排序,从而提高排序效率
代码示例:
#include <iostream>
using namespace std;
void ShellSort(int *arr, int len) {
int grp = len / 2; // 计算增量
while (grp > 0) {
for (int i = grp; i < len; i++) {
int cur = arr[i]; // 当前元素
int j = i - grp;
while (j >= 0 && arr[j] > cur) {
arr[j + grp] = arr[j]; // 移动元素
j -= grp;
}
arr[j + grp] = cur; // 插入元素
}
grp /= 2; // 缩小增量
}
}
int main() {
int arr[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
int len = sizeof(arr) / sizeof(arr[0]);
ShellSort(arr, len);
for (int i = 0; i < len; i++) {
cout << arr[i] << ", ";
}
cout << endl;
return 0;
}
复杂度:希尔排序的时间复杂度并不是一个固定的值,而是根据增量序列的不同而有所变化。在最好的情况下,希尔排序的时间复杂度可以达到
稳定性:希尔排序是一种基于插入排序的改进算法,通过分组和逐步缩小间隔来对数据进行排序。然而,它并不是一种稳定的排序算法。 在排序过程中,希尔排序会根据不同的步长对数据进行多次插入排序。虽然单次插入排序是稳定的,但在不同步长的插入排序中,相同的元素可能会被移动到不同的位置,从而打乱它们的相对顺序。这种特性导致了希尔排序的不稳定性
堆排序
堆排序是一种基于堆数据结构的比较排序算法。在堆排序中,我们首先将待排序的序列构建成一个大顶堆,然后将堆顶元素与序列末尾元素交换,再调整剩余元素构成新的大顶堆。这个过程重复进行,直到整个序列变成有序序列。
代码示例:
#include <iostream>
using namespace std;
// 调整大顶堆
void adjustHeap(int arr[], int i, int length) {
int temp = arr[i];
for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
if (k + 1 < length && arr[k] < arr[k + 1]) {
k++;
}
if (arr[k] > temp) {
arr[i] = arr[k];
i = k;
} else {
break;
}
}
arr[i] = temp;
}
// 堆排序
void heapSort(int arr[], int length) {
// 构建大顶堆
for (int i = length / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, length);
}
// 排序
for (int j = length - 1; j > 0; j--) {
swap(arr[0], arr[j]); // 将堆顶元素与末尾元素交换
adjustHeap(arr, 0, j); // 重新调整堆
}
}
int main() {
int arr[] = {9, 79, 46, 30, 58, 49};
int n = sizeof(arr) / sizeof(arr[0]);
heapSort(arr, n);
for (int i = 0; i < n; ++i) {
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
时间复杂度:堆排序的时间复杂度主要包括两个部分:建立初始堆和每次选取最大数后重新建堆的过程。
建立初始堆的时间复杂度:在调整堆时,每交换一次节点,最多比较关键字
综上所述,堆排序的总时间复杂度为
空间复杂度:堆排序是一种就地排序算法,即在排序过程中只需要常数级别的额外空间。因此,堆排序的空间复杂度为
稳定性:堆排序是一种基于堆数据结构的排序算法,其不稳定性主要源于排序过程中元素位置的调整和交换操作。 在建堆阶段,堆排序会将待排序的元素构建成一个堆(大顶堆或小顶堆)。从最后一个非叶子节点开始,逐步向上调整以满足堆的性质。在这个过程中,可能需要交换节点的位置。如果两个元素的键值相同,交换操作可能会改变它们在原始数组中的相对顺序,从而导致不稳定性。 在排序阶段,堆排序通过反复从堆顶取出最大(或最小)元素,将其放置到已排序部分的末尾。然后对剩余未排序部分重新调整堆结构。这一过程中同样涉及交换操作,进一步破坏了相同键值元素的相对顺序。 堆排序的不稳定性归因于其核心操作——通过交换调整堆结构。这种交换可能导致相同键值的元素在排序后顺序发生变化。如果需要稳定的排序算法,可以选择冒泡排序、插入排序或归并排序等稳定算法
计数排序
计数排序是一种非比较型的排序算法,它的基本思想是统计待排序数组中每个元素的出现次数,然后根据统计信息将元素放置到正确的位置上,从而实现排序。这种算法特别适用于整数排序,尤其是当输入数据范围不是很大时,其性能表现非常出色
代码示例:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 计数排序函数
// 参数:待排序数组,排序结果将存储在原数组中
void countingSort(vector<int>& arr) {
if (arr.empty()) return;
// 找到数组中的最大值和最小值,确定数据范围
int min_val = *min_element(arr.begin(), arr.end());
int max_val = *max_element(arr.begin(), arr.end());
// 计算计数数组的大小
int range = max_val - min_val + 1;
// 创建计数数组并初始化
vector<int> count(range, 0);
vector<int> output(arr.size());
// 统计每个元素出现的次数
for (int num : arr) {
count[num - min_val]++;
}
// 计算前缀和,确定每个元素在输出数组中的位置
for (int i = 1; i < count.size(); i++) {
count[i] += count[i - 1];
}
// 构建输出数组(从后往前遍历,保持稳定性)
for (int i = arr.size() - 1; i >= 0; i--) {
output[count[arr[i] - min_val] - 1] = arr[i];
count[arr[i] - min_val]--;
}
// 将排序结果复制回原数组
arr = output;
}
// 测试函数
int main() {
// 测试用例1
vector<int> arr1 = {4, 2, 2, 8, 3, 3, 1};
cout << "排序前: ";
for (int num : arr1) {
cout << num << " ";
}
cout << endl;
countingSort(arr1);
cout << "排序后: ";
for (int num : arr1) {
cout << num << " ";
}
cout << endl << endl;
// 测试用例2(包含负数)
vector<int> arr2 = {-2, 5, 3, -5, 2, 8, -1};
cout << "排序前: ";
for (int num : arr2) {
cout << num << " ";
}
cout << endl;
countingSort(arr2);
cout << "排序后: ";
for (int num : arr2) {
cout << num << " ";
}
cout << endl;
return 0;
}
时间复杂度:
其中 n 是元素数量,k 是数据范围(max_val - min_val + 1)。主要由查找最值
空间复杂度:
需额外占用两个数组:大小为
桶排序
桶排序是一种分布式排序算法,它通过将数组分成多个区间,即“桶”,来组织数据。每个桶代表一个值范围的集合,然后单独对每个桶进行排序,最后将它们合并以得到排序完成的数组
代码示例:
#include <bits/stdc++.h>
using namespace std;
void bucketSort(vector<int>& arr) {
int n = arr.size();
if (n <= 1) return;
int maxVal = *max_element(arr.begin(), arr.end());
int minVal = *min_element(arr.begin(), arr.end());
int bucketSize = maxVal - minVal + 1;
int bucketCount = 10; // 桶的数量
vector<vector<int>> bucket(bucketCount);
// 将元素分配到桶中
for (int num : arr) {
int index = (num - minVal) * bucketCount / bucketSize;
bucket[index].push_back(num);
}
// 对每个桶中的元素进行排序
for (auto& b : bucket) {
sort(b.begin(), b.end());
}
// 将排序后的元素依次放回原数组
int index = 0;
for (auto& b : bucket) {
for (int num : b) {
arr[index++] = num;
}
}
}
int main() {
vector<int> arr = {61, 17, 29, 22, 34, 60, 72, 21, 50, 1, 62};
bucketSort(arr);
for (int nums : arr) {
printf("%d ", nums);
}
return 0;
}
最优时间复杂度:
假设有
最坏时间复杂度:
如果
稳定性:桶排序是稳定的排序算法,只要内部排序是稳定的,并且在放入桶中时不改变元素间的相对顺序
小结
排序算法在C++中的实现通常涉及数组操作和递归调用。选择合适的排序算法取决于数据的大小、数据的结构和性能要求