算法导论读书笔记 第二章 算法入门

此文的PDF版本下载地址http://download.csdn.net/source/465698

由于时间问题有些问题没有写的很仔细,而且估计这里会存在不少不恰当之处。另,思考题2-3关于霍纳规则,有些部分没有完成,故没把解答写上去,我对其c问题有疑问,请有解答方法者提供个意见。

给出的代码目前也仅仅为解决问题,没有做优化,请见谅,等有时间了我再好好修改。

插入排序算法伪代码

INSERTION-SORT(A)

1          for j2 to length[A]

2             do keyA[j]

3                  //Insert A[j] into the sorted sequence A[1..j-1]

4                  ij-1

5                  while i>0 and A[i]>key

6                      do A[i+1] A[i]

7                          ii-1

8                  A[i+1] key

C#对插入排序算法的实现:

        public static void InsertionSort<T>(T[] Input) where T:IComparable<T>

        {

            T key;

            int i;

            for (int j = 1; j < Input.Length; j++)

            {

                key = Input[j];

                i = j - 1;

                for (; i >= 0 && Input[i].CompareTo(key)>0;i-- )

                    Input[i + 1] = Input[i];

                Input[i+1]=key;

            }

        }

插入算法的设计使用的是增量(incremental)方法:在排好子数组A[1..j-1]后,将元素A[j]插入,形成排好序的子数组A[1..j]

这里需要注意的是由于大部分编程语言的数组都是从0开始算起,这个与伪代码认为的数组的数是第1个有所不同,一般要注意有几个关键值要比伪代码的小1.

如果按照大部分计算机编程语言的思路,修改为:

INSERTION-SORT(A)

1        for j1 to length[A]

2           do keyA[j]

3                ij-1

4                while i>=0 and A[i]>key

5                    do A[i+1] A[i]

6                        ii-1

7                A[i+1] key

循环不变式(Loop Invariant)是证明算法正确性的一个重要工具。对于循环不变式,必须证明它的三个性质:

初始化(Initialization):它在循环的第一轮迭代开始之前,应该是正确的。

保持(Maintenance):如果在循环的某一次迭代开始之前它是正确的,那么,在下一次迭代开始之前,它也是正确的。

终止(Termination):当循环结束时,不变式给了我们一个有用的性质,它有助于表明算法是正确的。

运用循环不变式对插入排序算法的正确性进行证明:

初始化j=2,子数组A[1..j-1]只包含一个元素A[1],显然它是已排序的。

保持:若A[1..j-1]是已排序的,则按照大小确定了插入元素A[j]位置之后的数组A[1..j]显然也是已排序的。

终止:当j=n+1时,退出循环,此时已排序的数组是由A[1],A[2],A[3]…A[n]组成的A[1..n],此即原始数组A

练习

2.1-1:以图2-2为模型,说明INSERTION-SORT在数组A=<31,41,59,26,41,58>上的执行过程。

31

41

59

26

41

58

 

31

41

59

26

41

58

31

41

59

26

41

58

 

26

31

41

59

41

58

 

26

31

41

41

59

58

 

26

31

41

41

58

59

2.1-2:重写过程INSERTION-SORT,使之按非升序(而不是按非降序)排序。

INSERTION-SORT(A)

1          for j2 to length[A]

2             do keyA[j]

3                 //Insert A[j] into the sorted sequence A[1..j-1]

4                 ij-1

5                 while  and A[i]<key

6                     do A[i+1] A[i]

7                ii-1

7                  A[i+1] key

2.1-3:考虑下面的查找问题:

              输入:一列数A=<a1,a2,…,an >和一个值v

              输出:下标i,使得v=A[i],或者当v不在A中出现时为NIL

              写出针对这个问题的现行查找的伪代码,它顺序地扫描整个序列以查找v。利用循环不变式证明算法的正确性。确保所给出的循环不变式满足三个必要的性质。

LINEAR-SEARCH(A,v)

1     for i1 to length[A]

2         if v=A[i]

3            return i

4     return NIL

现行查找算法正确性的证明。

初始化: i=1,子数组为A[1..i],只有一个元素A[1],如果v=A[1]就返回1,否则返回NIL,算法显然是正确的。

保持:若算法对数组A[1..i]正确,则在数组增加一个元素A[i+1]时,只需要多作一次比较,因此显然对A[1..i+1]也正确。

终止:算法如果在非最坏情况下定能返回一个值此时查找成功,如果n次查找(遍历了所有的数)都没有成功,则返回NIL。算法在有限次查找后肯定能够给出一个返回值,要么说明查找成功并给出下标,要么说明无此值。因此算法正确。

该算法用C#实现的代码:

        public static int LinearSearch<T>(T[] Input, T v) where T:IComparable<T>

        {

            for (int i = 0; i < Input.Length;i++ )

                if (Input[i].Equals(v))

                    return i;

            return -1;

        }

