常见排序模板(冒泡排序,希尔排序,堆排序,归并排序,快速排序)

文章目录

介绍

更多算法模板见 github :https://github.com/surtr1/Algorithm/tree/main/BasicAlgorithm/Sort

持续更新中。。。

冒泡排序

原理

它的工作原理是每次检查相邻两个元素,如果前面的元素与后面的元素满足给定的排序条件,就将相邻两个元素交换。当没有相邻的元素需要交换时,排序就完成了。

经过 i i i 次扫描后,数列的末尾 i i i 项必然是最大的 i i i 项,因此冒泡排序最多需要扫描 n − 1 n - 1 n−1 遍数组就能完成排序。

是一种稳定的排序算法,时间复杂度为 O ( n 2 ) O(n^2) O(n2) , 辅助空间复杂度: O ( 1 ) O(1) O(1)

cpp 复制代码
template<class T>
void BubbleSort(vector<T> & a, int l, int r) {
    int len = r - l;
    for (int k = 1; k <= len ; k++) {
        bool swaped = false;
        for (int i = l + 1; i <= r; i++) {
            if (a[i] < a[i - 1]) {
                std::swap(a[i], a[i - 1]);
                swaped = true;
            }
        }
        if (!swaped) break;
    }
}

希尔排序

原理

希尔排序(英语:Shell sort),也称为缩小增量排序法,是 插入排序 的一种改进版本。

过程:

  1. 排序对不相邻的记录进行比较和移动:将待排序序列分为若干子序列(每个子序列的元素在原始数组中间距相同);
  2. 对这些子序列进行插入排序;
  3. 减小每个子序列中元素之间的间距,重复上述过程直至间距减少为 1。

是一种不稳定的排序算法,平均时间复杂度为 O ( n 1.3 ) O(n^{1.3}) O(n1.3), 辅助空间复杂度: O ( 1 ) O(1) O(1)

插入排序的工作原理为将待排列元素划分为「已排序」和「未排序」两部分,每次从「未排序的」元素中选择一个插入到「已排序的」元素中的正确位置。

cpp 复制代码
template<class T>
void ShellSort(vector<T> & a, int l, int r) {
    int len = r - l + 1;
    for (int gap = len >> 1; gap >= 1; gap >>= 1 ){
        for (int i = l + gap; i <= r; i++) {
            T key = a[i];
            int j = i;
            while (j >= l + gap && a[j - gap] > key) {
                a[j] = a[j - gap];
                j -= gap;
            }
            a[j] = key;
        }
    }
}

题目练习

题目链接:https://www.luogu.com.cn/problem/P1177

cpp 复制代码
void solve() {
    int n;
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) cin >> a[i];
    ShellSort(a, 1, n);
    for (int i = 1; i <= n; i++) cout << a[i] << " ";
    cout << "\n"; 
}

堆排序

原理

堆排序(英语:Heapsort)是指利用 二叉堆 这种数据结构所设计的一种排序算法。堆排序的适用数据结构为数组。

过程

堆排序的本质是建立在堆上的选择排序。

排序

  1. 首先建立大顶堆,然后将堆顶的元素取出,作为最大值,与数组尾部的元素交换,并维持残余堆的性质;
  2. 之后将堆顶的元素取出,作为次大值,与数组倒数第二位元素交换,并维持残余堆的性质;
  3. 以此类推,在第 n - 1 次操作后,整个数组就完成了排序。

是一种不稳定的排序算法,时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn) , 辅助空间复杂度: O ( 1 ) O(1) O(1)

cpp 复制代码
/*
基于 0 -idx 的堆排序
Parent(i) = (i - 1) / 2;
LeftChild(i) = 2 * i + 1;
RightChild(i) = 2 * i + 2;
*/

/*
start: 当前堆在数组中的起始绝对下标 (即 l)
end:   当前堆的结束绝对下标 (即 r 或 i-1)
parent: 当前要下沉的节点的绝对下标
*/
template<class T> 
void sift_down(vector<T> & a, int l, int r, int parent) {
    
    int child = l + 2 * (parent - l) + 1;
    while (child <= r) {
        //找最大的儿子
        if (child + 1 <= r && a[child + 1] > a[child]) {
            child++;
        } 
        if (a[child] < a[parent]) return ;
        std::swap(a[parent], a[child]);
        parent = child;
        child = l + 2 * (parent - l) + 1;
    }
}


