快速排序

1、思路不对,编出的代码肯定不对,因此编码之前,一定要现在草稿纸上,根据思路写出伪码。

2、快速排序的思路:选一个目标,把数组分成两块,左边一块都比目标小,右边一块都比目标大。对每一块,递归做同样的事情,递归的出口时,左起点大于等于右起点。

3、递归的形参表为:数组和排序的区间,排序区间也就是左起点和右起点,QuickSort(int a[], int lhs,int rhs),

递归出口:if(lhs>=rhs) return;

4、方法1:思路是对排序的区间做一个拷贝,取第一个为目标,从第二个开始,遍历排序区间,比目标小,从前往后。以此放在原数组区间的左边,比目标大,从后向前,一次放在原数组区间的右边,最后中间有个空格,把目标放在空格里,并且很容易知道空格的位置,对空格的前后两块递归。

5、方法一很简单直观,但是要多分配一块内存,存储区间的内容。有没有更好的办法呢?

  方法2:思路是取第一个为目标,挖一个坑,从第二个开始遍历,比目标小,依次往前移,比目标大,应该放在后面,跟最后一个交换(下一次和倒数第二个交换,后面交换的位置前移一位),继续和目标比较,比目标小,往前移,比目标大,把倒数第二个换上来,以此类推。最后把目标填到空格里。

6、方法二有个问题,那就是从尾部换上来的元素,比目标大,还要把这个元素换回去,注意:这里换回去的位置,是尾部的前一个元素。有没有更好的办发呢?

  方法二在第一个位置挖了个坑,比目标小的往前移,比目标大的,和最后面的元素交换,如果比目标小了,前移,如果还是比目标大,和次后面的元素交换。这就导致了换上来,有可能还要换回去。更好的办法是:既然前面已经有一个坑了,我从后面找到小元素,放到前面的坑里,这样后面就有了一个坑,再从前面找到一个大元素,放到后面的坑里,这样前面就挖了一个坑,最后剩下的坑放目标。

  方法3:在前面挖个坑,从后面找到一个小元素,挖出来,放到前面的坑里,后面就有了一个坑,再从前面找一个大元素,挖出来,放到后面的坑了。循环继续的条件是:前后没有走到一起。注意,这个时候,不适合在使用for循环了,当然也可以使用for。使用while表达循序继续的条件:往后走的位置小于往前走的位置,在循环的内部,首先从后面找到小元素,挖坑,使用while找到小元素,添数挖坑,然后使用while从前面找到大元素,添数挖坑。也就是说,用到三个while,外部的while判断是否走到了一起。内部第一个while,从后面挖坑,内部第二个while从前面挖坑。

7、方法3代码如下: 

 1 template <typename T>
 2 void QuickSort(T* arr,int left,int right)
 3 {
 4  if(left < right)
 5  {
 6   int mid = Adjust(arr,left,right);
 7   QuickSort(arr,left,mid-1);
 8   QuickSort(arr,mid+1,right);
 9  }
10 }
11 
12 
13 template <typename T>
14 int Adjust(T* arr, int i,int j)
15 {
16  T target = arr[i];
17  while(i<j)
18  {
19   while(i<j && arr[j]>target)
20   {
21    --j;
22   }
23   if(i<j)
24   {
25    arr[i++] = arr[j];
26   }
27 
28   while(i<j && arr[i]<target)
29   {
30    ++i;
31   }
32   if(i<j)
33   {
34    arr[j--] = arr[i];
35   }
36  }
37 
38  arr[i] = target;
39  return i;
40 }

 8、特别注意:一般情况下,我们使用的范围是[left, rigth),也就是左闭合区间。但是这里的快速排序,使用的区间是[left, right],也就是左右都闭合区间。当然,也可以使用左闭合区间,只不过稍微复杂一点,如下: 

 1 template <typename T>
 2 void QuickSort(T* arr,int left,int right)
 3 {
 4  // 只有一个元素,不需要排序。因此对于左闭合区间,比较left+1 与 right。
 5  // 注意:千万不能写成:++left<right。
 6  if(left+1 < right) 
 7  {
 8   int mid = Adjust(arr,left,right);
 9   QuickSort(arr,left,mid); //递归调用左闭合区间
10   QuickSort(arr,mid+1,right);
11  }
12 }
13 
14 
15 template <typename T>
16 int Adjust(T* arr, int i,int j)
17 {
18  T target = arr[i];
19  --j; // 首先移到有效元素
20  while(i<j)
21  {
22   while(i<j && arr[j]>target)
23   {
24    --j;
25   }
26   if(i<j)
27   {
28    arr[i++] = arr[j];
29   }
30 
31   while(i<j && arr[i]<target)
32   {
33    ++i;
34   }
35   if(i<j)
36   {
37    arr[j--] = arr[i];
38   }
39  }
40 
41  arr[i] = target;
42  return i;
43 }

 注意:if( left+1 < right ) 与 if( ++left < right )之间的区别,如果left为5,对于前者,判断之后left还是5,;对于后者,判断之后left就是6了。在上面的快速排序中,对于左闭合区间,判断是否只是一个元素,使用left+1。如果使用++left,判断之后,left累加1,if语句中的递归就会漏掉一个元素。

posted on 2013-11-10 10:51  Andy Niu  阅读(398)  评论(0编辑  收藏  举报