【C语言】数据结构——排序二(快排)

💗个人主页💗
⭐个人专栏——数据结构学习⭐
💫点击关注🤩一起学习C语言💯💫

目录

  • 导读:
  • 数组打印与交换
  • 1. 交换排序
    • 1.1 基本思想:
    • 1.2 冒泡与快排的异同
  • 2. 冒泡排序
    • 2.1 基本思想
    • 2.2 实现代码
  • 3. 快速排序
    • 3.1 基本思想
    • 3.2 hoare版本
      • 3.2.1 动图讲解
      • 3.2.2 实现代码
      • 3.2.3 代码优化
    • 3.3 挖坑法
      • 3.3.1 动图详解
      • 3.3.2实现代码
    • 3.4 双指针
      • 3.4.1 动图详解
      • 3.4.2 实现代码
  • 4. 无递归实现快排
    • 4.1 基本思想
    • 4.2 实现代码

导读:

我们在前面学习了排序,包括直接插入排序,希尔排序,选择排序,堆排序。
今天我们来学习交换排序,也就是冒泡排序和快排。
下期我们来讲一讲归并排序。
关注博主或是订阅专栏,掌握第一消息。

数组打印与交换

为了方便我们来测试每个排序是否完成,我们来写个打印数组和交换两数的函数,方便我们后续的打印与交换。

void PrintArray(int* a, int n)
{for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}

1. 交换排序

1.1 基本思想:

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

1.2 冒泡与快排的异同

相似之处:

都是基于比较的排序算法,通过比较元素的大小来确定它们的顺序。

不同之处:

  1. 思想不同:冒泡排序是通过相邻元素的比较和交换来将较大的元素“浮”到序列的末尾,而快速排序是通过在序列中选择一个基准元素,将序列分割成两个子序列,并递归地对子序列进行排序。

  2. 时间复杂度不同:冒泡排序的平均时间复杂度为O(n2),最好情况下为O(n),最坏情况下为O(n2)。快速排序的平均时间复杂度为O(nlogn),最坏情况下为O(n^2)。

  3. 空间复杂度不同:冒泡排序的空间复杂度为O(1),原地排序。快速排序的空间复杂度为O(logn),需要使用递归栈空间。

  4. 稳定性不同:冒泡排序是稳定的排序算法,相等元素的相对顺序不会改变。快速排序是不稳定的排序算法,相等元素的相对顺序可能会改变。

总结:冒泡排序和快速排序都是基于比较的排序算法,它们的主要区别在于思想、时间复杂度、空间复杂度和稳定性等方面。快速排序通常比冒泡排序更高效,但在某些情况下冒泡排序可能更适合,例如对较小规模的数据进行排序。

2. 冒泡排序

2.1 基本思想

冒泡排序算法的基本思想是要将待排序序列中的元素逐个比较并交换位置,使得较大的元素逐渐“浮”到序列的末尾。

  1. 从待排序序列的第一个元素开始,逐个比较相邻的两个元素。

  2. 如果前一个元素比后一个元素大,则交换它们的位置。这样就使得较大的元素逐渐“浮”到序列的末尾。

  3. 继续对剩下的元素重复上述比较和交换的过程,直到整个序列有序为止。

  4. 重复上述步骤,每次将待排序序列的长度减1,直到待排序序列的长度为1。

冒泡排序算法的思想类似于水中冒泡,较大的元素会逐渐向上浮动,而较小的元素会逐渐沉淀到底部。因此,最终结果是将待排序序列从小到大排序。

冒泡排序算法的时间复杂度为O(n^2),其中n为待排序序列的长度。尽管冒泡排序算法的效率相对较低,但由于其实现简单,适用于数据量较小的情况。

2.2 实现代码

// 冒泡排序
void BubbleSort(int* a, int n)
{int i = 0;int j = 0;for (i = 0; i < n; i++){for (j = 0; j < n - i -1; j++){if (a[j + 1] < a[j]){Swap(&a[j + 1], &a[j]);}}}
}
void TestBubbleSort()
{int arr[10] = { 5, 1, 8, 7, 3, 9, 2, 0, 4, 6 };int sz = sizeof(arr) / sizeof(arr[0]);BubbleSort(arr, 10);PrintArray(arr, 10);
}
int main()
{TestBubbleSort();return 0;
}

3. 快速排序

快速排序有三种实现方法:

  1. hoare版本
  2. 挖坑法
  3. 双指针

