排序——交换排序

简介: 介绍交换排序

交换排序

基本思想

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

冒泡排序

冒泡排序是我们最早接触的算法了,对于冒泡排序我们应该是最熟悉的了

代码实现
void BubbleSort(int* a, int n)
{
  for (int i = 0; i < n; i++)
  {
    int flag = 0;
    for (int j = 0; j < n - 1 - i; j++)
    {
      if (a[j] > a[j + 1])
      {
        Swap(&a[j], &a[j + 1]);
        flag = 1;
      }
    }
    if (flag == 0)
    {
      break;
    }
  }
}
void TestBubbleSort()
{
  int a[] = { 105,5,8,2,50,7,-1,100,66 };
  int n = sizeof(a) / sizeof(a[0]);
  BubbleSort(a, n);
  Print(a, n);
}
int main()
{
  TestBubbleSort();
}

快速排序

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

简单来说,就是左边比基准值小,右边比基准值大(基准值我们一般选取最左边的值、中间的值、最右边的值,或者随机值)选取最左边的值的话,让右边先走,选取最右边的值的话,让左边先走

很明显,我们可以利用递归来实现快排。快速排序这部分我们会说的比较多。

  1. hoare版本

    下面,进行代码实现:
//单趟排序
int PartSort(int* a, int left, int right)
{
  int keyi = left;
  while (left < right)
  {
    //R找小(记得给等于号,不然会造成死循环)
    while (left<right && a[right] >= a[keyi])
    {
      --right;
    }
    //L找大
    if (left<right && a[left] <= a[keyi])
    {
      ++left;
    }
    if(left<right)
       Swap(&a[left], &a[right]);
  }
  int meeti = left;
  Swap(&a[left], &a[keyi]);
  return meeti;
}
void QuickSort(int* a, int begin,int end)
{
  int keyi = PartSort(a, begin, end);
  if (begin >= end)
    return;
  QuickSort(a, begin, keyi - 1);
  QuickSort(a, keyi + 1, end);
}
void TestQuickSort()
{
  int a[] = { 105,5,8,2,50,7,-1,100,66 };
  int n = sizeof(a) / sizeof(a[0]);
  QuickSort(a, 0,n-1);
  Print(a, n);
}
int main()
{
  TestQuickSort();
}

但是,对于hoare版本,我们还可以对其进行优化:

在比较理想的情况下,我们选择key单趟排完基本都是在中间,这样子才是二分logN O(N*logN)

如果在有序/接近有序的情况下,那么key每次单趟排完的效果是比较差的O(N^2),所以下面进入快排的另一个主题,优化问题👇

快速排序优化
  1. 优化选key问题:
  • 随机选一个数作为key(太随意了)
  • 针对有序,选正中间值做key(具有针对性)
  • 三数取中(第一个 中间位置 最后一个 选出中间值),三数取中法选key(具有普遍性)

2.递归到小的子区间时,可以考虑使用插入排序

下面,我们用代码来实现三数取中的算法:

//三数取中
int GetMidIndex(int* a, int left, int right)
{
  int mid = left + (right - left) / 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
  {
    if (a[mid] > a[right])
    {
      return mid;
    }
    else if (a[right] > a[left])
    {
      return left;
    }
    else
    {
      return right;
    }
  }
}

下面,我们就可以优化我们的快排代码了:

//三数取中
int GetMidIndex(int* a, int left, int right)
{
  int mid = left + (right - left) / 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
  {
    if (a[mid] > a[right])
    {
      return mid;
    }
    else if (a[right] > a[left])
    {
      return left;
    }
    else
    {
      return right;
    }
  }
}
//单趟排序
int PartSort(int* a, int left, int right)
{
    //三数取中
  int mid = GetMidIndex(a, left, right);
  Swap(&a[left], &a[mid]);
  int keyi = left;
  while (left < right)
  {
    //R找小(记得给等于号,不然会造成死循环)
    while (left < right && a[right] >= a[keyi])
    {
      --right;
    }
    //L找大
    if (left < right && a[left] <= a[keyi])
    {
      ++left;
    }
    if (left < right)
      Swap(&a[left], &a[right]);
  }
  int meeti = left;
  Swap(&a[left], &a[keyi]);
  return meeti;
}
void QuickSort(int* a, int begin, int end)
{
  int keyi = PartSort(a, begin, end);
  if (begin >= end)
    return;
  //小区间优化
  if (end - begin <= 8)
  {
    InsertSort(a+begin,end-begin+1);
  }
  else
  {
    QuickSort(a, begin, keyi - 1);
    QuickSort(a, keyi + 1, end);
  }
}
  1. 挖坑法
    简单来说,就是对单趟排序进行改造,三数取中后我们还是以左边作为key,然后把左边作为坑,然后右边找小,在把找到的值放在坑位上去,在把坑位置为右边找到的位置。再从左边找大,把找到的值放在坑位上,在更新一下坑位。重复以上过程。整体思路与hoare方法类似。
int PartSort2(int* a, int left, int right)
{
  int mid = GetMidIndex(a, left, right);
  Swap(&a[left], &a[mid]);
  int key = a[left];
  int hole = 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;
}
void QuickSort(int* a, int begin, int end)
{
  int keyi = PartSort2(a, begin, end);
  if (begin >= end)
    return;
  //小区间优化
  if (end - begin <= 8)
  {
    InsertSort(a+begin,end-begin+1);
  }
  else
  {
    QuickSort(a, begin, keyi - 1);
    QuickSort(a, keyi + 1, end);
  }
}
  1. 前后指针版本

