(面试必知)必知必会的冒泡排序和快速排序
前一篇给大家介绍了《优化的直接插入排序(二分查找插入排序,希尔排序)》,现在继续介绍其他排序算法
本博文介绍两个最常被提起的排序算法:冒泡排序和快速排序。冒泡排序是入门排序算法,思路比较常规,但确是最耗时的排序算法,所以听到冒泡排序笑一笑就好了,千万不要拿来装B。另一个是被誉为“20世纪最伟大的十大经典算法”的面试必知算法快速排序,以及针对数组特征进行优化的“随机快排”和“平衡快排”。
冒泡排序
(一)概念及实现
冒泡排序的原理:重复的遍历要排序的数组,每次遍历过程中从头至尾比较两个相邻的元素,若顺序错误则交换两个元素。
具体如下(实现为升序):
设数组为a[0…n]。
1. 从头至尾比较相邻的元素。如果第一个元素比第二个元素大,就交换。
2. 重复步骤1,每次遍历都将冒出一个最大数,直到排序完成。
实现代码:
public static void Sort<T>(IList<T> arr) where T : IComparable<T>{if (arr == null)throw new ArgumentNullException("arr");int length = arr.Count();if (length > 1){bool isSorted = false;// 循环n-2次,每循环完一次,冒泡的一个最大值for (int i = 0; i < length - 1; i++){// 如果一次循环没有发生交换,则说明剩余的元素是有序的。isSorted = true;for (int j = 1; j <= length - 1 - i; j++){// 相邻两个元素比较,将较大的交换到右边arr[j]中if (arr[j - 1].CompareTo(arr[j]) > 0){isSorted = false;Swap(arr, j - 1, j);}}if (isSorted)break;}}}/// <summary>/// 数组元素交换/// </summary>/// <typeparam name="T"></typeparam>/// <param name="arr">数组</param>/// <param name="i">交换元素1</param>/// <param name="j">交换元素2</param>static void Swap<T>(IList<T> arr, int i, int j){T temp = arr[i];arr[i] = arr[j];arr[j] = temp;}
示例:
89,-7,999,-89,7,0,-888,7,-7
排序的过程:
-7 89 -89 7 0 -888 7 -7 [999]
-7 -89 7 0 -888 7 -7 [89] 999
……
……
-888 [-89] -7 -7 0 7 7 89 999
(二)算法复杂度
1. 时间复杂度:O(n^2)
冒泡排序耗时的操作有:比较 + 交换(每次交换两次赋值)。时间复杂度如下:
1) 最好情况:序列是升序排列,在这种情况下,需要进行的比较操作为(n-1)次。交换操作为0次。即O(n)
2) 最坏情况:序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。交换操作数和比较操作数一样。即O(n^2)
3) 渐进时间复杂度(平均时间复杂度):O(n^2)
2. 空间复杂度:O(1)
从实现原理可知,冒泡排序是在原输入数组上进行比较交换的(称“就地排序”),所需开辟的辅助空间跟输入数组规模无关,所以空间复杂度为:O(1)
(三)稳定性
冒泡排序是稳定的,不会改变相同元素的相对顺序。
(四)优化改进
1. 有序优化:在进行相邻元素比较时,可以记录下循环中没有发生交换的多个连续索引对(起始索引和结束索引),在下次轮询时直接对有序区间的最大值进行比较。
2. 双向冒泡:参考资料过程中看到了双向冒泡,不同之处在于“从左至右与从右至左两种冒泡方式交替执行”,个人认为不能提高算法效率并且增加代码复杂度。
快速排序
(一)概念及实现
思想:分治策略。
快速排序的原理:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序。
"保证列表的前半部分都小于后半部分"就使得前半部分的任何一个数从此以后都不再跟后半部分的数进行比较了,大大减少了数字间的比较次数。
具体如下(实现为升序):
设数组为a[0…n]。
1. 数组中找一个元素做为基准(pivot),通常选数组的第一个数。
2. 对数组进行分区操作。使基准元素左边的值都小于pivot,基准元素右边的值都大于等于pivot。
3. 将pivot值调整到分区后的正确位置。
4. 将基准两边的分区序列,分别进行步骤1~3。(递归)
5. 重复步骤1~4,直到排序完成。
实现代码:
/// <summary>/// 快速排序/// </summary>/// <param name="arr">待排序数组</param>/// <param name="left">左边界索引</param>/// <param name="right">右边界索引</param>/// <param name="SelectPivot">选择基准元素委托,并且将基元素与左边界元素交换</param>private static void DoSort<T>(IList<T> arr, int left, int right, Action<IList<T>, int, int> SelectPivot) where T : IComparable<T>{count++;if (left < right){// 选择基准元素委托if (SelectPivot != null)SelectPivot(arr, left, right);// 临时存储基准元素,以让其他数组元素来填充该位置T pivot = arr[left];int low = left;int high = right;while (low < high){// 从右向左找第一个小于 基数 的数 while (low < high && arr[high].CompareTo(pivot) >= 0)high--;if (low < high)arr[low++] = arr[high];// 从左向右找第一个大于等于 基数 的数while (low < high && arr[low].CompareTo(pivot) < 0)low++;if (low < high)arr[high--] = arr[low];}arr[low] = pivot; // 此时 low = high// 两个分区递归。基准元素无需在参与下一次快速排序// 加入if判断可以减少50%-55%的递归次数if (left < low - 1)DoSort<T>(arr, left, low - 1, SelectPivot);if (low + 1 < right)DoSort<T>(arr, low + 1, right, SelectPivot);}}
特别注意在递归前的判断语句,他减少了50%-55%的递归次数。
示例:
89,-7,999,-89,7,0,-888,7,-7
排序的过程:
(以数组第一个元素做为基准值。蓝色代表被分区出来的子数组,绿色代表本次分区基准值)
(二)算法复杂度
1. 时间复杂度:O(nlog2n)
快速排序耗时的操作有:比较 + 交换(每次交换两次赋值)。时间复杂度如下:
1) 最好情况:选择的基准值刚好是中间值,分区后两分区包含元素个数接近相等。因为,总共经过x次分区,根据2^x<=n得出x=log2n,每次分区需用n-1个元素与基准比较。所以O(nlog2n)
2) 最坏情况:每次分区后,只有一个分区包含除基准元素之外的元素。这样就和冒泡排序一样慢,需n(n-1)/2次比较。即O(n^2)
3) 渐进时间复杂度(平均时间复杂度):O(nlog2n)
2. 空间复杂度:O(1)
从实现原理可知,快速排序是在原输入数组上进行比较分区的(称“就地排序”),所需开辟的辅助空间跟输入数组规模无关,所以空间复杂度为:O(1)
(三)稳定性
快速是不稳定的,会改变相同元素的相对顺序。如示例,以第一个基准89排序时,首先将最后一个元素-7移到了第一个分区的第一个位置上。改变了与第二个-7的相对顺序。
(四)优化改进
当每次分区后,两个分区的元素个数相近时,效率最高。所以找一个比较有代表性的基准值就是关键。通常会采取如下方式:
1. 选取分区的第一个元素做为基准值。这种方式在分区基本有序情况下会分区不均。
2. 随机快排:每次分区的基准值是该分区的随机元素,这样就避免了有序导致的分布不均的问题
3. 平衡快排:取开头、结尾、中间3个数据,通过比较选出其中的中值。
根据改进方案,写了如下基准值选取委托:
static Random random = null;public static void Sort<T>(IList<T> arr, QuickType quickType = QuickType.Normal) where T : IComparable<T>{if (arr == null)throw new ArgumentNullException("arr");switch (quickType){case QuickType.Normal:{DoSort<T>(arr, 0, arr.Count() - 1,(subArr, left, right) =>{// 默认以第1个数为基数。// 所以,什么都不用做});}break;case QuickType.Random: // 随机快排{DoSort<T>(arr, 0, arr.Count() - 1,(subArr, left, right) =>{// 2个元素就取默认第一个元素if ((right - left + 1) > 2) {// 随机化快排:随机数组中一个元素做基准if (random == null)random = new Random(new Guid().GetHashCode());int index = random.Next(left, right);T temp = subArr[left];subArr[left] = subArr[index];subArr[index] = temp;}});}break;case QuickType.Balance: // 平衡快排{DoSort<T>(arr, 0, arr.Count() - 1,(subArr, left, right) =>{// 2个元素就取默认第一个元素if ((right - left + 1) > 2) {int index = -1;// 平衡快排:取开头、结尾、中间3个数据,通过比较选出其中的中值int middle = (left + right) / 2;int maxIndex = -1;for (int i = 0; i <= 1; i++){if (i == 0) // 找最大值{if (subArr[middle].CompareTo(subArr[left]) >= 0){maxIndex = middle;}else{maxIndex = left;}if (subArr[maxIndex].CompareTo(subArr[right]) >= 0){// maxIndex本身为最大值}else{maxIndex = right;}}if (i == 1) // 找第二大值{if (maxIndex == left){if (subArr[middle].CompareTo(subArr[right]) >= 0)index = middle;elseindex = right;}else if (maxIndex == middle){if (subArr[left].CompareTo(subArr[right]) >= 0)index = left;elseindex = right;}else if (maxIndex == right){if (subArr[middle].CompareTo(subArr[left]) >= 0)index = middle;elseindex = left;}}}// 交换T temp = subArr[left];subArr[left] = subArr[index];subArr[index] = temp;}});}break;}}
性能测试
测试步骤:
1. 随机生成10个测试数组。
2. 每个数组中包含5000个元素。
3. 对这个数组集合进行本博文中介绍的三种排序。
4. 重复执行1~3步骤。执行20次。
5. 部分顺序测试用例:顺序率5%。
共测试 10*20 次,长度为5000的数组排序
参数说明:
(Time Elapsed:所耗时间。CPU Cycles:CPU时钟周期。Gen0+Gen1+Gen2:垃圾回收器的3个代各自的回收次数)
随机快排和平衡快排都比较稳定高效。顺序率约高,平衡快排的优势越明显。
更加详细的测试报告以及整个源代码,会在写完基础排序算法后,写一篇总结性博文分享。
喜欢这个系列的小伙伴,还请多多推荐啊……
(面试必知)必知必会的冒泡排序和快速排序相关推荐
- 程序员必知的8大排序(三)-------冒泡排序,快速排序(java实现) .
http://blog.csdn.net/pzhtpf/article/details/7560294 5.冒泡排序 (1)基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相 ...
- 必学比知性能指标和常用术语
开心一笑 [问:为什么女生有「体香」,而男人没有? 答:化妆品腌入味了→_→] 点击购买 >>> -- 提出问题 必学比知性能指标和常用术语??? 学习地址 CSDN学院: http ...
- 酒桌上的潜规则,男人必学,女人必知
酒桌上的潜规则,男人必学,女人必知酒桌上的潜规则,男人必学,女人必知!酒桌上的规矩: (一)如果自己真不能喝,丫就别开第一口,端着饭碗夹了菜一边吃着去 (二)如果确信自己要喝,就别装墨迹,接下来就 ...
- 计算机应知应会培训班,必知必会应知应会学习手册信号工定岗题库
必知必会应知应会学习手册 (信号工) (2009版) 一.信号工必知必会 1."三不动"安全制度的内容是什么? 答:未联系登记好不动:对设备性能.状态不清楚不动:正在使用中的设备( ...
- “六如真言”,战必克,攻必取,无往不胜!
疾如风,徐如林,侵略如火,不动如山,难知如阴,动如雷霆. --孙子兵法. 这就是被无数军事家奉为经典的"六如真言",兵家有云,达"六如"者,战必克,攻必取,无往 ...
- 大一大学计算机考试难吗,新生必看!大一期间必考的3个证书,不考后悔,越拖越难考!...
原标题:新生必看!大一期间必考的3个证书,不考后悔,越拖越难考! 9月开学季,大学新生也陆陆续续来到了学校报到,开启自己美好的大学生活!但是!小编要提醒大家的是千万不要相信高中老师说的那句:" ...
- ktt算法 约化_答:那些深度学习《面试》你可能需要知道的
本文是问题"那些深度学习<面试>你可能需要知道的"的回答,答案均以英文版Deep Learning页标标记. 1.列举常见的一些范数及其应用场景,如L0,L1,L2,L ...
- HDU - 2147 巴什博弈(必败点和必胜点)
先解释一下必败点和必胜点: 必败点P:前一个选手取胜的位置,即谁先走到这个位置谁赢. 必胜点N:后一个选手取胜的位置,即谁先走到这个位置谁输. 对于P-N表格,我们采用从终点往前推导的方法,因为,终点 ...
- 小手拍拍机器人_2016年吉林省教师资格证面试:课堂律动知多少
2016年吉林省教师资格证面试:课堂律动知多 少 2016年吉林教师资格证考试公告已出,笔试已经考完,接下来就是面试了,笔试查询时间是:4月19 日:面试报名:4月19日-4月24日:面试时间为:5月 ...
最新文章
- Hadoop生态圈-hive五种数据格式比较
- 配置bind主域名服务器
- 20189218 2018-2019-2 《密码与安全新技术专题》第9周作业
- 如何拯救收录直线下滑的网站?
- PyQt5 图片兼容性问题:libpng warning: bKGD: invalid.,原因及解决办法。
- 实例讲解override和new的区别
- eureka 集群失败的原因_Eureka集群的那些坑
- 《更友好的网站url设计》
- 针对Web系统常用的功能测试方法浅析
- python余弦定理求角_余弦定理计算文章相似度
- java软件工程师自我评价_java开发简历自我评价【java简历自我评价模板】
- 万能的MATLAB丨大厂工程师必备技能,免费学习
- 易语言黑月c编译器,易语言黑月编译器
- MATLAB图形绘制--添加图例
- C stdlib.h
- 隐私计算头条周刊(12.4-12.10)
- 办公室实现无线网络全面覆盖的方案
- 路由器设置技巧之-台式机如何无线上网
- 图的遍历c语言数据结构实验报告,数据结构图的遍历实验报告.doc
- Python实现孤立森林(IForest)+SVR的组合预测模型