【蓝桥杯备赛Day5】排序

冒泡排序

思想: 两两比较,大的往后移,每轮确定一个最大值。(一个一个交换)

对于数组a[ ],每次确定操作就是从左往右扫描,如果a[ i ]>a[ i+1 ],就执行 swap(a[ i ],a[ i+1 ])将两项交换,然后再往右检查,这样可以找出最大的并将其丢到最右边。

  • 第一次确定操作是将a[1]~a[n]中最大的放到a[n]
  • 第二次确定操作是将a[1]~a[n-1]中最大的放到a[n-1]
  • ...

复杂度:O(n²)

模板:

复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 9;
int a[N];

int main()
{
    int n;cin >>n;
    for(int i = 1;i <= n; ++i)cin >> a[i];
    
    //i表示当前要确定的位置,从右往左
    for(int i = n;i >= 1; -- i)
    {
        //j从左往右扫,最后一个j+1就到i了
        for(int j = 1;j <= i - 1; ++ j)
        {
            if(a[j] > a[j + 1])swap(a[j], a[j + 1]);
        }
    }
    
    //输出
    for(int i = 1;i <= n; ++ i)cout << a[i] << " \n"[i == n];
    
    return 0;
}

外层循环 i :从n递减到1,表示当前要确定最大值的位置(即右边界)。

内层循环 j :从1遍历到i-1,比较相邻元素并交换,将当前最大值 "冒泡" 到i位置。

输出部分" \n"[i == n] 表示最后一个元素后输出换行,其余元素后输出空格。

选择排序

思想 :每轮找出最小值,直接交换到前面;或者每轮找出最大值,直接交换到末尾。(直接选)

对于数组a[ ],每次确定操作(假设当前要确定的是i位置)就是从左往右扫描,计算出最大元素的下标 mid_id,最后执行一次 swap(a[ max_id ],a[ i ]) 将两项交换即可。

复杂度:O(n²)

复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 9;
int a[N];

int main()
{
    int n;cin >>n;
    for(int i = 1;i <= n; ++i)cin >> a[i];
    
    //i表示当前要确定的位置
    for(int i = n;i >= 1; -- i)
    {
        int max_id = 1;//初始化为1
        //j从左往右扫求出max_id
        for(int j = 1;j <= i; ++ j)
        {
            if(a[j] > a[max_id])max_id = j;
        }
        swap(a[max_id], a[i]);
    }
    
    //输出
    for(int i = 1;i <= n; ++ i)cout << a[i] << " \n"[i == n];
    
    return 0;
}

对比冒泡:冒泡是相邻交换 "冒泡",选择是先找最值再一次交换。

循环细节 :外层i从n递减到1,内层j遍历[1,i]找最大值下标。

插入排序

思想 :将待排序的元素逐个插入到已排序序列的合适位置中。

复杂度:O(n²)

模板:

复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 9;
int a[N];

int main()
{
    int n; cin >> n;
    for(int i = 1; i <= n; ++i) cin >> a[i];

    // i表示当前要确定的位置(一个数字默认有序)
    for(int i = 2;i <= n; ++ i)
    {
        //此时[1, i - 1]已经为有序的数组,j表示要插入的位置
        int val = a[i], j;

        //将val和a[j - 1]比较,如果val小,就将a[j - 1]往后移动一格,给val留出位置
        for(j = i;j > 1 && val < a[j - 1]; -- j)a[j] = a[j - 1];

        //当循环跳出时,j = 1或val >= a[j],且原本的a[j]已经往后移动,此时的j为给val腾出的位置
        a[j] = val;
    }

    for(int i = 1; i <= n; ++i) cout << a[i] << " ";
    return 0;
}

快速排序

思想 :选一个基准数,数组中小于基准的都要放左边,大于基准的放右边,再递归两边。

通过不断地将数组分成两个子数组,递归地对子数组进行排序,最终得到一个有序的数组。这个过程通过选择合适的基准和分区操作来实现。不需要格外的空间。

复杂度:O(n log n)

模板:

快速排序的递归主体QuickSort()

复制代码
// 快速排序的递归主体QuickSort()。
// 传入参数要排序的数组和区间的左右端点。
void QuickSort(int a[], int l, int r)
{
    // l=r表示单个元素区间,l>r表示空区间,它们之间是相互独立的
    if(l < r)
    {
        // Partition函数会将数组a[l]~a[r]这个区间中某个基准数字放到正确的位置并将这个位置返回。
        int mid = Partition(a, l, r);
        // 在确定了mid的位置之后,可以保证a[l]~a[mid-1]都 < a[mid] < a[mid+1]~a[r],只需要将左右两边分别向下递归地排序即可。
        QuickSort(a, l, mid - 1);
        QuickSort(a, mid + 1, r);
    }
}

分区函数 partition()

