快速排序的核心思想可以参照分治三步法:
1.划分问题 把数组元素重排后分成左右两块,使得左边的元素都小于右边的元素
2.递归求解 再把左右两边分别排序
3.合并问题 不需要合并,因为数组已经有序
1 #include <iostream> 2 #include <cstdio> 3 #include <fstream> 4 using namespace std; 5 6 void quicksort(int a[],int left, int right) 7 { 8 if (left>=right) return; //递归结束 9 int mark = a[left]; //取第一个数为标志 10 int st = left; 11 int ed = right; 12 while(st<ed) 13 { 14 while(st<ed && a[ed] >= mark) ed--; //从末尾开始找比mark小的数 15 if (st<ed) a[st++] = a[ed]; //先移到前端,然后st再加一(挖洞) 16 while(st<ed && a[st] < mark) st++; //从开头找比mark大的数 17 if (st<ed) a[ed--] = a[st]; //先移到后端,然后ed再减一(填坑) 18 } 19 a[st] = mark; //将mark移到中间 20 quicksort(a,left,st-1); 21 quicksort(a,st+1,right); 22 } 23 24 int main() 25 { 26 ifstream fin("data.in"); 27 int num ; 28 int a[100]; 29 fin >> num ; 30 for (int i=0;i<num;i++) 31 fin >> a[i]; 32 quicksort(a,0,num-1); 33 for (int i=0;i<num;i++) cout<<a[i]<<' '; 34 return 0; 35 }
快速排序的重点和难点应该在于如何划分数组,这里给出最常用的方法:以数组的第一位为标志位。先从末尾找到比标志位小的数A,表明该数应该被放置在标志位之前。然后把标志位当做一个需要数字来填的坑,将A填入坑中,此时A原本的位置变成了一个新坑。然后我们再从开头寻找比标志位大的数B,表明该数应该被放置在标志位之后。将B填入A原本的坑中,此时B原本的位置成为一个新坑等待下一个A来填。
例如我们用快速排序的思想对下面的数组进行排序:
初始状态:
5 3 9 6 1 4 2 7 8 标志位为5,所以我们的目的是把5放在数组中间使得在5之前的数字比5小,在5之后的数字比5大。
执行 while(st<ed && a[ed] >= mark) ed--; 得:
5 3 9 6 1 4 2 7 8 7,8都大于5, 2是第一个小于5的数字
执行 if (st<ed) a[st++] = a[ed]; 得:
2 3 9 6 1 4 2 7 8 我们会在最后将5放到合适的位置,所以不必担心5被覆盖,而2虽然还在原来的位置,但是我们下一次将会用其他数字覆盖它
执行 while(st<ed && a[st] < mark) st++;得:
2 3 9 6 1 4 2 7 8 3小于5,9是第一个大于5的数字
执行 if (st<ed) a[ed--] = a[st]; 得:
2 3 9 6 1 4 9 7 8 ,同理9原来的位置会在下一次被其他数字覆盖(不难看出来是4)
如此循环下去直到两个标志指针遇见,表示数组已经遍历完成。最后,应该剩下一个B的坑位还没有填,最终我们将标志位填到里面,此时划分数组完成。
由于我们第一个挖的坑是处于数组首的标志位,所以我们需要先从末尾找起,如果先从开头找比标志位大的数B,那么这个数B将无地放置。
(如果我们以最后一个数为标志位,那么我们将先从开头找比标志位大的数B填入最后一位,然后再找A填入B原来的位置)
划分数组的本质是数字相对位置的交换,使得最终的数组成为前端任意数比后端任意数小的两个分支。对于快速排序的优化可以将标志位设定为中间的某个数字而不是第一个,然后将两个数的位置交换。这样可以使得划分的数组前后端长度相近,使算法效率提高。