快速排序算法分析

 1 void QuickSort(int data[], int left, int right)
2 {
3 int temp = data[left];
4 int p = left;
5 int i = left, j = right;
6
7 while (i <= j)
8 {
9 while (data[j] >= temp && j >= p )
10 j--;
11 if(j >= p)
12 {
13 data[p] = data[j];
14 p = j;
15 }
16
17 while (data[i] <= temp && i <= p)
18 i++;
19 if (i <= p)
20 {
21 data[p] = data[i];
22 p = i;
23 }
24 }
25 data[p] = temp;
26 if(p-left > 1)
27 QuickSort(data, left, p-1);
28 if(right - p > 1)
29 QuickSort(data, p+1, right);
30 }

这个是从维基百科上粘贴下来的一个例程,用递归实现。

所有递归程序都可以分为两部分:程序主体(3~25)和调用部分(26~29)。

举一个例子:八个数如下

                                    5 7 2 8 6 4 3 1..............i=0,j=7,p=0,temp=5

开始执行代码:

执行9~15行代码后:         1 7 2 8 6 4 3 1..............i=0,j=7,p=7,temp=5

执行17~23行代码后:       1 7 2 8 6 4 3 7..............i=1,j=7,p=1,temp=5

此时仍满足(i=1)<(j=7),进行第二轮:

执行9~15行代码后:        1 3 2 8 6 4 3 7..............i=1,j=6,p=6,temp=5

执行17~23行代码后:      1 3 2 8 6 4 8 7..............i=3,j=6,p=3,temp=5

此时仍满足(i=3)<(j=6),进行第三轮:

执行9~15行代码后:        1 3 2 4 6 4 8 7..............i=3,j=5,p=5,temp=5

执行17~23行代码后,一直到i=p=5,所以执行第25行。

执行第25行代码后:        1 3 2 4 6 5 8 7...............i=5,j=5,p=5,temp=5

可见此时是按照5为中心,将数组分成>5和<5两类。left=0,right=7,p=5,按这个值进行递归调用。另外观察上述各行数组中红色值的位置可以发现,这个过程和循环移位有些像,通过增加temp完成数据的交换。似乎下面的程序更好理解一些。另外,通过上面的分析可以看出int temp = data[left];这句使得每次分块后的第一个数都作为分界,然后将值排在两边。

这就是数学归纳法的优点,它帮助我们更快的理解程序的执行过程,但却无法让我们窥得程序设计的思路。

如下这个程序的思想相似,但似乎更简洁一些:

 1 void quicksort(int a[], int low, int high)
2 {
3 int pivotpos;
4 if (low<high)
5 {
6 int pivotkey=a[low];
7 while (low<high)
8 {
9 while (low<high && a[high]>=pivotkey)
10 --high;
11 a[low]=a[high];
12 while (low<high && a[low]<=pivotkey)
13 ++low;
14 a[high]=a[low];
15 }
16 a[low]=pivotkey;
17 pivotpos=low;
18 quicksort(a,low,pivotpos-1);
19 quicksort(a,pivotpos+1,high);
20 }
21 }

这个程序是后看到的,但是感觉比前一个好理解多了。去掉了一个p和两个比较部分。这两部分合到一起的目的是防止在循环途中i>j。

之后运行了一下,才发现上面的程序有错误。传入函数中的边界值不应在函数中被修改,而上面函数将每次传入的边界值都进行了修改并传递给下次调用。修改后的函数为如下代码:

void quickSort(int a[], int low, int high)
{
int low2=low;
int high2=high;
int pivotpos;
if (low2<high2)
{
int pivotkey=a[low2];
while (low2<high2)
{
while (low2<high2 && a[high2]>=pivotkey)
--high2;
a[low2]=a[high2];
while (low2<high2 && a[low2]<=pivotkey)
++low2;
a[high2]=a[low2];
}
a[low2]=pivotkey;
pivotpos=low2;
quickSort(a,low,pivotpos-1);
quickSort(a,pivotpos+1,high);
}
}

非递归方法

上面两个算法已经清晰地说明了快速排序的思路,但是由于采用递归算法,并不适合数据量很大的情况。下面的代码是采用非递归快速排序算法的代码:

下面的博文部分转自http://hex.iteye.com/blog/777858

自己用栈来模拟递归的过程。每次从栈顶取出一部分做划分后,都把新的两部分的起始位置分别入栈。

 1 #ifndef MAX_STACK_DEPTH  
