C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++常见排序算法

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

作者:落羽的落羽

在计算机科学中,排序算法是数据处理的基础工具,这篇文章主要介绍了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; // 插入到正确位置
    }
}

测试:

复杂度分析:

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 > 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; // 插入到正确位置
        }
    }
}

测试:

复杂度分析:

二、选择排序

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

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]);
    }
}

复杂度分析:

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>());
    }
}

测试:

复杂度分析:

三、交换排序

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

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

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; // 无交换, 则已有序
    }
}

复杂度分析:

2. 快速排序

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

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

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

代码实现:

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);
}

测试:

复杂度分析:

四、归并排序

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

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

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

代码实现:

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);
}

测试:

复杂度分析:

总结 

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

您可能感兴趣的文章:
阅读全文