当前位置: 首页 > news >正文

交换排序(冒泡/快排)

一 . 交换排序

交换排序基本思想 : 

所谓交换 , 就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置 。

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

 

 1.1 冒泡排序

在前面中 , 介绍了冒泡排序的思路了 冒泡排序是一种最基础的交换排序 。 之所以叫做冒泡排序 , 因为每个元素都可以像小气泡一样 , 根据自身大小一点一点移动到对应的位置 。其本质就是两两数值进行比较 。 

//冒泡排序
void BubbleSort(int* arr, int n)
{//比较的趟数for (int i = 0; i < n-1; i++){int exchange = 0;//一趟中比较的次数for (int j = 0; j < n - i - 1; j++) {if (arr[j] > arr[j + 1]){Swap(&arr[j], &arr[j + 1]);exchange = 1;}}if (exchange == 0){break;}}
}

时间复杂度 : O( n ^ 2 )

空间复杂度 : O ( 1 ) 

1.2 快速排序

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

 快速排序实现的主框架 : 

//快速排序
void QuickSort(int* arr, int left, int right)
{if (left >= right){return;}//找基准值int keyi = _QuickSort(arr, left, right);//二分查找// [0 , keyi -1 ]      keyi     [ keyi + 1 , right]QuickSort(arr, 0, keyi - 1);QuickSort(arr, keyi + 1 ,right);
}

将区域中的元素进行划分的   _QuickSort方法  主要有以下几种实现方式 :  

 

1.2.1 Hoare 版本

基本思路 :

1 ) 创建左右指针 , 以确定基准值

2 ) 左指针从左往右找比 基准值要大的数据, 右指针从右往左找比基准值要小的数据 , 然后左右指针数据交换 , 继续循环 , 直到 左指针的下标值比右指针的 大

3 )当 左指针的下标值比右指针的大时 , 基准值 与 右指针的数值交换

例如 对数组 a [ ] = { 6 , 1 , 2 , 7 , 9 , 3 } 进行快速排序  : 

 

  •  问题 一 : 为什么跳出循环后right 位置的值一定不大于 key ?

因为在 left > right 时 , right 走到了 left 的左侧 , 而再次之前 left 已经把最大的值找到并且与 right 交换 ( left 扫描过的数据均不大于key ) ;

相遇点的值有三种情况 : 

 

  •  问题 二 : 为什么 left 和 right 指定的数据和 key 值相等时候也交换 ? (也就是以下代码加个 "  =  " 为啥不行 )

相等的值参与交换确实会有一定额外的消耗 。 实际还有各种复杂的场景 ,假设数组中的数据大量重复的时候 无法进行有效的分割排序 !时间复杂度会变高 !

 时间复杂度分析 :

递归算法的时间复杂度的计算 : 递归次数 * 单次递归的时间复杂度

                                                      \log_{2}n    *       n

快速排序的时间复杂度为 : O(n*logn )

如果数组为有序 : 快排的时间复杂度为 O(n^2)

1.2.2 挖坑法

思路 : 

创建左右指针 。 首先从右向左找出比基准小的数据 , 找到后立即放入到左边坑中 , 当前位置变为新  “坑” , 然后从左向右找出比基准大的数据 , 找到后立即放入到右边坑中 , 当前位置变为新的 “坑” , 结束循环后将最开始存储的分界值放入到当前的 "坑“ 中 , 返回当前 ” 坑“ 下标(即分界值的下标)

//挖坑法
int _QuickSort1(int* arr, int left, int right)
{int key = arr[left];int hole = left;while (left < right){while (left < right && arr[right] > key){right--;}arr[hole] = arr[right];hole = right;while (left < right && arr[left] < key){left++;}arr[hole] = arr[left];hole = left;}arr[hole] = key;return hole;
}

 

1.2.3 lomuto指针

创建前后指针 , 从左往右找比  基准值小  的进行交换 , 使得小的都排在基准值的左边 。 

//双指针法
int _QuickSort2(int* arr, int left, int right)
{int prev = left, cur = prev + 1;int key = left;while (cur <= right){if (arr[cur] < arr[key] && ++prev !=cur){Swap(&arr[prev], &arr[cur]);}cur++;}Swap(&arr[prev], &arr[key]);return prev;
}

快速排序特性总结 :

1 . 时间复杂度 : O(n * logn)

2 . 空间复杂度 :O(logn) 

 1.2.4 非递归版本

非递归版本的快速排序需要借助数据结构 : 栈

void QuickSortNonR(int* arr, int left, int right)
{ST st;STInit(&st);StackPush(&st, right);StackPush(&st, left);while (!StackEmpty(&st)){//取栈顶元素——两次int begin = STTop(&st);StackPop(&st);int end = STTop(&st);StackPop(&st);//对区间[begin,end]找基准值---双指针法int prev = begin, cur = prev + 1;int keyi = begin;while (cur <= end){if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[prev], &arr[cur]);}++cur;}Swap(&arr[keyi], &arr[prev]);keyi = prev;//此时基准值的下标:keyi//划分左右序列:[begin,keyi-1] [keyi+1,end]if (keyi + 1 < end){StackPush(&st, end);StackPush(&st, keyi + 1);}if (keyi - 1 > begin){StackPush(&st, keyi - 1);StackPush(&st, begin);}}STDestory(&st);
}

http://www.mrgr.cn/news/65064.html

相关文章:

  • 苹果安装python环境
  • unity搭建场景学习
  • Freertos学习日志(1)-基础知识
  • mac安装brew
  • scala的属性访问权限
  • IP系列之bscan讨论
  • GPU架构概述
  • 高级java每日一道面试题-2024年10月28日-JVM篇-详细介绍一下CMD垃圾回收器?
  • Vue-Router详解【学习Vue-Router看这一篇就够了!!!】
  • RK3568平台开发系列讲解(SPI篇)SPI 控制器驱动分析
  • 如何使用Get进行状态管理
  • ts:使用typeof运算符输出各对象的类型
  • Linux 信号
  • 算法——递推
  • 各地级市能源消耗量数据-基于灯光数据的反演(2000-2022年)
  • 虚拟内存与物理内存之间的映射关系
  • 无人机场景数据集大全「包含数据标注+划分脚本+训练脚本」 (持续原地更新)
  • 【C++】多态的语法与底层原理
  • Yocto - 使用Yocto开发嵌入式Linux系统_12 开发定制层
  • 基于规则碎纸片的拼接复原模型
  • Nginx 学习指南
  • 清华双臂机器人扩散大模型RDT:先预训练后微调,支持语言、图像、动作多种输入(1B参数)
  • ctfshow(91,96,97)--PHP特性
  • WPF+MVVM案例实战(二十一)- 制作一个侧边弹窗栏(AB类)
  • 基于向量检索的RAG大模型
  • [ shell 脚本实战篇 ] 编写恶意程序实现需求(恶意程序A监测特定目录B出现特定文件C执行恶意操作D-linux)