对快速排序的一点小探究

  快速排序算法是一种划分交换的方法,采用了分治法进行排序。

1 public static void quikSort(int a[],int left,int right)
2     {
3         if(left >= right)return;
4         int p = partition(a,left,right);
5         quikSort(a,left,p-1);
6         quikSort(a,p+1,right);
7     }

 

  开始,快速排序中划分方法partition()的基准元素都选取为最左侧 left 处的值:

 1 public static int partition(int a[],int left,int right)
 2     {
 3         int index =  left;
 4         int value = a[index];//基准元素
 5         while(left < right)
 6         {
 7             //比较中带等号,针对重复元素如:4,3,4,检测指针才会移动,不然就死循环了
 8             while(left < right && a[right] >= value)
 9             {
10                 right--;
11             }
12             a[index] = a[right];
         index = right;
13 // if(left < right) 14 // { 15 // //需要比较一次left和right是否重合。虽然可以避免了它自己的值与自己比较,但是多了一次判断比较;效果上没有改进 16 // left++; 17 // } 18 //left++;//直接动left指针就是错误。当left与right指针重合时,这句话执行就不对,强制的多加了一次!重合之前都是对的 19 while(left < right && a[left] <= value) 20 { 21 left++; 22 } 23 a[index] = a[left];
         index = left;
24 } 25 //跳出循环必然是left==right了,left和right重合就找到了基准元素就位时的位置 26 a[left] = value; 27 //System.out.println(" 划分点(该元素最终位置): " + left); 28 return left; 29 }

 

 

  后来,想到使用随机值,在某些程度上(比如在基本已经有序的情况下)改进快速排序(以下代码执行结果是错误的,例如[4,9,10]随机选取了9的话,index指向9,执行结果为[9,9,10]),有误的代码如下:

 1 public static int partition(int a[],int left,int right)
 2     {
 3       int index =  left + r.nextInt(right - left + 1);//随机选基准
 4         int value = a[index];
 5         while(left < right)
 6         {
 7             while(left < right && a[right] >= value)
 8             {
 9                 right--;
10             }
11             a[index] = a[right];
12          index = right;
13             while(left < right && a[left] <= value)
14             {
15                 left++;
16             }
17             a[index] = a[left];
18          index = left;
19         }
20         a[left] = value;
21         return left;
22     }

 

 经过调试和思考,得知以上代码错误的原因:一定要把基准元素交换至最左边或最右边(放到最左就从right开始检测,最右就应该从left开始检测),使得腾空的位置从最边上位置,分别从最左最右两个方向,向基准元素就位的真正位置逼近,而不是一开始就从枢轴元素的原始位置index开始移动,因为无法确定基准元素原始位置是在基准元素最终位置的哪一侧(或正好已经就位,却通过移动检测指针达不到   left==right==最终位置   的状态),如果index腾空的位置在轴左侧(如[1,5,4,9]选了5)(另外,假如5初始位置在最左位置,则5必然在枢轴的左侧或已经就位进而理所应当从最右侧开始找元素与腾出的空位交换)就应该在轴右侧找一个小于基准元素的元素去兑换,那么就应该从右侧right开始检测,反之亦然。

 

  修改代码如下即可:

 1 public static int partition(int a[],int left,int right)
 2     {
 3         
 4         int index =  left + r.nextInt(right - left + 1);//随机选基准
 5         int value = a[index];
6 //交换随即选取的基准元素a[index]至最左侧left处,由于用value保存了交换到最左侧的该基准元素值,就必须要先从right开始检测;如【4,3,9】,假如选取了a[0] = 4为基准元素,从       //left开始检测,执行结果为[4,3,9],就错误了。 7 int temp = a[left];, 8 a[left] = a[index]; 9 a[index] = temp; 10 11 while(left < right) 12 { 13 14 while(left < right && a[right] >= value) 15 { 16 right--; 17 }
         a[index] = a[right];
          index = right;
18 while(left < right && a[left] <= value) 19 { 20 left++; 21 } 22 a[index] = a[left];
         index = left;
23 } 24 a[left] = value;//基准元素就位 25 return left; 26 }

 

 另一种划分函数的实现方式,从一个方向确定基准元素的最终位置(每次发现一个比基准元素小的元素,就通过使用index挨个从最前往后放。从前往后统统扫描一遍,把所有小于value的都放前面了,自然大于value的都在后面):

 1 public static int partition(int a[],int left,int right)
 2 {
 3   int index = left;
 4   int value = a[left];
 5 //从左端向右端一直检测,i在前面开路,找小的,index在前面负责安放好小的元素。假如另外开辟一个空间b[],直接大的放后面,小的放前面,即可。原因就在于在原来数组上实现算法,增加了实现复杂度和细节。
 6   for(int i=left+1;i<=right;i++)
 7   {
 8     if(a[i] < value)
 9     {
10        index++;
11       if(index != i)
12       {
13           swap(a[i],a[index]);
14       }
15      }              
16   }   
17     a[left] = a[index];//当前index指向的元素是(最后)一个位于枢轴左侧的元素a[index],故可以被放到最左的left处。index指向的位置是基准元素的最终位置。
18     a[index] = value;//基准元素就位
19 
20     return index;               
21 }            

  

  总结:快速排序定位轴的位置,关键就看你是怎么做到,小于枢轴元素(基准元素)值的放到枢轴左边(而不是靠左边的位置就可以,一定要保证是基准元素最终位置的左边),大于枢轴元素值的放到枢轴右边;用腾空一个位置的办法也好,用swap的想法也好,都是如此。

 

  扩展思考:

一、考虑使用双向链表,设置头指针和尾指针,与基准元素值进行比较后,(拔下结点)在头结点之前插入小的,在尾结点之后插入大的。

二、排序的改进算法:(序列长度在5~25时)采用直接插入排序对划分后的子序列排序进行排序。

 

posted @ 2014-09-10 21:44  jht_newbie  阅读(269)  评论(0编辑  收藏  举报