3.1 基本思想

快速排序是一种基于分治思想的排序算法,其基本思想可以概括为以下步骤:

  1. 选择一个基准元素:从待排序序列中选择一个元素作为基准元素。

  2. 分割操作:以基准元素为准,将序列分割成两个子序列,使得左边的子序列中的所有元素都小于等于基准元素,右边的子序列中的所有元素都大于基准元素。这个分割操作可以采用多种方式实现,常见的有挖坑法、交换法和指针交替法等。

  3. 递归排序:对分割后的两个子序列分别递归地进行快速排序,直到子序列的长度为1或0,即递归的终止条件。

  4. 合并操作:将分割后的子序列进行合并,即将左边子序列、基准元素和右边子序列按照顺序拼接起来,得到完整的排序序列。

整个过程可以通过递归来实现,每次递归中选取一个基准元素,将序列分割成两部分,然后递归地对两部分进行排序,最后将结果合并起来得到有序序列。

快速排序的时间复杂度为O(nlogn),其中n为待排序序列的长度。在最坏情况下,快速排序的时间复杂度为O(n^2),但通常情况下快速排序是一种高效的排序算法。快速排序是原地排序的算法,不需要额外的空间。然而,由于快速排序是递归实现的,所以需要使用递归栈空间。

3.2 hoare版本

3.2.1 动图讲解

我们需要定义一个key来指向第一个数,也就是基准元素。
同时定义leftright来指向数组的两端。
我们让right先走,来找比key小的值,找到后left开始走,去找比key大的值,找到之后交换leftright的值,之后继续right找小,left找大,直到两者相遇,循环结束。
这时再把key处的值和left处的值交换,让key走到left位置。

请添加图片描述
第一次循环结束后如图:
左边的值都比key处的值小,而右边的值都比key处的值大。
这时key所指向的值就是我们上面所说的基准元素。
在这里插入图片描述
下面我们就需要以key为中点,分为左右两个序列。
在这里插入图片描述
然后我们可以用递归继续调用这个函数,分别把左子序列和右子序列继续排序划分为若干左右子序列,这样就能实现排序。

3.2.2 实现代码

递归结束条件就是当第n个子序列长度为1或者0时结束,也就是子序列起点和尾点重合。