2.1-4:有两个各存放在数组AB中的n位二进制整数,考虑它们的相加问题。两个整数的和以二进制形式存放在具有(n+1)个元素的数组C中。请给出这个问题的形式化描述,并写出伪代码。

A存放了一个二进制n位整数的各位数值,B存放了另一个同样是二进制n位整数的各位上的数值,现在通过二进制的加法对这两个数进行计算,结果以二进制形式把各位上的数值存放在数组Cn+1位)中。

BINARY-ADD(A,B,C)

1       flag 0

2       for j1 to n

3           do keyA[j]+B[j]+flag

4           C[j] key mod 2

5            if key1

6               flag1

7       if flag=1

8          C[n+1] 1

1.RAM(Random-Access Machine)模型分析通常能够很好地预测实际计算机上的性能,RAM计算模型中,指令一条接一条地执行,没有并发操作。RAM模型中包含了真实计算机中常见的指令:算术指令(加法、剑法、乘法、出发、取余、向下取整、向上取整指令)、数据移动指令(装入、存储、复制指令)和控制指令(条件和非条件转移、子程序调用和返回指令)。其中每天指令所需时间都为常量。

RAM模型中的数据类型有整数类型和浮点实数类型。

2.算法的运行时间是指在特定输入时,所执行的基本操作数(或步数)。

插入算法的分析比较简单,但是不是很有用,所以略过。(在解思考题2-1时有具体的实例分析,请参看)

3.一般考察算法的最坏情况运行时间。这样做的理由有三点:

A.一个算法的最坏情况运行时间是在任何输入下运行时间的一个上界。

B.对于某些算法,最坏情况出现的是相当频繁的。

C.大致上来看,“平均情况“通常与最坏情况一样差。

4.如果一个算法的最坏情况运行时间要比另一个算法的低,我们常常就认为它的效率更高。

练习

2.2-1:用Θ形式表示表示函数 /1000- -100n+3

Θ(n^3)

2.2-2:考虑对数组A中的n个数进行排序的问题:首先找出A中的最小元素,并将其与A[1]中的元素进行交换。接着,找出A中的次最小元素,并将其与A[2]中的元素进行交换。对A中头n-1个元素继续这一过程。写出这个算法的伪代码,该算法称为选择排序(selection sort)。对这个算法来说,循环不变式是什么?为什么它仅需要在头n-1个元素上运行,而不是在所有n个元素上运行?以 形式写出选择排序的最佳和最坏情况下的运行时间。

假设函数MIN(A,i,n)从子数组A[i..n]中找出最小值并返回最小值的下标。

SELECTION-SORT(A)

1       for i1 to n-1

2           jMIN(A,i,n)

3           exchange A[i]←→ A[j]

选择排序算法正确性的证明

初始化:i=1,从子数组A[1..n]里找到最小值A[j],并与A[i]互换,此时子数组A[1..i]只有一个元素A[1],显然是已排序的。

保持:A[1..i]是已排序子数组。这里显然A[1] A[2] A[3] A[i],而A[i+1..n]里最小值也必大于A[i],找出此最小值与A[i+1]互换并将A[i+1]插入A[1..i]得到子数组A[1..i+1]A[1..i+1]显然也是已排序的。

终止:i=n时终止,此时已得到已排序数组A[1..n-1],而A[n]是经过n-1次比较后剩下的元素,因此A[n]大于A[1..n-1]中任意元素,故数组A[1..n]也即是原数组此时已是已排序的。所以,算法正确。

仅需要在头n-1个元素上运行是因为经过n-1次比较后剩下的是最大元素,其理应排在最后一个位置上,因此可以不必对此元素进行交换位置操作。

由于MIN()函数和SWAP()函数对于任意情况运行时间都相等,故这里最佳和最坏情况下运行时间是一样的。         Θ(n^2)

 

选择算法的的C#实现:

private static int Min<T>(T[] Input,int start,int end) where T:IComparable<T>

        {

            int flag=start;

            for (int i = start; i < end; i++)

                if (Input[flag].CompareTo(Input[i]) > 0)

                    flag = i;

            return flag;

        }

        private static void Swap<T>(ref T a,ref T b) where T : IComparable<T>

        {

            T temp;

            temp = a;

            a = b;

            b = temp;

        }

        public static T[] SelectionSort<T>(T[] Input) where T:IComparable<T>

        {

            for (int i = 0; i < Input.Length - 1; i++)

                Swap(ref Input[Min(Input, i, Input.Length)],ref Input[i]);

            return Input;

        }

2.2-3:再次考虑线性查找问题(见练习2.1-3)。在平均情况下,需要检查输入序列中的多少个元素?假定查找的元素是数组中任何一个元素的可能性都是相等的。在最坏情况下又怎么样呢?用Θ相似表示的话,线性查找的平均情况和最坏情况运行时间怎么样?对你的答案加以说明。