template<class T>
void HeapSort(vector<T> & a, int l, int r) {
    // 从最后一个节点的父节点开始 sift down 以完成堆化 (heapify)
    int sz = r - l + 1;
    for (int i = sz / 2 - 1; i >= 0; i--) {
        int parent = l + i; //偏移量 + 起点 就是真实值
        sift_down(a, l, r, parent);
    }
    //先将第一个元素和已经排好的元素前一位做交换,
    //再重新调整(刚调整的元素之前的元素),直到排序完毕
    for (int i = r; i > l; i--) {
        std::swap(a[i], a[l]);
        sift_down(a, l, i - 1, l); //剩余区间 [l, i - 1]
    }

}

题目练习

题目链接:https://www.luogu.com.cn/problem/P1177

cpp 复制代码
//https://www.luogu.com.cn/problem/P1177
void solve() {
    int n;
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) cin >> a[i];
    HeapSort(a, 1, n);
    for (int i = 1; i <= n; i++) cout << a[i] << " ";
    cout << "\n"; 
}

归并排序

原理

归并排序基于分治思想将数组分段排序后合并,时间复杂度在最优、最坏与平均情况下均为 O ( n l o g ⁡ n ) O(n log⁡n) O(nlog⁡n),空间复杂度为 O ( n ) O(n) O(n)。

归并排序可以只使用 O ( 1 ) O(1) O(1) 的辅助空间,但为便捷通常使用与原数组等长的辅助数组。

最重要的步骤 合并

归并排序最核心的部分是合并(merge)过程:将两个有序的数组 a [ i ] a[i] a[i] 和 b [ j ] b[j] b[j] 合并为一个有序数组 c [ k ] c[k] c[k]。

从左往右枚举 a [ i ] a[i] a[i] 和 b [ j ] b[j] b[j],找出最小的值并放入数组 c [ k ] c[k] c[k];重复上述过程直到 a [ i ] a[i] a[i] 和 b [ j ] b[j] b[j] 有一个为空时,将另一个数组剩下的元素放入 c [ k ] c[k] c[k]。

为保证排序的稳定性,前段首元素小于或等于后段首元素时( a [ i ] < = b [ j ] a[i] <= b[j] a[i]<=b[j])而非小于时( a [ i ] < b [ j ] a[i] < b[j] a[i]<b[j])就要作为最小值放入 c [ k ] c[k] c[k]。

分治法实现归并排序

  • 当数组长度为 1 时,该数组就已经是有序的,不用再分解。

  • 当数组长度大于 1 时,该数组很可能不是有序的。此时将该数组分为两段,再分别检查两个数组是否有序(用第 1 条)。如果有序,则将它们合并为一个有序数组;否则对不有序的数组重复第 2 条,再合并。

用数学归纳法可以证明该流程可以将一个数组转变为有序数组。

cpp 复制代码
template<class T>
void _MergeSort(vector<T>& a, int l, int r, vector<T>& tmp) {
    if (l >= r) return ;
    int mid = l + ((r - l) >> 1);
    _MergeSort(a, l, mid, tmp), _MergeSort(a, mid + 1, r, tmp);
    int i = l, j = mid + 1, k = l;
    while (i <= mid && j <= r) {
        if (a[i] <= a[j]) tmp[k++] = a[i++];
        else tmp[k++] = a[j++];
    }
    while (i <= mid) tmp[k++] = a[i++];
    while (j <= r) tmp[k++] = a[j++];
    for (i = l; i <= r; i++) {
        a[i] = tmp[i];
    }
}

template<class T>
void MergeSort(vector<T>& a, int l, int r) {
    if(l < r && !a.empty()) {
        vector<T> tmp(a.size());
        _MergeSort(a, l, r, tmp);
    }
}

题目练习

