一文带你彻底看懂C++常见排序算法

 更新时间:2026年01月06日 08:32:39   作者:落羽的落羽  
在计算机科学中,排序算法是数据处理的基础工具,这篇文章主要介绍了C++常见排序算法的相关资料,文中通过代码介绍的非常详细,对大家学习或者使用C++具有一定的参考借鉴价值,需要的朋友可以参考下

前言

排序,就是使一组数据,按照某种规则,递增或递减排列起来的操作。是生活中应用最广泛的算法之一。包括多种排序算法,今天我们介绍最常见的几种,下面, 我默认都是用int数据排成升序作为演示

一、插入排序

1. 直接插入排序

直接插入排序是一种简单的插入排序法,基本思想是:把待排序的数据按照其大小逐个插入到一个已经排好序的有序序列中,直到所有的记录都插入完为止,得到一个新的有序序列。

这种排序思想,类比像我们玩扑克牌时一张张插入手牌。

当插入第i个元素时,前面的arr[0] arr[1] ... arr[i-1]已经是有序的了,此时用arr[i],与这些数据进行比较,找到正确的位置插入,原位置及之后的元素后移一位。

代码实现:

void InsertSort(vector<int>& arr)
{
	int n = arr.size();
    for (int i = 1; i < n; ++i) 
    {
        int tmp = arr[i]; // 当前待插入元素

        int j = i - 1;
        // 向前找比tmp大的元素,大就往后移一位
        while (j >= 0 && arr[j] > tmp) 
        {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = tmp; // 插入到正确位置
    }
}

测试:

复杂度分析:

  • 时间复杂度O(n2),数组越接近有序,时间效率越高
  • 空间复杂度O(1)

2. 希尔排序

希尔排序是直接插入排序的优化版本,也叫 “缩小增量排序”。它的基本思想是:先选定一个整数gap(通常是gap = n/3 + 1),把待排序数据按照下标间隔gap分成各组(如n=10,gap = 4,则下标0, 4, 8在同一组),并对每一组内的数据进行插入排序,然后gap = gap/3 + 1,再用新gap分成新组,进行插入排序。当gap = 1时,就相当于直接插入排序了。

举个直观的分析过程例子:

原数组[8 9 1 7 2 3 5 4 6 0]

  • gap = 10/3 + 1 = 4按 gap=4 分组,每组元素下标差为 4:
    组 1:[8, 2, 6] → 插入排序后 [2, 6, 8]
    组 2:[9, 3, 0] → 插入排序后 [0, 3, 9]
    组 3:[1, 5] → 插入排序后 [1, 5]
    组 4:[7, 4] → 插入排序后 [4, 7]
    数组变为:[2, 0, 1, 4, 6, 3, 5, 7, 8, 9]

  • gap = 4/3 + 1 = 2按 gap=2 分组,每组元素下标差为 2:
    组 1:[2, 1, 6, 5, 8] → 插入排序后 [1, 2, 5, 6, 8]
    组 2:[0, 4, 3, 7, 9] → 插入排序后 [0, 3, 4, 7, 9]
    数组变为:[1, 0, 2, 3, 5, 4, 6, 7, 8, 9]

  • gap = 2/3 + 1 = 1此时 gap=1,相当于对整个基本有序的数组做直接插入排序:
    排序后最终结果:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

gap > 1时都是预排序,目的是让数据更接近有序。gap == 1时,数组已经接近有序了,进行直接插入排序就会很快

代码实现:

void ShellSort(vector<int>& arr)
{
    int n = arr.size();

    int gap = n;
    while (gap > 1) // gap > 1 时做预排序
    {
        gap = gap / 3 + 1;

        // 直接遍历所有元素,按gap做插入排序
        for (int i = gap; i < n; i++)
        {
            int tmp = arr[i]; // 当前待插入元素
            int j = i - gap; // 同组的前一个元素下标

            // 向前找比tmp大的元素,大则后移
            while (j >= 0 && arr[j] > tmp)
            {
                arr[j + gap] = arr[j];
                j -= gap;
            }
            arr[j + gap] = tmp; // 插入到正确位置
        }
    }
}

测试:

复杂度分析:

  • 时间复杂度:希尔排序的时间复杂度受gap的影响,很难去计算,通常介于O(n1.3)~O(n2)之间,数组越接近有序,时间效率越高。综合来说,它的效率还是优于直接插入排序的。
  • 空间复杂度O(1)

二、选择排序

选择排序的基本思想是,每一次从待排序元素中选出最小的元素,放在已排好序列的起始位置,直到全部待排序数据选择完毕。

1. 直接选择排序

直接选择排序,就是按照上面的思路进行:

代码实现:

void SelectSort(vector<int>& arr)
{
    int n = arr.size();
    for (int i = 0; i < n - 1; i++)
    {
        int min = i; // min记录当前最小元素的下标

        // 找到[i+1, n)中的最小元素
        for (int j = i + 1; j < n; j++)
        {
            if (arr[j] < arr[min])
            {
                min = j;
            }
        }
        //将找到的最小位置交换到当前位置
        swap(arr[i], arr[min]);
    }
}

复杂度分析:

  • 时间复杂度O(n2),实际中很少使用
  • 空间复杂度O(1)

2. 堆排序

堆排序是指利用“堆”这种数据结构设计的一种排序算法,它是选择排序的一种,利用堆来进行选择数据。以前我有一篇文章讲过了:堆的讲解当时我是用C语言实现的,在C++中,我们有了std::priority_queue,而它的底层也是使用的堆;或者STL的算法库中还有一个std::make_head,可以直接在一段迭代器区间上建堆!所以C++中写堆排序可以直接利用它们了。

代码实现:

// 写法1, 用priority_queue
void HeapSort1(vector<int>& arr) 
{
    // 1. 构建小根堆(priority_queue默认是大根堆,要传greater使堆顶为最小值)
    priority_queue<int, vector<int>, greater<int>> pq(arr.begin(), arr.end());

    int n = arr.size();
    // 堆顶是当前最小值,从前往后填充数组
    for (int i = 0; i < n; i++)
    {
        arr[i] = pq.top();
        pq.pop();
    }
}

// 写法2, 用make_heap算法
void HeapSort2(vector<int>& arr) 
{
    int n = arr.size();
    for (int i = 0; i < n; i++) 
    {
        //每次调整未排序部分为小根堆, 每次调整后堆顶位置i都是最小值
        make_heap(arr.begin() + i, arr.end(), greater<int>());
    }
}

测试:

复杂度分析:

  • 时间复杂度:O(nlogn)
  • 空间复杂度O(1)

三、交换排序

所谓交换,就是根据序列中两个数据的比较结果来交换它们在序列中的位置。

交换排序的特点是:将键值较大的数据向序列的尾部移动,键值较小的记录向序列的前部移动。

1. 冒泡排序

冒泡排序应该是我们最早学习的一种排序算法了,不必多言

代码实现:

void BubbleSort(vector<int>& arr) 
{
    int n = arr.size();
    for (int i = 0; i < n - 1; ++i) 
    {
        bool swapped = false; // 标记是否发生交换

        // 每轮将最大元素“冒泡”到末尾
        for (int j = 0; j < n - 1 - i; ++j) 
        {
            if (arr[j] > arr[j + 1]) 
            {
                swap(arr[j], arr[j + 1]);
                swapped = true;
            }
        }
        if (!swapped) 
            break; // 无交换, 则已有序
    }
}

复杂度分析:

  • 时间复杂度:O(n2)
  • 空间复杂度O(1)

2. 快速排序

快速排序是一种二叉树结构的交换排序方法,其基本思想是:取待排序元素序列中的某元素为基准值,按照该值将待排序集合分割成两子序列,左子序列中元素小于基准值,右子序列元素大于基准值。然后左右子序列重复该过程,直到所有元素排列完成。

以数组 [8, 9, 1, 7, 2, 3, 5, 4, 6, 0] 为例,选第一个元素8作为基准:

  • 第一次分区(基准 = 8)
    遍历数组,将 <8 的元素放左边,>8 的放右边:
    左区:[1,7,2,3,5,4,6,0]右区:[9]数组变为:[1,7,2,3,5,4,6,0,8,9]
  • 递归处理左区 [1,7,2,3,5,4,6,0](基准 = 1)
    分区后:
    左区:[0]右区:[7,2,3,5,4,6]数组变为:[0,1,7,2,3,5,4,6,8,9]
  • 递归处理右区 [7,2,3,5,4,6](基准 = 7)
    分区后:
    左区:[2,3,5,4,6]右区:[]数组变为:[0,1,2,3,5,4,6,7,8,9]
  • 递归处理剩余子数组…
    直到所有子数组长度为 1,最终得到有序数组:[0,1,2,3,4,5,6,7,8,9]

如何分出左右区是核心,有多种方法,这里介绍一种“挖坑法”:

代码实现:

void _QuickSort(vector<int>& arr, int left, int right)
{
    if (left >= right)
        return;

    int l = left;
    int r = right;

    // 选第一个元素作为基准,也可以选其他的
    int mid = arr[l];
    while (l < r)
    {
        // 从右找比基准小的元素
        while (l < r && arr[r] >= mid)
        {
            r--;
        }
        arr[l] = arr[r]; // 放到基准左边的坑l,r成为新坑

        // 从左找比基准大的元素
        while (l < r && arr[l] <= mid)
        {
            l++;
        }
        arr[r] = arr[l]; // 放到基准右边的坑r,l成为新坑
    }
    arr[l] = mid; // 基准放到最终位置

    // 递归处理左右区间
    _QuickSort(arr, left, l - 1);  // 左区间:[left, l-1]
    _QuickSort(arr, l + 1, right); // 右区间:[l+1, right]
}

void QuickSort(vector<int>& arr)
{
    _QuickSort(arr, 0, arr.size() - 1);
}

测试:

复杂度分析:

  • 时间复杂度:O(nlogn),std::sort主要就是快速排序实现的
  • 空间复杂度O(logn)

四、归并排序

归并排序是建立在归并操作上的一种排序算法,采用分治的思路。

分:将待排序数组递归地拆分成两个子数组,直到每个子数组只有1个元素(天然有序)治:递归地对每个子数组进行归并排序合并:将两个有序的子数组合并成一个有序的大数组

以数组 [8, 9, 1, 7, 2, 3, 5, 4, 6, 0] 为例,分步展示过程:

  • 从最小的有序子数组开始,两两合并为更大的有序数组:
  • 合并 [8] 和 [9] → [8,9];
  • 合并 [7] 和 [2] → [2,7],再合并 [1] 和 [2,7] → [1,2,7];
  • 合并 [8,9] 和 [1,2,7] → [1,2,7,8,9];
  • 同理,右半部分合并为 [0,3,4,5,6];
  • 最终合并 [1,2,7,8,9] 和 [0,3,4,5,6] → [0,1,2,3,4,5,6,7,8,9]。

代码实现:

void _MergeSort(vector<int>& arr, int left, int right) 
{
    if (left >= right) 
    {
        return; // 子数组长度为1,递归终止
    }

    // 等价于 (low + high)/2,但防止low+high超出int范围
    int mid = left + (right - left) / 2;

    _MergeSort(arr, left, mid);       // 递归排序左子数组
    _MergeSort(arr, mid + 1, right);  // 递归排序右子数组

    // 合并左右数组
    // 临时数组存储合并结果
    vector<int> tmp(right - left + 1);

    int i = left;    // 指向左数组
    int j = mid + 1; // 指向右数组
    int k = 0;       // 指向临时数组

    // 合并两个有序子数组到临时数组
    while (i <= mid && j <= right)
    {
        // 小的放入临时数组
        if (arr[i] <= arr[j])
        {
            tmp[k++] = arr[i++];
        }
        else
        {
            tmp[k++] = arr[j++];
        }
    }

    // 处理左子数组剩余元素
    while (i <= mid)
        tmp[k++] = arr[i++];

    // 处理右子数组剩余元素
    while (j <= right)
        tmp[k++] = arr[j++];

    // 将临时数组拷贝回原数组
    for (k = 0; k < tmp.size(); k++)
        arr[left + k] = tmp[k];
}

void MergeSort(vector<int>& arr) 
{
    _MergeSort(arr, 0, arr.size() - 1);
}

测试:

复杂度分析:

  • 时间复杂度:O(nlogn)
  • 空间复杂度O(n)

总结 

到此这篇关于C++常见排序算法的文章就介绍到这了,更多相关C++常见排序算法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解C语言中scanf函数使用的一些注意点

    详解C语言中scanf函数使用的一些注意点

    这篇文章主要介绍了C语言中scanf函数使用的一些注意点,scanf函数的使用是C语言入门学习中的基础知识,需要的朋友可以参考下
    2016-04-04
  • Visual Studio2022下Opencv的配置图文教程

    Visual Studio2022下Opencv的配置图文教程

    本文主要介绍了Visual Studio2022下Opencv的配置图文教程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • c语言实现可自定义的游戏地图

    c语言实现可自定义的游戏地图

    这篇文章主要为大家详细介绍了c语言实现可自定义的游戏地图,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • C语言动态内存函数(malloc、calloc、realloc、free)详解

    C语言动态内存函数(malloc、calloc、realloc、free)详解

    在C语言中,动态内存函数是块重要的知识点,以往,我们开辟空间都是固定得,数组编译结束后就不能继续给它开辟空间了,开辟的空间满了,就不能在开辟空间了,学习本文章,我们就可以解决这个问题,向内存申请空间,感兴趣的小伙伴跟着小编一起来看看吧
    2023-08-08
  • C++大小字母的转换方式

    C++大小字母的转换方式

    这篇文章主要介绍了C++大小字母的转换方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • C语言中的常见进制转换详解(从二进制到十六进制)

    C语言中的常见进制转换详解(从二进制到十六进制)

    进制转换是计算机编程中的一个常见任务,特别是在处理低级别的数据操作时,C语言作为一门底层编程语言,在进制转换方面提供了灵活的操作方式,今天,我们将深入探讨C语言中的进制转换,了解如何在二进制、八进制、十进制和十六进制之间相互转换,需要的朋友可以参考下
    2025-05-05
  • C++实现将输入复制到输出的方法

    C++实现将输入复制到输出的方法

    这篇文章主要介绍了C++实现将输入复制到输出的方法,实例分析了C++字符串转换及输入输出操作的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07
  • C++超详细探究new/delete的使用

    C++超详细探究new/delete的使用

    这篇文章主要介绍了C++中new与deleted关键字的使用,new在动态内存中为对象分配空间并返回一个指向该对象的指针;delete接受一个动态对象的指针, 销毁该对象, 并释放与之关联的内存
    2022-07-07
  • 如何基于C语言socket编程实现TCP通信

    如何基于C语言socket编程实现TCP通信

    本文介绍了如何基于C语言socket编程实现TCP通信,下面小编来简单介绍下
    2019-05-05
  • C++实现线程池的简单方法示例

    C++实现线程池的简单方法示例

    这篇文章主要给大家介绍了关于C++实现线程池的简单方法,文中通过示例代码介绍的非常详细,对大家学习或者使用C++具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2020-05-05

最新评论