一次快速排序错误引发的思考(1)

  快速排序是目前基于关键字的内部排序算法中平均性能最好的,它采用了分治策略,这既是快速排序的优点也是它的缺点。从快速排序的算法描述上我们可以发现它具有递归的结构:

    (1)确定一个分界,将待排序的数组分为左、右两个部分;

    (2)使所有小(大)于临界值的数据移到左部分,大(小)于临界值的数据移到右部分;

    (3)这时左、右两个部分成为了两个独立的数组,分别对它们执行(1)(2)(3)的操作,直到所有数据都是有序的状态为止。

  照这样的描述我们不难写出快排的代码,我平时遇到排序的问题,只要数据量上了100,想都不想就用快排来解决,但是当我用下面这个程序测试时却出现了问题:

 1 #include <stdio.h>
 2 #include <time.h>
 3 #include <stdlib.h>
 4
 5 #define NUM 10000000    /*待排序的数据量*/
 6
 7 void quick_sort(double a[], long left, long right);
 8
 9 int main(void)
10 {
11     clock_t t_s, t_e;
12     long i;
13     double a[NUM];
14
15     srand(time(NULL));
16     for (i = 0; i < NUM; ++i) {
17         a[i] = rand();
18     }
19
20     t_s = clock();
21     quick_sort(a, 0, NUM-1);
22     t_e = clock();
23     double t = (t_e - t_s) / (double)CLOCKS_PER_SEC;  /*计算排序用时*/
24
25     printf("Quick sort %d items used time:%f s\n", NUM, t);
26
27     return 0;
28 }
29
30 void quick_sort(double a[], long left, long right)
31 {
32     long i = left;
33     long j = right;
34     double mid = a[(i + j) / 2]; /*以中间元素作为比较的基准*/
35
36     while (i <= j) {
37         while (a[i] < mid)
38             ++i;
39         while (mid < a[j])
40             --j;
41         if (i <= j) {
42             double t = a[i];
43             a[i] = a[j];
44             a[j] =t;
45             ++i;
46             --j;
47         }
48     }
49
50     if (i < right) quick_sort(a, i, right);
51     if (left < j) quick_sort(a, left, j);
52 }

  我在Linux上运行这个程序出现了"Segmentation fault "错误,而当NUM==1000000时却没有这个错误。查阅相关资料得知这是由于程序递归次数太多,大量的压栈使程序占用的栈空间超过了操作系统所规定的大小,从而出现的内存错误。

  我用ulimit -s指令的得到的结果是8192,也就是说我的系统默认给每个程序分配的大概是8M的栈空间。用指令ulimit -s unlimited使栈空间变成实际内存大小后,上面的程序就可以顺利运行而不出错误了(因为Linux上不像Windows可以把栈的大小写入可执行文件中,所以只能用ulimit -s更改的方法了)。难道因为栈的限制,快速排序能够处理的数据量就有上限了吗?那还不如用选择排序——虽然慢,但至少不会出错,于是我找到了这篇文章:快速排序的非递归实现。其实说是“非递归”,只不过是用自己管理的栈来消除递归,算法本质上没有区别,而且从这篇文章作者的测试来看,用栈的方法比用递归的方法反而更慢(作者将其解释为:“用栈的效率比递归高,但是在这个程序中局部变量也就是要每次压栈的数据很少,栈的优势体现不出来,反而更慢……”,我认为这种观点是不对的,由于递归可以理解为有了一个“系统帮你自动管理的栈”,它的效率肯定是要比你自己管理的栈要高的,况且你在进行弹栈和压栈操作时又调用了新函数,算上调用的开支,用栈的方法肯定比递归慢),不过栈在这里的优势是可以不用考虑操作系统的问题,而且能够处理的数据量只和内存大小有关,不必受到操作系统对栈空间大小的限制(即使用栈,快排也比很多排序算法要快得多)。

  以前在学排序算法的时候,专门有讲怎样根据实际问题来选择合适的排序算法,但是我图“省事”,就只用快排和简单选择排序。遇到了这个问题也让我对算法的选择和实现上有了更多认识,同时也了解到用栈消除递归在有些场合(比如系统栈空间受限)的重要意义。

时间: 2024-10-05 04:45:01

一次快速排序错误引发的思考(1)的相关文章

一次快速排序错误引发的思考(2)

上一次我说到所谓的“非递归”快速排序算法,不过是用栈来消除了递归,它的运行时间肯定比递归算法长,我们不妨来实际实现一下.代码如下: 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <time.h> 4 5 #define MAX_TOP 10000 /*一个很大的栈*/ 6 #define NUM 500L 7 8 /*有关栈的数据结构*/ 9 struct Region { 10 long left; 11

常见函数错误引发的思考.

