NO.55十六届蓝桥杯备战|排序|插入|选择|冒泡|堆|快速|归并(C++)

插⼊排序

插⼊排序(Insertion Sort)类似于玩扑克牌插牌过程,每次将⼀个待排序的元素按照其关键字⼤⼩插⼊到前⾯已排好序的序列中,按照该种⽅式将所有元素全部插⼊完成即可

#include <iostream>  
using namespace std;  const int N = 1e5 + 10;  
int n;  
int a[N];  void insert_sort()  
{  // 依次枚举待排序的元素  for(int i = 2; i <= n; i++) // 第⼀个位置默认就是有序的  {  int key = a[i];  // 前⾯⽐ key ⼤的,统⼀右移  int j = i - 1;  while(j >= 1 && a[j] > key)  {  a[j + 1] = a[j];  j--;  }  a[j + 1] = key;  }  
}int main()  
{  cin >> n;  for(int i = 1; i <= n; i++) cin >> a[i];  insert_sort();  for(int i = 1; i <= n; i++) cout << a[i] << " ";  return 0;  
}

时间复杂度

  • 当整个序列有序的时候,插⼊排序最优,此时时间复杂度为O(n) 。
  • 当整个序列逆序的时候,每个元素都要跑到最前⾯,时间复杂度为O(n2)

选择排序

选择排序(Selection Sort)是⼀种特别直观的排序算法。每次找出未排序序列中最⼩的元素,然后放进有序序列的后⾯

#include <iostream>  
using namespace std;  
const int N = 1e5 + 10;  
int n;  
int a[N];  
void selection_sort()  
{  for(int i = 1; i < n; i++) // 待排序区间的⾸位置  {  // [i, n] 区间就是待排序的区间  int pos = i;for(int j = i + 1; j <= n; j++) // 查找待排序区间最⼩的元素的下标  {  if(a[j] < a[pos])  {  pos = j;  }  }  swap(a[i], a[pos]);  }  
}  
int main()  
{  cin >> n;  for(int i = 1; i <= n; i++) cin >> a[i];  selection_sort();  for(int i = 1; i <= n; i++) cout << a[i] << " ";  return 0;  
}

【时间复杂度】

  • ⽆论数组有序还是⽆序,在未排序区间内找最⼩值的时候,都需要遍历⼀遍待排序的区间,因此时间复杂度为O(n2)

冒泡排序

冒泡排序(Bubble Sort)也是⼀种简单的排序算法。它的⼯作原理是每次检查相邻两个元素,如果前⾯的元素与后⾯的元素满⾜给定的排序条件,就将相邻两个元素交换。当没有相邻的元素需要交换时,排序就完成了。
由于在算法的执⾏过程中,较⼤的元素像是⽓泡般慢慢浮到数列的末端,故叫做冒泡排序

#include <iostream>
using namespace std;  
const int N = 1e5 + 10;  
int n;  
int a[N];  
// 优化后的冒泡排序  
void bubble_sort()  
{  // 依次枚举待排序区间的最后⼀个元素  for(int i = n; i > 1; i--)  {  bool flag = false;  // [1, i] 就是待排序区间  for(int j = 1; j < i; j++)  {  if(a[j] > a[j + 1])  {  swap(a[j], a[j + 1]);  flag = true;  }  }  if(flag == false) return;  }  
}  
int main()  
{  cin >> n;  for(int i = 1; i <= n; i++) cin >> a[i];  bubble_sort();  for(int i = 1; i <= n; i++) cout << a[i] << " ";  return 0;  
}

时间复杂度

  • 如果数组有序,只会扫描⼀遍,时间复杂度为O(n) 。
  • 如果逆序,所有元素都会向后冒⼀次到合适位置,时间复杂度为O(n2)

堆排序

堆排序(Heap Sort)是指利⽤堆这种数据结构所设计的⼀种排序算法。本质上是优化了选择排序算法,如果将数据放在堆中,能够快速找到待排序元素中的最⼩值或最⼤值。
堆排序的过程分两步:

  1. 建堆。升序建⼤堆,降序建⼩堆。
    建堆过程,从倒数第⼀个⾮叶⼦节点开始,倒着⼀直到根结点位置,每个结点进⾏向下调整。
  2. 排序。删除堆顶的逻辑。
    每次将堆顶元素与堆中最后⼀个元素交换,然后将堆顶元素往下调整。直到堆中剩余⼀个元素,所有元素就都有序了。
    因此,堆排序仅需⽤到向下调整算法
