七大排序算法 C++实现

目录

数据

1.选择排序

选择排序的原理和步骤:

代码

算法分析

2.冒泡排序

冒泡排序的原理和步骤:

代码:

算法分析

3.插入排序

插入排序的原理和步骤:

代码:

算法分析:

4.希尔排序

希尔排序的原理和步骤:

代码

算法分析

5.堆排序

堆排序的原理和步骤:

代码

​编辑

算法分析

6.归并排序

归并排序的原理和步骤:

代码

算法分析

7.快速排序

快速排序的原理和步骤:

代码

算法分析

总结

算法性能比较

完整代码如下


本文作为学习记录,请谨慎参考

<chrono> 头文件中,C++ 提供了一个强大的时间处理库,其中包括了各种时间单位。常见的时间单位: 

  1. std::chrono::hours 小时。
  2. std::chrono::minutes 分钟。
  3. std::chrono::seconds 秒。
  4. std::chrono::milliseconds 毫秒(1秒的千分之一)。
  5. std::chrono::microseconds 微秒(1秒的百万分之一)。
  6. 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.选择排序

选择排序是一种简单直观的排序算法。它的核心思想是每次从未排序的部分选择一个最小(或最大)的元素,然后将其与未排序部分的第一个元素交换位置。通过重复这个过程,就可以实现整个序列的排序。

选择排序的原理和步骤:

  1. 初始状态:将整个序列分为已排序和未排序两部分,初始时已排序部分为空,未排序部分包含所有元素。

  2. 选择最小元素:从未排序部分选择最小的元素,并将其与未排序部分的第一个元素交换位置。

  3. 已排序部分扩展:将刚刚选择的最小元素加入到已排序部分。

  4. 重复步骤2和3:重复进行选择和交换,直到未排序部分为空。

  5. 排序完成:当未排序部分为空时,整个序列就排好序了。

代码

#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. 比较相邻元素:从第一个元素开始,比较相邻的两个元素。

  2. 交换元素位置:如果顺序不正确(比如,当前元素大于下一个元素),则交换这两个元素的位置。

  3. 一轮遍历完成:一次遍历后,最大的元素已经被交换到了最后的位置。

  4. 重复步骤1-3:重复进行相邻元素的比较和交换,直到整个序列有序。

  5. 排序完成:当没有发生交换时,说明序列已经有序,排序完成。

代码:

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.插入排序

它的核心思想是将未排序的元素逐个插入到已排序的部分,从而不断扩大已排序的范围。

插入排序的原理和步骤:

  1. 初始状态:将整个序列分为已排序和未排序两部分,初始时已排序部分只包含第一个元素,未排序部分包含剩下的元素。

  2. 逐个插入:从未排序部分取出一个元素,将其插入到已排序部分的适当位置,使得插入后的序列仍然有序。

  3. 已排序部分扩展:将已排序部分扩大一步。

  4. 重复步骤2和3:重复进行逐个插入的操作,直到未排序部分为空。

  5. 排序完成:当未排序部分为空时,整个序列就排好序了。

代码:

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。

  2. 按间隔进行插入排序:对每个子序列进行插入排序,其中子序列由间隔指定。这样,相距较远的元素可以交换位置,使得数组局部有序。

  3. 缩小间隔:重复进行插入排序,每次减小间隔,直到间隔为1,此时进行最后一次插入排序。

  4. 排序完成:当间隔为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)是一种基于二叉堆数据结构的排序算法。它的核心思想是通过构建一个最大堆(或最小堆),然后反复从堆顶取出最大(或最小)元素,将其放到数组末尾,再调整堆使其保持堆的性质,重复这个过程直到整个数组有序。

堆排序的原理和步骤:

  1. 构建堆:将待排序的数组构建成一个二叉堆。如果是升序排序,构建最大堆;如果是降序排序,构建最小堆。

  2. 堆排序:反复从堆顶取出最大(或最小)元素,将其放到数组末尾,然后调整堆使其保持堆的性质。

  3. 重复操作:重复步骤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. 分割:将待排序的数组分成两个相等(或近似相等)的子数组。

  2. 递归排序:递归地对两个子数组进行排序。如果子数组的长度大于1,则继续分割和排序。

  3. 合并:将两个已排序的子数组合并成一个有序数组。这是归并排序的关键步骤。

  4. 重复操作:重复上述步骤,直到整个数组有序。

代码

// 合并两个有序数组
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) 的排序算法更快。

快速排序的原理和步骤:

  1. 选择基准元素:从数组中选择一个基准元素。

  2. 分割数组:将数组分成两个子数组,左子数组的元素小于基准元素,右子数组的元素大于基准元素。

  3. 递归排序:递归地对左右子数组进行快速排序。

  4. 合并:无需合并步骤,因为在分割数组的过程中就已经完成了排序。

  5. 重复操作:重复上述步骤,直到整个数组有序。

代码

//将数组分割成两部分,返回基准元素的索引
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;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值