荷兰国旗问题与快速排序

本文探讨了荷兰国旗问题的简化版与正式版,如何使用双指针和三指针策略对数组进行划分,并展示了快速排序的不同版本,从1.0到3.0,包括随机选择划分值的改进,以及另一种经典排序方法的实现。重点讲解了时间复杂度优化和空间复杂度分析。
摘要由CSDN通过智能技术生成

将一个数组按照某个划分值,划分为三部分,即小于该值的部分,等于部分和大于部分,这个问题称为荷兰国旗问题。
由于荷兰国旗由三种颜色组成,而该问题是将数组划分为三部分,两者具有相似性,所以该问题由此得名。

简化版荷兰国旗问题

先考虑简化版问题。将一个数组划分为两部分,如小于等于划分值的部分和大于部分。这里可以想到使用双指针。两个指针分别是小于等于区的右边界和遍历时下标,这样就把数组分为三部分,小于等于区、大于区、待遍历区。
在这里插入图片描述
这里划分值取为5。
这样,就相当于是小于等于区推着大于区往后遍历,当数组遍历完成后,数组划分也顺利完成。

int flag1(vector<int> &arr)
{
    int i=0,small=-1;//遍历数组的指针和小于等于区域的左边界
    int key=arr[0];//划分值这里取数组的第一个值,也可以取其他值
    while(i<arr.size())
    {
        if(arr[i]<=key)
        {
            swap(arr[small+1],arr[i]);//交换小于等于区的下个数和该遍历数
            ++small;
        }
        ++i;
    }
    return 0;
}

这里swap()的功能是将两个数进行交换,这里代码省略。

荷兰国旗问题

现在考虑将数组划分为三部分的问题。由上面简化版问题而来,这里再多设置一个指针,表示大于区右边界。这样,数组就分为了四部分,小于区、等于区、待遍历区、大于区。
在这里插入图片描述
这里划分值依然取为5。
这样,就相当于是小于区推着等于区往后遍历,而大于区逐渐向左扩。当数组遍历到大于区边界时,数组划分也顺利完成。

int flag2(vector<int> &arr)
{
    int i=0,small=-1;//遍历数组的指针和小于区域的右边界
    int big=arr.size();//大于区域的左边界
    int key=arr[0];//划分值这里取数组的第一个值,也可以取其他值
    while(i<big)
    {
        if(arr[i]<key)
        {
            swap(arr[small+1],arr[i]);//交换小于区的下个数和该遍历数
            ++small;
            ++i;
        }else if(arr[i]==key)
        {
            ++i;
        }else
        {
            swap(arr[big-1],arr[i]);
            --big;
        }
    }
    return 0;
}

快速排序

快速排序是应用较广的排序算法,其本质就是以数组中的某个值为划分值,将数组进行划分,从而确定这个值的位置,而后再进行递归或迭代。这里面就用到了荷兰国旗问题。

快速排序1.0

1.0版本的快速排序算法,就是以数组中的第一个值为划分值,将数组划分为小于等于区和大于区两部分,而后再将该值与小于等于区最后一个值交换,将该值放到最后的位置。而后继续递归或迭代。

int partition1(vector<int> &arr,int left,int right)
{
    if(left>=right)
    {
        return 0;
    }
    int i=left,small=left-1;//遍历数组的指针和小于等于区域的左边界
    int key=arr[left];//划分值这里依然取数组的第一个值
    while(i<=right)
    {
        if(arr[i]<=key)
        {
            swap(arr[small+1],arr[i]);
            ++small;
        }
        ++i;
    }
    swap(arr[left],arr[small]);//将划分值放到小于等于区的最后一个位置上
    partition1(arr,0,small-1);
    partition1(arr,small+1,right);

}

int qsort1(vector<int> &arr)
{
    partition1(arr,0,arr.size()-1);
    return 0;
}

快速排序2.0

2.0版本,与1.0版本的区别,在于多划分了一个等于区。1.0与2.0的区别就如同荷兰国旗问题的简化版与正式版。

int partition2(vector<int> &arr,int left,int right)
{
    if(left>=right)
    {
        return 0;
    }
    int i=left,small=left-1;//遍历数组的指针和小于区域的右边界
    int big=right+1;//大于区域的左边界
    int key=arr[left];
    while(i<big)
    {
        if(arr[i]<key)
        {
            swap(arr[small+1],arr[i]);//交换小于区的下个数和该遍历数
            ++small;
            ++i;
        }else if(arr[i]==key)
        {
            ++i;
        }else
        {
            swap(arr[big-1],arr[i]);
            --big;
        }
    }
    partition2(arr,0,small);
    partition2(arr,big,right);
    return 0;
}