平均:n/2次。因为任意一个元素大于、小于查找数的概率一样。

最坏:n次。最后一个元素才是要查找的元素。

Θ表示都是:Θ(n)

2.2-4:应如何修改一个算法,才能使之具有较好的最佳情况运行时间?

要使算法具有较好的最佳情况运行时间就一定要对输入进行控制,使之偏向能够使得算法具有最佳运行情况的排列。

5.分治法(divide-and-conquer:有很多算法在结构上是递归的:为了解决一个给定的问题,算法要一次或多次地递归调用其自身来解决相关的问题。这些算法通常采用分治策略:将原问题划分成n个规模较小而结构与原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。

容易确定运行时间,是分治算法的有点之一。

6.分治模式在每一层递归上都有三个步骤:

分解(Divide):将原问题分解成一系列子问题;

解决(Conquer):递归地解各子问题。若子问题足够小,则直接求解;

合并(Combine):将子问题的结果合并成原问题的解。

7.合并排序(Merge Sort)算法完全依照了分治模式。

分解:n个元素分成各含n/2个元素的子序列;

解决:用合并排序法对两个子序列递归地排序;

合并:合并两个已排序的子序列以得到排序结果。

在对子序列排序时,其长度为1时递归结束。单个元素被视为是已排好序的。

合并排序的关键步骤在于合并步骤中的合并两个已排序子序列。为做合并,引入一个辅助过程MERGE(A,p,q,r),其中A是个数组,pqr是下标,满足 。该过程假设子数组A[p..q]A[q+1..r]都已排好序,并将他们合并成一个已排好序的子数组代替当前子数组A[p..r]

MERGE过程的时间代价为Θ(n)其中n=r-p+1是待合并的元素个数。

MERGE过程:

MERGE(A,p,q,r)

1       n1q-p+n

2       n2r-p

3       //create arrays L[1..n1+1] and R[1..n2+1]

4       for i1 to n1

5           do L[i] A[p+i-1]

6       for j1 to n2

7           do R[j] A[q+j]

8       L[ ] ←无穷

9       R[ ] ←无穷

10    i1

11    j1

12    for kp to r

13        do if L[i]<= R[j]

14            then A[k] L[i]

15                 ii+1

16            else A[k] R[j]

17                 jj+1

MERGE过程正确性的证明

初始化:第一轮循环,k=pi=1j=1,已排序数组LR,比较两数组中最小元素L[i]R[j],取较小的置于A[p],此时子数组A[p..p]不仅是已排序的(仅有一个元素),而且是所有待排序元素中最小的。若最小元素是L[i],取i=i+1,即i指向L中未排入A的所有元素中最小的一个;同理,j之于R数组也是如此。

保持:A[p..k]是已排序的,由计算方法知,Li所指、Rj所指及其后任意元素均大于等于A[p..k]中最大元素A[k],k=k+1A[k+1]中存入的是L[i]R[j]中较小的一个,但是仍有A[k] <= A[k+1],而此时,子数组A[p..k+1]也必是有序的,ij仍是分别指向LR中未排入A的所有元素中最小的一个。

终止: k=r+1时终止跳出循环,此时,A[p..r]是已排序的,且显有A[p] A[p+1] .. A[r]。此即原待排序子数组,故算法正确。

MERGE-SORT(A,p,r)

1       if p<r

2           then q[(p+r)/2]

3           MERGE-SORT(A,p,r)

4           MERGE-SORT(A,q+1,r)

5           MERGE-SORT(A,p,q,r)

算法与二叉树的后序遍历算法(先左子树,然后右子树,最后根)相似。

(第三行、第四行顺序可以互换)

合并排序算法的C#实现代码:

public static void MergeSort<T>(T[] Input,int p,int r) where T:IComparable<T>

        {

            int q;

            if (p < r)

            {

                q = (p + r) / 2;

                MergeSort(Input, p, q);

                MergeSort(Input, q + 1, r);

                Merge(Input,p,q,r);

            }

        }

        private static void Merge<T>(T[] Input,int p,int q,int r) where T:IComparable<T>

        {

            int n1 = q - p + 1;

            int n2 = r - q;

            T[] L = new T[n1];

            T[] R = new T[n2];

            for (int i = 0; i < n1; i++)

                L[i] = Input[p + i];

            for (int j = 0; j < n2; j++)

                R[j] = Input[q + 1 + j];

 

            for (int i = 0, j = 0, k = p; k <= r; k++)

            {

                if(i<n1&&j<n2)

                    if (L[i].CompareTo(R[j]) < 0||L[i].Equals(R[j]))

                    {

                        Input[k] = L[i];

                        ++i;

                        continue;

                    }

                    else

                    {

                        Input[k] = R[j];

                        ++j;

                        continue;

                    }

                if (i >= n1 && j < n2)

                {

                    Input[k] = R[j];

                    ++j;

                    continue;

                }

                if (i < n1 && j >= n2)

                {

                    Input[k] = L[i];

                    ++i;

                    continue;

                }

            }

        }

8.当一个算法中含有对其自身的递归调用时,其运行时间可以用一个递归方程(或递归式)来表示。

合并算法的递归式:

n<=c,T(n)=Θ(1),否则T(n)=aT(n/b)+D(n)+C(n)       

D(n)是分解该问题所用时间,C(n)是合并解的时间;对于合并排序算法,ab都是2

T(n)在最坏的情况下合并排序n个数的运行时间分析:

n>1时,将运行时间如下分解:

分解:这一步仅仅算出子数组的中间位置,需要常量时间,因而D(n)=Θ(1)

解决:递归地解为两个规模为n/2的子问题,时间为T(n/2)

合并:含有n个元素的子数组上,MERGE过程的运行时间为C(n) =Θ(n)

n=1,T(n)=Θ(1),n>1T(n)=2T(n/2)+ Θ(n)      

将上式改写:

n=1,T(n)=c,n>1T(n)=2T(n/2)+ cn

在所构造的递归树中,顶层总代价为cnn个点的集合)。往下每层总代价不变,第i层的任一节点代价为c(n/2^i)(共2^i个节点总代价仍然是cn)。最底层有n个节点(n*1),每个点代价为c。此树共有lgn+1层,深度为lgn

