1、算法思想
快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。
(1) 分治法的基本思想
分治法的基本思想是:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。
(2)快速排序的基本思想
设当前待排序的无序区为R[low..high],利用分治法可将快速排序的基本思想描述为:
①分解:
在R[low..high]中任选一个记录作为基准(Pivot),以此基准将当前无序区划分为左、右两个较小的子区间R[low..pivotpos-1)和R[pivotpos+1..high],并使左边子区间中所有记录的关键字均小于等于基准记录(不妨记为pivot)的关键字pivot.key,右边的子区间中所有记录的关键字均大于等于pivot.key,而基准记录pivot则位于正确的位置(pivotpos)上,它无须参加后续的排序。
注意:
划分的关键是要求出基准记录所在的位置pivotpos。划分的结果可以简单地表示为(注意pivot=R[pivotpos]):
R[low..pivotpos-1].keys≤R[pivotpos].key≤R[pivotpos+1..high].keys
其中low≤pivotpos≤high。
②求解:
通过递归调用快速排序对左、右子区间R[low..pivotpos-1]和R[pivotpos+1..high]快速排序。
③组合:
可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。
上面函数是快速排序的一次实现,返回需要划分的位置。
完整的代码如下,经过测试没有发现问题,测试是用rand()随机产生的100000数据排序,并计算递归和非递归算法花费时间:
运行结果:
可以看到,递归的速度要比非递归快!!
快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。
(1) 分治法的基本思想
分治法的基本思想是:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。
(2)快速排序的基本思想
设当前待排序的无序区为R[low..high],利用分治法可将快速排序的基本思想描述为:
①分解:
在R[low..high]中任选一个记录作为基准(Pivot),以此基准将当前无序区划分为左、右两个较小的子区间R[low..pivotpos-1)和R[pivotpos+1..high],并使左边子区间中所有记录的关键字均小于等于基准记录(不妨记为pivot)的关键字pivot.key,右边的子区间中所有记录的关键字均大于等于pivot.key,而基准记录pivot则位于正确的位置(pivotpos)上,它无须参加后续的排序。
注意:
划分的关键是要求出基准记录所在的位置pivotpos。划分的结果可以简单地表示为(注意pivot=R[pivotpos]):
R[low..pivotpos-1].keys≤R[pivotpos].key≤R[pivotpos+1..high].keys
其中low≤pivotpos≤high。
②求解:
通过递归调用快速排序对左、右子区间R[low..pivotpos-1]和R[pivotpos+1..high]快速排序。
③组合:
因为当"求解"步骤中的两个递归调用结束时,其左、右两个子区间已有序。对快速排序而言,"组合"步骤无须做什么,可看作是空操作。
以图为例子,更好理解:
以一个数组作为示例,取区间第一个数为基准数。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
72 | 6 | 57 | 88 | 60 | 42 | 83 | 73 | 48 | 85 |
初始时,i = 0; j = 9; X = a[i] = 72
由于已经将a[0]中的数保存到X中,可以理解成在数组a[0]上挖了个坑,可以将其它数据填充到这来。
从j开始向前找一个比X小或等于X的数。当j=8,符合条件,将a[8]挖出再填到上一个坑a[0]中。a[0]=a[8]; i++; 这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。这次从i开始向后找一个大于X的数,当i=3,符合条件,将a[3]挖出再填到上一个坑中a[8]=a[3]; j--;
数组变为:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
48 | 6 | 57 | 88 | 60 | 42 | 83 | 73 | 88 | 85 |
i = 3; j = 7; X=72
再重复上面的步骤,先从后向前找,再从前向后找。
从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;
从i开始向后找,当i=5时,由于i==j退出。
此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将X填入a[5]。
数组变为:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
48 | 6 | 57 | 42 | 60 | 72 | 83 | 73 | 88 | 85 |
代码实现:
/*the quickSort,partion method first */
template<class DataType>
int Partition(vector<DataType> &vect, int low, int high)
{
DataType base = vect[low];//the first element as compare-base
while( low < high)
{
while( low < high && vect[high] >= base )
high--;
if( low < high )
vect[low] = vect[high];//move the value of high to the low
while( low < high && vect[low] < base)
low++;
if( low < high )
vect[high] = vect[low];
}
vect[low] = base;//at this time,low==high
return low;
}
上面函数是快速排序的一次实现,返回需要划分的位置。
递归的快速排序:
/*recursive*/
template<class DataType>
void QuickSort_Recursive(vector<DataType> &vect, int low, int high )
{
if( low < high)
{
int mid = Partition(vect, low, high);
QuickSort_Recursive(vect, low, mid-1);
QuickSort_Recursive(vect, mid+1, high);
}
}
非递归的快速排序代码,用栈来实现。
/*no recursive,using stack*/
template<class DataType>
void QuickSort_NoRecursive(vector<DataType> &vect, int low, int high)
{
stack<int> stack;
if( low < high )
{
int mid = Partition(vect, low, high);
/*using devide-and-conquer,stack only save the first and the last element in the child*/
if( low < mid-1 )
{
stack.push(low);
stack.push(mid-1);
}
if( high > mid+1 )
{
stack.push(mid+1);
stack.push(high);
}
while(!stack.empty())
{
int q = stack.top();
stack.pop();
int p = stack.top();
stack.pop();
mid = Partition(vect, p, q);
if( p < mid-1 )
{
stack.push(p);
stack.push(mid-1);
}
if( q > mid+1 )
{
stack.push(mid+1);
stack.push(q);
}
}
}
}
完整的代码如下,经过测试没有发现问题,测试是用rand()随机产生的100000数据排序,并计算递归和非递归算法花费时间:
#include <ctime>//clock()
#include <cstdlib>
#include <algorithm>//random_shuffle
#include <stack>
#include <vector>
#include <iostream>
using namespace std;
/*the quickSort,partion method first */
template<class DataType>
int Partition(vector<DataType> &vect, int low, int high)
{
DataType base = vect[low];//the first element as compare-base
while( low < high)
{
while( low < high && vect[high] >= base )
high--;
if( low < high )
vect[low] = vect[high];//move the value of high to the low
while( low < high && vect[low] < base)
low++;
if( low < high )
vect[high] = vect[low];
}
vect[low] = base;//at this time,low==high
return low;
}
/*recursive*/
template<class DataType>
void QuickSort_Recursive(vector<DataType> &vect, int low, int high )
{
if( low < high)
{
int mid = Partition(vect, low, high);
QuickSort_Recursive(vect, low, mid-1);
QuickSort_Recursive(vect, mid+1, high);
}
}
/*no recursive,using stack*/
template<class DataType>
void QuickSort_NoRecursive(vector<DataType> &vect, int low, int high)
{
stack<int> stack;
if( low < high )
{
int mid = Partition(vect, low, high);
/*using devide-and-conquer,stack only save the first and the last element in the child*/
if( low < mid-1 )
{
stack.push(low);
stack.push(mid-1);
}
if( high > mid+1 )
{
stack.push(mid+1);
stack.push(high);
}
while(!stack.empty())
{
int q = stack.top();
stack.pop();
int p = stack.top();
stack.pop();
mid = Partition(vect, p, q);
if( p < mid-1 )
{
stack.push(p);
stack.push(mid-1);
}
if( q > mid+1 )
{
stack.push(mid+1);
stack.push(q);
}
}
}
}
int main(int argc, char** argv)
{
int len = 1000000;
vector<int> vect;
for(int i=0; i<len; i++)
vect.push_back(rand());
clock_t t1 = clock();
QuickSort_Recursive(vect, 0, len-1);
clock_t t2 = clock();
cout<<"recursive using time:"<<1.0*(t2-t1)/CLOCKS_PER_SEC<<endl;
random_shuffle(vect.begin(), vect.end());//Rearranges the elements in the range [first,last) randomly
t1 = clock();
QuickSort_NoRecursive(vect, 0, len-1);
t2 = clock();
cout<<"No recursive using time:"<<1.0*(t2-t1)/CLOCKS_PER_SEC<<endl;
return 0;
}
运行结果:
[liujl@localhost mycpp]$ g++ -g quickSort.cxx -o quickSort
[liujl@localhost mycpp]$ ./quickSort
recursive using time:0.34
No recursive using time:0.43
可以看到,递归的速度要比非递归快!!