题目链接:https://www.luogu.com.cn/problem/P1177

cpp 复制代码
//https://www.luogu.com.cn/problem/P1177
void solve() {
    int n;
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) cin >> a[i];
    MergeSort(a, 1, n);
    for (int i = 1; i <= n; i++) cout << a[i] << " ";
    cout << "\n"; 
}

快速排序

原理

快速排序的工作原理是通过 分治 的方式来将一个数组排序。

快速排序分为三个过程:

  • 将数列划分为两部分(要求保证相对大小关系);
  • 递归到两个子序列中分别进行快速排序;
  • 不用合并,因为此时数列已经完全有序。

和归并排序不同,第一步并不是直接分成前后两个序列,而是在分的过程中要保证相对大小关系。具体来说,第一步要是要把数列分成两个部分,然后保证前一个子数列中的数都小于后一个子数列中的数。为了保证平均时间复杂度,一般是随机选择一个数 𝑚

m 来当做两个子数列的分界。

之后,维护一前一后两个指针 i 和 j,依次考虑当前的数是否放在了应该放的位置,i 从前往后找到第一个大于等于 m的数,j 从后往前找到第一个小于等于 m 的数,那么可以交换 i 和 j 位置上的数,再移动指针继续处理,直到两个指针相遇。

快速排序的最优时间复杂度和平均时间复杂度为 O ( n l o g ⁡ n ) O(nlog⁡n) O(nlog⁡n),最坏时间复杂度为 O ( n 2 ) O(n^2) O(n2)。

cpp 复制代码
template<class T>
void QuickSort(vector<T>& a, int low, int high) {
    if (low >= high) return;
    
    static std::mt19937 gen(std::random_device{}());
    std::uniform_int_distribution<int> dis(low, high);
    T pivot = a[dis(gen)];

    //双指针划分
    int i = low, j = high;
    while (i <= j) {
        while (a[i] < pivot) i++;
        while (a[j] > pivot) j--;
        
        if (i <= j) {
            std::swap(a[i], a[j]);
            i++;
            j--;
        }
    }
    QuickSort(a, low, j);
    QuickSort(a, i, high);
}

template<class T>
void QuickSort(vector<T>& a) {
    if(!a.empty()) {
        QuickSort(a, 0, (int)a.size() - 1);
    }
}

题目练习

题目链接:https://www.luogu.com.cn/problem/P1177

cpp 复制代码
//https://www.luogu.com.cn/problem/P1177
void solve() {
    int n;
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) cin >> a[i];
    QuickSort(a, 1, n);
    for (int i = 1; i <= n; i++) cout << a[i] << " ";
    cout << "\n"; 
}
相关推荐
fengfuyao9852 小时前
经典MUSIC算法程序以及测角精度与阵元间距、阵元数、信噪比、快拍数等的关系
算法
十八岁讨厌编程2 小时前
【算法训练营 · 补充】LeetCode Hot100(下)
算法·leetcode·职场和发展
一路往蓝-Anbo2 小时前
C语言从句柄到对象 (三) —— 抛弃 Malloc:静态对象池与索引句柄的终极形态
c语言·开发语言·数据结构·stm32·单片机·算法
fantasy_arch3 小时前
SVT-AV1 B帧决策和mini-GOP决策分析
算法·av1
声声codeGrandMaster3 小时前
逻辑回归-泰坦尼克号
算法·机器学习·逻辑回归
集芯微电科技有限公司3 小时前
PC1001超高频率(50HMZ)单通单低侧GaN FET驱动器支持正负相位配置
数据结构·人工智能·单片机·嵌入式硬件·神经网络·生成对抗网络·fpga开发
一路往蓝-Anbo3 小时前
C语言从句柄到对象 (二) —— 极致的封装:不透明指针与 SDK 级设计
c语言·开发语言·数据结构·stm32·单片机·嵌入式硬件
上天_去_做颗惺星 EVE_BLUE3 小时前
C++学习:学生成绩管理系统
c语言·开发语言·数据结构·c++·学习
mu_guang_4 小时前
算法图解2-选择排序
数据结构·算法·排序算法