因此n层的总代价为:cn*(lgn+1)=cnlgn+cn=Θ(nlgn)

 

练习

2.3-12-4为模型,说明合并排序在输入数组A=<3,41,52,26,38,57,9,49>上的执行过程。

以文字代替图示

1.(3)(41)(3,41);(52)(26) (26,52);(38)(57) (38,57);(9)(49) (9,49)

2.(3,41)(26,52) (3,26,41,52);(38,57)(9,49) (9,38,49,57)

3.(3,26,41,52)(9,38,49,57) (3,9,26,38,41,49,52,57)

2.3-2MERGE过程,使之不适用哨兵元素,而是在一旦数组LR中的所有元素都被复制回数组A后,就立即停止,再将另一个数组中余下的元素复制回数组A

MERGE(A,p,q,r)

1       n1q-p+n

2       n2r-p

3       //create arrays L[1..n1] and R[1..n2]

4       for i1 to n1

5           do L[i] A[p+i-1]

6       for j1 to n2

7           do R[j] A[q+j]

8       i1

9       j1

10    for kp to r

11        do if i<n1 and j<n2

12            if L[i]<= R[j]

13               A[k] L[i]

14               ii+1

15               continue

16            else A[k] R[j]

17                 jj+1

18                 continue

19        do if i>=n1 and j<n2

20            A[k] R[j]

21            jj+1

22            continue

23        do if i<n1 and j>n2

24            A[k] L[i]

25            ii+1

26            continue

2.3-3:利用数学归纳法证明:当n2的整数次幂时,递归式

这个公式比较难贴上来,请大家看PDF

2.3-4:插入排序可以如下改写成一个递归过程:为排序A[1..n],先递归地排序A[1..n-1],然后再将A[n]插入到已排序的数组A[1..n-1]中去。对于插入排序的这一递归版本,为它的运行时间写一个递归式。

首先是INSERTION过程

INSERTION (A,p,r)

1         for jp to r

2           do keyA[j]

3                ij-1

4                while i>0 and A[i]>key

5                    do A[i+1] A[i]

6                        ii-1

7                A[i+1] key

插入排序的递归调用算法:

RECURSION-INSERTION-SORT(A,p,r)

1          if p<r

2              rr-1

3              RECURSION-INSERTION-SORT(A,p,r)

4              INSERTION(A,p,r)

该算法的C#实现代码:

public static void RecursionInsertionSort<T>(T[] Input,int p,int r) where T:IComparable<T>

        {

            if (p < r)

            {

                --r;

                RecursionInsertionSort(Input, p, r);

                Insertion(Input,p,r);

            }

        }

        private static void Insertion<T>(T[] Input, int p, int r) where T : IComparable<T>

        {

            T key;

            int i;

            for (int j = 1; j < r; j++)

            {

                key = Input[j];

                i = j - 1;

                for (; i >= 0 && Input[i].CompareTo(key) > 0; i--)

                    Input[i + 1] = Input[i];

                Input[i + 1] = key;

            }

        }

n<=C时,T(n)=Θ(1),否则T(n)=(n-1)/n*T(n-1)+ Θ(n^2)

                                                                    

2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点与v进行比较。根据比较的结果,原序列中有一半就可以不用再做进一步的考虑了。二分查找(binary search)就是一个不断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做进一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是Θ(lgn)

