7大排序算法-- 堆排 快速排序 --精解(上)

简介: 7大排序算法-- 堆排 快速排序 --精解

堆排序:

关于堆排序的讲解 大家可以看一下我的这篇文章:【数据结构】堆的建立 (时间复杂度计算-堆排序)---超细致_luck++的博客-CSDN博客


快速排序:

实际上快速排序和 冒泡排序被分在同一类:都是比较后进行交换的操作

快速排序这里我知道的有三个版本:


hoare版本:

这个版本是原始发明快速排序的人 想出来的


我们之前写排序也是先把 单趟排序 写出来 在来解决多趟排序

咋们这里的单趟排序的要求:

首先要选出一个 keyi 的 标准值

我们排序后的数组是:

左边都是小于keyi的值的数

右边都是大于keyi的值的数

25a32c0b9aeb43dfaadd48a7f5ec2a32.gif大家发现这个动图的特点了嘛?

首先我们的keyi的选择 一般都是选择 最左边 或者是 最右边 的数

🥇 我们的右边的 猩猩惺惺先走 找比keyi小的数 找到停下

🥈 左边的熊猫后走 找比keyi大的数 找到停下 与猩猩的位置的数据交换

🥉 直到熊猫与猩猩相遇 再将相遇点与keyi的数据交换

这样就达到我们的目的了 大家思考一下有没有什么问题


一定会相遇?

首先他们之间相遇肯定是必然的 因为我们每次移动都是移动一边的动物 永远都只有一个动物在动所以是一定会有个动物动并又可能相遇的


为什么与keyi交换就达到目标?

因为 我们每次停下的都是找小的猩猩 所以相遇的时候的位置也是一个小于keyi的位置 当然这是右边的 熊猫 来与 猩猩 相遇 注意:我们的猩猩是停下来的 猩猩没停下来而相遇是下面的这些情况

还有一种可能就是我们的猩猩一直没找到一直往前面走 直到相遇 此时我们的熊猫是没有动的 所以相遇交换也只是原地交换

还有一种可能就是我们得左右已经交换过了现在轮到右边得猩猩走 但是一直没有找到最终与左边还没有走得熊猫相遇 此时与keyi交换依旧是满足条件的 因为交换过后 我们还没来得及走的熊猫的位置已经因为交换变成一个小于keyi的值了 所以不需要担心相遇的时候交换会是一个比keyi大的数

还有一点就是我们的猩猩从右边往左走 找的是小 这样隐含的意思就是不找大呗 那么我们的猩猩就会略过大的数 让那些大的数依旧留在那边  左边找大也是同样的道理 让小的数留在左边(注意:这个也许可以帮助你理解 排升序 还是 降序)

上述就是为什么我们这样操作会达到我们的目标了

那现在大家想想为什么要我们的右边的猩猩先走呢 不能左边先走呢?


为了解决我们上面的问题  现在我们假设我们依然要排序 一个左小有大的数组(keyi依旧选择左边得数) 假设我们右边的熊猫先走 要排一个左小右大的数组 那我们有右边先走就是找大 把小的数留在左边 那就只右边后走找小咯 根据上面的道理类推 可以得出最终相遇得点是一个比keyi大得位置  最后与keyi交换 那么咋们这个数组不就成了 大-小-大 了嘛

15c02479147f4af18b3919a5a4d02f12.gif

总结: 如果keyi在左 那么一定是右先走  keyi在右 一定是左先走 至于找大找小 就涉及到是排升序还是降序 后面会说到哦

    int keyi = left;
  while (left < right)//升序
  {
    while (a[right] > a[keyi])
    {
      right--;
    }
    while (a[left] < a[keyi])
    {
      left++;
    }
    swap(a + right, a + left);
  }
  swap(a + keyi, a + right);


这个是我们初步写成的代码 还是让大家找找问题吧(大家可以对照着下面这两组数据查找)


f72f0febc3464e87ae4724bec30ff245.png 

首先我们不加 = 号的话是会死循环的 大家看:

8f3554b4e8294b50983566d8c1abbc1d.png

我们的右边先走 我们找小 6小于6嘛 不小唉 那我右边不能走了 左边呢是不是也是一样 我们左边的其实位置是在keyi的位置 这样咋们不就死循环了嘛

不加限制条件就会越界(注意:这里我们已经加上):

97a03e525ad74ce0af5105cfa90660f8.png

如果不加限制条件 我们右边先走 6>1 5>1 4>1 都是大于keyi的值的 我们已经加了 = 符号了最后 1=1 right 又 减减 一次那我们的right不就越界了嘛?

大家应该知道问题该怎么解决了吧 来看我们正确的代码:

    int keyi = left;
  while (left < right)//升序
  {
    while (left < right && a[right] >= a[keyi])
    {
      right--;
    }
    while (left < right && a[left] <= a[keyi])
    {
      left++;
    }
    swap(a + right, a + left);
  }
  swap(a + keyi, a + right);

现在问题来了 我们单趟已经排完序了 接下来的多趟怎么排呢?

写之前大家想一想 我们单趟排序给我们带来的是什么 给我们队数组的排序提供了什么帮助?