void QuickSort1(int* a, int begin, int end)
{if (begin >= end){return;}int key = begin;int left = begin;int right = end;while (left < right){// 右边先走,找比key值小的while (right > left && a[right] >= a[key]){right--;}// 左边后走,找比key值大的while (right > left && a[left] <= a[key]){left++;}Swap(&a[left], &a[right]);}Swap(&a[key], &a[left]);key = left;//分左右两边//左边QuickSort1(a, begin, key - 1);//右边QuickSort1(a, key + 1, end);
}
void TestQuickSort1()
{int arr[10] = { 5, 1, 8, 7, 3, 9, 2, 0, 4, 6 };int sz = sizeof(arr) / sizeof(arr[0]);QuickSort1(arr, 0, sz - 1);PrintArray(arr, 10);
}
int main()
{TestQuickSort1();return 0;
}

3.2.3 代码优化

如果我们遇到一些极端的情况,比如数组是以排序好或者逆序,这可能就会使我们的时间复杂度大大增加,以及递归调用的太多,可能会导致Stack Overflow的情况。
我们就可以在key,left和right三者中选择一个中位数来当key值。
还有比如数据较少的情况下,选择快排是不大理想的,这时我们用直接插入排序效果会更好。

//优化
int GetMidi(int* a, int begin, int end)
{int midi = (begin + end) / 2;// begin midi end 三个数选中位数if (a[begin] < a[midi]){if (a[midi] < a[end])return midi;else if (a[begin] > a[end])return begin;elsereturn end;}else // a[begin] > a[midi]{if (a[midi] > a[end])return midi;else if (a[begin] < a[end])return begin;elsereturn end;}
}// hoare
void QuickSort1(int* a, int begin, int end)
{if (begin >= end){return;}if (end - begin + 1 <= 10){InsertSort(a + begin, end - begin + 1);}else{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int key = begin;int left = begin;int right = end;while (left < right){// 右边先走,找比key值小的while (right > left && a[right] >= a[key]){right--;}// 左边后走,找比key值大的while (right > left && a[left] <= a[key]){left++;}Swap(&a[left], &a[right]);}Swap(&a[key], &a[left]);key = left;//分左右两边//左边QuickSort1(a, begin, key - 1);//右边QuickSort1(a, key + 1, end);}}
void TestQuickSort1()
{int arr[10] = { 5, 1, 8, 7, 3, 9, 2, 0, 4, 6 };int sz = sizeof(arr) / sizeof(arr[0]);QuickSort1(arr, 0, sz - 1);PrintArray(arr, 10);
}
int main()
{TestQuickSort1();return 0;
}

3.3 挖坑法

3.3.1 动图详解

挖坑法与hoare版本的快排并无太大差别。
我们另定义一个hole变量,left和right不变,这时的key不再是下标,而是一个具体的数值,key是首元素的值。
我们仍旧是右边先找小,找到之后并不是直接让左边去找大,而是让这个小的值赋值给hole的位置,注意不是交换,key的值记录着最开始的hole位置的值,不必担心丢失。
接着让hole移动到right位置,左边开始找大,找到后也是把left指向的值赋值给hole位置,hole移动到left位置。
仍旧是left和right相遇之后,把key记录的值赋值给hole位置。
请添加图片描述

3.3.2实现代码

//挖坑法
void QuickSort2(int* a, int begin, int end)
{if (begin >= end){return;}int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int left = begin;int right = end;int hole = left;int key = a[hole];while (left < right){while (a[right] >= key && right > left){right--;}a[hole] = a[right];hole = right;while (a[left] <= key && right > left){left++;}a[hole] = a[left];hole = left;}a[hole] = key;QuickSort2(a, begin, hole - 1);QuickSort2(a, hole + 1, end);
}void TestQuickSort2()
{int arr[10] = { 5, 1, 8, 7, 3, 9, 2, 0, 4, 6 };int sz = sizeof(arr) / sizeof(arr[0]);QuickSort2(arr, 0, sz - 1);PrintArray(arr, 10);
}
int main()
{TestQuickSort2();return 0;
}

3.4 双指针

3.4.1 动图详解

双指针相对于上面两个代码是相较少的,不必再去三位取中。
定义两个指针cur,prev,而key仍然是值。
开始的时候两个指针都指向第一个元素
在这里插入图片描述
之后cur先走,找比key值小的数,找到之后,prev向前走一步,如果这时prev和cur的指向的不是同一个元素,就交换两者指向的值。
反之,如果两者重叠,cur则继续往前走一步。
请添加图片描述
直到cur出了数组,循环结束,这时再交换key值和prev。
再以prev为中心,分为左右子序列。

3.4.2 实现代码

// 双指针
void QuickSort3(int* a, int begin, int end)
{if (begin >= end){return;}int key = begin;int prev = begin;int cur = prev + 1;while (cur <= end){if (a[cur] < a[key] && ++prev != cur){Swap(&a[cur], &a[prev]);}cur++;}Swap(&a[key], &a[prev]);QuickSort3(a, begin, prev - 1);QuickSort3(a, prev + 1, end);
}
void TestQuickSort3()
{int arr[10] = { 5, 1, 8, 7, 3, 9, 2, 0, 4, 6 };int sz = sizeof(arr) / sizeof(arr[0]);QuickSort3(arr, 0, sz - 1);PrintArray(arr, 10);
}
int main()
{TestQuickSort3();return 0;
}

4. 无递归实现快排

4.1 基本思想

非递归实现快速排序算法的关键是使用栈来模拟递归过程。具体步骤如下:

  1. 创建一个栈,并将待排序数组的起始索引和结束索引入栈。
  2. 循环直到栈为空:
  • 弹出栈顶的起始索引和结束索引,作为当前子数组的范围。
  • 选择一个基准元素,将数组按照基准元素进行划分,得到基准元素的索引。
  • 如果基准元素左侧的子数组长度大于1,则将左侧子数组的起始索引和结束索引入栈。
  • 如果基准元素右侧的子数组长度大于1,则将右侧子数组的起始索引和结束索引入栈。
  1. 返回排序后的数组。

4.2 实现代码

前面我们都是用递归的方法来实现快排的,思考一下不用递归该怎么写。
这里我们可以用栈来实现,我们每次把需要排列的左右子序列的开始和结束的下标入栈,记录范围,之后再出栈赋给left和right,又或者其它变量,最后调用上面的函数来实现排序。
我们先来看代码,后面来具体分析。
在这里的栈,我还是调用之前写的栈代码,感兴趣的小伙伴可以看数据结构的专栏。
在这里插入图片描述

// hoare
int PartSort1(int* a, int begin, int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int left = begin, right = end;int keyi = begin;while (left < right){// 右边找小while (left < right && a[right] >= a[keyi]){--right;}// 左边找大while (left < right && a[left] <= a[keyi]){++left;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);return left;
}
//挖坑法
int PartSort2(int* a, int begin, int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int left = begin;int right = end;int hole = left;int key = a[hole];while (left < right){while (a[right] >= key && right > left){right--;}a[hole] = a[right];hole = right;while (a[left] <= key && right > left){left++;}a[hole] = a[left];hole = left;}a[hole] = key;return hole;
}
//双指针
int PartSort3(int* a, int begin, int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int key = begin;int prev = begin;int cur = prev + 1;while (cur <= end){if (a[cur] < a[key] && ++prev != cur){Swap(&a[cur], &a[prev]);}cur++;}Swap(&a[key], &a[prev]);key = prev;return key;
}
//无递归方法实现
void QuickSortNonR(int* a, int begin, int end)
{ST s;STInit(&s);STPush(&s, end);STPush(&s, begin);while (!STEmpty(&s)){int left = STTop(&s);STPop(&s);int right = STTop(&s);STPop(&s);int keyi = PartSort3(a, left, right);// [left, keyi-1] keyi [keyi+1, right]if (left < keyi - 1){STPush(&s, keyi - 1);STPush(&s, left);}if (keyi + 1 < right){STPush(&s, right);STPush(&s, keyi + 1);}}STDestroy(&s);
}

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

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

相关文章

【Unity动画系统】Animator有限状态机参数详解

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

校园跑腿小程序源码系统:取快递+寄快递+食堂超市跑腿 带完整的搭建教程

现如今&#xff0c;越来越多的校园生活服务需求涌现出来。为了满足学生们的日常需求&#xff0c;校园跑腿小程序应运而生。这款小程序集成了取快递、寄快递、食堂超市跑腿等功能&#xff0c;为学生们提供了一个方便、快捷的生活服务平台。 以下是部分代码的示例&#xff1a; 系…

如何使用工具查看机系统信息,应用管理、文件管理

摘要 克魔助手是一款功能丰富的手机助手软件&#xff0c;提供了诸多实用的功能模块&#xff0c;包括手机系统信息显示、应用管理、文件管理、描述文件安装与测试、崩溃日志、实时日志、截图、活跃程序、性能监控和网络抓包等。本文将对克魔助手的界面概览和各功能模块进行详细…

数据结构面试题目

什么是数据结构&#xff1f;计算机存储、组织数据的方式。数据结构包括逻辑结构、物理结构和对数据的一系列操作。其中逻辑结构包括了线性结构&#xff08;线性表、栈和队列&#xff09;和非线性结构&#xff08;树、图&#xff09;&#xff1b;物理结构包括顺序存储结构和链式…

UGUI:一个轻量级的TFTLCD彩色显示屏GUI库

目录 一、引言 二、移植说明 三、如何使用UGUI函数 2.1 UGUI函数介绍 2.2 窗口控件管理 2.3 如何建立一个按键 四、如何实现触摸控制 一、引言 UGUI是一个经过精心设计的轻量级TFT-LCD彩色显示屏GUI库&#xff0c;旨在为用户提供高效、稳定且易于使用的图形用户界面。该…

邮件营销最佳时段:提升邮件打开率与转化率的策略

在如今数字时代&#xff0c;电子邮件营销已成为企业推广及与客户互动的有效途径。但是&#xff0c;一个普遍的现象是&#xff1a;何时发送电子邮件才能更合理&#xff1f;本文将探讨电子邮件营销的出色推送机会&#xff0c;并提供一些有用的提议&#xff0c;以帮助企业更好地规…

OrientDB使用教程:全面了解图数据库

图数据库在当今数据处理领域中扮演着越来越重要的角色&#xff0c;而OrientDB作为一种多模型的数据库&#xff0c;具有图数据库、文档数据库和对象数据库的特性&#xff0c;为应对不同场景提供了灵活的解决方案。本教程将简要介绍OrientDB的使用&#xff0c;包括基本概念、安装…

python subprocess run 和 Popen 的一些使用和注意事项

文章目录 一、run二、Popen NAME subprocess - Subprocesses with accessible I/O streams MODULE REFERENCE https://docs.python.org/3.9/library/subprocess The following documentation is automatically generated from the Python source files. It may be incomplete, …

QGraphicsItem器件移动及旋转相关问题

一、前言 Qt的图形视图框架中&#xff0c;可以使用如下接口设置图元坐标&#xff1a; void QGraphicsItem::setPos(const QPointF &pos)Sets the position of the item to pos, which is in parent coordinates. For items with no parent, pos is in scene coordinates.…

【数据结构】排序之插入排序

排序目录 1.前言2. 排序的概念及其运用2.1 排序的概念2.2 排序的运用2.3 常见的排序算法 3. 插入排序3.1 基本思想3.2 直接插入排序3.2.1 直接插入排序实现3.2.1.1 分析3.2.1.2 代码实现 3.3 希尔排序3.3.1 希尔排序实现3.3.1.1 分析3.3.1.2 代码实现 4. 附代码4.1 sort.h4.2 s…

Lumerical------按键中断程序执行

Lumerical------中断程序执行 引言正文 引言 在 Lumerical 中&#xff0c;很多时候我们需要通过 sweep 的方式来获取我们想要的结果&#xff0c;然而&#xff0c;有时候当我们运行程序后发现书写的脚本有问题时&#xff0c;我们想要强行终止程序的执行&#xff0c;该怎么办呢&…

K8s攻击案例:组件未授权访问导致集群入侵

K8s集群往往会因为组件的不安全配置存在未授权访问的情况&#xff0c;如果攻击者能够进行未授权访问&#xff0c;可能导致集群节点遭受入侵。比较常见的的组件未授权访问漏洞&#xff0c;主要包括 API Server 未授权访问、kubelet 未授权访问、etcd 未授权访问、kube-proxy 不安…

C++系列-第1章顺序结构-4-整型int

C系列-第1章顺序结构-4-整型int 在线练习&#xff1a; http://noi.openjudge.cn/ https://www.luogu.com.cn/ 总结 本文是C系列博客&#xff0c;主要讲述整型int的用法 整型int 在C中&#xff0c;int 是一个关键字&#xff0c;用于声明整型变量。int 类型用于存储整数&…

01的token的年度总结

​ 大家好&#xff0c;我是token&#xff0c;一个热爱.NET的普通人&#xff0c;同样我来自湖南衡阳&#xff0c;再次之前我已经遇到非常多的湖南衡阳的老乡&#xff0c;比如李哥。 ​ 在这里一年中&#xff0c;我的成长也是非常迅速的&#xff0c;每一年的的每一天&#xff0c…

【Linux】深挖进程地址空间

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;熟悉【Linux】进程地址空间 > 毒鸡汤&#xff…

Thinkphp+vue+mysql学生作业管理系统21j0r

运行环境:phpstudy/wamp/xammp等 开发语言&#xff1a;php 后端框架&#xff1a;Thinkphp5 前端框架&#xff1a;vue.js 服务器&#xff1a;apache 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat/phpmyadmin 为设计一个安全便捷&#xff0c;并且使用户更好获取本学院…

解决jenkins、git拉取代码仓库失败Please make sure you have the correct access rights

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; https://gitee.com/nbacheng/n…

Redis主从

一、为何需要主从 单节点Redis的并发能力是有上限的&#xff0c;要进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离 二、如何设置主从 有临时和永久两种模式&#xff1a; 修改配置文件&#xff08;永久生效&#xff09; 在redis.conf中添…

【23.12.29期--Spring篇】Spring的 IOC 介绍

介绍一下Spring的IOC ✔️引言✔️ lOC的优点✔️Spring的IOC✔️ 拓展知识仓✔️IOC是如何实现的&#xff1f; ✔️引言 所谓的IOC (inversion of control) &#xff0c;就是控制反转的意思。何为控制反转? 在传统的程序设计中&#xff0c;应用程序代码通常控制着对象的创建和…

Typora使用PicGo+Gitee上传图片

Typora使用PicGoGitee上传图片 1.下载PicGo(国内镜像) https://mirrors.sdu.edu.cn/github-release/Molunerfinn_PicGo/ 点击PicGo-Setup-2.3.0-x64.exe &#xff08;64位安装&#xff09; 然后打开gitee&#xff08;没注册先注册&#xff09; 2.下载node.js插件 https:/…