您的位置:首页 > 其它

学习快速排序的坑里的若干扯淡

2016-01-11 18:48 169 查看
写快速排序的人真的蛮多了,但我选择自己写一写,首先是检验自己是否能够用语言准确自己学过的算法,如果不能,那说明我的理解是不到位的。我自己的体会是,算法真的很容易忘。针对这个问题,我还查过不少资料,其中刘未鹏的“知其所以然”系列序列算是最有说服力的。他提出,很多情况下,我们是在记忆算法,而忽略了背后的原理。当然,这个也归因于教材很少去讲算法的来龙去脉。经典算法往往是发明者比较长期的摸索,期间也应该经历了大量的试错。而我们的学习则是直接指向大师们不断试错后得到的广为流传的结果。

我们常常把我们学得的算法过程视作理所应当的过程,仿佛它生来如此。但是当我们关上书本,或者隔上几天,那些理所应当的算法过程又在我们的脑海里就立马成了不连续的片段。而且,即使我们默写某个算法多次,但要换一个角度对算法进行应用时,我们又通常容易感到沮丧。尽管之前默写的代码还装在大脑里,但是对于要解决的相关问题,又感到束手无策。如果仅仅听信“无他,唯手熟尔”,我觉得这是远远不够的。简单的重复并不能给带来想要的成长。

回到正题,从快速排序算法(此后简称快排)说起。我们常常听到对快速排序的诸多褒奖,言说它的应用场景非常之多,而实际上呢,我见到的算法系列里,并没有对这个问题进行延展,而是对快速算法做了自己的讲解,不过有些讲解确实很到位,例如MoreWindows的白话经典算法系列之快速排序,把快排算法描述成挖坑和填坑的动作。这样确实蛮形象,估计很多学习者也想到了这种思路来理解和记忆,不过作者把这种方式用博客的形式分享给了更多人而已。有趣的是,我也看到一些博客写出来的快速排序是有错的,而这个时候,评论就很有意思,看出错误来的读者会毫不客气,而没看出来的呢,自然会表扬和感谢一下作者。我的重心逐渐转移到了这些错误上。这些错误,往往也是学习者的盲区,我自己也是如此。但看了一些精彩+表述不错但瑕疵却使得某些情形不能正确的代码之后,就会看到自己的盲区所在。

void my_quicksort(int *a,int left,int right)//排序方法
{
if(left<right)
{
int i,j,temp;//i,j分别为向右,向左移动的指针,temp存储基准元素的值
i=left;
j=right;
temp=a[left];
while(i<j)
{
while(i<j && a[j]>=temp) j--;//j向左移动寻找比temp小的元素
if(i<j)
a[i++]=a[j];//a[j]填补a[i]腾出来的位置
while(i<j && a[i]<=temp) i++;//i向右移动寻找比temp大的元素
if(i<j)
a[j--]=a[i];//a[i]填补a[j]腾出来的位置
}
a[i]=temp;//i,j相遇时即为temp的最终位置
my_quicksort(a,left,i-1);
my_quicksort(a,i+1,right);
}
}
int main()//main方法
{
int i=0;
int a[]={34,56,12,67,12};
int len=sizeof(a)/sizeof(int);
my_quicksort(a,0,len-1);
for(;i<len;i++)
{
printf("%d\n",a[i]);
}
return 0;
}


上面的代码,我在VS 2012中是可以运行通过的,而在VC 6.0下,却不能编译通过。当初学者看到这样的代码,又使用了VC 6.0的环境,可能就有点心塞了,因为VC 6.0下,变量的定义必须在其他语句之前(我是用的绿色版,有这个限制,正式版未做测试),而VS 2012却无此限制。这是初学者容易遇到的障碍。在学习代码的时候,可能会因为环境不同,而原作者又没有做足够说明,而使学习者质疑源代码的正确性。而作者的原意呢,是在left < right条件不满足的情形下,根本没有创建变量的必要,而是直接结束方法,目的是为了高效,却给学习者带来了困难。

再来看另外一个问题:

while(i<j && a[j]>temp) j--;//j向左移动寻找比temp小的元素
while(i<j && a[i]<temp) i++;//i向右移动寻找比temp大的元素


粗略看没什么问题,甚至我有时在纸上写的时候,也把“=”给忘记了。这个问题,当我们脱离记忆,去写快排的代码时,是很容易发生的,其他排序算法也有类似现象。 而原因在于,我们可能根本没有注意到 “=”的作用。我在用数据做算法演练时,有一个习惯,会尽量不使用两个相同的数据,因为我通常发现这样可以减小思考量。用思维在大脑里和纸上演练算法的稳定性的前提,对我而言,是我首先知道了这个算法代码怎么写,然后再去考虑有重复数据的情形,而有时则会忽略。写出上述代码的人,可能也存在着类似的问题,而评论里叫好的读者,大概也存在这样的问题。他们检验代码正确性的时候,也忽略了要检验快排的稳定性。但有个评论者却很隐晦,“大家试试2,3,2这个数据序列,会有奇迹发生的”,我敢说,这个读者是不存在这个思维盲点的,因为他有很强的意识用最少量的数据来检验快排的稳定性。当然,这里不是稳定性的问题,而是会出现死循环。而在我看来,忽略这里“=”的学习者,也容易忽略对算法稳定性的考虑。

if(i<j)
a[i++]=a[j];//a[j]填补a[i]腾出来的位置


我看到的另外一个问题,是有博客作者忘记了i < j的逻辑判断。同样地,这应该也不是某一个人会出现的错误,而是一类人容易出现的错误。当我们如果深刻地意识到,最外层的while循环,包括里层的各个语句,都在确保退出循环i一定与j相等时,我们就相对容易逃脱这样的错误。

我总结的这几点,对于有些人而言,已经是显而易见的事情,但我想应该并不是每位学习者都能避开每一个坑。我们在某处出现错误或者失误,从思维上来讲,一般并不是偶然的,而是我们的思维确实存在着缺陷。但是,如果我们愿意去搞清为什么,而不是简单的重复记忆,那么再次犯错的概率就会降低,对同类问题的思考能力会不断得到修正和改进,而另一方面,我们的记忆量也会大为减少,因为我们掌握了关键点。而其中一部分关键点,往往是我们思维的缺陷,因此我们难以发现它们,一旦发现,就要及时修正,如果拒绝修正,我们也就拒绝了进步。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  快速排序 算法