其实结果很简单  我们不是在最后 猩猩 与 熊猫 相遇后 keyi的值与相遇点的值进行了交换嘛那我们此时相遇点的位置放的keyi的值

我们keyi的值是不是放到那里就可以不变了呢?因为它左边是比他小的 右边是比他大的数  假设我们本身排完序之后 keyi 的值要大于 5 个数 那我这里把小于它的数排到它的左边不正好就满足keyi的位置在这 5 个位置的前面了嘛 只不过左边和右边还不一定就有序了 这样我们此时keyi的值的位置就是 正确✔ 的不用再移动它了

我们提到了单趟排完之后 左边和右边是不一定有序  但是如果我们把左边和右边变得有序那我们整体是不是就有序了 大家想想是不是这个道理? 可是怎么解决左右的问题呢? 

我们此时就要用到 二叉树中提到的一个 分治 的思想 可以这么理解:

26c44737c8ae42648c97df931d31b32a.png就是这种一边的工作完成完 最后一起提交给上一级 上一级也是同样的操作

所以对于这种分治的思想 我们一般是采用递归来实现

 我们是在我们目前第一次单趟排序后keyi的值 左边 和 右边再进行一次同样的排序 紧接着继续递归到下一层

e39e400f2e6e4b089528f45d90ecd06e.png

大家看这样能理解嘛  每选出一个 keyi 那么这个keyi位置的数就会排到应该在的位置上 而他两边的数也会接近有序 也可能已经有序

我们要递归到下一层 就必须选出该层的keyi 最终结果:

d359476fd7f2476eb4f34e7049dde2d9.gif


当然大家要注意 我们选择区间的时候要注意 有的区间只有一个数 有的区间不存在

int PartSort1(int* a, int left, int right)//key在左那先走得一方必在右 选大选小 取决于排升序 or 
{                                         //排降序
  int keyi = left;
  while (left < right)//升序
  {
    while (left < right && a[right] >= a[keyi])
    {
      right--;
    }
    while (left < right && a[left] <= a[keyi])
    {
      left++;
    }
    swap(a + right, a + left);
  }
  swap(a + keyi, a + right);
  return right;
}
void QuickSort(int* a, int left, int right)
{
  if (left >= right) return; = 意思只有一个值 > 意思区间不存在
  int keyi = PartSort1(a, left, right);
    //分区间
  QuickSort(a, left, keyi - 1);
  QuickSort(a, keyi + 1, right);
}

55d2cab127924367b968772a36606dd9.png

 这个是我们部分的栈帧展开图 希望可以帮助大家理解

目录
相关文章
|
1月前
|
搜索推荐 C语言
【排序算法】快速排序升级版--三路快排详解 + 实现(c语言)
本文介绍了快速排序的升级版——三路快排。传统快速排序在处理大量相同元素时效率较低,而三路快排通过将数组分为三部分(小于、等于、大于基准值)来优化这一问题。文章详细讲解了三路快排的实现步骤,并提供了完整的代码示例。
57 4
|
25天前
|
存储 搜索推荐 Python
用 Python 实现快速排序算法。
快速排序的平均时间复杂度为$O(nlogn)$,空间复杂度为$O(logn)$。它在大多数情况下表现良好,但在某些特殊情况下可能会退化为最坏情况,时间复杂度为$O(n^2)$。你可以根据实际需求对代码进行调整和修改,或者尝试使用其他优化策略来提高快速排序的性能
116 61
|
2月前
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
36 1
|
2月前
|
搜索推荐 Java Go
深入了解快速排序算法
深入了解快速排序算法
52 2
|
2月前
|
存储 搜索推荐 算法
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
|
2月前
|
算法 Python
Python算法编程:冒泡排序、选择排序、快速排序
Python算法编程:冒泡排序、选择排序、快速排序
34 0
|
2月前
|
搜索推荐 C语言 C++
【C语言】指针篇-精通库中的快速排序算法:巧妙掌握技巧(4/5)
【C语言】指针篇-精通库中的快速排序算法:巧妙掌握技巧(4/5)
|
4月前
|
搜索推荐 算法 Java
现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法
该博客文章通过UML类图和Java源码示例,展示了如何使用适配器模式将QuickSort类和BinarySearch类的排序和查找功能适配到DataOperation接口中,实现算法的解耦和复用。
55 1
现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法
|
4月前
|
算法 搜索推荐
算法设计 (分治法应用实验报告)基于分治法的合并排序、快速排序、最近对问题
这篇文章是关于分治法应用的实验报告,详细介绍了如何利用分治法实现合并排序和快速排序算法,并探讨了使用分治法解决二维平面上的最近对问题的方法,包括伪代码、源代码实现及时间效率分析,并附有运行结果和小结。
|
5月前
|
算法 搜索推荐 编译器
算法高手养成记:Python快速排序的深度优化与实战案例分析
【7月更文挑战第11天】快速排序是编程基础,以O(n log n)时间复杂度和原址排序著称。其核心是“分而治之”,通过选择基准元素分割数组并递归排序两部分。优化包括:选择中位数作基准、尾递归优化、小数组用简单排序。以下是一个考虑优化的Python实现片段,展示了随机基准选择。通过实践和优化,能提升算法技能。**
66 3