【交换排序】冒泡排序 与 快速排序

交换排序基本思想:

所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

目录

1.冒泡排序

2.快速排序

2.1 递归实现 

2.2 快速排序优化

2.3 非递归实现


1.冒泡排序

假设升序。每次遍历,两两比较,将大的元素向后交换,直到选出最大的元素放在最后,这时已经确定了升序中最后一个元素,然后多次遍历前面无序的元素,每次可以确定一个最大的数,直到排序完成。

动态图解:

代码实现:

//交换函数
void Swap(int* p1, int* p2)
{int t = *p1;*p1 = *p2;*p2 = t;
}void BubbleSort(int* arr, int n)
{for (int i = 0; i < n - 1; i++){for (int j = 0; j < n - 1 - i; j++){if (arr[j] > arr[j + 1]){Swap(&arr[j], &arr[j + 1]);}}}
}

如果在排序有序数据时,我们还可以优化一下,提高一下效率,代码如下

void BubbleSort(int* arr, int n)
{for (int i = 0; i < n - 1; i++){int flag = 1;//假设已经有序for (int j = 0; j < n - 1 - i; j++){if (arr[j] > arr[j + 1]){Swap(&arr[j], &arr[j + 1]);flag = 0;//发生交换,说明无序}}if(flag == 1)//已经有序,不在继续排序{break;}}
}

冒泡排序的特性总结:

  1. 冒泡排序是─种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定
     

2.快速排序

2.1 递归实现 

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值key,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值key,右子序列中所有元素均大于基准值key,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止

图示:

代码实现:

假设按照升序对array数组中【left, right】左右都是闭区间中的元素进行排序。

void QuickSort(int* a, int left, int right)
{if (left >= right){return;}//按照基准值(中间位置)对array数组的[left, right]区间中的元素进行划分int keyi = PartSort(a, left, right);//划分成功后以div为边界形成了左右两部分[left, keyi-1]和[key+1,right]//递归排 [left, keyi-1]QuickSort(a, left, keyi - 1);//递归排[key+1,right]QuickSort(a, keyi + 1, right);
}

上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,大家在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。

将区间按照基准值划分为左右两半部分(PartSort)的常见方式有:

1. hoare 版本

我们先看一下动图,方便理解 

巧妙之处: 

  1. 左边做key,右边先走;保障了相遇位置的值比key小,或者就是key的位置
  2. 右边做key,左边先走;保障了相遇位置的值比key大,或者就是key的位置

我们这里使用第一种方法:

L和R相遇无非就是两种情况,L遇R和R遇L:

  1. 情况一:L遇R,R是停下来,L在走,R先走,R停下来的位置一定比key小,相遇的位置就是R停下的位置,就一定比key要小
  2. 情况二:R遇L,在相遇这一轮,L就没动,R在移动,跟L相遇,相遇位置就是L的位置,L的位置就是key的位置或者这个位置交换过,换成了比key小的,相遇L位置一定比key小

代码实现:

int PartSort1(int* a, int left, int right)
{int keyi = left;while (left < right){//右边找小 与key相等的数据放在左边右边都可以while (left < right && a[right] >= a[keyi]){right--;}//左边找大 与key相等的数据放在左边右边都可以while (left < right && a[left] <= a[keyi]){left++;}Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[right]);return right;
}

2. 挖坑法

 left 和 right 中有一个一定是坑位,右边找小,左边找大,找到就将值放入原坑位,该位置成为新坑位。

代码实现:

int PartSort2(int* a, int left, int right)
{int hole = left;int key = a[left];while (left < right){//右边找小while (left < right && a[right] >= key){right--;}a[hole] = a[right];hole = right;//左边找大while (left < right && a[left] <= key){left++;}a[hole] = a[left];hole = left;}a[hole] = key;return hole;
}

3. 前后指针版本

  1. 最开始prev和cur相邻的
  2. 当cur遇到比key的大的值以后,他们之间的值都是比key大的值
  3. cur找小,找到小的以后,跟++prev位置的值交换,相当于把大翻滚式往右边推同时把小的换到左边
     

 代码实现:

int PartSort3(int* a, int left, int right)
{int keyi = left;int prev = left;int cur = left + 1;while (cur <= right){if (a[cur] <= a[keyi]&&++prev!=cur)//自己不与自己交换{Swap(&a[cur], &a[prev]);}cur++;}Swap(&a[prev], &a[keyi]);return prev;
}

2.2 快速排序优化

当我们遇到有序数据时,由于我们的key是选的第一个元素,时间复杂度会变成O(N^2)。有两种优化方法:

  1. 三数取中法选key
  2. 随机数选key
  3. 小区间优化

三数取中,代码实现:

int GetMidIndex(int* a, int left, int right)
{int mid = (left + right) / 2;if (a[left] < a[mid]){if (a[mid] < a[right]){return mid;}else if(a[left]>a[right]){return left;}else{return right;}}else//a[left]>a[mid]{if (a[mid] > a[right]){return mid;}else if (a[left] > a[right]){return right;}else{return left;}}
}

然后我们需要在上面3种划分方式开头都加上下面代码,就可以达到优化的目的

int midi = GetMidIndex(a, left, right);
Swap(&a[midi], &a[left]);

2. 随机数选key

int GetRandIndex(int* arr, int left, int right)
{int i = rand()%(right-left+1)+left;return i;
}

当然我们也需要使用 srand((unsigned int)time(NULL)) 随机数种子。

3.小区间优化

//小区间优化
if (end - begin +1<10)
{//使用插入排序InsertSort(arr + begin, end - begin + 1);return;
}

 优化的本质是减小递归调用的次数,由于二叉树的性质。我们可以得出满二叉树后三层大约占总个数的85%。为了减小递归开销,我们可以将小区间的递归调用改为直接插入排序可以提高一点排序的性能,但也不会提高很多

2.3 非递归实现

我们这里使用栈来实现,栈内存放需要划分的区间端点值,利用栈先入后出的特点模拟实现递归

void QuickSortNonR(int* a, int left, int right)
{Stack st;StackInit(&st);//入栈StackPush(&st, right);StackPush(&st, left);while (!StackEmpty(&st)){left = StackTop(&st);StackPop(&st);right = StackTop(&st);StackPop(&st);int keyi = PartSort1(a, left, right);//想先处理左边,就先右边区间先入栈//以基准值为分割点,形成左右两部分:[left, keyi-1]和[keyi+1,right)if(right > keyi + 1){StackPush(&st, right);StackPush(&st, keyi + 1);}if (left < keyi - 1){StackPush(&st, keyi - 1);StackPush(&st, left);}}StackDestroy(&st);
}

当然也可以使用队列来模拟。队列相当于广度优先,二叉树中的层序遍历,栈是深度优先,二叉树的先序遍历。

快速排序的特性总结:

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定

2.4 存在大量相同元素情况

当要排序的数组存在大量相同元素时,快排的时间复杂度会下降到O(N^2)。

三路划分:

 

 步骤:

  1. a[c] < key,交换c和l位置的值,++l,++c
  2. a[c] > key,交换c和r位置的值,--r
  3. a[c] == key,++cu

三路划分本质:

  1. 小的甩到左边,大的甩到右边
  2. 跟key相等的值推到中间
  3. l 一直指向第一个与key相等的数据

代码实现:

int RandIndex(int begin, int end)
{int i = rand() % (end - begin + 1) + begin;return i;
}void QuickSortThree(int* arr, int begin, int end)
{//1.区间只有一个值//2.区间不存在if (begin >= end){return;}//三路划分 效率有一定的下降int left = begin;int cur = begin + 1;int right = end;int Index = RandIndex(begin, end);Swap(&arr[Index], &arr[left]);int key = arr[left];while (cur <= right){if (arr[cur] < key){Swap(&arr[cur++], &arr[left++]);}else if (arr[cur] == key){cur++;}else if (arr[cur] > key){Swap(&arr[cur], &arr[right--]);}}QuickSortThree(arr, begin, left - 1);QuickSortThree(arr, cur, end);}

注意:三路划分效率相对于二路划分有一定的下降。

本篇结束!我们下一篇文章来学习排序第四课【归并排序】。

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

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

相关文章

ZDH-wemock模块

本次介绍基于版本v5.1.1 目录 项目源码 预览地址 安装包下载地址 wemock模块 wemock模块前端 配置首页 配置mock wemock服务 下载地址 打包 运行 效果展示 项目源码 zdh_web: https://github.com/zhaoyachao/zdh_web zdh_mock: https://github.com/zhaoyachao/z…

Redis专题-队列

Redis专题-队列 首先&#xff0c;想一想 Redis 适合做消息队列吗&#xff1f; 1、消息队列的消息存取需求是什么&#xff1f;redis中的解决方案是什么&#xff1f; 无非就是下面这几点&#xff1a; 0、数据可以顺序读取 1、支持阻塞等待拉取消息 2、支持发布/订阅模式 3、重…

CSS3中的var()函数

目录 定义&#xff1a; 语法&#xff1a; 用法&#xff1a; 定义&#xff1a; var()函数是一个 CSS 函数用于插入自定义属性&#xff08;有时也被称为“CSS 变量”&#xff09;的值 语法&#xff1a; var(custom-property-name, value) 函数的第一个参数是要替换的自定义属性…

量化交易接口的开启条件--什么是a股自动交易接口?

a股自动交易接口是指可以通过程序自动进行A股买卖操作的接口。一般情况下&#xff0c;个人投资者可以通过证券公司提供的交易API接口实现自动交易。 通常情况下&#xff0c;a股自动交易接口的开发途径主要有以下几种&#xff1a; 1. 使用第三方交易接口&#xff1a;许多证券经…

gulimall-缓存-缓存使用

文章目录 前言一、本地缓存与分布式缓存1.1 使用缓存1.2 本地缓存1.3 本地模式在分布式下的问题1.4 分布式缓存 二、整合redis测试2.1 引入依赖2.2 配置信息2.3 测试 三、改造三级分类业务3.1 代码改造 四、高并发下缓存失效问题4.1 缓存穿透4.2 缓存雪崩4.3 缓存击穿 五、分布…

jenkins自动化构建保姆级教程(持续更新中)

1.安装 1.1版本说明 访问jenkins官网 https://www.jenkins.io/&#xff0c;进入到首页 点击【Download】按钮进入到jenkins下载界面 左侧显示的是最新的长期支持版本&#xff0c;右侧显示的是最新的可测试版本&#xff08;可能不稳定&#xff09;&#xff0c;建议使用最新的…

虚拟机的创建与使用

一、虚拟机的下载 链接&#xff1a;百度网盘下载链接 提取码&#xff1a;a9p4 二、新建虚拟机系统 需要有版本序列号 注意: 选择 第一个是纯dos 的窗口指令 桌面没有任何东西 选择第二个就是正常的操作系统.有文件夹 我的电脑之类的 三、从主机中复制文件到虚拟机中需要安装 …

Java-Sec-Code靶场

文章目录 前言1.靶场搭建靶场地址、环境Window环境修改点 靶场通关和源码分析命令注入RCE反序列化fastjson反序列化目录穿越文件上传Spel表达式sql注入poi-ooxml组件XXE 总结 前言 一直都是一个Java盲&#xff0c;但是如今Java却占据了开发的半壁江山&#xff0c;平时遇见的多…

贴吧照片和酷狗音乐简单爬取

爬取的基本步骤 很简单&#xff0c;主要是两大步 向url发起请求 这里注意找准对应资源的url&#xff0c;如果对应资源不让程序代码访问&#xff0c;这里可以伪装成浏览器发起请求。 解析上一步返回的源代码&#xff0c;从中提取想要的资源 这里解析看具体情况&#xff0c;一…

每天一道leetcode:797. 所有可能的路径(图论中等深度优先遍历)

今日份题目&#xff1a; 给你一个有 n 个节点的 有向无环图&#xff08;DAG&#xff09;&#xff0c;请你找出所有从节点 0 到节点 n-1 的路径并输出&#xff08;不要求按特定顺序&#xff09; graph[i] 是一个从节点 i 可以访问的所有节点的列表&#xff08;即从节点 i 到节…

力扣 139. 单词拆分

题目来源&#xff1a;https://leetcode.cn/problems/word-break/description/ C题解&#xff1a;将该题视为完全背包问题&#xff0c;因为每个单词都可以多次用。动规五部曲分析如下&#xff1a; 确定dp数组以及下标的含义。dp[i] : 字符串长度为i的话&#xff0c;dp[i]为true…

206、仿真-51单片机锂电池蓄电池电压电流加按键控制开关状态Proteus仿真设计(程序+Proteus仿真+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、硬件设计 二、设计功能 三、Proteus仿真图 四、程序源码 资料包括&#xff1a; 需要完整的资料可以点击下面的名片加下我&#xff0c;找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选择 方案一&a…