排序算法(十大排序)
排序算法是《数据结构与算法》中最基本的算法之一。
所谓排序算法,即通过特定的算法因式将一组或多组数据按照既定模式进行重新排序。这种新序列遵循着一定的规则,体现出一定的规律,因此,经处理后的数据便于筛选和计算,大大提高了计算效率。对于排序,我们首先要求其具有一定的稳定性,即当两个相同的元素同时出现于某个序列之中,则经过一定的排序算法之后,两者在排序前后的相对位置不发生变化。换言之,即便是两个完全相同的元素,它们在排序过程中也是各有区别的,不允许混淆不清。
1、冒泡排序
冒泡排序简介
冒泡排序是一种简单的排序算法,它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小(或越大)的元素会经由交换慢慢“浮”到数列的顶端,就像水中的气泡一样上升到水面。
冒泡排序流程
-
比较相邻元素:首先比较数组中的相邻两个元素。如果第一个比第二个大,则交换这两个元素的位置。这样,较大的数就会逐渐“浮”到数组的末尾。
-
多轮比较:接下来,对数组进行下一轮比较,从开始到结尾,但排除已经排序好的最大数。这一轮中,较大的数会被继续交换到数组的末尾。
-
重复过程:持续进行上述步骤,直到整个数组有序排列。在每一轮中,都会有一个元素被“冒泡”到正确的位置。
冒泡排序程序
#include<bits/stdc++.h>
using namespace std;
// 冒泡排序函数
void bubbleSort(vector<int>& arr) {
int n = arr.size();
for (int i = 0; i < n - 1; i++) {
// flag用于标记这次循环是否发生了交换
bool flag = false;
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// 如果当前元素比后一个元素大,则交换它们
swap(arr[j], arr[j + 1]);
flag = true; // 发生了交换,将flag设置为true
}
}
// 如果这次遍历没有发生交换,说明数组已经有序,直接跳出循环
if (!flag) {
break;
}
}
}
int n;
int main() {
cin >> n;
vector<int> arr(n);
for (int i = 0; i < n; i++) cin >> arr[i];
bubbleSort(arr);
for (int i = 0; i < n; i++) cout << arr[i] << ' ';
return 0;
}
在这个函数中,我们首先通过for (int i = 0; i < n - 1; i++)
来控制排序的遍历次数。每次遍历后,最大的元素会被放到正确的位置上,因此我们不需要再检查已经排序好的元素,这就是为什么在内层循环中使用n - i - 1
作为条件的原因。
内部的flag
变量用于优化冒泡排序。如果在某一趟排序中没有发生任何交换,这意味着数组已经是有序的,因此没有必要继续后续的排序过程,可以提前结束排序,这样可以减少不必要的比较。
2、选择排序
选择排序简介
选择排序是一种简单的排序算法,其基本思想是在待排序的序列中依次选出最小(或最大)的元素,存放到序列的起始位置,然后再从剩余未排序元素中继续寻找最小(或最大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
选择排序流程
- 首先,选择算法从待排序的数据元素中选出最小(或最大)的一个元素,并将其存放到序列的起始位置。
- 然后,算法在剩余未排序的元素中继续寻找最小(或最大)的元素,并将其放到已排序序列的末尾。
- 这一过程会持续进行,直到所有待排序的数据元素都被处理完毕。
选择排序程序
#include<bits/stdc++.h>
using namespace std;
// 选择排序函数
void selectionSort(vector<int>& arr) {
int n = arr.size();
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;
}
}
// 如果找到一个索引不等于当前的最小值索引,交换它们
if (minIndex != i) {
swap(arr[i], arr[minIndex]);
}
}
}
int n;
int main() {
cin >> n;
vector<int> arr(n);
for (int i = 0; i < n; i++) cin >> arr[i];
selectionSort(arr);
for (int i = 0; i < n; i++) cout << arr[i] << ' ';
return 0;
}
在这个函数中,我们首先通过for (int i = 0; i < n - 1; i++)
来控制排序的遍历次数。每次遍历时,我们都假设当前位置i
处的元素是未排序序列中的最小值,然后通过嵌套的for
循环在未排序的元素中寻找真正的最小值并记录其索引minIndex
。
如果minIndex
不等于i
,说明找到了一个比当前位置i
处的元素更小的元素,我们就用std::swap
函数交换这两个元素,这样保证了每次遍历结束后,i
位置上的元素都是未排序部分中的最小值。
3、插入排序
插入排序简介
插入排序是一种简单直观的比较排序算法,它的工作原理是构建有序序列。它通过将元素插入到已经排序好的序列中来进行排序。这个算法在实践中对于小数组或部分有序的数组往往表现得非常好。
插入排序流程
- 初始时,假设数组的第一个元素是有序的。
- 从第二个元素开始,将每个元素与已排序的元素逐个比较。
- 如果当前元素小于已排序序列的最后一个元素,则将已排序序列的最后一个元素后移。
- 重复步骤3,直到找到当前元素应该插入的位置。
- 将当前元素插入到正确的位置。
- 重复步骤2至5,直到所有元素都被处理。
插入排序程序
以下是插入排序算法的C++实现:
#include<bits/stdc++.h>
using namespace std;
// 插入排序函数
void insertionSort(vector<int>& arr) {
int n = arr.size();
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--; // 向前移动索引
}
// 将key插入到正确的位置
arr[j + 1] = key;
}
}
int n;
int main() {
cin >> n;
vector<int> arr(n);
for (int i = 0; i < n; i++) cin >> arr[i];
insertionSort(arr);
for (int i = 0; i < n; i++) cout << arr[i] << ' ';
return 0;
}
在这段代码中,我们定义了insertionSort
函数,它接受一个整数向量的引用作为参数,并将其排序。在函数内部,我们从第二个元素开始迭代,假设前面的元素已经是排好序的,然后将每个新元素插入到已排序部分的适当位置。
最后,在main
函数中,打印原始数组。
4、希尔排序
希尔排序简介
希尔排序的基本思想是将待排序的记录序列分割成若干个子序列,每个子序列都是由相隔某个“增量”的记录组成的。然后对这些子序列分别进行直接插入排序,接着逐步缩小增量,直到整个序列变得“基本有序”,最后对全体记录进行一次直接插入排序以完成排序。
希尔排序流程
- 选择一个合适的增量(gap),通常是数组长度的一半。
- 使用直接插入排序对每个子数组进行排序。
- 将增量缩小一半,重复步骤2,直到增量降至1,这时数组已经基本有序。
- 对整个数组进行最后一次直接插入排序,以确保所有元素都在其最终位置。
希尔排序程序
下面是用C++实现的希尔排序算法示例代码:
#include<bits/stdc++.h>
using namespace std;
// 希尔排序函数
void shellSort(vector<int>& arr) {
// 初始化增量为数组长度的一半
for (int gap = arr.size() / 2; gap > 0; gap /= 2) {
// 对每个子数组进行直接插入排序
for (int i = gap; i < arr.size(); i++) {
int temp = arr[i];
int j;
// 比较相距gap的元素,并交换位置
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
arr[j] = arr[j - gap];
}
arr[j] = temp;
}
}
}
int n;
int main() {
cin >> n;
vector<int> arr(n);
for (int i = 0; i < n; i++) cin >> arr[i];
// 调用希尔排序函数
shellSort(arr);
for (int i = 0; i < n; i++) cout << arr[i] << ' ';
return 0;
}
在这段代码中,我们首先定义了一个shellSort
函数来实现希尔排序。函数接受一个std::vector<int>
类型的引用作为参数,表示对原始向量进行排序。在shellSort
函数中,我们通过一个外层循环来控制增量gap
的减小,并且内层循环使用直接插入排序算法来对每个子数组进行排序。
然后,我们还定义了一个printArray
函数来打印数组的元素,这在调试和展示排序结果时非常有用。
最后,在main
函数中,打印原始数组。
5、归并排序
归并排序简介
归并排序是一种分而治之的算法,它将数组分割成更小的数组,然后将它们排序,最后将排序后的数组合并。归并排序的一个优点是它是稳定的,并且有很好的性能,时间复杂度为O(n log n)。
归并排序流程
- 分解:将数组分为两部分,如果数组长度为n,则分为n/2和n/2的两部分。
- 归并:对每一部分进行归并排序,即从小到大排序。
- 合并:将排序好的两部分合并为一个有序的整体。
归并排序程序
下面是使用C++实现的归并排序算法示例代码:
#include<bits/stdc++.h>
using namespace std;
// 合并两个子数组的函数
void merge(vector<int>& arr, int left, int mid, int right) {
int n1 = mid - left + 1; // 左侧子数组的大小
int n2 = right - mid; // 右侧子数组的大小
// 创建临时数组
vector<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;
int j = 0;
int 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(vector<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 n;
int main() {
cin >> n;
vector<int> arr(n);
for (int i = 0; i < n; i++) cin >> arr[i];
// 调用归并排序函数
mergeSort(arr, 0, arr.size() - 1);
for (int i = 0; i < n; i++) cout << arr[i] << ' ';
return 0;
}
在这段代码中,我们首先定义了一个merge
函数,它将两个子数组合并成一个有序的数组。然后,我们定义了mergeSort
函数,它是归并排序算法的核心。mergeSort
函数递归地将数组分割成更小的部分,直到不能再分割为止,然后调用merge
函数将这些子数组合并。
最后,在main
函数中,打印原始数组。
6、快速排序
快速排序简介
快速排序是一种分而治之的算法,它选择一个基准值(pivot)并围绕它对数组进行分区,将小于基准值的元素移到其左侧,将大于基准值的元素移到其右侧。然后递归地对基准值左右两边的子数组进行相同的操作,直到整个数组排序完成。快速排序的平均时间复杂度为O(n log n),但在最坏情况下,时间复杂度可以退化到O(n^2)。
快速排序流程
- 选择基准值。从数组中选择一个元素作为基准(pivot),通常选择数组的第一个元素或者最后一个元素。
- 分区操作。通过基准值,将数组分成两个部分,一部分包含所有小于基准值的元素,另一部分包含所有大于或等于基准值的元素。这一步确保了数组的一部分是有序的。
- 递归排序。对基准值左右两边的子数组分别进行快速排序,这一过程通过递归实现。
- 重复过程。重复步骤1到3,直到整个数组有序。
快速排序程序
下面是一个快速排序算法示例代码:
#include<bits/stdc++.h>
using namespace std;
// 分区函数
int partition(vector<int>& arr, int low, int high) {
int pivot = arr[high]; // 选择最右侧的元素作为基准
int i = (low - 1); // 小于基准的元素的索引
for (int j = low; j <= high - 1; j++) {
// 如果当前元素小于或等于基准
if (arr[j] <= pivot) {
i++; // 增加小于基准元素的索引
swap(arr[i], arr[j]); // 交换元素
}
}
swap(arr[i + 1], arr[high]); // 交换基准元素到正确的位置
return (i + 1);
}
// 快速排序的主函数
void quickSort(vector<int>& arr, int low, int high) {
if (low < high) {
// pi是分区索引,arr[pi]现在位于正确的位置
int pi = partition(arr, low, high);
// 独立地对基准左侧和右侧的元素进行快速排序
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
int n;
int main() {
cin >> n;
vector<int> arr(n);
for (int i = 0; i < n; i++) cin >> arr[i];
// 调用快速排序函数
quickSort(arr, 0, arr.size() - 1);
for (int i = 0; i < n; i++) cout << arr[i] << ' ';
return 0;
}
在这段代码中,我们定义了partition
函数,它将数组分为两部分,并返回基准值的正确位置。
quickSort
函数是快速排序的核心,它递归地调用partition
函数来对小于和大于基准值的子数组进行排序。
最后,在main
函数中,打印原始数组。
7、堆排序
堆排序简介
堆排序是一种基于比较的排序算法,它利用了二叉堆(一种特殊的完全二叉树)的性质来进行排序。在最大堆中,每个节点的值都不小于其子节点的值;在最小堆中,每个节点的值都不大于其子节点的值。
堆排序流程
- 建堆:从最后一个非叶子节点(通常是最后一个元素的父节点)开始,自底向上、自右向左进行下沉调整,确保每个节点都满足堆的性质。最终整个序列成为一个大顶堆(或小顶堆)。
- 堆排序:将堆顶元素(最大或最小元素)与堆尾元素交换。堆长度减一,表示移除了已排序的最大(或最小)元素。然后重新对剩余元素进行下沉调整,恢复堆性质。重复上述步骤,直至堆长度为1,整个序列有序。
堆排序程序
以下是堆排序算法示例代码:
#include<bits/stdc++.h>
using namespace std;
// 维护堆的性质的函数
void heapify(vector<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) {
swap(arr[i], arr[largest]); // 交换根节点和最大值节点
// 递归地对受影响的子树进行堆化
heapify(arr, n, largest);
}
}
// 堆排序的主函数
void heapSort(vector<int>& arr) {
int n = arr.size();
// 构建堆(重新排列数组)
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
// 逐个提取元素
for (int i = n - 1; i > 0; i--) {
// 将当前根节点移到末尾
swap(arr[0], arr[i]);
// 调用heapify在减少的堆上
heapify(arr, i, 0);
}
}
int n;
int main() {
cin >> n;
vector<int> arr(n);
for (int i = 0; i < n; i++) cin >> arr[i];
// 调用堆排序函数
heapSort(arr);
for (int i = 0; i < n; i++) cout << arr[i] << ' ';
return 0;
}
在这段代码中,我们定义了heapify
函数,它用于维护堆的性质。heapify
函数假设根节点的两个子树都是有效的堆,但可能以i
节点为根的子树不是堆。heapSort
函数首先构建一个堆,然后将堆的根节点与最后一个节点交换,并对剩下的节点进行堆化,这个过程称为排序。
最后,在main
函数中,打印原始数组。
8、计数排序
计数排序简介
计数排序是一种线性时间排序算法,特别适合于处理具有一定范围的整数数组。它的工作原理是计算每个元素在数组中出现的次数,然后根据这些计数来确定元素在排序数组中的位置。计数排序不是比较排序,因此其时间复杂度为O(n)。
计数排序流程
- 找出待排序的数组中最大和最小的元素。
- 统计数组中每个值为 i 的元素出现的次数,存入数组 C 的第 i 项。
- 对所有的计数累加(从 C 中的第一个元素开始,每一项和前一项相加)。
- 反向填充目标数组:将每个元素 i 放在新数组的第 C(i) 项,每放一个元素就将 C(i) 减去1。
计数排序程序
以下是计数排序算法示例代码:
#include<bits/stdc++.h>
using namespace std;
// 计数排序函数
void countingSort(vector<int>& arr) {
// 如果数组为空,则直接返回
if (arr.empty())
return;
// 找到数组中的最大值以确定计数数组的大小
int max_val = *std::max_element(arr.begin(), arr.end());
// 初始化计数数组,并将所有元素设置为0
vector<int> count(max_val + 1, 0);
// 遍历数组,计算每个元素的出现次数
for (int value : arr) {
count[value]++;
}
// 修改计数数组,使其每个元素都包含小于或等于其索引值的元素数量
for (size_t i = 1; i < count.size(); i++) {
count[i] += count[i - 1];
}
// 创建输出数组
vector<int> output(arr.size());
// 构建输出数组
for (int i = arr.size() - 1; i >= 0; i--) {
output[--count[arr[i]]] = arr[i];
}
// 将输出数组复制回原数组
arr = output;
}
int n;
int main() {
cin >> n;
vector<int> arr(n);
for (int i = 0; i < n; i++) cin >> arr[i];
// 调用计数排序函数
countingSort(arr);
for (int i = 0; i < n; i++) cout << arr[i] << ' ';
return 0;
}
在这段代码中,我们首先检查数组是否为空。然后我们找到数组中的最大值以确定计数数组的大小,并初始化一个计数数组count
,其中count[i]
存储了数组中值小于或等于i
的元素数量。
接下来,我们通过遍历数组并增加count
数组中相应的元素来计算每个元素的出现次数。然后我们修改count
数组,使其变成累积计数数组,即count[i]
现在包含小于或等于i
的元素总数。
最后,我们创建一个输出数组output
,并通过逆向遍历原数组来构建这个输出数组。在每次遍历中,我们从计数数组中找到正确的位置,并将元素放置在输出数组中,同时减少计数数组中相应的计数。
9、桶排序
桶排序简介
桶排序是一种分布式排序算法,它将元素分布到多个“桶”中,每个桶内部再分别进行排序(可以使用其他排序算法或递归地使用桶排序)。桶排序特别适合用于数据分布较均匀且可以均匀划分到各个桶中的情况。其平均时间复杂度为O(n + k),其中n是元素数量,k是桶的数量。
桶排序流程
- 确定桶的数量和大小,以便数据能尽可能均匀地分布到各个桶中。
- 将数据根据其值分配到对应的桶中。
- 对每个非空的桶内的数据进行排序。
- 将排序后的数据收集起来形成最终的排序序列。
桶排序程序
以下是一个桶排序算法示例代码:
#include<bits/stdc++.h>
using namespace std;
// 桶排序函数
void bucketSort(vector<int>& arr) {
// 如果数组为空,则直接返回
if (arr.empty())
return;
// 找到数组中的最大值和最小值
int max_val = *max_element(arr.begin(), arr.end());
int min_val = *min_element(arr.begin(), arr.end());
// 计算桶的数量
int bucket_num = (max_val - min_val) / arr.size() + 1;
// 创建桶
vector<std::vector<int>> buckets(bucket_num);
// 将元素分布到各个桶中
for (int value : arr) {
int bucket_index = (value - min_val) / arr.size();
buckets[bucket_index].push_back(value);
}
// 对每个桶进行排序,并将结果收集到原数组中
int index = 0;
for (auto& bucket : buckets) {
sort(bucket.begin(), bucket.end()); // 可以使用其他排序算法或递归调用桶排序
for (int value : bucket) {
arr[index++] = value;
}
}
}
int n;
int main() {
cin >> n;
vector<int> arr(n);
for (int i = 0; i < n; i++) cin >> arr[i];
// 调用桶排序函数
bucketSort(arr);
for (int i = 0; i < n; i++) cout << arr[i] << ' ';
return 0;
}
在这段代码中,我们首先检查数组是否为空。然后我们找到数组中的最大值和最小值,以便确定桶的数量和范围。
我们创建了一个桶的二维向量buckets
,并根据元素的值将它们分配到不同的桶中。这里我们使用了简单的映射函数(value - min_val) / arr.size()
将元素分布到各个桶中。
然后,我们对每个桶中的元素进行排序,这里我们使用了sort
函数,可以根据需要选择其他排序算法,或者递归地使用桶排序。最后,我们将所有桶中的元素收集回原数组中。
最后,在main函数中,打印原始数组。
10、基数排序
基数排序简介
基数排序是一种非比较型整数排序算法,它按照数字的每一位(或字符)来进行排序。基数排序的基本思想是将所有元素按照某个位上的数字进行排序,接着按照更高位进行排序,依此类推,直到最低位。
基数排序的时间复杂度为O(d * (n + b)),其中n是元素数量,d是数字的位数(或字符的长度),b是基数。
基数排序流程
- 确定最大位数:首先找出待排序数组中的最大数的位数,以确定需要按多少位进行排序。
- 按位分配:从最低位开始,根据该位的值将数组中的元素分配到不同的“桶”中。
- 收集元素:将各个桶中的元素按照顺序合并回原数组,此时数组已经按照最低有效位进行了排序。
- 重复排序过程:对次低有效位、第三位有效位…直到最高有效位重复进行排序和合并的过程。
- 获得最终结果:当最高有效位排序完成后,数组中的所有元素已经按照从最低位到最高位的顺序排好序。
基数排序程序
#include<bits/stdc++.h>
using namespace std;
// 获取数组中最大数的位数
int getMaxDigit(const std::vector<int>& arr) {
int max_val = *std::max_element(arr.begin(), arr.end());
int digit = 0;
while (max_val > 0) {
max_val /= 10;
digit++;
}
return digit;
}
// 基数排序函数
void radixSort(std::vector<int>& arr) {
int max_digit = getMaxDigit(arr);
int mod = 10;
int dev = 1;
vector<vector<int>> counter(10); // 因为我们有十个数字(0-9)
for (int i = 0; i < max_digit; i++, dev *= 10, mod *= 10) {
// 清空计数器
for (auto& bucket : counter) {
bucket.clear();
}
// 根据当前位数将元素分配到计数器中
for (int value : arr) {
int bucket_index = (value % mod) / dev;
counter[bucket_index].push_back(value);
}
// 将计数器中的元素收集回原数组
int index = 0;
for (auto& bucket : counter) {
for (int value : bucket) {
arr[index++] = value;
}
}
}
}
int n;
int main() {
cin >> n;
vector<int> arr(n);
for (int i = 0; i < n; i++) cin >> arr[i];
// 调用基数排序函数
radixSort(arr);
for (int i = 0; i < n; i++) cout << arr[i] << ' ';
return 0;
}
在这段代码中,我们首先定义了一个辅助函数getMaxDigit
来获取数组中最大数的位数,以便确定排序的次数。
radixSort
函数是基数排序的核心,它通过两个变量mod
和dev
来确定当前处理的位数。mod
用于取余数,dev
用于获取当前位的数字。
我们使用了一个计数器counter
作为桶,它是一个二维向量,用来存储每个桶中的元素。在每次循环中,我们根据当前位数将元素分配到对应的桶中,然后按照桶的顺序将元素收集回原数组中。
最后,在main函数中,打印原始数组。
本结就到这里啦,感谢你的支持!