测试环境

  Ubuntu 18.04, gcc 8.4

复习一下快排算法,不料却得到了非预期的结果。示例代码如下

 1 #include <stdio.h>
2
3 void mySwap(int *p, int *q)
4 {
5 *p ^= *q;
6 *q ^= *p;
7 *p ^= *q;
8 }
9
10 void getPivot(int *srcArr, int left, int right)
11 {
12 int mid = left + (right-left)/2;
13
14 if(srcArr[left] > srcArr[mid])
15 {
16 mySwap(&srcArr[left], &srcArr[mid]);
17 }
18
19 if(srcArr[mid] > srcArr[right])
20 {
21 mySwap(&srcArr[mid], &srcArr[right]);
22 }
23
24 if(srcArr[left] > srcArr[right])
25 {
26 mySwap(&srcArr[mid], &srcArr[right]);
27 }
28 }
29
30 void quickSort(int *srcArr, int left, int right)
31 {
32 if (left >= right)
33 {
34 return;
35 }
36
37 int low = left;
38 int high = right;
39
40 int pivot;
41 //getPivot(srcArr, low, high);
42
43 int mid = low + (high-low)/2;
44
45 mySwap(&srcArr[low], &srcArr[mid]);
46
47 pivot = srcArr[low];
48
49 while (low < high)
50 {
51 while (srcArr[high] >= pivot && low < high)
52 {
53 --high;
54 }
55
56 srcArr[low] = srcArr[high];
57
58 while (srcArr[low] <= pivot && low < high)
59 {
60 ++low;
61 }
62
63 srcArr[high] = srcArr[low];
64
65 }
66
67 srcArr[low] = pivot;
68 quickSort(srcArr, left, low-1);
69 quickSort(srcArr, low+1, right);
70 }
71
72 int main()
73 {
74 int srcArr[10] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 10};
75
76 quickSort(srcArr, 0, 9);
77
78 for(int i = 0; i < 10; ++i)
79 {
80 printf("%d ", srcArr[i]);
81 }
82
83 putchar(10);
84
85 return 0;
86 }

运行结果如下

第一个元素为0,本应该是1的。在这个过程中我并没有修改数组中的元素呀,为什么出现了0呢?为什么只有第一个元素有问题呢?其它的元素为什么没有问题呢?

使用gdb调试,在排序的序列尽量靠近左侧时打印下标和元素的值,发现了一个非预期的现象。当排序第0个和第1个元素这两个元素所在的序列时,执行到截图中第44行(交换两个元素的值)代码后,下标都没有问题,low = 0, mid = 0, high = 1。

问题是第0个元素值此时变成了0了。只是一个交换,第0个元素咋就变成0了呢?再看看交换两个元素的值是通过异或来进行的,一下子反应过来了,估计是与自身异或了。任何整数与自身异或结果为0。

(截图中的行号与前面示例代码中的行号有点出入,请知悉)

调整,在交换之前判断下是否为同一个元素,为不同的元素才进行交换。

1 if (low != mid)
2 {
3 mySwap(&srcArr[low], &srcArr[mid]);
4 }

再次运行

结果符合预期。

再回到这个问题,为什么只有第一个元素有问题呢?其它的元素为什么没有问题呢?

按照上面提供的数组中的数据,复盘了下排序的过程,排序过程中,中轴两边的序列(左边的元素小于中轴,右边的元素大于中轴),前面的几轮中,右边都只有一个元素。最后几轮中,刚好有一轮是4个元素,即第0个到第3个元素。排序后,中轴左边剩下两个元素,即经0个和第1个元素,中轴右侧只剩下第3个元素。

这两个元素的序列在排序前,先进行了交换,想着尽可能取中间的元素作为中轴(假设中间的元素为均值),于是将最左侧的元素与序列中间的元素进行了交换。注意,此时,中间的元素与第0个元素的下标一致,即中间的元素即为第0个元素。相同的元素异或,结果为0,结果使第0个元素值为0了。

