免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2231 | 回复: 1
打印 上一主题 下一主题

对于快速排序的理解 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-11-10 20:22 |只看该作者 |倒序浏览
对于快速排序的理解




这两天一直在看排序的算法,当看到快速排序的时候卡了,真是那个郁闷啊。之前几个排序(选择、冒泡......)很快就可以过了。

可是看到快速排序的时候就怎么的也不能理解。

然后就到网上去搜了下,搜到的也就这么几种解释,多数都是大同小异。

网上搜到的,解析得也有点官方,看了会觉得还是看不懂。

怎么的......只能自己慢慢理解吧!

最后......也算是有点小成就吧,自己能理解,不知道对还是不对。



费话就不多说了,以下附上代码,和我自己对快速排序的理解!请指教!
  1. public void quickSort(int[] array, int left, int right) { //array,要排序的数组;

  2.                                                                        //left,从数组left位置开始排序;

  3.                                                                        //right,排序到数组的right位置。 排序整个数组:left=0;right=array.length。

  4.                 if (left < right) { //如果是一个正确的开始和结束的排序位置(开始位置肯定小于结束位置)

  5.                         int mid = partition(array, left, right);  //通过partition方法得到排序的分隔点(关于partition方法原理可以往下看注解)

  6.                         quickSort(array, left, mid - 1);  //递归分隔点以前部分的排序(以left为开始点,分隔点前一个元素为结束点)

  7.                         quickSort(array, mid + 1, right); //递归分隔点以后部分的排序(以分隔点的后一个元素为开始点,以right为结束点)

  8.                 }

  9.         }
复制代码
//在不管partition方法的实现的情况下,以上方法相信大家都应该可以容易的理解吧!

//既:通过partition方法将数组array分成以分隔点为中心,左边是比其小的元素集,右边是比其大的元素集。

//       然后返回数组的分隔点,通过递归对分隔点左右两边再进行排序。




//下面来看partition方法的实现——->



        public int partition(int[] array, int left, int right) { //排序数组array的left到right位置,并返回分隔点(分隔点左边是小元素,右边是大元素)

                                                                           //array:要分隔的数组

                                                                           //left:要分隔部分的开始位置

                                                                           //right:要分隔部分的结束位置

                int pivot = array[left]; //设定第一个元素为基准元素(一般都以第一个元素为基准,本例也一样,不做特殊化)

                int head = left; //这两个值(head/tail)的设定我觉得是挺重要的。

                                      //head:指向要分隔部分的左(头)位置

                //从小到大排列 //head++,向右(tail)移动;tail——,向左(head)移动。

                int tail = right + 1;//tail:指向要分隔部分的右(尾)位置 ,为什么要后移一位,请看后注。



//以下连起来为一句话:①②③④
  1.         while (head < tail) { //①在head和tail碰到(==) 之前,

  2.                         if (array[head] <= pivot) {   //②如果在head位置找到了比基准元素大的元素,

  3.                                 head++;  //否则找下一个元素

  4.                         } else if (array[tail - 1] > pivot) { //③并且在(tail-1)位置找到了比基准元素小或等于的元素。

  5.                                 tail——; //否则找上一个元素

  6.                         } else {

  7.                                 swap(array, head, tail - 1);//④那么交换head和(tail-1)位置的元素。

  8.                                                                     //来实现把大的元素放到基准元素后面,把小的元素放基准元素前面。

  9.                                 head++; //并让head指向下一个元素

  10.                                 tail——;//让tail指向上一个元素                    注:“++“、”——”来分别指向下一个元素是因为这两个位置的元素已经比较过了,

  11.                                                                                             //不需要再过它们比较,来提高效率

  12.                         }

  13.                 }
复制代码
//到这里已经结束上面的交换,既:head>=tail。

//下面将把基准元素放到"中间位置",并非真正的中间,只是它的左边是比基准元素小的元素,它的右边是比基准元素大的元素)

//记:是本次比较的中间位置(left到right)

                array[left] = array[head - 1];  //把第一个元素(left)赋值为head指针的上一个元素.

                                                         //为什么是上一个元素,而不是head指向位置的元素?

                                                         //因为head指向位置的元素,你不知道它的大小,

                                                         //把head指向的元素放到小元素集(基准元素的左边),将无法保证小元素集(可能head指向的元素比基准元素大)

                                                         //这样就破坏了排序(排列的目的:将基准的左边放比基准小的元素,将基准的右边放比基准大的元素)

                                                        //所以,把head的上一个元素和基准交换就能保证排列的目的。因为head的上一个元素一定是小元素
  1.                 array[head - 1] = pivot;//把head指针的上一个元素赋值为基准元素 (完成交换)

  2.                 return head - 1; //返回基准元素位置(通过上面的交换,这里基准元素的位置已经变成了head-1),作为排序的分隔点。

  3.         }



  4.         private void swap(int[] array, int a, int b) { //交换数组array中的a和b位置的元素
复制代码
//该方法用异或"^"来完成交换,当然也可以简单的第三个临时元素来完成
  1.                 array[a] = array[a] ^ array[b];

  2.                 array[b] = array[a] ^ array[b];

  3.                 array[a] = array[a] ^ array[b];

  4.         }
复制代码
//后注:为什么tail = right+1 ; head ≠left-1 ?

//left指向排列首 ,right指向排列尾。

//排列开始的指针head当然应该指向排列首(left)啊,结束tail当然指向排列尾(right)啦。可tail为什么要后移一位,这样不就会造成越界吗?

//我的理解是这样的:

//head++,向tail移动。为了能让head指针在最后能够指向最尾right ,如果在这里不加一,那么在下面while判断首尾相遇(head== tail)的时候,就要写成while(head<=tail)。

//这样感觉应该也可以......为什么不用while(head<=tail)?

//因为,如果用“<=”的话,当head == tail的时候还要比较一次,这样不是多余,会降低效率!

//所以在这里用后移一位来处理(tail = right+1).让在“while(head < tail)”的条件下,head可以指向最尾(right).

//相应的,在以下的计算中要用“tail-1”来计算,为的是不让数组下标越界。

//为什么head不用上移一位呢?

//因为在while循环中,head判断在前,基准是第一个位置元素array[left],而且找的是比基准元素大的元素,所以tail指针不可能也不需要指向第一个位置元素.顾不用做上移一位处理!







以上是本人对快速排序的一点点理解!

如果大家有什么更好的见解,可以分享一下哦!

论坛徽章:
0
2 [报告]
发表于 2011-11-11 11:18 |只看该作者
其实快排不难,不用对其妖魔化。

直观地来说:
由于传统的排序法,其排序过程时间复杂度都是以平方关系来递增的,因此会出现快速排序法这样的分治方法。

你可以构思一下,假设有100条数据,那么用传统排序法,需要付出的代价可能就是约为100的平方---10000次。(实际差额针对大数可以基本忽略不计)
如果按照枢轴数字,对其进行分治,分成大小范围不同的两组,各自独立,那么各自的排序工作就互不干涉了,那么,此次分治需要的付出是100次,剩下的两堆的付出是各自的平方,假设是40/60关系,那么就是1600+3600+100=5300,付出已经大幅度减少了。

当然,既然有效,干嘛不乘胜追击呢,接着分呗,每分一次,结果的平方之总和就会更小,分到极端的情况,每一堆都只有一个数字,然后,就不用继续分了,排序也就完成了。

其中的付出就是对数乘以总长了呗,比单纯的总长平方要大大减少了。

所有的代码都是按照这种思想来设计的,想通了这种思想,代码就无难度了,也就不需要死记硬背了呢。

祝你好运呀!
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP