3.3.1 计数排序
【问题】 假设待排序记录均为整数且取自区间[0,k],计数排序(count sort)的基本思想是对每一个记录x,确定小于x的记录个数,然后直接将x放在应该的位置。例如,小于x的记录个数是10,则x就位于第11个位置。
【想法】 对于待排序序列 A[n]=(2,1,5, 2,4, 3, 0,5, 3, 2)k=5, 首先统计值为i(0<=i<= k)的记录个数存储在 num[i]中,则 num[k]=(1,1,3,2,1,2), 再统计小于等于i(1<=i<=k)的记录个数存储在 num[i]中,则 num[k]=(1, 2,5, 7, 8, 10),最后反向读取数组 A[n]填到数组 B中,例如读取 A[9]的值是2,则将 num[2]减1,然后将2填到 B[4]中,如图3-3所示。注意,统计小于i(1<=i<=k)的记录个数不能就地进行(利用数组 num),需要再设一个数组存放小于i的记录个数,就可以正向读取数组 A[n]。
【算法】 设函数 CountSort 实现计数排序,数组 num[k+1]存储每个记录出现的次数以及小于等于值为i的记录个数,算法如下。
算法:计数排序 CountSort
输入:待排序记录序列 A[n],记录的取值区间k
输出:排序数组 B[n]
1.统计值为i的记录个数存入 num[i];
2.统计小于等于i的记录个数存人 num[i];
3.反向填充目标数组,将 A[i]放在 B[--num[A[i]]]中;
4.输出数组B[n];
【算法分析】 计数排序是一种以空间换时间的排序算法,并且只适用于对一定范围内的整数进行排序,时间复杂度为O(n+k),其中k为序列中整数的范围。
【算法实现】 计数排序的关键在于确定待排序记录 A[i]在目标数组B[n]中的位置,由于数组元素 num[i]存储的是A[n]中小于等于i的记录个数,所以填充数组 B[n]时要反向读取 A[n]。程序如下:
#include <iostream>
using namespace std;
void CountSort(int A[ ], int n, int k, int B[ ])
{
int i, num[k+1] = {0};
for(i = 0; i < n; i++)
num[A[i]]++;
for (i = 1; i <= k; i++)
num[i] = num[i] + num[i-1];
for (i = n-1; i >= 0; i--)
B[--num[A[i]]] = A[i];
}
int main( )
{
int n = 10, k = 5, i;
int A[n] = {2, 1, 5, 2, 4, 3, 0, 5, 3, 2}; // 修改数组大小声明为 n
int B[n]; // 修改数组大小声明为 n
for (i = 0; i < n; i++) {
cout << A[i] << " "; // 输出 A 数组的元素
}
cout << endl;
CountSort(A, n, k, B); // 调用 CountSort 函数进行排序
for (i = 0; i < n; i++) {
cout << B[i] << " "; // 输出排序后的 B 数组的元素
}
}
3.3.2 颜色排序
【问题】现有 Red、Green 和 Blue 三种不同颜色(色彩中不能再分解的三种颜色, 称为三原色)的小球,乱序排列在一起,请按照 Red、Green 和 Blue 顺序重新排列这些小球,使得相同颜色的小球排在一起。
【想法】 设数组a[n]存储 Red、Green 和 Blue 三种元素,设置三个参数i、j、k,其中i之前的元素(不包括a[i])全部为 Red;k 之后的元素(不包括 a[k])全部为 Blue;i和j之间的元素(不包括 a[j])全部为 Green;j指向当前正在处理的元素。首先将i初始化为0, k初始化为n—1,j初始化为0。然后j从前向后扫描,在扫描过程中根据a[j]的颜色,将其交换到序列的前面或后面,当j等于k时,算法结束。颜色排序的求解思想如图3-4所示。
注意,当j扫描到 Red时,将a[i]和a[j]交换,只有当前面全部是Red时,交换到位置j的元素是 Red,否则交换到位置j的元素一定是Green,因此交换后j应该加1;当j扫描到 Blue时,将a[k]和a[j]交换, Red、Green 和Blue均有可能交换到位置j,则a[j]需要再次判断,因此交换后不能改变j的值。
【算法】 设数组 a[n]有 Red,Green和 Blue 三种元素,函数 ColorSort 实现颜色重排问题,算法如下。
算法:颜色排序 ColorSort
输入:待排序记录序列a[n]
输出:排好序的数组 a[n]
1.初始化i=0;k=n-1;j=0;
2.当j<=k时,依次考查元素a[j],有以下三种情况:
(1)如果a[j]是Red,则交换 a[i]和a[j];i++;j++;
(2)如果 a[j]是Green,则j++;
(3)如果 a[j]是 Blue, 则交换 a[k]和 a[j];--;
【算法分析】 由于下标j和k整体将数组扫描一遍,因此时间复杂度为O(n)。
【算法实现】 由于数组 a[n]只有三种元素,假设 Red、Green 和 Blue 三种颜色分别用1、2、3来代替,程序如下。
#include <iostream>
using namespace std;
void ColorSort(int a[ ], int n)
{
int i = 0, k = n - 1, j = 0, temp;
while (j <= k)
switch (a[j]) //考查当前元素
{
case 1: temp = a[i]; a[i] = a[j]; a[j] = temp; i++; j++; break;
case 2: j++; break;
case 3: temp = a[j]; a[j] = a[k]; a[k] = temp; k--; break;
}
}
int main( )
{
int n = 10, i, a[n] = {2, 1, 3, 2, 3, 3, 1, 2, 3, 2};
for (i = 0; i < n; i++)
cout<<a[i]<<" ";
cout<<endl;
ColorSort(a,n);
for (i = 0; i < n; i++)
cout<<a[i]<<" ";
return 0;
}