目录
本文作为学习记录,请谨慎参考
在 <chrono>
头文件中,C++ 提供了一个强大的时间处理库,其中包括了各种时间单位。常见的时间单位:
std::chrono::hours
: 小时。std::chrono::minutes
: 分钟。std::chrono::seconds
: 秒。std::chrono::milliseconds
: 毫秒(1秒的千分之一)。std::chrono::microseconds
: 微秒(1秒的百万分之一)。std::chrono::nanoseconds
: 纳秒(1秒的十亿分之一)。
数据
排序的数据是由radom库随机生成的10000个随机数
// 创建一个随机数生成器引擎
random_device rd;
mt19937 gen(rd());
uniform_int_distribution<int> dis(1, 10000); // 定义整数分布,范围是1到100
// 生成一百个随机整数并存储在 vector 中
vector<int> arr;
for (int i = 0; i < 10000; ++i) {
arr.push_back(dis(gen));
}
int n = arr.size();
1.选择排序
选择排序是一种简单直观的排序算法。它的核心思想是每次从未排序的部分选择一个最小(或最大)的元素,然后将其与未排序部分的第一个元素交换位置。通过重复这个过程,就可以实现整个序列的排序。
选择排序的原理和步骤:
-
初始状态:将整个序列分为已排序和未排序两部分,初始时已排序部分为空,未排序部分包含所有元素。
-
选择最小元素:从未排序部分选择最小的元素,并将其与未排序部分的第一个元素交换位置。
-
已排序部分扩展:将刚刚选择的最小元素加入到已排序部分。
-
重复步骤2和3:重复进行选择和交换,直到未排序部分为空。
-
排序完成:当未排序部分为空时,整个序列就排好序了。
代码
#include <iostream>
#include <random>
#include<chrono>
#include<vector>
using namespace std;
using namespace chrono;
void selectionSort(vector<int>& arr)
{
int n = arr.size();
for (int i = 0; i < n; i++)
{
int minIndex = i;
for (int j = i + 1; j < n; j++)
{
if (arr[j] < arr[minIndex])
{
minIndex = j;
}
}
swap(arr[i], arr[minIndex]);
}
}
int main()
{
// 创建一个随机数生成器引擎
random_device rd;
mt19937 gen(rd());
uniform_int_distribution<int> dis(1, 100); // 定义整数分布,范围是1到100
// 生成一百个随机整数并存储在 vector 中
vector<int> arr;
for (int i = 0; i < 100; ++i) {
arr.push_back(dis(gen));
}
cout << "原始数据为: ";
for (int num : arr) {
cout << num << " ";
}
cout << endl;
cout << endl;
// 记录开始时间点
auto start_time = high_resolution_clock::now();
selectionSort(arr); //选择排序
// 记录结束时间点
auto end_time = high_resolution_clock::now();
// 计算时间差
auto duration = duration_cast<microseconds>(end_time - start_time);
cout << "排序后的数据为: ";
for (int num : arr) {
cout << num << " ";
}
cout << endl;
cout << endl;
cout<<"排序时间为: "<<duration.count() << duration.count() << " microseconds" << endl;
return 0;
}
运行结果
算法分析
selectionSort
函数实现了选择排序的核心逻辑。通过遍历数组,找到未排序部分的最小值,并将其与未排序部分的第一个元素交换,从而完成一轮选择排序。
在主函数中,我们创建一个整数数组,然后调用 selectionSort
函数进行排序,并输出原始数组和排序后的数组。
选择排序的时间复杂度为O(n^2),在实际应用中,效率较低,但它的思想简单直观,适用于小型数据集。
2.冒泡排序
冒泡排序是一种简单的排序算法,它重复地遍历待排序序列,一次比较两个元素,如果它们的顺序错误就将它们交换。重复这个过程,直到整个序列有序。
冒泡排序的原理和步骤:
-
比较相邻元素:从第一个元素开始,比较相邻的两个元素。
-
交换元素位置:如果顺序不正确(比如,当前元素大于下一个元素),则交换这两个元素的位置。
-
一轮遍历完成:一次遍历后,最大的元素已经被交换到了最后的位置。
-
重复步骤1-3:重复进行相邻元素的比较和交换,直到整个序列有序。
-
排序完成:当没有发生交换时,说明序列已经有序,排序完成。
代码:
void bubbleSort(vector<int>& arr)
{
int n = arr.size();
for (int i = 0; i < n - 1; i++)
{
for (int j = 0; j < n-1-i; j++)
{
if (arr[j]>arr[j+1])
{
swap(arr[j], arr[j + 1]);
}
}
}
}
算法分析
冒泡排序的时间复杂度为O(n^2),在实际应用中,效率较低
3.插入排序
它的核心思想是将未排序的元素逐个插入到已排序的部分,从而不断扩大已排序的范围。
插入排序的原理和步骤:
-
初始状态:将整个序列分为已排序和未排序两部分,初始时已排序部分只包含第一个元素,未排序部分包含剩下的元素。
-
逐个插入:从未排序部分取出一个元素,将其插入到已排序部分的适当位置,使得插入后的序列仍然有序。
-
已排序部分扩展:将已排序部分扩大一步。
-
重复步骤2和3:重复进行逐个插入的操作,直到未排序部分为空。
-
排序完成:当未排序部分为空时,整个序列就排好序了。
代码:
void insertionSort(vector<int>& arr)
{
int n = arr.size();
for (int i = 1; i < n-1; i++)
{
int temp = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > temp)
{
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = temp;
}
}
算法分析:
插入排序的时间复杂度为O(n^2),但它在实际应用中对于部分有序的序列有较好的性能。插入排序是一种稳定的排序算法,适用于小型数据集。
4.希尔排序
希尔排序是插入排序的一种改进版本,它通过比较相距一定间隔的元素来提高插入排序的效率。希尔排序的核心思想是将数组分成若干个子序列,分别对子序列进行插入排序,然后逐渐减小子序列的间隔,最终完成整个序列的排序。
希尔排序的原理和步骤:
-
选择间隔序列:希尔排序的关键是选择一个递减的间隔序列(也称为增量序列),间隔序列的最后一个值通常为1。
-
按间隔进行插入排序:对每个子序列进行插入排序,其中子序列由间隔指定。这样,相距较远的元素可以交换位置,使得数组局部有序。
-
缩小间隔:重复进行插入排序,每次减小间隔,直到间隔为1,此时进行最后一次插入排序。
-
排序完成:当间隔为1时,整个序列基本有序,进行最后一次插入排序后,排序完成。
代码
void shellSort(vector<int>& arr)
{
int n = arr.size();
//初始间隔设置为数组长度的一半,然后逐渐减半
for (int gap = n / 2; gap > 0; gap /= 2)
{
//对每个子序列进行插入排序
for (int i = gap; i < n; i++)
{
int temp = arr[i];
int j = i;
//插入排序
while (j >= gap&&arr[j-gap]>temp)
{
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = temp;
}
}
}
算法分析
希尔排序的时间复杂度取决于所选的间隔序列,最好的情况下可以达到 O(n log n),但最坏情况下仍为 O(n^2)。希尔排序相对于简单插入排序来说,在处理大型数据集时有更好的性能。希尔排序是不稳定的排序算法。
5.堆排序
堆排序(Heap Sort)是一种基于二叉堆数据结构的排序算法。它的核心思想是通过构建一个最大堆(或最小堆),然后反复从堆顶取出最大(或最小)元素,将其放到数组末尾,再调整堆使其保持堆的性质,重复这个过程直到整个数组有序。
堆排序的原理和步骤:
-
构建堆:将待排序的数组构建成一个二叉堆。如果是升序排序,构建最大堆;如果是降序排序,构建最小堆。
-
堆排序:反复从堆顶取出最大(或最小)元素,将其放到数组末尾,然后调整堆使其保持堆的性质。
-
重复操作:重复步骤2,直到整个数组有序。
代码
//调整堆,使其满足最大堆的性质
void heapify(vector<int>& arr ,int n, int i) //数组 arr、数组的长度 n,以及当前根节点的索引 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(arr, i, 0); //将未完成排序的部分继续进行堆排序
}
}
算法分析
堆排序的时间复杂度为 O(n log n),不需要额外的空间。堆排序是一种不稳定的排序算法。
6.归并排序
归并排序(Merge Sort)是一种分治算法,它的核心思想是将待排序的数组分成两个较小的数组,递归地对这两个子数组进行排序,然后将它们合并成一个有序数组。归并排序采用了分而治之的策略,通过不断将问题分解为更小的子问题并解决子问题,最终合并子问题的解得到整体的解。
归并排序的原理和步骤:
-
分割:将待排序的数组分成两个相等(或近似相等)的子数组。
-
递归排序:递归地对两个子数组进行排序。如果子数组的长度大于1,则继续分割和排序。
-
合并:将两个已排序的子数组合并成一个有序数组。这是归并排序的关键步骤。
-
重复操作:重复上述步骤,直到整个数组有序。
代码
// 合并两个有序数组
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);
// 将数据复制到临时数组 L 和 R
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];
}
// 合并临时数组到 arr
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;
}
// 将剩余的元素复制到 arr(如果有的话)
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);
}
}
算法分析
归并排序的时间复杂度为 O(n log n),是一种稳定的排序算法,它需要额外的空间用于合并过程。
7.快速排序
来了,最快的排序来了,来看看他是不是像他名字所说的那样快
快速排序(Quick Sort)是一种基于分治策略的排序算法。它的核心思想是选择一个基准元素,将数组分成两个子数组,左子数组的元素小于基准元素,右子数组的元素大于基准元素。然后对左右子数组递归地进行快速排序。快速排序具有较高的性能,通常比其他 O(n log n) 的排序算法更快。
快速排序的原理和步骤:
-
选择基准元素:从数组中选择一个基准元素。
-
分割数组:将数组分成两个子数组,左子数组的元素小于基准元素,右子数组的元素大于基准元素。
-
递归排序:递归地对左右子数组进行快速排序。
-
合并:无需合并步骤,因为在分割数组的过程中就已经完成了排序。
-
重复操作:重复上述步骤,直到整个数组有序。
代码
//将数组分割成两部分,返回基准元素的索引
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]);
}
}
i++;
swap(arr[high], arr[i]);
return i;
}
//快速排序
void quickSort(vector<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);
}
}
哇,确实是非常的快啊,速度遥遥领先
算法分析
快速排序的平均时间复杂度为 O(n log n),在最坏情况下为 O(n^2)。快速排序是一种原地排序算法,不需要额外的空间。它是一种不稳定的排序算法。
总结
算法性能比较
选择排序
冒泡排序
插入排序
希尔排序
堆排序
归并排序
快速排序
- 选择、冒泡、插入排序适用于小规模或部分有序数据。
- 希尔排序对中等规模数据有效。
- 归并、堆、快速排序适用于大规模数据,其中快速排序具有较高性能。
- 归并排序和堆排序需要额外空间,快速排序是原地排序。
- 不同排序算法适用于不同场景,选择排序算法时需要考虑数据规模、数据分布以及是否需要稳定排序。
完整代码如下
#include <iostream>
#include <random>
#include<chrono>
#include<vector>
using namespace std;
using namespace chrono;
//选择排序
void selectionSort(vector<int>& arr)
{
int n = arr.size();
for (int i = 0; i < n; i++)
{
int minIndex = i;
for (int j = i + 1; j < n; j++)
{
if (arr[j] < arr[minIndex])
{
minIndex = j;
}
}
swap(arr[i], arr[minIndex]);
}
}
//冒泡排序
void bubbleSort(vector<int>& arr)
{
int n = arr.size();
for (int i = 0; i < n - 1; i++)
{
for (int j = 0; j < n-1-i; j++)
{
if (arr[j]>arr[j+1])
{
swap(arr[j], arr[j + 1]);
}
}
}
}
//插入排序
void insertionSort(vector<int>& arr)
{
int n = arr.size();
for (int i = 1; i < n-1; i++)
{
int temp = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > temp)
{
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = temp;
}
}
//希尔排序
void shellSort(vector<int>& arr)
{
int n = arr.size();
//初始间隔设置为数组长度的一半,然后逐渐减半
for (int gap = n / 2; gap > 0; gap /= 2)
{
//对每个子序列进行插入排序
for (int i = gap; i < n; i++)
{
int temp = arr[i];
int j = i;
//插入排序
while (j >= gap&&arr[j-gap]>temp)
{
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = temp;
}
}
}
//调整堆,使其满足最大堆的性质
void heapify(vector<int>& arr ,int n, int i) //数组 arr、数组的长度 n,以及当前根节点的索引 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(arr, i, 0); //将未完成排序的部分继续进行堆排序
}
}
// 合并两个有序数组
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);
// 将数据复制到临时数组 L 和 R
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];
}
// 合并临时数组到 arr
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;
}
// 将剩余的元素复制到 arr(如果有的话)
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 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]);
}
}
i++;
swap(arr[high], arr[i]);
return i;
}
//快速排序
void quickSort(vector<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);
}
}
int main()
{
// 创建一个随机数生成器引擎
random_device rd;
mt19937 gen(rd());
uniform_int_distribution<int> dis(1, 10000); // 定义整数分布,范围是1到100
// 生成一百个随机整数并存储在 vector 中
vector<int> arr;
for (int i = 0; i < 10000; ++i) {
arr.push_back(dis(gen));
}
int n = arr.size();
//cout << "原始数据为: ";
//for (int num : arr) {
// cout << num << " ";
//}
//cout << endl;
//cout << endl;
cout<<"数据量为: "<<n<<endl;
cout<<"开始排序!"<<endl;
cout << "... ..."<<endl;
// 记录开始时间点
auto start_time = high_resolution_clock::now();
//selectionSort(arr); //选择排序
//bubbleSort(arr); //冒泡排序
//insertionSort(arr); //插入排序
//shellSort(arr); //希尔排序
//heapSort(arr); //堆排序
//mergeSort(arr,0,n-1); //归并排序
quickSort(arr, 0, n-1);
// 记录结束时间点
auto end_time = high_resolution_clock::now();
// 计算时间差
//auto duration = duration_cast<microseconds>(end_time - start_time);
auto duration = duration_cast<milliseconds>(end_time - start_time);
//auto duration = duration_cast<seconds>(end_time - start_time);
//cout << "排序后的数据为: ";
//for (int num : arr) {
// cout << num << " ";
//}
//cout << endl;
//cout << endl;
cout<<"排序完成!"<<endl;
cout<<"排序时间为: "<<duration.count() << duration.count() << " microseconds" << endl;
//cout<<"排序时间为: "<<duration.count() << duration.count() << " microseconds" << endl;
//cout<<"排序时间为: "<<duration.count() << duration.count() << " microseconds" << endl;
return 0;
}