#include <iostream>  
using namespace std;  
const int N = 1e5 + 10;  
int n;  
int a[N];  
void down(int parent, int len)  
{  int child = parent * 2;  while(child <= len)  {  if(child + 1 <= len && a[child + 1] > a[child]) child++;  if(a[parent] >= a[child]) return;swap(a[parent], a[child]);  parent = child;  child = parent * 2;  }  
}  
void heap_sort()  
{  // 1. 建堆  for(int i = n / 2; i >= 1; i--)  {  down(i, n);  }  // 2. 排序  for(int i = n; i > 1; i--) // 枚举堆⾥⾯最后⼀个元素的位置  {  swap(a[1], a[i]);  down(1, i - 1);  }  
}  
int main()  
{  cin >> n;  for(int i = 1; i <= n; i++) cin >> a[i];  heap_sort();  for(int i = 1; i <= n; i++) cout << a[i] << " ";  return 0;  
}

时间复杂度
时间复杂度需要计算两部分:⼀部分是建堆的时间复杂度,另⼀部分是排序。

  • 排序部分的时间复杂度很好估计,每次都是从堆中选择⼀个最⼤值,然后与最后⼀个元素交换,然后调整。⼀次选择的时间复杂度为log n ,⼀共执⾏n 次,⼤概就是n log n 级别。
  • 建堆的时间复杂度:
    计算向下调整算法建堆时间复杂度
    ![[Pasted image 20250322161630.png]]

则需要移动结点总的移动步数为:每层结点个数*向下调整次数
T ( h ) = 2 h − 1 − h T(h) = 2^h-1-h T(h)=2h1h
根据⼆叉树的性质:n = 2^h - 1 和h = log2 (n + 1)
T ( n ) = n − log ⁡ 2 ( n + 1 ) ≈ n T(n) = n - \log_{2}(n+1) \approx n T(n)=nlog2(n+1)n

综上所述,堆排序的时间复杂度主要取决于排序的过程,也就是n log n

快速排序

快速排序(Quick Sort),既然敢起这样的名字,说明它是常⻅排序算法中较为优秀的。事实上,在很多情况下,快排确实是效率较⾼的算法

算法原理

常规快排:在待排序区间中任取⼀个元素作为枢轴(pivot,或称基准值,通常选取区间⾸元素),然后按照基准值⼤⼩将区间中元素分割成左右两部分,左侧区间中元素⼩于基准值,右侧区间中元素⼤于等于基准值,即基准值已经放在其该放的位置上,该过程称为⼀次划分。划分结束后,再递归排基准值左侧,递归排基准值右侧即可
![[Pasted image 20250322163740.png]]

优化⼀:三路划分

三路划分优化:其实可以做到按照基准元素,将所有元素分成三个区间。左部分全部⼩于pivot,中间部分全部等于pivot,右部分全部⼤于pivot。然后中间部分就不⽤管了,直接递归处理左右部分

  1. 从区间中任取一个元素作为基准值,一般取区间最左侧元素,然后按照基准值对区间中元素进行划分
    ![[Pasted image 20250322164110.png]]

  2. 递归排基准值左侧;递归排基准值右侧
    ⽽这个三路划分,就是典型的数组分三块算法

优化⼆:随机选择基准元素

选择基准元素的⽅式:

  • 每次选择待排序序列的最左边元素
    那么,当整个序列有序的时候,每次递归就会划分出特别⻓的⼀段右区间,递归的层数是n次,每次要遍历整个数组⼀遍,时间复杂度就退化成n^2 。
    每次选择最右边元素也是同理。
  • 每次选择待排序序列的中间元素
    可以构造⼀个特殊的序列,使得每次取中间元素的时候都会取到最⼩值,依旧会退化成n^2。
  • 随机选择基准元素
    每次选择基准元素的时候,都从待排序的序列中随机选择⼀个数。在随机性的前提下,可以证明算法的时间复杂度趋近于nlogn 。
    因此,每次选择基准元素时,都使⽤随机函数选择
