荷兰国旗问题与快速排序

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

简化版荷兰国旗问题

先考虑简化版问题。将一个数组划分为两部分,如小于等于划分值的部分和大于部分。这里可以想到使用双指针。两个指针分别是小于等于区的右边界和遍历时下标,这样就把数组分为三部分,小于等于区、大于区、待遍历区。
荷兰国旗问题与快速排序
这里划分值取为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 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则说明算法基本没有问题。

上一篇:Python4.4 三元操作符


下一篇:【java】Java 中的 Exchanger 线程同步使用方法