使用递归,先确定一个过程BINARY(A,p,r,v)

BINARY(A,p,r,v)

1          for jp to r

2              if A[j]=v

3                  return j

4          return NIL

然后是二分查找的递归过程

BINARY-SEARCH(A,p,r,v)

1       if p=0 and r=0 and A[0]=v

2       return 0

3       if p<r

4           q[(p+r)/2]

5           if A[q]> v

6               BINARY-SEARCH(A,p,q,v)

7               return BINARY(A,p,q,v)

8           else BINARY-SEARCH(A,q+1,r,v)

9                return BINARY(A,q+1,r,v)

10    return NIL

该算法的C#实现代码:

        public static int BinarySearch<T>(T[] Input,int p,int r,T v) where T:IComparable<T>

        {

            int q;

            if (p == 0 && r == 0 && Input[0].Equals(v))

                return 0;

            if (p < r)

            {

                q = (p + r) / 2;

                if (Input[q].CompareTo(v) > 0 )

                {

                    BinarySearch(Input, p, q, v);

                    return Binary(Input, p, q, v);

                }

 

                else

                {

                    BinarySearch(Input, q + 1, r, v);

                    return Binary(Input, q+1, r, v);

                }

            }

            return -1;

        }

        private static int Binary<T>(T[] Input, int p, int r, T v) where T:IComparable<T>

        {

            for (int j = p; j <= r; j++)

                if (Input[j].Equals(v))

                    return j;

            return -1;

        }

由公式N=a^(log a N)得:n*1/(2^(lgn))=1

因经过n次的与中点比较后肯定能找到最后一个点(最坏情况了),如果是返回下标,否则返回NIL,故最坏情况下时间复杂度为

2.3-6:观察一下2.1节中给出的INSERTION-SORT过程,在第5~7行的while循环中,采用了一种线性查找策略,在已排序的子数组A[1..j-1]中(反向)扫描。是否可以改为二分查找策略(见练习2.3-5),来将插入排序的总体最坏情况运行时间改善至Θ(nlgn)

首先引入一个二分查找策略(与2.3-5Binary Search略有不同)

BINARY(A,p,r,v)

5          for jp to r

6              if A[j]> v

7                  return j

8          return NIL

然后是二分查找的递归过程

BINARY-SEARCH(A,p,r,v)

10    if p=0 and r=0 and A[0] v

11        return 0

12    if p<r

13        

14        if A[q] v

15            BINARY-SEARCH(A,p,q,v)

16            return BINARY(A,p,q,v)

17        else BINARY-SEARCH(A,q+1,r,v)

18             return BINARY(A,q+1,r,v)

10    return NIL

利用了二分查找策略的插入排序:

BINARYINSERTION-SORT(A)

1          for j 2 to length[A]

2              do key A[j]

3              i j-1

4              k BINARY-SEARCH(A,0,i,key)

5              if k!= NIL

6                 for s i downto k

7                     A[s+1] A[s]

8                 A[k] key

此算法的在最坏情况下的运行时间是

该算法的C#实现代码:

private static int BinarySearchForInsertionSort<T>(T[] Input, int p, int r, T v) where T : IComparable<T>

        {

            int q;

            if (p == 0 && r == 0 && Input[0].CompareTo(v)>0)

                return 0;

            if (p < r)

            {

                q = (p + r) / 2;

                if (Input[q].CompareTo(v) > 0)

                {

                    BinarySearchForInsertionSort(Input, p, q, v);

                    return BinaryForInsertionSort(Input, p, q, v);

                }

                else

                {

                    BinarySearchForInsertionSort(Input, q+1, r, v);

                    return BinaryForInsertionSort(Input, q+1, r, v);

                }

            }

            return -1;

        }

        private static int BinaryForInsertionSort<T>(T[] Input, int p, int r, T v) where T : IComparable<T>

        {

            for (int j = p; j <= r; j++)

                if (Input[j].CompareTo(v) > 0)

                    return j;

            return -1;

        }

        public static void BinaryInsertionSort<T>(T[] Input) where T : IComparable<T>

        {

            T key;

            int i, k;

            for (int j = 1; j < Input.Length; j++)

            {

                key = Input[j];

                i = j - 1;

                k = BinarySearchForInsertionSort(Input, 0, i, key);

                if (k != -1)

                {

                    for (int s = i; s>=k ; s--)

                        Input[s + 1] = Input[s];

                    Input[k] = key;

                }

            }

        }

*2.3-7:请给出一个运行时间为Θ(nlgn)的算法,使之能在给定一个由n个整数构成的集合 和另一个整数 时,判断出 中是否存在有两个其和等于 的元素。

利用2.3-5中的BINARY-SEARCH(A,v)2.3-6中的BINARYINSERTION-SORT(S)算法

ISEXISTSUM(S,x)

1       BINARYINSERTION-SORT(S)