C++中的随机函数

C++提供了函数srand和rand,搭配使⽤可以返回⼀个随机值

#include <iostream>  
#include <ctime>  
using namespace std;  
int main()  
{  srand(time(0)); // 种下⼀个随机数种⼦  for(int i = 1; i <= 10; i++)  {  cout << rand() << endl; // 每次⽣成⼀个随机值  }  return 0;  
}

![[Pasted image 20250322171920.png]]

left和right指向第一个和最后一个元素,假设p等于4
![[Pasted image 20250322172121.png]]

i从第一个元素往最后一个元素遍历
现在ai是4,和p一样大,i++
![[Pasted image 20250322180638.png]]

ai是2,比p小,交换到前面,++l和i交换,i++
![[Pasted image 20250322180758.png]]

现在ai是4,和p一样大,i++
![[Pasted image 20250322180822.png]]

现在ai是5,比p大,交换到最右边,i和–r交换
![[Pasted image 20250322180911.png]]

i这是下标为4,ai为1,比p小,++l和i交换,i++
![[Pasted image 20250322181036.png]]

这是i等于5,r也是5,遍历结束,此时p=4,两个4已经在正确的位置上
中间区域也就是相等的区域,是l+1到r-1
左边比p小的区域是left到l,右边比p大的区域是r到right
接着遍历left到l和r到right

#include <iostream>  
#include <ctime>  
using namespace std;  
const int N = 1e5 + 10;  
int n;  
int a[N];  
// 优化⼀:随机选择基准元素  
int get_random(int left, int right)  
{  return a[rand() % (right - left + 1) + left];  
}  void quick_sort(int left, int right)  
{  if(left >= right) return;  // 1. 选择⼀个基准元素  int p = get_random(left, right);  // 2. 数组分三块 - 荷兰国旗问题  int l = left - 1, i = left, r = right + 1;  while(i < r)  {  if(a[i] < p) swap(a[++l], a[i++]);  else if(a[i] == p) i++;  else swap(a[--r], a[i]);  }  // [left, l] [l + 1, r - 1] [r, right]  quick_sort(left, l);  quick_sort(r, right);  
}  
int main()  
{  srand(time(0));  cin >> n;  for(int i = 1; i <= n; i++) cin >> a[i];  quick_sort(1, n);for(int i = 1; i <= n; i++) cout << a[i] << " ";  return 0;  
}

时间复杂度

  • 如果每次基准元素都选择得当,数组划分的比较均匀,时间复杂度=递归层数*N*logN
  • 如果划分不当,数组分布比较极端,时间复杂度退化成N^2

归并排序

归并排序(Merge Sort)是⽆论数据有什么特性,时间复杂度就能稳定O(n log n) 的排序算法
归并排序⽤的是分治思想,不知道分治是啥也没关系,后续算法课会讲到。它的主要过程分两步:

  1. 将整个区间从中间⼀分为⼆,先把左边和右边排排序;
  2. 然后将左右两个有序的区间合并在⼀起。
    其中,如何让左右两边有序,就继续交给归并排序,因此归并排序是⽤递归来实现的;合并两个有序区间的操作,在顺序表中讲过类似的算法题
#include <iostream>  
using namespace std;  
const int N = 1e5 + 10;  
int n;  
int a[N];  
int tmp[N]; // 辅助归并排序时,合并两个有序数组  
void merge_sort(int left, int right)  
{  if(left >= right) return;  // 1. 先⼀分为⼆  int mid = (left + right) >> 1;  // [left, mid] [mid + 1, right]  // 2. 先让左右区间有序  merge_sort(left, mid);  merge_sort(mid + 1, right);// 3. 合并两个有序数组  int cur1 = left, cur2 = mid + 1, i = left;  // [left, mid] [mid + 1, right]  while(cur1 <= mid && cur2 <= right)  {  if(a[cur1] <= a[cur2]) tmp[i++] = a[cur1++];  else tmp[i++] = a[cur2++];  }  while(cur1 <= mid) tmp[i++] = a[cur1++];  while(cur2 <= right) tmp[i++] = a[cur2++];  for(int j = left; j <= right; j++)  {  a[j] = tmp[j];  }  
}  int main()  
{  cin >> n;  for(int i = 1; i <= n; i++) cin >> a[i];  merge_sort(1, n);  for(int i = 1; i <= n; i++) cout << a[i] << " ";  return 0;  
}