复制代码
// 分区函数:将基准放到正确位置,返回基准下标
// 传入数组以及想要排序的区间范围(默认升序)
int partition(int a[], int l, int r)
{
    // 先设置a[r]为基准
    int pivot = a[r];
    // 设左指针i,右指针j,分别从l, r开始往中间走
    int i = l, j = r;
    while(i < j)
    {
        // 左指针i找比pivot大的
        while(i < j && a[i] <= pivot) i++;
        // 循环出来后有i >= j 或 找到在左区间但是比pivot大的(说明找到了需要交换的位置)
        while(i < j && a[j] >= pivot) j--;
        // 循环出来后有i >= j 或 找到在右区间但是比pivot小的(说明找到了需要交换的位置)
        // 如果i < j说明没相遇,就把左右区间的这两个数交换;相遇了说明左右两边分区的其他元素都分配好了,剩下这个是大于pivot的数,将这个数跟基准数交换,基准数就被放到正确位置上了
        if(i < j) swap(a[i], a[j]);
        else swap(a[i], a[r]);
    }
    return i;//返回基准下标
}

归并排序

思想 :先把数组拆成两半,分别递归排好,再将两边按大小合并到新数组中,最后把新数组复制回原数组。

先把无序数组从中间不断递归拆分成更小的子数组,直到每个子数组只剩一个元素(单个元素天然有序),然后从最底层开始逐层往上合并------ 合并时用双指针分别遍历两个有序子数组,每次取出较小的元素按顺序放入临时数组,等两个子数组全部合并完成后,再把有序结果复制回原数组,直到整个数组最终合并为一个完整的有序数组,整个过程稳定且时间复杂度为 O (n log n)

复杂度:O(n log n)

模板:

归并排序的递归主体MergeSort()

复制代码
// 归并排序的递归主体MergeSort()。
// 传入要排序的数组和区间的左右端点。
void MergeSort(int a[], int l, int r)
{
    // 递归结束条件:当区间大小为1时,直接返回即可。
    if(l == r)return;
    
    // 找到中间位置(默认向下取整)
    int mid = (l + r) / 2;
    //递归排序
    MergeSort(a, l, mid);
    MergeSort(a, mid + 1, r);
    // 排序完成后a[l, mid]和a[mid + 1, r]都是分别有序的

    // 将a[l, r]两部分一个个地放入到b[l, r]
    int pl = l, pr = mid + 1, pb = l;
    
    while(pl <= mid || pr <= r)
    {
        if(pl > mid)
        {
            // 左半边已经放完,剩下的b全放右边的
            b[pb ++] = a[pr ++];
        }
        else if(pr > r)
        {
            // 右半边已经放完,剩下的b全放左边的
            b[pb ++] = a[pl ++];
        }
        else
        {
            // 两边都还有元素,取个小的放到b里
            if(a[pl] < a[pr])b[pb ++] = a[pl ++];
            else b[pb ++] = a[pr ++];
        }
    }
    // 完成后复制回去
    for(int i = l;i <= r; ++ i)a[i] = b[i];
}

桶排序

思想: 把数字按大小分到不同 "桶" 里,每个桶内部再简单排一下,最后按桶顺序倒出来,就是有序数组。适合数据量大但是值域小(<=1e6)的数据。

复杂度: O(n)

模板:

每个桶对应一个数值

复制代码
#include <bits/stdc++.h>
using namespace std;
const int MAX = 500010;  // 数据最大值(根据题目改)
int cnt[MAX];            // 桶数组:记录每个数出现次数

int main()
{
    int n;cin >> n;
    
    // 1. 统计每个数字出现次数
    for (int i = 1; i <= n; i++) {
        int x;cin >> x;
        cnt[x]++;
    }
    
    // 2. 从小到大输出(排序完成)
    for (int i = 0; i < MAX; i++) {
        while (cnt[i]--) {    // 出现几次就输出几次,没出现不会输出
            cout << i << " ";
        }
    }
    
    return 0;
}

每个桶对应一段区间,复杂度与每个桶内排序方法有关,不推荐。

相关练习

1.宝藏排序Ⅰ - 蓝桥云课

2.宝藏排序Ⅱ - 蓝桥云课

3.计数排序 - 蓝桥云课

相关推荐
今儿敲了吗2 小时前
Linux学习笔记第三章——基础命令(一)
linux·笔记·学习
浅念-2 小时前
LeetCode 双指针题型 C++ 解题整理
开发语言·数据结构·c++·笔记·算法·leetcode·职场和发展
数据与后端架构提升之路2 小时前
系统架构设计师常见高频考点总结之信息化基础与系统规划、项目管理
笔记
诸神缄默不语2 小时前
论文阅读笔记:Claude如何思考
论文阅读·笔记·大模型·llm·大语言模型·claude·大规模预训练语言模型
zzb15802 小时前
Agent案例-智能文档问答助手
java·人工智能·笔记·python
_kerneler2 小时前
LUKS学习笔记(1)
网络·笔记·学习
EQ-雪梨蛋花汤2 小时前
【存档笔记】三阶贝塞尔 vs 赫米特曲线:原理、公式与工程统一理解
笔记
叫我六胖子2 小时前
策划面试记录
笔记
弘毅 失败的 mian3 小时前
Linux 进程属性详解
linux·运维·服务器·经验分享·笔记