int qsort2(vector<int> &arr)
{
    partition2(arr,0,arr.size()-1);
    return 0;
}

注意arr[i]比划分值大时,i值不变。
2.0版本比1.0版本稍快,因为2.0版本一次解决了一批数。

快速排序3.0

1.0和2.0最大的弊端,在于原数组在基本有序的情况下,划分值有可能选的很偏,使得时间复杂度逼近O(N2),为了改善这一点,我们可以随机选取划分值,而后将其与原数组第一个值交换,这样就与1.0,2.0版本一致了。

int partition3(vector<int> &arr,int left,int right)
{
    if(left>=right)
    {
        return 0;
    }
    int i=left,small=left-1;//遍历数组的指针和小于等于区域的左边界
    int index=left+rand()%(right-left+1);//划分值在排序范围内随机抽取一个数
    swap(arr[index],arr[left]);//将该抽取值与范围内第一个值交换,接下来就和1.0一样了
    int key=arr[left];
    while(i<=right)
    {
        if(arr[i]<=key)
        {
            swap(arr[small+1],arr[i]);
            ++small;
        }
        ++i;
    }
    swap(arr[left],arr[small]);//将划分值放到小于等于区的最后一个位置上
    partition3(arr,0,small-1);
    partition3(arr,small+1,right);

}

int qsort3(vector<int> &arr)
{
    partition3(arr,0,arr.size()-1);
    return 0;
}

注意,如果分为四部分,可以不用交换随机选取的值和数组第一个值。

int partition4(vector<int> &arr,int left,int right)
{
    if(left>=right)
    {
        return 0;
    }
    int i=left,small=left-1;//遍历数组的指针和小于区域的右边界
    int big=right+1;//大于区域的左边界
    int key=arr[left+rand()%(right-left+1)];
    while(i<big)
    {
        if(arr[i]<key)
        {
            swap(arr[small+1],arr[i]);//交换小于区的下个数和该遍历数
            ++small;
            ++i;
        }else if(arr[i]==key)
        {
            ++i;
        }else
        {
            swap(arr[big-1],arr[i]);
            --big;
        }
    }
    partition4(arr,0,small);
    partition4(arr,big,right);
    return 0;
}

int qsort4(vector<int> &arr)
{
    partition4(arr,0,arr.size()-1);
    return 0;
}

这样的话,由于都是随机选取划分值,时间复杂度为O(Nlog N)。
另外,快速排序的空间复杂度为O(log N)。在1.0与2.0版本中,最差情况会变为O(N)。

另一版快速排序

这里再介绍另一种快速排序的方法,这种方法在教科书上经常出现。这种方法也是利用两个指针,只不过是在开始时两个指针在数组两端,而后通过比对数组各元素与划分值大小来移动指针并交换对应数值,最后使得指针重合。完成一次比对之后,同样需要通过递归或迭代来完成整个排序过程。

int partition5(vector<int> &arr,int left,int right)
{
    if(left>=right)
    {
        return 0;
    }
    int i=left,j=right;
    int key=arr[left];
    while(i<j)
    {
        while(i<j&&arr[j]>key)
        {
            --j;//右指针向左移
        }
        arr[i]=arr[j];//把这个小于等于划分值的数移动到左侧小于等于区
        while(i<j&&arr[i]<=key)
        {
            ++i;//左指针向右移
        }
        arr[j]=arr[i];//把这个大于划分值的数移动到右侧大于区
    }
    //此时i=j,左右两指针重合,把划分值放到该位置
    arr[i]=key;
    partition5(arr,0,i-1);
    partition5(arr,j+1,right);
    return 0;
}

int qsort5(vector<int> &arr)
{
    partition5(arr,0,arr.size()-1);
    return 0;
}

对数器

最后,还是把对数器代码贴出来。

int compare()//对数器
{
    int times=1;
    vector<int> arr1;
    for(;times<=TIME_N;++times)
    {
        for(int i=0;i<LEN;++i)
        {
            arr1.push_back(rand()&1023);
        }
        vector<int> arr2(arr1);
        qsort3(arr1);
        qsort4(arr2);
        //比较两个排序后的数据
        if(arr1!=arr2)
        {
            return times;
        }
    }
    return 0;
}

这里可以用不同的排序方法对比,返回值为0则说明算法基本没有问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值