【时间复杂度】
每次递归都是标准的⼀分为⼆,因此时间复杂度为O(n log n)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/39379.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenGL入门

一、环境搭建 ‌库依赖安装‌ 需要安装GLFW&#xff08;窗口管理&#xff09;和GLAD&#xff08;函数指针加载库&#xff09;。在Windows下推荐使用Visual Studio的vcpkg包管理工具进行安装&#xff0c;Linux下通过apt-get安装相关依赖‌。 ‌窗口初始化‌ 使用GLFW创建窗口并…

JVM(基础篇)

一.初识JVM 1.什么是JVM JVM全称Java Virtyal Machine&#xff0c;中文译名 Java虚拟机 。JVM本质上是一个运行在计算机上的程序&#xff0c;他的职责是运行Java字节码文件(将字节码解释成机器码)。 2.JVM的功能 解释和运行&#xff1a;对字节码文件中的指令号&#xff0c;实时…

VMware安装ubuntu22.04.5 server

下载Ubuntu镜像 https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/22.04.5/ 安装系统 打开vmware 点击创建新的虚拟机 选择自定义 点击下一步 选择稍后安装操作系统&#xff0c;点击下一步 选择Linux系统&#xff0c;选择ubuntu64&#xff0c;点击下一步 选择安装位置&…

Docker容器之Dockerfile

用来构建镜像的文件。是指就是命令&#xff0c;参数&#xff0c;脚本。 指令合集以及说明 构建镜像图解: 实战测试&#xff1a; 构建自己的ubuntu&#xff1a; FROM ubuntu MAINTAINER liux ENV MYPATH /usr/local WORKDIR $MYPATH RUN apt-get update RUN apt install net-…

STM32G030移植RT-Thread

移植流程 移植前需要安装Keil.STM32G0xx_DFP.1.2.0.pack组件&#xff0c;大致的移植过程&#xff1a; CubeMX配置RT-Thread组件配置工程模板配置 参考例程配置&#xff1a;拷贝仓库原有的stm32g070-st-nucleo工程&#xff0c;然后另起一个名字&#xff0c;目录结构如下 完整…

【网络】网关

【网络】网关 网关 是计算机网络中用于连接两个不同网络的设备或服务器&#xff0c;它充当着“翻译器”和“转发器”的角色&#xff0c;将数据包从一个网络传递到另一个网络&#xff0c;并在必要时进行协议转换和数据重包装。 主要功能 数据转发&#xff1a;当本地网络设备发…

用JS+Promise实现简单消息队列

一、什么是消息队列 消息队列是一种用于在软件系统之间传递消息的技术。它常被用于解耦不同组件或模块之间的通信&#xff0c;减少系统中各个部分之间的直接依赖关系。消息队列可以实现异步通信&#xff0c;发送方将消息发送到队列中&#xff0c;接收方从队列中获取消息并进行处…

【Python爬虫】使用python脚本拉取汽车网站品牌数据

示例代码说明&#xff1a; 在汽车之家网站拉取当月排行榜中汽车品牌、销量和价格信息&#xff0c;存为csv文档输出&#xff0c;使用正则表达式获取网页内容 import re import pandas as pd import requests# 汽车之家车型列表页URL url https://cars.app.autohome.com.cn/ca…

批量修改 PPT 文档中主题、编辑时长、来源等元数据信息

每一个 PPT 文档被创建之后&#xff0c;都会包含一些元数据信息。这些元数据信息记录着文件的作者、创建时间、修改时间、打印时间等信息。这些信息默认都是自动生成的&#xff0c;如果我们想要对这些元数据进行修改&#xff0c;当然也是可以的。今天就给大家介绍一下如何批量修…

丐版插入selectdb模拟

为了模拟不断插入数据到库里&#xff0c;写个简单的循环脚本 #!/bin/bash #计算时差 function getTiming(){start$1end$2start_secho $start | cut -d . -f 1start_nsecho $start | cut -d . -f 2end_secho $end | cut -d . -f 1end_nsecho $end | cut -d . -f 2time_micro$((…

