LeetCode(75)Sort Colors (荷兰三色旗问题 Dutch National Flag)

题目如下:

The flag of the Netherlands consists of three colours: red, white and blue. Given balls of these three colours arranged randomly in a line (the actual number of balls does not matter), the task is to arrange them such that all balls of the same colour are together and their collective colour groups are in the correct order.

Given an array A[] consisting 0s, 1s and 2s, write a function that sorts A[]. The functions should put all 0s first, then all 1s and all 2s in last.
Example
Input = {0, 1, 1, 0, 1, 2, 1, 2, 0, 0, 0, 1};
Output = {0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2};


分析如下:

非常有意思的一道题目。由 Dijkstra 提出。题目的本质是让你给一个数组按照元素的值分区,尽可能地块。比如上例中的Input = {0, 1, 1, 0, 1, 2, 1, 2, 0, 0, 0, 1}分区后变成了Output = {0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2}

联想类似的例子,是快速排序。快速排序其实就是不断地调用partition分区函数。快速排序只需要分2个区。

这里需要分3个区。比较自然的想法是,可以先分出第1个区,再分第2个区,最后剩下的就是第3个区。这样的做法不够优雅。

优雅的做法在这里,文章说得很清楚,不搬运了。需要注意的是low ,mid, high和二分搜索中的low, mid, high是不同的含义。这里的low, mid, high只是起到一个标记作用,标记四个不同的区间。所以,这里的mid并不需要mid=(low+high)/2

LeetCode(75)Sort Colors (荷兰三色旗问题 Dutch National Flag)_第1张图片


代码如下:

//[0-----low-------mid--------high------end]
class Solution{
public:
    void swap(vector<int> &input, int a, int b){
        int tmp=input[a];
        input[a]=input[b];
        input[b]=tmp;
    }
    
    void dutch_national_flag(vector<int> & input){
        vector<int> res;
        if(input.size()==0)
            return ;
        int low=0, mid=0, high=(int)input.size()-1;
        while(mid<=high){
            switch (input[mid]) {
                case 0:
                    swap(input,low,mid);
                    low++;
                    mid++;
                    break;
                case 1:
                    mid++;
                    break;
                case 2:
                    swap(input,mid,high);
                    high--;
                    break;
            }
        }
    }
};

后来才发现LeetCode上也有这道题目。于是类似的,提交通过。

// 和上面一样 8ms过大集合。
class Solution {
public:
    void swap(int* arr, int a, int b){
        int tmp=arr[a];
        arr[a]=arr[b];
        arr[b]=tmp;
    }
    void sortColors(int A[], int n) {
        //0-----low------mid-------high-----end;
        int low=0;
        int mid=0;
        int high=n-1;
        while(mid<=high){
            switch(A[mid]){
            case 0:
                swap(A,low, mid);
                low++;
                mid++;
                break;
            case 1:
                mid++;
                break;
            case 2:
                swap(A,mid, high);
                high--;
                break;
            }
        }
    }
};


扩展分析:

首先,这道题最天然的思路是counting sort。需要两次遍历数组。第一次使用O(N)的复杂度,输出来有几个0,有几个1,有几个2。第二次,用这个计数去改写原数组,复杂度为2*O(N),也就是O(N)。上面解法的思路是1*O(N),所以二者虽然时间复杂度的量级相同,但是乘数不同。第一种只遍历一次就得出结果,确实更好。也可以看做counting sort在不同元素比较少的时候的特殊优化。

其次,本题目可以看做是快速排序的横向扩展。快去排序使用1个pivotal把一个数组分成两个区间,大于pivotal的和小于等于pivotal的区间。而本题是使用2个pivotal把数组分成三个区间。代码实现部分的trick是,本来实际需要的是两个pivotal,但是这样不方便代码实现,于是增加了第3个pitvotal(代码中的mid),从而增加了第4个区间来代表unsorted elements.不断进行下去直到unsorted elements被消耗完,此时也就完成了三个区间的排序。
之前按照CLRS算法导论写过quick sort的代码。现在借助和蓝三色旗类似的思路,扩展一下快速排序的另外一种做法,看上去比之前的那个好理解一下。如下:

class Solution{
public:
    void quick_sort2(int* arr, int start, int end)
    {
        if(start>=end)
            return;
        int low=start;
        int high=end;
        int pivotal=arr[low];
        
        while(low<=high){
            if(arr[low]<=pivotal){
                low++;
            }else{
                swap(arr,low,high);
                high--;
            }
        }
        low--;
        swap(arr,start,low);
        
        quick_sort2(arr,start,low-1);
        quick_sort2(arr,low+1,end);
        return;
    }

    void quick_sort(int* arr, int length){
        if(length<=1)
            return;
        int start=0;
        int end=length-1;
        quick_sort2(arr,start, end);
    }
};

参考资料:

(1) http://www.csse.monash.edu.au/~lloyd/tildeAlgDS/Sort/Flag/

(2) http://www.geeksforgeeks.org/sort-an-array-of-0s-1s-and-2s/

你可能感兴趣的:(排序算法)