MIT算法导论-第五讲-线性时间排序

在比较排序算法中,在最坏情况下,都需要做Ω(nlgn)此比较。合并排序和堆排序以及随机快速排序平均情况和最坏情况下达到O(nlgn),因此合并排序和堆排序以及随机快速排序是渐进最优的。

1.决策树的概念及如何用决策树确定排序算法时间的下界

在比较排序算法中,用比较操作来确定输入序列《a1,a2,……,an》的元素间次序。决策树是一棵完全二叉树,比较排序可以被抽象视为决策树。在决策树中,节点表示为i:j,其中1≤i,j≤n,n是待排序元素个数,叶子节点是排序的结果。节点的左子树满足ai≤aj,右子树满足ai>aj。

决策树模型如下图所示。

MIT算法导论-第五讲-线性时间排序_第1张图片

我们利用决策树分析来证明:任何排序n个元素的决策树的高度是Ω(nlgn)。

如下图所示,因为n个待排序元素的可能出现的排列组合数量为n!,决策树必须包含大于等于n!叶节点。一个高度为h的二叉树的叶节点数量总是小于等于2^h个。因此,n!≤2^h,即h≥nlgn。

MIT算法导论-第五讲-线性时间排序_第2张图片

2.线性时间排序

2.1 计数排序

伪代码

COUNTING_SORT(A,B,k)
      for i=0 to k
        do C[i] = 0
      for j=1 to length(A)
          do C[A[j]] = C[A[j]]+1   //C[i]中包含等于元素i的个数
      for i=1 to k
          do C[i] = C[i] + C[i-1]  //C[i]中包含小于等于元素i的个数
      for j=length[A] downto 1
          do B[C[A[j]]] = A[j]
             C[A[j]] = C[A[j]] -1

举例说明。

初始情况:

MIT算法导论-第五讲-线性时间排序_第3张图片

第1个for循环简单,初始化将C置0。

MIT算法导论-第五讲-线性时间排序_第4张图片

第2个for循环遍历输入序列A,计算A中等于C对应位置的个数来填充辅助数组C。

MIT算法导论-第五讲-线性时间排序_第5张图片

第3个for循环,做的操作便是计算C中当前位置之前所有数字之和并填充C。

MIT算法导论-第五讲-线性时间排序_第6张图片

第4个for循环,从后往前遍历A中数据,根据C中计算结果,将A中数据放在B中正确的位置。

MIT算法导论-第五讲-线性时间排序_第7张图片
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

代码:

//计数排序
void CountSort(int a[], int b[], int length, int k) {
    int count[k + 1];
    for (int i = 0; i <= k; i++)
        count[i] = 0;
    for (int i = 0; i < length; i++)
        count[a[i]]++;  //C[i]中包含等于元素i的个数
    for (int i = 1; i <= k; i++)
        count[i] = count[i] + count[i - 1];//C[i]中包含小于等于元素i的个数
    for (int i = length - 1; i >= 0; i--) {
        b[count[a[i]]-1] = a[i]; //查找元素在已排序序列中的位置
        count[a[i]]--;
    }
}

在COUNTING_SORT过程中,第四个for循环为什么是 for j=length[A] downto 1,而不是 for j=1 to length[A]?

为了保证算法是稳定的。因为如果有两个元素相同,改为 for j=1 to length[A],那么就导致排序后前面的出现在后面,后面的出现在前面,即相同值的元素在输出数组中的相对次序与它们在输入数组中的次序是不同的。

2.2 基数排序

给定n个d位数,每一个数位可以取k种可能值。如果所用的稳定排序需要θ(n+k)的时间,基数排序算法性能以θ(d(n+k))的时间正确对这些数进行排序。

伪代码

 RADIX_SORT(A,d)
    for i=1 to d
          do usage a stable sort to sort array A on digit 

例子

MIT算法导论-第五讲-线性时间排序_第8张图片

代码

/**
 * 获取数字x的第d位值
 * radix:数字x采用的进制
 */
int GetDigit(int x,int d,int radix) {
    while(x && d>1) {
        x = x/radix;
        d--;
    }
    return x%radix;
}

/*
 * a:待排序数组
 * length:a数组长度
 * radix:数字划分所使用的进制,比如10进制,16进制等。
 * d:a数组中所有数组的根据进制所能得到的最高层次,比如数组中最大的元素为987,进制采用10进制,那么最高层次为3。
 */
void RadixSort(int a[], int length,int radix,int d) {
    int bucket[length];//辅助桶,用于暂存某位排序后的结果
    int count[radix];//用于存储第k位中数字的频率

    //按照分配标准依次进行排序过程
    for(int k=1;k<=d;k++) {
        //1.1 置空count
        for(int i=0;i0;
        //1.2 统计a数组中第k位数介于(0-radix)数字的个数
        for(int i=0;iint digit = GetDigit(a[i],k,radix);//获取数字的第k位值
            count[digit]++;
        }
        //1.3 count[i]中包含小于等于元素i(0-radix-1)的个数
        for(int i=1;i1];

        //2 把数据依次装入桶
        for(int i=length-1;i>=0;i--) {
            int digit = GetDigit(a[i],k,radix);//获取数字的第k位值,例如:576的第3位是5
            bucket[count[digit]-1] = a[i];//放入对应的桶中
            count[digit]--;//对应桶的装入数据索引减一
        }

        //3 从各个桶中收集数据
        for(int i=0;i

你可能感兴趣的:(算法导论)