2       for j)1 to n

3           k BINARY-SEARCH(S,x-S[j])

4           if k!=NIL

5              return TRUE

6           else return FALSE

该算法的运行时间为: Θ(nlgn)

思考题

2-1:在合并排序中对小数组采用插入排序

尽管合并排序的最坏情况运行时间为Θ(nlgn),插入排序的最坏情况运行时间为Θ(n^2),但插入排序中的常数因子使得它在n较小时,运行得要更快一些。因此,在合并排序算法中,当子问题足够小时,采用插入排序就比较合适了。考虑对合并排序做这样的修改,即采用插入排序策略,对n/k个长度为k的子列表进行排序,然后,再用标准的合并机制将它们合并起来,此处k是一个特定的值。

a)     证明最坏情况下,n/k个子列表(每一个子列表的长度为k)可以用插入排序在Θ(nk)时间内完成排序。

b)    证明这些子列表可以在Θ(nlg(n/k))最坏情况时间内完成合并。

c)     如果已知修改后的合并排序算法的最坏情况运行时间为Θ(nk+nlg(n/k)),要使修改后的算法具有与标准合并排序算法一样的渐进运行时间,k的最大渐进值(即 形式)是什么(以n的函数形式表示)?

d)    在实践中,k的值应该如何选取?

a. Θ(k^2*n/k)= Θ(nk)

b.每一层代价都是Θ(n),共lg(n/k)+1层,因此相乘得Θ(nlg(n/k))

c.k=lgn 

d.在满足插入排序比合并排序更快的情况下,k取最大值。

2-2:冒泡排序算法的正确性

冒泡排序(bubblesort)算法是一种流行的排序算法,它重复地交换相邻两个反序元素。

BUBBLESORT(A)

1       for i1 to length[A]

2           do for jlength[A] downto i+1

3                do if A[j]< A[j-1]

4                    then exchange A[j]←→ A[j-1]

a)   A’表示BULLESORT(A)的输出,为了证明BUBBLESORT是正确的,需要证明它能够终止,并且有:A’[1]<=A’[2]<=..<=A’[n]

其中n=length[A]。为了证明BUBBLESORT的确能实现排序的效果,还需要证明什么?

下面两个部分将证明不等式(2.3)。

b)   对第2~4行中的for循环,给出一个准确的循环不变式,并证明该循环不变式是成立的。在证明中采用本章中给出的循环不变式证明结构。

c)    利用在b)部分证明的循环不变式的终止条件,为第1~4行中的for循环给出一个循环不变式,它可以用来证明不等式(2.3)。你的证明因采用本章中给出的循环不变式的证明结构。

d)   冒泡排序算法的最坏情况运行时间是什么?比较它与插入排序的运行时间。

a.    A’中的元素全部来自于A中变换后的元素。

b.     

初始化:j=n,子数组为A[j..n]A[n..n],此中仅有一个元素因此是已排序的。

保持:如果A[j..n]是已排序的,按计算过程知A[j] A[j+1] A[n],当插入元素A[j-1]时,如果A[j] A[j-1]则互换A[j]A[j-1],否则A[j-1]直接插入A[j..n]的最前,因此A[j-1..n]也是已排序的。

终止:j=i时循环结束,此时A[i..n]是已排序的。与外层循环条件一直,所以算法正确。

c.

初始化:i=1时,子数组A[1..i-1]是空的,因此在第一轮迭代前成立。

保持:假设子数组A[1..i-1]已排序,则之中元素是A[1..n]中最小的i-1个元素,按b证明的循环不变式,知插入A[i]元素后的子数组A[1..i]A[1..n]中最小的i个元素,并且A[1..i]亦是已排序的。

终止:i=n+1时循环终止,此时已处理的子数组是A[1..n]A[1..n]是已排序的,这个数组就是要排序的数组。因此算法正确。

d.Σ(n-i+Σ(n-i-1=Θ(n^2,与插入排序相同

2-3:霍纳规则的正确性

以下的代码片段实现了用于计算多项式

代码片段见PDF

的霍纳规则(Horner’s Rule)。

给定系数a0,a1,…an以及x的值,有

1                y0

2                in

3                while i>=0

4                    do yi+x*y

5                       ii-1

a)   这一段实现霍纳规则的代码的渐进运行时间是什么?

b)   写出伪代码以实现朴素多项式求值(native polynomial-evaluation)算法,它从头开始计算多项式的每一个项。这个算法的运行时间是多少?它与实现霍纳规则的代码段的运行时间相比怎样?

c)    证明一下给出的是针对第3~5行中while循环的一个循环不变式:

在第3~5行中while循环每一轮迭代的开始,有:公式略

不包含任何项的和视为等于0。你的证明应遵循本章中给出的循环不变式的证明结构,并应证明在终止时,有:公式略(请见PDF

d)   最后证明以上给出的代码片段能够正确的计算由系数a0,a1,…,an