今天在写代码的时候,我犯了一个很low的错误,废话不多说,直接上代码: 1 function () { 2 console.log('hello world'); 3 }() 大家看到之后,第一反应肯定会认为是个语法错误,可是自己仔细想想,这是什么原因?似乎还不能解释清楚,好奇宝宝模式立即启动,经过查阅相关资料得到了答案,接下来我们一起来探讨下其中的原理. 疑惑解答 大家有没有考虑过为什么上面这种写法会报错? 原来,浏览器遇到function关键字的时候会认为这是一个函数声明,函数声明必须包括:

Android中的 Multiple dex files define 编译错误引发的思考

昨天我龙哥问我一个问题,他说如果一个工程中,有一个com.x.A枚举,导入的第三方jar中也有一个com.x.A枚举,那么我在工程中用A枚举的时候,会用到那个枚举呢?我当时一想,这个不是类(枚举是个特殊类)定义冲突吗?应该在编译的时候就报错呢,而且这个问题我之前遇到过,所以我很自信的和他说,这个应该在编译的时候就报错,结果他来了一句:没有呀?运行成功了,而且导入的是工程中的那个枚举A,我擦,我一想这不是打我脸吗?我记得非常清楚,是会报错的呀,所以我就自己写了一个AndroidDemo工程: 工程

【ROC曲线】关于ROC曲线、PR曲线对于不平衡样本的不敏感性分析说引发的思考

ROC曲线 在网上有很多地方都有说ROC曲线对于正负样本比例不敏感,即正负样本比例的变化不会改变ROC曲线.但是对于PR曲线就不一样了.PR曲线会随着正负样本比例的变化而变化.但是没有一个有十分具体和严谨地对此做出过分析和论证(至少我没有找到). 此处记为结论1: 结论1:PR曲线会随着正负样本比例的变化而变化:但是ROC曲线不会. 此处我就这一问题进行了详细的分析论证,并在这个过程中引发了很多思考. 首先,如何分析这个问题呢? 看下ROC曲线是由TPR和FPR组成的 下面我们这样来分析这个问题

由DBCursor的“can&#39;t switch cursor access methods”异常引发的思考

先谈谈我是怎么用的: DBCollection dbcollection = XXXXXXXXXX(); //连接mongo DBCursor dbCursor = mergeVideoDB.find(XXXX); //根据name查出若干个 if (dbCursor.length() == 1) { return videoinfos; } while(dbcursor.hasNext()){ // 这一步产生错误,报出DBCursor的"can't switch cursor access

由一个emoji引发的思考

由一个emoji引发的思考 从毕业以来,基本就一直在做移动端,但是一直就关于移动端的开发,各种适配问题的解决,在日常搬砖中处理了就过了,也没有把东西都沉淀下来,觉得甚是寒颜.现就一个小bug,让我们来了解一下我们天天都在用的emoji,对于开发来说,是一个怎么样的存在. 背景 之前在做一个留言功能时,发现在其中一台安卓5.0的手机上,输入emoji糊掉了,成了如下这样的情况 这是skr啥玩意儿呀,怎么看上去像某白色幼虫. 与是我又试了好几个手机,ios都没有问题,甚至一台安卓机中之霸(安卓4.0

曲演杂坛--一条DELETE引发的思考

原文:曲演杂坛--一条DELETE引发的思考 场景介绍: 我们有一张表,专门用来生成自增ID供业务使用,表结构如下: CREATE TABLE TB001 ( ID INT IDENTITY(1,1) PRIMARY KEY, DT DATETIME ) 每次业务想要获取一个新ID,就执行以下SQL: INSERT INTO TB001(DT) SELECT GETDATE(); SELECT @@IDENTITY 由于这些数据只需保留最近一天的数据,因此建立一个SQL作业来定期删除数据,删除脚

一次部署HTTPS的相关事件引发的思考

前言: 上周五快要下班的时候,突然收到通知客户希望了解一下部署HTTPS的流程,这种事情谁听了都会有几分诧异的.因为这件事虽然和工作有一定的相关度,但平时不会走这个方向,实际上也较少接触.此外,客户手下应该不缺人,做运维和开发的肯定比我更懂这个,但情况却和我想的不一样. 正文: 客户有需求,就应该尽量满足!因此,尽管之前对Apache.Tomcat的一些配置不熟,也未有过自己部署HTTPS的经验[当然失败的尝试还是有的],便趁着周末了解了一下相关的东西,在本地搭建了环境.实践表明,当你对一个东西

UPDATE 时主键冲突引发的思考【转】

假设有一个表,结构如下: root@localhost : yayun 22:59:43> create table t1 ( -> id int unsigned not null auto_increment, -> id2 int unsigned not null default '0', -> primary key (id) -> )engine=myisam; Query OK, 0 rows affected (0.00 sec) root@localhost