2 #define MAX_STACK_DEPTH 1000
3 #endif
4 template <typename T>
5 void NonrecursiveQuickSort(T arr[], size_t size)
6 {
7 // typedef vector<int> Stack_t;
8 int stack[MAX_STACK_DEPTH];
9 int top = 0;
10 int low,high,i,j,pivot;
11 T temp;
12 //首先把整个数组入栈
13 stack[top++] = size-1;
14 stack[top++] = 0;
15 while(top != 0)
16 {
17 //取出栈顶元素,进行划分
18 low = stack[--top];
19 high = stack[--top];
20 pivot = high; //将最后一个元素作为轴
21 i = low; //保证i之前的元素的不大于轴
22 //j从前往后滑动
23 for(j=low; j < high; j++)
24 {
25 //如果碰到某个元素不大于轴,则与arr[i]交换
26 if(arr[j]<=arr[pivot])
27 {
28 temp = arr[j];
29 arr[j] = arr[i];
30 arr[i] = temp;
31 i++;
32 }
33 }
34 //i为分界点,交换arr[i]和轴
35 if(i != pivot)
36 {
37 /*swap arr[i] and arr[pivot]*/
38 temp = arr[i];
39 arr[i] = arr[pivot];
40 arr[pivot] = temp;
41 }
42 //判断小于轴的部分元素如果多于一个的话, 则入栈
43 if(i-low > 1)
44 {
45 stack[top++] = i-1;
46 stack[top++] = low;
47 }
48 //判断大于轴的部分元素如果多于一个的话, 则入栈
49 if(high-i > 1)
50 {
51 stack[top++] = high;
52 stack[top++] = i+1;
53 }
54 }
55 }

这里面模拟了栈结构用来存储位置。另外还有一项不同,对两部分的划分并没有使用最开始提到的划分方法,而是采用了另一种(参考文章中说这种文章出自算法导论,而之前的一种出自严蔚敏的数据结构)。

思路如下:也是给定两个数组下标i和j,i和j都从前往后移动; 保证i之前的部分都不大于轴,j之后的部分(包含j)都未知; j向后滑动中如果某个元素不大于轴,这把这个元素的位置和i交换,并且i向前移动一个位置. 当j到达末尾后,把轴的位置与i位置上的元素交换,i即为分界点。也就是这段代码的26~41部分,虽然远离我已经懂了但是这段代码实在是没看懂,要命啊。

下面这段代码则非常合我的品位(摘自:http://www.cnblogs.com/zhangchaoyang/articles/2234815.html)

 1 #include<iostream>
2 #include<vector>
3 #include<stack>
4 #include<cstdlib>
5 #include<algorithm>
6 using namespace std;
7
8 /**把数组分为两部分,轴pivot左边的部分都小于轴右边的部分**/
9 template <typename Comparable>
10 int partition(vector<Comparable> &vec,int low,int high){
11 Comparable pivot=vec[low]; //任选元素作为轴,这里选首元素
12 while(low<high){
13 while(low<high && vec[high]>=pivot)
14 high--;
15 vec[low]=vec[high];
16 while(low<high && vec[low]<=pivot)
17 low++;
18 vec[high]=vec[low];
19 }
20 //此时low==high
21 vec[low]=pivot;
22 return low;
23 }
24 /**使用栈的非递归快速排序**/
25 template<typename Comparable>
26 void quicksort2(vector<Comparable> &vec,int low,int high){
27 stack<int> st;
28 if(low<high){
29 int mid=partition(vec,low,high);
30 if(low<mid-1){
31 st.push(low);
32 st.push(mid-1);
33 }
34 if(mid+1<high){
35 st.push(mid+1);
36 st.push(high);
37 }
38 //其实就是用栈保存每一个待排序子串的首尾元素下标,下一次while循环时取出这个范围,对这段子序列进行partition操作
39 while(!st.empty()){
40 int q=st.top();
41 st.pop();
42 int p=st.top();
43 st.pop();
44 mid=partition(vec,p,q);
45 if(p<mid-1){
46 st.push(p);
47 st.push(mid-1);
48 }
49 if(mid+1<q){
50 st.push(mid+1);
51 st.push(q);
52 }
53 }
54 }
55 }

注意这里并没有自己编写堆栈的实现代码,而是直接应用了模板库。



posted on 2012-02-28 20:28  专吃兔子的大黑猫  阅读(495)  评论(0编辑  收藏  举报