2-4:逆序对

A[1..n]是一个包含n个不同数的数组。如果在i<j的情况下,有A[i]>A[j],则(i,j)就称为A中的一个逆序对(inversion)。

a)   列出数组<2,3,8,6,1>5个逆序。

b)   如果数组的元素取自集合{1,2,…,n},那么,怎样的数组含有最多的逆序对?它包含多少个逆序对?

c)    插入排序的运行时间与输入数组中逆序对的数量之间有怎样的关系?说明你的理由。

d)   给出一个算法,它能用Θ(lgn)间,确定n个元素的任何排列中逆序对的数目。(提示:修改合并排序)

a.(2,1),(3,1),(8,6),(8,1),(6,1)

b.{n,n-1,n-2,…,1}有最多的逆序对。共n*(n-1)/2

c.逆序对越多,说明运行情况越坏,所以逆序对的数量与插入排序的运行效率成反比。

d.修改MERGE过程的最后一个FOR循环即可。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
在有关算法的书中,有一些叙述非常严谨,但不够全面;另一些涉及了大量的题材,但又缺乏严谨性。本书将严谨性和全面性融为一体,深入讨论各类算法,并着力使这些算法的设计和分析能为各个层次的读者接受。全书各章自成体系,可以作为独立的学习单元;算法以英语和伪代码的形式描述,具备初步程序设计经验的人就能看懂;说明和解释力求浅显易懂,不失深度和数学严谨性。 --------------------------------------------------------------- 目录 Introduction to Algorithms, Third Edition 出版者的话 译者序 前言 第一部分 基础知识 第1章 算法在计算中的作用  1.1 算法  1.2 作为一种技术的算法  思考题  本章注记 第2章 算法基础  2.1 插入排序  2.2 分析算法  2.3 设计算法   2.3.1 分治法   2.3.2 分析分治算法  思考题  本章注记 第3章 函数的增长  3.1 渐近记号  3.2 标准记号与常用函数  思考题  本章注记 第4章 分治策略  4.1 最大子数组问题  4.2 矩阵乘法的Strassen算法  4.3 用代入法求解递归式  4.4 用递归树方法求解递归式  4.5 用主方法求解递归式  4.6 证明主定理   4.6.1 对b的幂证明主定理   4.6.2 向下取整和向上取整  思考题  本章注记 第5章 概率分析和随机算法  5.1 雇用问题  5.2 指示器随机变量  5.3 随机算法  ?5.4 概率分析和指示器随机变量的进一步使用   5.4.1 生日悖论   5.4.2 球与箱子   5.4.3 特征序列   5.4.4 在线雇用问题  思考题  本章注记 第二部分 排序和顺序统计量 第6章 堆排序  6.1 堆  6.2 维护堆的性质  6.3 建堆  6.4 堆排序算法  6.5 优先队列  思考题  本章注记 第7章 快速排序  7.1 快速排序的描述  7.2 快速排序的性能  7.3 快速排序的随机化版本  7.4 快速排序分析   7.4.1 最坏情况分析   7.4.2 期望运行时间  思考题  本章注记 第8章 线性时间排序  8.1 排序算法的下界  8.2 计数排序  8.3 基数排序  8.4 桶排序  思考题  本章注记 第9章 中位数和顺序统计量  9.1 最小值和最大值  9.2 期望为线性时间的选择算法  9.3 最坏情况为线性时间的选择算法  思考题  本章注记 第三部分 数据结构 第10章 基本数据结构  10.1 栈和队列  10.2 链表  10.3 指针和对象的实现  10.4 有根树的表示  思考题  本章注记 第11章 散列表  11.1 直接寻址表  11.2 散列表  11.3 散列函数   11.3.1 除法散列法   11.3.2 乘法散列法   11.3.3 全域散列法  11.4 开放寻址法  11.5 完全散列  思考题  本章注记 第12章 二叉搜索树  12.1 什么是二叉搜索树  12.2 查询二叉搜索树  12.3 插入和删除  12.4 随机构建二叉搜索树  思考题  本章注记 第13章 红黑树  13.1 红黑树的性质  13.2 旋转  13.3 插入  13.4 删除  思考题  本章注记 第14章 数据结构的扩张  14.1 动态顺序统计  14.2 如何扩张数据结构  14.3 区间树  思考题  本章注记 第四部分 高级设计和分析技术 第15章 动态规划  15.1 钢条切割  15.2 矩阵链乘法  15.3 动态规划原理  15.4 最长公共子序列  15.5 最优二叉搜索树  思考题  本章注记 第16章 贪心算法  16.1 活动选择问题  16.2 贪心算法原理  16.3 赫夫曼编码  16.4 拟阵和贪心算法  16.5 用拟阵求解任务调度问题  思考题  本章注记 第17章 摊还分析  17.1 聚合分析  17.2 核算法  17.3 势能法  17.4 动态表   17.4.1 表扩张   17.4.2 表扩张和收缩  思考题  本章注记 第五部分 高级数据结构 第18章 B树  18.1 B树的定义  18.2 B树上的基本操作  18.3 从B树中删除关键字  思考题  本章注记 第19章 斐波那契堆  19.1 斐波那契堆结构  19.2 可合并堆操作  19.3 关键字减值和删除一个结点  19.4 最大度数的界  思考题  本章注记 第20章 van Emde Boas树  20.1 基本方法  20.2 递归结构   20.2.1 原型van Emde Boas结构   20.2.2 原型van Emde Boas结构上的操作  20.3 van Emde Boas树及其操作   20.3.1 van Emde Boas树   20.3.2 van Emde Boas树的操作  思考题  本章注记 第21章 用于不相交集合的数据结构  21.1 不相交集合的操作  21.2 不相交集合的链表表示  21.3 不相交集合森林  *21.4 带路径压缩的按秩合并的分析  思考题  本章注记 第六部分 图算法 第22章 基本的图算法  22.1 图的表示  22.2 广度优先搜索  22.3 深度优先搜索  22.4 拓扑排序  22.5 强连通分量  思考题  本章注记 第23章 最小生成树  23.1 最小生成树的形成  23.2 Kruskal算法和Prim算法  思考题  本章注记 第24章 单源最短路径  24.1 Bellman?Ford算法  24.2 有向无环图中的单源最短路径问题  24.3 Dijkstra算法  24.4 差分约束和最短路径  24.5 最短路径性质的证明  思考题  本章注记 第25章 所有结点对的最短路径问题  25.1 最短路径和矩阵乘法  25.2 Floyd?Warshall算法  25.3 用于稀疏图的Johnson算法  思考题  本章注记 第26章 最大流  26.1 流网络  26.2 Ford\Fulkerson方法  26.3 最大二分匹配  26.4 推送重贴标签算法  26.5 前置重贴标签算法  思考题  本章注记 第七部分 算法问题选编 第27章 多线程算法  27.1 动态多线程基础  27.2 多线程矩阵乘法  27.3 多线程归并排序  思考题  本章注记 第28章 矩阵运算  28.1 求解线性方程组  28.2 矩阵求逆  28.3 对称正定矩阵和最小二乘逼近  思考题  本章注记 第29章 线性规划  29.1 标准型和松弛型  29.2 将问题表达为线性规划  29.3 单纯形算法  29.4 对偶性  29.5 初始基本可行解  思考题  本章注记 第30章 多项式与快速傅里叶变换  30.1 多项式的表示  30.2 DFT与FFT  30.3 高效FFT实现  思考题  本章注记 第31章 数论算法  31.1 基础数论概念  31.2 最大公约数  31.3 模运算  31.4 求解模线性方程  31.5 中国余数定理  31.6 元素的幂  31.7 RSA公钥加密系统  31.8 素数的测试  31.9 整数的因子分解  思考题  本章注记 第32章 字符串匹配  32.1 朴素字符串匹配算法  32.2 Rabin\Karp算法  32.3 利用有限自动机进行字符串匹配  32.4 Knuth?Morris?Pratt算法  思考题  本章注记 第33章 计算几何学  33.1 线段的性质  33.2 确定任意一对线段是否相交  33.3 寻找凸包  33.4 寻找最近点对  思考题  本章注记 第34章 NP完全性  34.1 多项式时间  34.2 多项式时间的验证  34.3 NP完全性与可归约性  34.4 NP完全性的证明  34.5 NP完全问题   34.5.1 团问题   34.5.2 顶点覆盖问题   34.5.3 哈密顿回路问题   34.5.4 旅行商问题   34.5.5 子集和问题  思考题  本章注记 第35章 近似算法  35.1 顶点覆盖问题  35.2 旅行商问题  35.2.1 满足三角不等式的旅行商问题  35.2.2 一般旅行商问题  35.3 集合覆盖问题  35.4 随机化和线性规划  35.5 子集和问题  思考题  本章注记 第八部分 附录:数学基础知识 附录A 求和  A.1 求和公式及其性质  A.2 确定求和时间的界  思考题  附录注记 附录B 集合等离散数学内容  B.1 集合  B.2 关系  B.3 函数  B.4 图  B.5 树   B.5.1 自由树   B.5.2 有根树和有序树   B.5.3 二叉树和位置树  思考题  附录注记 附录C 计数与概率  C.1 计数  C.2 概率 C.3 离散随机变量  C.4 几何分布与二项分布  *C.5 二项分布的尾部  思考题  附录注记 附录D 矩阵  D.1 矩阵与矩阵运算  D.2 矩阵基本性质  思考题  附录注记

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值