线性时间排序
涉及概念
比较排序:排序是通过比较元素的大小来进行的。如插入排序,堆排序,归并排序,快速排序。
稳定性的排序算法(Stable sort):对于相同的元素,排序算法并不改变它们的相对顺序。如插入排序,归并排序。快速排序与堆排序不是稳定性的排序算法。
Part 1
我们之前所学的算法都是比较排序,对于比较排序的运行时间提出一个定理:
任意一个比较排序算法,在最坏情况下,最需要做 Omega(nlgn) 次比较(即最坏情况下运行时间为 Omega(nlgn)).
利用决策树可以证明这个定理。
如何继续压缩排序算法的运行时间,这是我们需要思考的问题。对于这个问题,提出了线性时间排序问题。
Part 2 计数排序 (非比较排序,稳定性排序)
int* Countingsort(int* A, int n, int k)
{
int* C = new int[k];
int* B = new int[n];//记录排序后的数组,作为输出
for (int i = 0; i < k; i++)
C[i] = 0;
//C数组的第i个元素用于记录取值为i的元素个数
for (int i = 0; i < n; i++)
C[A[i]]++;
for (int i = 1; i < k; i++)
C[i] = C[i - 1] + C[i];
//C数组的第i个元素用于记录取值为i的元素在新数组中应该位于的位置->第几个元素
for (int i = n - 1; i >= 0; i--) //从数组的最后开始排序,为了保持相同元素的相对顺序——稳定性
B[--C[A[i]]] = A[i];
return B;
}
运行时间为 Theta(k+n),不仅取决于输入规模n,也取决于待排序数据范围k。当k比较小(为Omega(n))时,计数排序运行时间为 Theta(n),是比较优的算法,但当待排序数的范围很大时,算法将会变得很差。
(一开始调试这段代码时总是出错,原来我在new的时候把[]写成(),这个问题要注意!)
Part 3 基数排序 (非比较排序,稳定性排序)
对于待排序数范围很大时,使用这个非排序算法可以使运行时间低于Omega(nlgn)。它是利用排序的稳定性,从低位到高位对每一位数字进行排序。
假如待排序数占k个比特位,则每次对 (lgn) 个比特位进行稳定性排序的算法效率为最佳,为方便起见,在此处的算法实现每次对二进制数化为十进制数的每个数位进行依次排序。
void Countingsort1(int* A, int n, int digit) //修改后的计数排序
{
int* C = new int[10];
int* B = new int[n];//记录排序后的数组,作为输出
for (int i = 0; i < 10; i++)
C[i] = 0;
//C数组的第i个元素用于记录取值为i的元素个数
for (int i = 0; i < n; i++)
{
int p = pow(10, digit);
int remainder = A[i] % p;
int count = remainder * 10 / p;
C[count]++;
}
for (int i = 1; i < 10; i++)
C[i] = C[i - 1] + C[i];
//C数组的第i个元素用于记录取值为i的元素在新数组中应该位于的位置->第几个元素
for (int i = n - 1; i >= 0; i--) //从数组的最后开始排序,为了保持相同元素的相对顺序——稳定性
{
int p = pow(10, digit);
int remainder = A[i] % p;
int count = remainder * 10 / p;
B[--C[count]] = A[i];
}
for (int i = 0; i < n; i++)
A[i] = B[i];
}
//利用计数排序实现的基数排序
void Radixsort(int* A, int number, int digit)
{
for (int i = 1; i <= digit; i++) //对每位数进行计数排序
{
Countingsort1(A, number, i);
cout << endl;
}
}