【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.hqwc.cn/news/307829.html

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

相关文章

C++:第十一讲DFS深搜

Everyday English Your optimal career is simply this: Share the real you with physical world through th e process of creative self-expression. 你的最佳职业很简单&#xff0c;就是这样&#xff1a;通过创造性自我表达的途径和世界分享真实的你。 前言 今天带着大家…

卷积核和学习和训练

如果我们已知原始的图像数据&#xff0c;和这个图像经过某个卷积核计算后&#xff0c;得到的新的数据&#xff0c;那么如何计算出这个卷积核中的具体参数呢&#xff1f; 例如&#xff0c;已知输入图像是12*12的&#xff0c;经过某个3*3的卷积核&#xff0c;得到的输出数据是10*…

Go 如何通过代码进行格式化 gomft命令

我们都知道 使用JetBrains GoLand 快捷键可以快速对代码进行格式化 现在将使用go的代码进行格式化 源代码如下&#xff1a; package mainimport "fmt"func main() {var n1 int 19var n2 float32 4.78//var n3 bool false//var n4 byte avar s1 string…

JoySSL怎么样

JoySSL是一家专业的SSL证书服务提供商&#xff0c;致力于为网站和在线业务提供最佳的安全加密解决方案。以下是JoySSL的一些优点&#xff1a; 提供多种类型的SSL证书选择&#xff1a;JoySSL提供单域名、多域名、通配符等不同类型的SSL证书&#xff0c;以满足不同网站的需求。证…

web自动化上传文件

1&#xff0c;web 自动化文件上传不要太简单 熟悉 web 自动化测试的大佬应该都懂&#xff0c;当采用 js 调用原生控件进行文件上传的时候&#xff0c;最常用的是使用 pywin32 等系统交互库。 当看到 pywin32 那丑陋的 api 封装只能爆粗口。就为了输入一个文件地址&#xff0c;…

大数据Doris(四十四):查询物化视图和自动匹配

文章目录 查询物化视图和自动匹配 一、​​​​​​​查询物化视图

网络运行状况监控工具

网络运行状况是网络在其操作和环境约束范围内按预期运行的能力&#xff0c;但是&#xff0c;随着云和人工智能等技术的出现&#xff0c;网络变得越来越复杂&#xff0c;维护其 IT 基础设施是一项越来越繁琐的任务。为了确保网络可靠性&#xff0c;组织需要了解每个端点的运行状…

Walker S敲锣,人形机器人第一股优必选在港交所主板挂牌上市

12月29日&#xff0c;优必选&#xff08;股份代码&#xff1a;9880.HK&#xff09;正式在香港交易所主板挂牌上市&#xff0c;股价最高达91港元&#xff0c;对应市值约380亿港元。 图片1&#xff1a;从左至右依次为&#xff1a;香港交易所联席营运总监陈翊庭&#xff0c;香港联…

TG7050CKN,TG7050SKN ,TG7050CMN,TG7050SMN

爱普生推出的温补晶振型号&#xff1a;TG7050CKN&#xff0c;TG7050SKN &#xff0c;TG7050CMN&#xff0c;TG7050SMN频率范围为 10mhz ~ 54mhz 适用于广泛的频率需求。这几款的特点就是耐高温&#xff0c;温度可达105℃高温&#xff0c;而且都是高稳定性温补晶振&#xff0c;&…

计量经济学|学习笔记以及学习感悟

初级计量经济学着重于介绍基本的统计工具和经济模型&#xff0c;以帮助理解经济数据和经济现象之间的关系。它包括回归分析、假设检验和预测方法等内容。中级计量经济学则深入研究这些方法的理论基础和实际应用&#xff0c;包括更复杂的模型和技术&#xff0c;如面板数据分析、…

Python 操作 MySQL:使用 mysql-connector-python 操作 MySQL 数据库

大家好&#xff0c;我是水滴~~ 当涉及到使用 Python 操作 MySQL 数据库时&#xff0c;mysql-connector-python 库是一个强大而常用的选择。该库提供了与 MySQL 数据库的交互功能&#xff0c;使您能够执行各种数据库操作&#xff0c;如连接数据库、执行查询和插入数据等。在本文…

IDEA使用HDFS的JavaApi

注&#xff1a;以下代码操作是利用junit在java测试文件夹中实现。 1. 准备工作 1.1 创建测试类 创建测试类&#xff0c;并定义基本变量 public class HDFSJAVAAPI {// 定义后续会用到的基本变量public final String HDFS_PATH "hdfs://hadoop00/";Configuration …