题目
问题分析现有红、白、蓝三个不同颜色的小球,乱序排列在一起,请重新排列这些小球,使得红白蓝三色的同颜色的球在一起。
问题转换为:给定数组A[0...N-1],元素只能取0、1、2三个值,设计算法,使得数组排列成“00...0011...1122...22”的形式
借鉴快速排序中partition的过程,定义三个指针begin=0,current=0,end=N-1
A[cur]==2,则A[cur]与A[end]交换,end--,cur不变
A[cur]==1,则cur++,begin不变,end不变
A[cur]==0,则:
若begin==cur,则begin++,cur++
若begin!=cur,则A[cur]与A[begin]交换,begin++,cur不变
代码如下
void Holland1(int* a, int length)
{
int begin = 0;
int current = 0;
int end = length - 1;
while (current <= end)
{
if (a[current] == 2)
{
swap(a[end], a[current]);
end--;
}
else if (a[current] == 1)
{
current++;
}
else
{
if (begin == current)
{
begin++;
current++;
}
else
{
swap(a[current], a[begin]);
begin++;
}
}
}
}
第二个版本:
cur扫过的位置,即:[begin,cur)区间内,一定没有2
因此:A[begin]要么是0,要么是1,不可能是2在前面的A[cur]==2中,已经被替换到数组后面了
考察begin指向的元素的值:
归纳法:若begin!=cur,则必有A[begin]=1
因此,当A[cur]==0时,
若begin==cur,则begin++,cur++;
若begin!=cur,因为A[begin]==1,则交换后,A[cur]==1,此时,可以cur++;
void Holland2(int* a, int length)
{
int begin = 0;
int current = 0;
int end = length - 1;
while (current <= end)
{
if (a[current] == 2)
{
swap(a[end], a[current]);
end--;
}
else if (a[current] == 1)
{
current++;
}
else
{
if (begin == current)
{
begin++;
current++;
}
else
{
swap(a[current], a[begin]);
begin++;
current++;
}
}
}
}
终极版本
void Holland(int* a, int length)
{
int begin = 0;
int current = 0;
int end = length - 1;
while (current <= end)
{
if (a[current] == 2)
{
swap(a[end], a[current]);
end--;
}
else if (a[current] == 1)
{
current++;
}
else
{
if (current != begin)
{
swap(a[current], a[begin]);
}
begin++;
current++;
}
}
}
荷兰国旗问题扩展
- 将0/1/2分别计数,根据三个计数值c0/c1/c2:前c0个元素赋值为0,中间c1个元素赋值为1,最后c2个元素赋值为2;实际意义比较小,可能排序结构比较复杂的时候就用着不方便了
- 将(0,1)(2)根据快速排序的Partition,划分为两部分(如PivotKey1.5);将(0)(1)根据快速排序Partition,分成两部分(如PivotKey选择0.5);那么可以得到结论“两次Partition==一次荷兰国旗”,这样可以优化快速排序的Partition过程
优化快速排序根据PivotKey分成大于、小于等于两部分或者大于等于、小于两部分
根据PivotKey的大小,将Partition过程盖在成大于、等于、小于三部分
优点:对于快速排序的等于PivotKey的数值,可以在执行下一次Partition时直接跳过,利于数据规模的降低