Off-Road-Freespace-Detection配置pytorch2.0.0

一、概述 在github上进行开源代码搜索&#xff0c;发现了Off-Road-Freespace-Detection&#xff08;链接如下所示&#xff09;。这是对越野环境可通行区域的检测&#xff0c;在经过测试之后&#xff0c;发现对自己有益。 GitHub - chaytonmin/Off-Road-Freespace-Detection: O…

常见中间件漏洞之四:Apache

1. CVE-2021-41773 Apache HTTP Server 路径穿越漏洞 漏洞简介 该漏洞是由于Apache HTTP Server 2.4.49版本存在⽬录穿越漏洞,在路径穿越⽬录<Directory/>Require all granted</Directory>允许被访问的的情况下&#xff08;默认开启&#xff09;&#xff0c;攻击…

Pytorch中Tensorboard的学习

1、Tensorboard介绍 TensorBoard 是 TensorFlow 开发的一个可视化工具&#xff0c;用于帮助用户理解和调试机器学习模型的训练过程。尽管它最初是为 TensorFlow 设计的&#xff0c;但通过 PyTorch 的 torch.utils.tensorboard 模块&#xff0c;PyTorch 用户也可以方便地使用 Te…

刷机维修进阶教程-----adb禁用错了系统app导致无法开机 如何保数据无损恢复机型

在刷机维修过程中 。我们会遇到一些由于客户使用adb指令来禁用手机app而导致手机无法开机进入系统的故障机型。通常此类问题机型有好几种解决方法。但如果客户需要保数据来恢复机型。其实操作也是很简单的.还有类似误删除应用导致不开机等等如何保数据。 通过博文了解💝💝�…

哪吒汽车:一边熬夜蹦迪,一边找药投医

两年前&#xff0c;威马CEO沈晖发了个短视频&#xff0c;内容是“活下去&#xff0c;像牲口一样活下去”。 如今最能体会沈晖当时心情的&#xff0c;估计就是方运舟了。 作为哪吒汽车创始人兼董事长&#xff0c;他连续多次被限高&#xff0c;为了让哪吒汽车活下去&#xff0c…

2025 cs144 Lab Checkpoint 1小白超详细版

cs144官网&#xff1a;https://cs144.github.io/ 我的github&#xff1a;https://github.com/Albert-tru/cs144-2025 文章目录 1 手动发送internet数据报协议号5、7&#xff1f;思路&#xff1f; 2 实现一个Reassembler类2.1 几个帮助理解代码的Q&AQ1&#xff1a;insert的参…

使用 OpenCV 拼接进行图像处理对比:以形态学操作为例

图像处理在计算机视觉中起着至关重要的作用&#xff0c;而 OpenCV 作为一个强大的图像处理库&#xff0c;提供了丰富的函数来实现各类图像处理任务。形态学操作&#xff08;Morphological Operations&#xff09;是其中常用的技术&#xff0c;尤其适用于二值图像的处理。常见的…

单链表的查找和插入,删除操作

1.单链表的查找 snode* slistfind(snode* stlheap, stltype x) {while (stlheap){if (stlheap->data x){return stlheap;}stlheap stlheap->next;}return NULL; } 2.单链表的插入操作 2.1在指定位置之前插入节点 void slistinsert(snode** stlheap, snode* pos, stl…

一文速通Python并行计算:00 并行计算的基本概念

一文速通 Python 并行计算&#xff1a;00 并行计算的基本概念 摘要&#xff1a; 该文介绍了 Python 并行计算的核心概念、编程模型及其应用&#xff0c;并介绍了了并行程序的性能分析与优化方法&#xff0c;如并行效率、加速比及 Amdahl 定律。此外&#xff0c;该文介绍了共享…

vue中keep-alive组件的使用

keep-alive是vue的内置组件&#xff0c;它的主要作用是对组件进行缓存&#xff0c;避免组件在切换时被重复创建和销毁&#xff0c;从而提高应用的性能和用户体验。它自身不会渲染一个 DOM 元素&#xff0c;也不会出现在父组件链中。使用时&#xff0c;只需要将需要缓存的组件包…