zoukankan      html  css  js  c++  java
  • CLRS2e读书笔记—Chapter8

    计数排序C代码如下,显然这货是不能用于字符串排序的(非比较排序只有基数排序可以用来排序字符串):

    #include <stdio.h>
    #include <malloc.h>
    #include <assert.h>
    #include <stdlib.h>
    #define MAX 100         //取值范围k=99,为了保持main形式上的一致性,这里用了宏
    
    void counting_sort(int* A,int p,int r)
    {
        int* C=(int *)calloc(MAX,sizeof(int));
        int* B=(int *)malloc(r*sizeof(int));
        assert( C && B);
        
        for(int i=p;i<r;++i)
            C[A[i]]+=1;
        for(int j=1;j<MAX;++j)
            C[j]+=C[j-1];
        for(int i=r-1;i>=p;--i)
        {
            //Attention here!
            B[C[A[i]]-1]=A[i];
            --C[A[i]];
        }
        for(int j=p;j<r;++j)
            A[j]=B[j];
         //memcpy(A,B,sizeof(int)*r);
    
         free(C);
         free(B);
    }
    int main()
    {
        int A[30];
        int i=0;
        printf("Original:\n");
        for (;i<30;i++)
        {
            A[i]=rand()%(MAX);
            printf("%d\t",A[i]);
        }
        counting_sort(A,0,30);
        printf("\nRearrange:\n");
        for(i=0;i<30;++i)
        {
            printf("%d\t",A[i]);
        }
        printf("\n");
    }

    基数排序的抽象程度比较高(这也是为啥伪代码那么短的原因),它的思想就是对待排序元素进行拆分,然后从低位到高位进行列排序。不同类型的抽取方式差别很大,前面写的大部分C程序,如果改用C++的模板来实现,代码并不用做太多修改,泛型函数和普通函数的差别并不大(只要重载了<操作符)。这里比较妥当的方法是写一个模板类,实现“抽取各位”这个行为。另外,这里有个比较蛋疼的事情,就是数据的类型与数据各位的类型并不一致,不过简单来说可以都认为是char。

    桶排序假设输入平均分布,将输入范围均分为n个“桶”,然后遍历所有元素,将之放入对应的桶中。然后对所有的桶调用插入排序。

    exercises:

    8.1-1 显然是n

    8.1-2 利用

    \[  \int_{0+}^n lgk \le \sum_{k=1}^n lgk \le \int_1^{n+1} lg k \]

    积分左端可得。

    8.1-3 反证法,假设有一半以上的输入可以通过线性比较运算得出,那么有

    \[ 2^n \ge \frac{n!}{2} \Rightarrow n \ge lg(n!)-1 \]

    而右式显然不成立。

    其余类似。

    8.1-4 hint表示不能直接将n/k个klgk相加。实际上算法是先对子序列排序,然后再将所有子序列的首元素排序,复杂度为(n/k)*klgk+(n/k)*lg(n/k),忽略常数项后仍然是$\Omega(nlgk)$.

    8.2-2 Couting-Sort的稳定性主要是因为line9,假设A中有元素A[i]=A[j](i<j),在line9会先将A[j]放入B[C[A[j]]],然后将C[A[j]]的值减1,这样放入A[i]的时候,A[i]必定在A[j]左侧,因此稳定性成立(似乎归纳法更严格一些)。

    8.2-3 修改后算法不再稳定,除非把line11中的递减改为递增

    8.2-4 这个…算法其实有很多吧,不一定非要用计数排序的思想,直接遍历求和不也行,只有$\Theta(n)$。

    如果用计数排序的思想,就取其伪代码line1 to line 7,然后返回C[b]-C[a-1]即可。

    8.3-2 insertion-sort和merge-sort都是稳定的,而quick-sort和heap-sort都是不稳定的。

    想要保持稳定,必须有一个额外的数组记录其左右顺序,空间消耗为$\Theta(n)$,不过参考答案给的是nlgn,lgn代表的是bits…我搞不清楚为啥必须是lgn bits,这不和具体环境相关么?有人知道的话请告诉我:)

    8.3-4 将这n个整数看成是n进制的两位数,那么使用基数排序+计数排序就可以搞定了(每位的范围都是0~n-1)

    8.3-5 这个,我算的结果是n(1-1/(2d)),倒着算的,最低位最多有n/2堆,每堆元素有两个(如果元素只有一个就不用再建新堆),次低位就是n/4,这样加下去就是

    n/2+n/4+...+n/2d,最后的结果也就是上面那个了…

    8.4-2 最坏情况显然是输入非常不均匀——所有元素都在一个桶里;如果想要改善最坏状况,可以在桶内使用合并排序或者堆排序这些最坏nlgn的算法。

    8.4-3 概率题。E[x2]=1.5,E2[x]=1.

    8.4-4 将单位圆按面积均分成n份,依半径的区间分布是$[0,\sqrt{1/n}],[\sqrt{1/n},\sqrt{2/n}]\cdots [\sqrt{n-1/n},1] $

    problems:

    8-1 本题证明了比较排序的平均情况下界为nlgn

    a>这是决策树的叶节点计算方法…因为n个不同元素的可能排列方式有n!个,所以最终的比较结果也只能到达这n!个结点,且概率是等可能的。

    b>D(T)通过D(RT)和D(LT)到达叶节点的方式共有k条【可以从a中推出这个结论】,所以D(RT)+D(LT)+k=D(T).

    c>根据提示,假设有一颗这样的决策树:其LT可达的叶节点数是$i_0$,其RT可达的叶节点数就是$k-i_0$,那么根据b,D(T)的最小值是

    \[ D(T)_{min}=\min\{D(RT)+D(LT)+k\} \]

    假设d(k)是D(T)的最小值,d(k)的定义是到达k个结点的所有路径总和的最小值,那么d(k)是关于k的函数,根据树的递归结构,D(RT)的最小值应该是关于$k-i_0$的函数,D(LT)就是关于$i_0$的函数,即

    \[D(T)_{min}=\min\{d(i_0)+d(k-i_0)\} \]

    而$i_0$的取值范围就是[1,k-1],所以题设得证。

    d>简单但是通用的方法就是用导数证明单调性。由于i<=k-1,所以也可以直接用$a+b \ge 2\sqrt{ab}$当a=b时取得最小值。

    e>将k=n!代入d中结论可得。

    f>在随机化算法中抽取最短路径分支,然后剪掉同一随机化结点的其他分支,得到一个拥有最短结点的确定算法,该算法的复杂度显然不多于对应的随机化算法。

    8-2 线性时间原地排序的可能性

    a>计数排序(桶排序恐怕不行,因为并非均匀分布)

    b>由于只有两个key,所以一轮快排是可以达到目的的。

    c>插入排序

    d>将这n个记录看做2进制数,进行Radix-sort,那么每一列就是只有0和1的情况了。

    e>遍历A[1-n],在C[0-k]中存放A[i]出现的次数;

      遍历C,对于每一个C[i]依次在A中push_back i个C[i];

      以上算法运行时间为O(n+k),该算法显然是不稳定的。

    8-3 长度不同的数据如何使用radix-sort

    a>①将数据分成负数、0、整数三类——O(n);

      ②准备$\lceil n/2 \rceil$个桶,以位数对以上三类分别再分类【因为对正数而言,长度长的总比长度短的大;负数则相反】O(n);

      ③最后对桶中的数据进行线性排序——O(n)。

    b>①以首字母对数据进行分类(计数排序);

      ②以次字母对所有分类进行再分类;

      ③递归以上过程直至完成排序。

    由于总的字母是n个,因此需要的总时间也就是O(n+m),m是字母的种类(26个+空)

    8-4 配对水壶

    a> simple,对于每一个red,遍历blue得到pair

    b>使用决策树,每次比较的结果是3ge(< > =),所以是三叉树;总的结果是n!个(第一个可能和n个中任一个,第二个则是剩下n-1任一个,依次类推);假设决策树的高度是h,那么有$3^h \ge n!$,解得T(n)=Ω(nlgn)

    c>代码来自参考答案:

    Match-Jugs(R,B)

    if |R|=0

      then return

    if |R|=1

      then let R={r} and B={b}

      output "(r,b)"

      return

    else r←R[Random(1,n)]

      compare r to every jug of B

      $B_<$ ← the set of jugs in B that are smaller than r

      $B_>$ ← the set of jugs in B that are greater than r

      b ← the pair

      compare b to ever jug of A

      $A_<$ ← the set of jugs in A that are smaller than b

      $B_<$ ← the set of jugs in A that are larger than b

      Match-Jugs($A_<,B_<$)

      Match-Jugs($A_>,B_>$)

    显然,算法与插入排序的思想是一致的,其期望运行时间也是Θ(nlgn)

    8-5 如果数组中的以任意元素i开始的k个元素的和小于等于任意从i+x开始的k个元素的和(x>0)那么称数组是k排序的

    a>1排序就是指数组严格递增(严格来说是非递减)

    b>1 2 3 4 5 6 7 8 10 9【随意调换两个相邻的元素貌似都可以】

    c>展开题设中的不等式,消去等式两边的共有项就能得到A[i]<=A[i+k]

    d>根据c中的结论,只需对A[1,k+1,2k+1...],A[2,k+2,2k+2...]...A[k,2k,3k...]分别进行严格排序就可以了

    共k组,每组共n/k个元素,k*(n/k)lgn/k=nlg(n/k)

    e>显然只需对n/k个长度为k个小数组进行排序即:(n/k)*klgk=nlgk

    f>如果k是常数,根据d的结论…

    8-6 合并两个已排序的序列

    a>对于2n个数,划分成两个长度为n列表的方法显然就是$\binom{2n}{n}$,至于排序,和划分并无关系(一旦确定划分,排序是唯一的)

    b>决策树应该是三叉树,叶子数计算相当于在n个数中插入另外n个数的问题,共有n+1个位置,而待插入的数又有不同的分类法,总数应该是

    \[ \binom{n-1}{0}\binom{n+1}{1}+\binom{n-1}{1}\binom{n+1}{2}+\cdots+\binom{n-1}{n-1}\binom{n+1}{n} \]

    也就是$\sum_{i=0}^{n-1}\binom{n-1}{i}\binom{n+1}{i+1}$,这个计算的结果是…抱歉我不会算= =,总之得出叶子的数目m然后用

    $3^h \ge m$是可以算出这个下界的。不严格的推证:决策树最短路径应该是

    \[ <a1:b1> \xrightarrow{\rm =}<a2:b1>\xrightarrow{\rm =}<a2:b2>\rightarrow\cdots<an:bn>\]

    该路径的长度显然是2n-1,符合2n-o(n)的结论

    c>如果两个数无须比较就能确定大小关系,必须利用关系操作的传递性,但是这与两者相邻是矛盾的(除非相等)

    d>长度为2n,所有相邻的元素都必须比较过,即最少需要2n-1次比较才能确定。

  • 相关阅读:
    android大作业------任务领取
    读书笔记《编程珠矶》2
    学习进度第8周
    学习进度第7周
    世界疫情可视化展示-----echarts
    读书笔记《编程珠矶》1
    团队项目新闻app的需求分析
    学习进度第6周
    团队合作项目
    SpringMVC01
  • 原文地址:https://www.cnblogs.com/livewithnorest/p/2659962.html
Copyright © 2011-2022 走看看