快速排序
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语句中的递归就会漏掉一个元素。