这也导致了问题的出现。

从复盘过程中也明显感受到了快速排序的弊端,中轴的值取得不好的情况,像示例代码中,总是导致中轴一侧仅有一个元素,导致了最坏的情况和最坏的时间复杂度。取合适的中轴的值是值得思考的,取得一个好的中间的值,可以使得中轴两的序列分布比较均匀,可以凸显快排的优势。一是可以采用随机算法,得到一个随机的下标,但这也不能保证它一定就是最优的,另外随机算法也需要一定的开销。二是,取首元素、中间元素、尾元素三者中的中间者作为中间元素(即比较三者大小取中间者),这也要求序列中至少有三个元素,当然这个也是在拼人品。并不能保证中轴值最为合理。

下面示例代码采用了第二种方式实现了一下,为保证序列中至少有三个元素,增加了判断条件。

 1         int low = left;
2 int high = right;
3 int center = (left+right)/2;
4 int pivot;
5
6 if(right - left >= 2)
7 {
8 if(arr[left] > arr[right])
9 {
10 mySwap(&arr[left], &arr[right]);
11 }
12
13 if(arr[center] > arr[right])
14 {
15 mySwap(&arr[center], &arr[right]);
16 }
17
18 if(arr[left] > arr[center])
19 {
20 mySwap(&arr[left], &arr[center]);
21 }
22
23 mySwap(&arr[left], &arr[center]);
24 }
25
26 pivot = arr[left];

加个题外话,如果元素数量较少(如少于20)时,可以选择其它排序算法如插入排序等。上面的示例代码仅是作复习快速排序算法用的。

注:因为bug是后面重新复现的,截图的调试中代码行号与示例代码中的行号有3行内的误差,请知悉。

参考材料:

《数据结构与算法分析 C语言实现》 马克.艾伦.维斯

快速排序遇到的小bug的更多相关文章

  1. Chrome出了个小bug:论如何在Chrome下劫持原生只读对象

    Chrome出了个小bug:论如何在Chrome下劫持原生只读对象 概述 众所周知,虽然JavaScript是个很灵活的语言,浏览器里很多原生的方法都可以随意覆盖或者重写,比如alert.但是为了保证 ...

  2. 解决JqueryUI 拖放排序遇到滚动条时有可能无法执行排序的小bug

    前些日子不是在做 使用Jquery-UI实现一次拖拽多个选中的元素操作嘛,在持续完善这个组件时遇到了一个关于拖放排序的bug.今天就着图片和代码重现一下,也顺便告诉大家如何解决这个问题. 首先先上图描 ...

  3. 淘宝WAP版小BUG分析

    前几天发现的一个淘宝WAP版的小BUG,就是用桌面版chrome看的时候产品评价中的图片显示不出来,都是图裂了. 这是什么原因呢?图片为什么会显示不出来呢?淘宝的技术人员.测试人员不可能没发现啊.开启 ...

  4. 关于一个小bug的修正

    python初学者,非常喜欢虫师的文章. 练习时发现一个小bug,http://www.cnblogs.com/fnng/p/3782515.html 验证邮箱格式一题中,第三个x不允许有数字,但是测 ...

  5. 用 parseInt()解决的 小 bug

    在做轮播模块的时候遇到问题是:你在 连续指示小按钮 时候再去 只有 点击 下一张按钮,出现bug: 指示小按钮的 className 当前显示的 calssName 为 undefined ! // ...

  6. iOS开发之使用UICollectionView实现美团App的分类功能【偶现大众点评App的一个小bug】

    郝萌主倾心贡献,尊重作者的劳动成果,请勿转载. 假设文章对您有所帮助,欢迎给作者捐赠,支持郝萌主,捐赠数额任意,重在心意^_^ 我要捐赠: 点击捐赠 Cocos2d-X源代码下载:点我传送 游戏官方下 ...

  7. Flex Validator的小BUG

    Flex中对同一控件如TextInput进行多种格式校验的情况下,如不注意,可能导致错误信息不显示的BUG,比如 <fx:Array id="validators"> ...

  8. Flutter实战视频-移动电商-34.列表页_小BUG的修复

    34.列表页_小BUG的修复 当高粱酒的子类没有数据返回的时候就会报错. 解决接口空数据报错的问题 没有数据的时候,给用户一个友好的提示, 我们没有数据的时候还要告诉用户,提示一下他没有数据,在我们的 ...

  9. 观CSDN站点小Bug有感

            今天早上在浏览博客的时候偶然发现CSDN博客的数据出现了异常,我也是头一次看到这么明显的Bug.详细什么表现呢?先来看个截图.例如以下:             常常看CSDN博客的人 ...

  10. Fundebug前端JavaScript插件更新至1.8.2,修复2个小BUG

    摘要: 修复2个BUG,请大家及时更新. Fundebug前端异常监控服务 Fundebug是专业的程序异常监控平台,我们JavaScript插件可以提供全方位的异常监控,可以帮助开发者第一时间定位各 ...