下面,我们以动图的形式演示前后指针版本的过程:

代码实现:

//前后指针法
int PartSort3(int* a, int left, int right)
{
  int mid = GetMidIndex(a, left, right);
  Swap(&a[left], &a[mid]);
  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[keyi], &a[prev]);
  return prev;
}
void QuickSort(int* a, int begin, int end)
{
  int keyi = PartSort3(a, begin, end);
  if (begin >= end)
    return;
  //小区间优化
  if (end - begin <= 8)
  {
    InsertSort(a+begin,end-begin+1);
  }
  else
  {
    QuickSort(a, begin, keyi - 1);
    QuickSort(a, keyi + 1, end);
  }
}

至此,我们对于快排基本了解完了,这里还有另一点:那就是快速排序的非递归实现:

快速排序非递归

我们需要借助一个栈,对于栈,C语言我们肯定要自己去实现,栈的实现可参考我之前写过的博客,这里就直接来用了:

int PartSort3(int* a, int left, int right)
{
  int mid = GetMidIndex(a, left, right);
  Swap(&a[left], &a[mid]);
  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[keyi], &a[prev]);
  return prev;
}
void QuickSortNonR(int* a, int begin, int end)
{
  ST st;
  StackInit(&st);
  StackPush(&st, begin);
  StackPush(&st, end);
  while (!StackEmpty(&st))
  {
    int right = StackTop(&st);
    StackPop(&st);
    int left = StackTop(&st);
    StackPop(&st);
    if (left >= right)
    {
      continue;
    }
    int keyi = PartSort3(a, left, right);
    //[left,keyi-1] keyi  [keyi+1,right]
    //先入右边
    StackPush(&st, keyi+1);
    StackPush(&st, right);
    //后入左边
    StackPush(&st, left);
    StackPush(&st, keyi-1);
  }
  StackDestory(&st);
}

至此,我们终于把快速排序的大部分内容说完了。

快速排序的特性总结:

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

  1. 空间复杂度:O(logN)
  2. 稳定性:不稳定
相关文章
|
存储 前端开发 Linux
DPDK-mempool(1)
DPDK-mempool(1)
305 0
|
安全 Linux 网络安全
如何在 VM 虚拟机中安装 Red Hat Enterprise Linux 9.3 操作系统保姆级教程(附链接)
如何在 VM 虚拟机中安装 Red Hat Enterprise Linux 9.3 操作系统保姆级教程(附链接)
|
8月前
|
算法
重磅!2025年中科院预警期刊名单正式发布!
中国科学院文献情报中心定期发布《国际期刊预警名单》,旨在防范学术不端和不当出版行为。2025年最新名单聚焦两大问题:一是引用操纵、论文工厂等破坏科研生态的行为;二是中国作者占比过高或APC费用不合理,影响学术成果国际化传播。自2022年起,预警名单调整至年初发布,便于科研人员规划投稿。名单结合定量数据与专家评估,动态反映期刊风险。被列预警期刊可能影响职称评审及科研经费认可,建议优先选择中科院分区表推荐期刊,警惕快速代发陷阱,并关注期刊官网声明。未来,强化学术自律和技术工具应用将助力科研规范化,推动中国学术走向全球。
637 0
|
10月前
|
存储 前端开发 索引
React 图片轮播组件 Image Carousel
本文介绍了如何使用React创建图片轮播组件。首先,解释了图片轮播的基本概念和组件结构,包括图片容器、导航按钮、指示器和自动播放功能。接着,通过代码示例详细说明了创建基本组件、添加自动播放、处理边界情况的步骤。还探讨了常见问题如状态更新不及时、内存泄漏和样式问题,并提供了解决方案。最后,介绍了进阶优化,如添加过渡效果、支持触摸事件和动态加载图片,帮助读者构建更完善的轮播组件。
214 16
|
jenkins Java Shell
jenkins学习笔记之十三:配置SonarScanner扫描Java项目
jenkins学习笔记之十三:配置SonarScanner扫描Java项目
|
存储 自然语言处理 API
Transformers 4.37 中文文档(十三)(1)
Transformers 4.37 中文文档(十三)
227 1
|
测试技术 机器学习/深度学习 算法
智能化软件测试的演进与实践
随着人工智能技术的蓬勃发展,软件测试领域迎来了革命性的变革。本文深入探讨了智能化软件测试的发展脉络、关键技术及其在现代软件开发中的应用。我们将从自动化测试的基础出发,逐步解析机器学习和深度学习如何赋能测试流程,以及这些技术如何提升测试效率和准确性。此外,文章还将分享一系列成功的案例研究,展示智能化软件测试如何在不同类型的项目中发挥作用。
|
存储 缓存 自然语言处理
elasticsearch 聚合 : 指标聚合、桶聚合、管道聚合解析使用总结
elasticsearch 聚合 : 指标聚合、桶聚合、管道聚合解析使用总结
|
机器学习/深度学习 分布式计算 算法
spark ml特征转换操作StringIndexer、IndexToString、VectorIndexer、oneHotEncoder、Bucketizer、QuantileDiscretizer
spark ml特征转换操作StringIndexer、IndexToString、VectorIndexer、oneHotEncoder、Bucketizer、QuantileDiscretizer
399 0
spark ml特征转换操作StringIndexer、IndexToString、VectorIndexer、oneHotEncoder、Bucketizer、QuantileDiscretizer