快速排序的各种版

最近重新研究了快速排序,整理了一下快速排序的各种版本。

一、单向扫描

ok,先来看单向扫描。

算法导论上版本是这样的:

PARTITION(A, p, r)
1  x ← A[r]         //以最后一个元素,A[r]为主元
2  i ← p - 1
3  for j ← p to r - 1    //注,j从p指向的是r-1,不是r。
4       do if A[j] ≤ x
5             then i ← i + 1
6                  exchange A[i] <-> A[j]
7  exchange A[i + 1] <-> A[r]    //最后,交换主元
8  return i + 1

下面是c++代码:

void my_swap(int *a,int *b)
{
    if(a==b)
        return;
    *a=*a^*b;
    *b=*b^*a;
    *a=*a^*b;
}
int partition(int a[],int left,int right)//以最后一个元素为主元
{

    int key=a[right];
    int k=left-1;
    for(int i=left;i<right;i++)
    {
        if(a[i]<=key)  //可以不加=号
        {
            k++;
            if(k!=i)
                my_swap(&a[i],&a[k]);
        }
    }
    k++;
    my_swap(&a[k],&a[right]);
    return k;
}
void quickSort(int a[],int left,int right)
{
    if(left>=right)
        return;
    int k=partition(a,left,right);
    quickSort(a,left,k-1);
    quickSort(a,k+1,right);
}




算法导论上取最后一个元素为主元,当然可以选第一个为主元,请看下面的代码:

int partition2(int a[],int left,int right)//以第一个元素为主元
{
    int key=a[left];
    int k=left;
    for(int i=left+1;i<=right;i++)
    {
        if(a[i]<=key)  //可以不加=号
        {
            k++;
            if(k!=i)
                my_swap(&a[i],&a[k]);

        }
    }
	//注意此处没有k++
    my_swap(&a[left],&a[k]);
    return k;
}




这两种情况如果出现最坏情况,则算法效率就将为O(n*n),比如说取第一个为主元,刚好第一个是最小的数。

我们可以采用三数取中分割法避免最坏情况的发生。

ok,下面看一下什么是三数取中分割法,意思很简单,就是取第一个,中间一个,最后一个,取这三个数中的中间大的那个数作为主元。

好,意思明白,下面直接看代码:

int partition3(int a[],int left,int right)  //三数取中分割法
{
    int mid=(left+right)/2;
    if(a[left]<a[mid])
        my_swap(&a[left],&a[mid]);
    if(a[right]<a[mid])
        my_swap(&a[right],&a[mid]);  //经过以上两步后mid是最小的。
    if(a[left]>a[right])
        my_swap(&a[left],&a[right]);


//以上已经将中间的那个移动到第一个位置

    int key=a[left];
    int k=left;
    for(int i=left+1;i<=right;i++)
    {
        if(a[i]<=key)  //可以不加=号
        {
            k++;
            if(k!=i)
                my_swap(&a[i],&a[k]);

        }
    }
    my_swap(&a[left],&a[k]);
    return k;

}
代码测试:采用随机数极限测试,取0-100的随机数进行测试。

int main()
{
    srand(time(NULL));
    const int N = 100; //“极限”测试。为了保证程序的准确无误,你也可以让N=10000。
    int *data = new int[N];
    for(int i =0; i<N; i++)
        data[i] = rand()%100;   //同样,随机化的版本,采取随机输入。
    cout<<"初试值:"<<endl;
    for(int i=0; i<N; i++)
        cout<<data[i]<<" ";
    cout<<endl;
    quickSort(data,0,N-1);
    cout<<"after quick sort:"<<endl;
    for(int i=0; i<N; i++)
        cout<<data[i]<<" ";
    cout<<endl;
    delete []data;


    return 0;
}


二、双向扫描版

双向扫描主要是Hoare版本和其变形。

那么,什么是霍尔提出的快速排序版本列?如下,即是:

HOARE-PARTITION(A, p, r)
 1  x ← A[p]
 2  i ← p - 1
 3  j ← r + 1
 4  while TRUE
 5      do repeat j ← j - 1
 6           until A[j] ≤ x
 7         repeat i ← i + 1
 8           until A[i] ≥ x
 9         if i < j
10            then exchange A[i] <-> A[j]
11            else return j

c++代码:

int partition5(int a[],int left,int right)
{
    int key=a[left];
    int i=left-1;
    int j=right+1;
    while(true)
    {
        do
        {
            j--;
        }
        while(a[j]>key);
        do
        {
            i++;
        }
        while(a[i]<key);
        if(i<j)
        {
            my_swap(&a[i],&a[j]);
        }
        else
        {
            return j;  //此处返回的不是主元,只是满足a[left...j] <=a[j+1...right]
        }
    }


}

使用上述分割法,需要如下的代码:

void quickSort1(int a[],int left,int right)
{
    if(left>=right)
        return ;
    int k=partition5(a,left,right);
    quickSort1(a,left,k); //因为使用的是partition5的分割方法,所以此处是k而不是k-1
    quickSort1(a,k+1,right);
}

考虑这样一种情况:

int partition5(int a[],int left,int right)
{
	int key=a[left];
	int i=left;
	int j=right;
	while(true)
	{
		while(a[j]>key)
		{
			j--;
		}
		while(a[i]<key)
		{
			i++;
		}
		if(i<j)
		{
			my_swap(&a[i],&a[j]);
		}
		else
		{
			return j;
		}
	}
	
}



这种情况会出现死循环,考虑如下数组:2,3,4,5,6,2  数组中a[left]=a[right]=key 交换后还是2,3,4,5,6,2 进入死循环,有人说while循环加个等号,

这种情况的话如果数组全部相等,2,2,2,,,,,2,则会出现数组越界的错误。


一种变种算法:

int partition4(int a[],int left,int right)  //双向遍历分割
{
    int key=a[left];
    int i=left;
    int j=right;
    while(i<j)  //最终一定是i=j结束
    {
        while(a[j]>=key&&i<j)
            j--;
        my_swap(&a[j],&a[i]);
        while(a[i]<=key&&i<j)
            i++;
        my_swap(&a[j],&a[i]);

    }
    a[i]=key;  //可以不用这句,因为a[i]肯定是key
    return i;
}

看下面事例:

   a:3   8   7   1   2   5   6   4   //以第一个元素为主元
        2   8   7   1       5   6   4
   b:2       7   1   8   5   6   4
   c:2   1   7       8   5   6   4
   d:2   1       7   8   5   6   4
   e:2   1   3   7   8   5   6   4   //最后补上,关键字3



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值