随机推荐

  1. Oracle SQL 注入攻击

    All about Security - SQL Injection 最近做个一个有关ORACLE数据库安全的网上研讨.我们有1300多位参与者,反馈相当丰富. I recently did a we ...

  2. letcode-括号生成

    递归大法,空间换时间 就是记录左右括号数,一旦右括号数大于左括号数,退出. 当左右括号数相等,且等于n则为合法解. 使用char数组取代StringBuilder可以减少内存使用,这样每次进行回溯时不 ...

  3. Innodb存储引擎的文件

    目录 概述 参数文件 日志文件 错误日志 慢查询日志 查询日志 二进制日志 binary log 二进制日志的配置 二进制日志的作用 二进制日志的保存 socket 套接字文件 pid文件 MySQL ...

  4. New Questions

    1. C++/Qt 实现一个信号槽 #include <functional> #include <iostream> #include <vector> clas ...

  5. 异常处理try...except...finally---day26

    1.认识异常处理 # ### 认识异常处理 #IndexError 索引超出序列范围 #lst = [1,2,3,4] #print(lst[10]) #KeyError 字典中查找一个不存在的关键字 ...

  6. React 受控和非受控组件

    无论你做什么,都要相信自己可以做到,因为你的潜力是无限的. 把父组件的状态变成属性传递给子组件,子组件接受这个属性,听命于父组件.这个子组件就是叫做受控组件.在受控与非受控组件有两种理解方案,第一:狭 ...

  7. 【LeetCode排序专题01】由旋转数组的最小数字引出的关于排序算法的讨论(冒泡排序、二分查找+暴力法)

    旋转数组的最小数字 剑指 Offer 11. 旋转数组的最小数字 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转. 给你一个可能存在 重复 元素值的数组 numbers ,它原来是一 ...

  8. Fasfdfs搭建

    目录 介绍 参考 tracker-server: storage-server: group: meta data: 部署 FastDfs服务架构图 本地部署服务 安装 libfastcommon 和 ...

  9. 【Azure Redis 缓存】Azure Cache for Redis 是否记录具体读/写(Get/Set)或删除(Del)了哪些key呢?

    问题描述 在Azure Redis的门户活动日志中,可以查看到的是对于Redis资源本身的操作.但是对于客户端连接到Redis服务后,对服务所做出的读写,或删除操作,是否有日志可以查看到呢? 问题回答 ...

  10. Java 类中属性的使用

    1 类中属性的使用: 2 * 属性(成员变量) 局部变量 3 * 1.相同点: 4 * 定义变量的个格式: 数据类型 变量名 = 变量值 5 * 先声明 后使用 6 * 变量都有其对应的作用域 7 * ...