哈工大算法分析作业

By yejinru

1. 给定一个数组A,任务是设计一个算法求得数组中的“主元素”,即在数组中个数超过数组总元素个数一半的元素。但是数组中元素的数据类型可能是复杂类型,这意味着数组中的元素进能够比较是否相等而不存在序关系,设对于两个元素A[i]和A[j],判定是否A[i]=A[j]需要常数时间。

(a) 设计一个时间复杂性为O(n log n)的算法解此问题

(b) 设计一个时间复杂性为O(n)的算法解此问题.

A.O(n log n)采用分治的策略:

a) 由于数组存在主元素,所以如果把该数组平均分成两部分,则主元素必为两部分中至少一部分的主元素。因此我们:

b) 递归查找两部分T1,T2的主元素m1,m2,如果:

  1. m1m2相同,则m1即为该部分的主元素。
  2. 否则:遍历一遍T1T2组成的部分,判断主元素是m1还是m2

c) 递归结束返回该部分的主元素。

 

B.O(n)算法:参考自编程之美书上的算法。

我们同时删除两个不同的元素,不会改变剩下元素中的主元素。

因此我们可以每次删除两个不同的元素,直到不能删除为止,剩下的元素即为主元素。

 

2. 令I1, …, In是n个区间,其中任一区间Ii=(ai,bi),假设这些区间按照bi从小到大排序,每一个区间有一个权重vi,考虑如下两个问题:

区间安排问题 P1: 找到最大数量互不相交的区间,例如,对于四个区间I1 = (1,2); I2 = (2,3); I3 = (1,4); I4 = (4,5),一个解是{I1, I2, I4}

加权任务安排问题P2: 找一个互不相交区间的集合,使得这些区间的权重之和最大,例如I1 = (1,2), v1=0.9; I2 = (2,3), v2=0.5; I3 = (1,4), v3=4; I4 = (4,5), v4=2,解是{I3, I4}。

(a) 给出解决问题P1的线性时间算法。

(b) 给出解决问题P2的动态规划算法,要求写出递归方程和伪代码,并分析算法时间复杂性。

  1. 直接按照bi进行贪心:

a) 第一次找l1

b) 第二次时,找到最小的i,满足ai>=b1

c) 然后每次找最靠前的与当前所选区间不重叠的区间即可。

 

B. dp[i] = max{dp[j]} + v[i] j需要满足 bj<=ai1<=j<i ,初始化:dp[0] = 0

时间复杂度为O(n^2)

 

 

3. 考虑多背包问题,即给定n个物品,其中物品i的价格是vi, 重量是wi,有m个背包,背包j最大能装物品的重量均为Bj,求这些背包能够装下物品的最大价格,其中每个物品要么完全放入背包,要么不放入。

对于此问题一个显然的贪心算法如下:利用精确算法选择物品装第一个背包,然后移除装入第一个背包的物品,然后按此方法依次装后面的背包。证明此贪心算法不能给出精确解,在所有Bj都相等时也是如此。设计精确算法解决此问题。

反例:

物品:

重量:wi

价格:vi

4

4

3

2

2

1

背包:

重量:bj

5

4

贪心选出的结果是:背包1放物品1,背包2放物品2,总价值为6

实际上最优解为:背包1放物品2,3,背包2放物品1,总价值为7

 

貌似是一个NP问题,没有一个多项式复杂度的解法,因此我们可以通过dfs爆搜。

伪代码:

最优解为ans

 

// j表示第j个背包,has表示该背包已经装了has重量的物体,sum表示当前所有背包装的物品价值总和

dfs(j,has,sum){

    if(sum>ans) // 更新答案

        ans = sum;

    if(j>m) // 全部背包放满

        return;

    for(i = 1 to n) // 遍历所有的物品

        if( !use[i] && has+w[i]<=b[j] ){ // 如果物品i还没放入到背包且能放入当前背包中

            use[i] = true; // 把物品i放入到背包j

            dfs( j,has+w[i],sum+v[i] );

            use[i] = false; // 回溯

        }

    dfs(j+1,0,sum); // 背包j不再放入物品,用下一个背包安放物品

}

 

 

4. 给定一个城市集合,一些城市之间由高速公路连接,这些城市和城市之间的高速公路构成了一个无向图G = (V,E),每条边e=(u,v)∈E表示一条城市u到v的高速公路,e上的权重le表示该高速公路的长度。一辆车需要从城市s到达城市t,但该车的油箱存油最多能走L公里,每个城市有一个加油站,但是城市之间没有加油站,因此,只有当le<L的时候,才能走e对应的高速公路。回答下列问题:

(a) 设计一个时间复杂性O(E)的算法,判定是否这辆车能够从城市s走到城市t。

(b) 如果准备买一辆新车,需要知道保证车从城市s成功走到城市t最少需要多少大的油箱,请设计时间复杂性为O((|V | + |E|) log |V |)的算法解决该问题。

  1. 直接搜就行。

a) bfs

  1. 先把节点s放进队列,标记遍历过。
  2. 把队首元素出队,记为x,沿节点x的边(x,y)走,如果该边权<Ly未被标记过,把它放进队列,标记遍历过。
  3. 如果能够遍历到节点t,则表示这辆车能够从城市s走到城市t

b) dfs:

  1. 从节点s开始搜。
  2. 如果当前节点为t,把t标记为可达,返回可达。
  3. 如果当前搜到的节点为x,沿着边(x,y)走,如果该边权<L时:
  4. 如果y节点被标记过:
    1. 如果y标记的是可达,标记x可达,返回可达。
    2. 如果y标记的是不可达,继续搜索x的下一条边。
    3. 否则,搜索节点y
      1. 如果搜索y返回的是可达,标记x可达,返回可达
      2. 如果搜索y返回的是不可达,继续搜索x的下一条边。
      3. 遍历完x的所有边后,可直接标记x为不可达,返回不可达。

 

伪代码:

bfs版本的:

bfs() {

    queue<int> Q;

    Q.push(s);

    use[s] = true;

    while( Q.empty() == false ) {

        x = Q.front();

        Q.pop();

        if(x==t)

            return “可达”;

        for(  搜索所有的边(x,y)  ) {

            if( 边权>=L )

                continue;

            if( use[y]==false ) {

                use[y] = true;

                Q.push(y);

            }

        }

    }

    return “不可达”;

}

  

  

dfs版本的:

dfs(x) {

    if(x==t)

        return “可达”;

    for(  搜索所有的边(x,y)  ) {

        if( 边权>=L )

            continue;

        if( label[y] == “可达” ) {

            label[x] = “可达”;

            return “可达”;



        } else if( label[y]==”不可达” ) {

            continue;

        } else if( dfs(y)==”可达” ) {

            label[x] = “可达”;

            return “可达”;

        } else { // 搜索y时返回的是不可达

            continue;

             

        }  

    }

    label[x] = “不可达”;

    return “不可达”;

}

  

 

B. O((|V | + |E|) log |V |):

用数组d[x]表示从节点s到节点x最少需要d[x]容量的油箱。

初始化:d[s] = 0 , 非s节点x:d[x] = INF;

1.把d[s]加入到平衡树中,平衡树以d[x]作为第一关键字,以x作为第二关键字。

2.从平衡树中取出最左边的元素 d[x]和x,然后遍历x的相邻节点y。

  1.如果d[y],y在平衡树中,先把d[y],y从平衡树中删掉,然后再把更新后的d[y],y加入到平衡树中。

  2.如果d[y],y不在平衡树中,直接把更新后的d[y],y添加到平衡树中。

3.重复以上步骤,直到从平衡树中取出的最左元素为d[t],t时,d[t]就是答案。

 

Ps: O((|V | + |E|) log |E |):

用数组d[x]表示从节点s到节点x最少需要d[x]容量的油箱。

初始化:d[s] = 0 , s节点xd[x] = INF;

用数组use[x]表示节点x是否曾经进入到堆中。

1.d[s]放入到最小堆中,且最小堆需要额外维护d[s]对应的节点s

2.从最小堆中取出堆顶元素d[x]以及额外信息x

3.如果use[x]==true,忽视。

4.否则,use[x] = true,遍历x的相邻节点y,如果d[y]max{d[x],l(x,y)}大时,更新d[y],并把d[y]以及y放入到最小堆中。

5.重复以上步骤直到节点t出堆。

 

5. 考虑编辑距离的一种变形,其允许在字符串后无代价地插入无限多个字符,该编辑距离描述为:

ed'(A, B)=min{ed(A, C)|C是B的前缀}, 其中函数ed()是普通的编辑距离函数。根据要求设计算法,要求算法的时间复杂性都是O(|A||B|)

(a). 设计算法,对于给定的字符串A和B,计算ed'(A, B);

(b). 设计算法,对于给定的字符串A,B和整数k,判定是否B存在某个后缀B’,满足ed’(A, B’)≤k。

  1. 相当于求得编辑距离(用dp[i][j]表示)后,枚举一下dp[ |A| ][k]即可

伪代码:答案为ans

for( i=0 to |A| )

    dp[i][0] = i;

for( j=0 to |B| )

    dp[0][j] = j;

for( i=1 to |A| )

    for( j=1 to |B| )

        dp[i][j] = min{

            dp[i-1][j]+1,

            dp[i][j-1]+1,

            dp[i-1][j-1]+( A[i]!=B[j] )

        };

ans = INF;

for( j=0 to |B| )

    ans = min(ans,dp[ |A| ][j]);

 

B. 就是问题一的变形,我们只需要把A,B串翻转为A1,B1,求ed’(A1,B1),则ed’(A1,B1)即为问题所求。

reverse( A );

reverse( B );

for( i=0 to |A| )

    dp[i][0] = i;

for( j=0 to |B| )

    dp[0][j] = j;

for( i=1 to |A| )

    for( j=1 to |B| )

        dp[i][j] = min{

            dp[i-1][j]+1,

            dp[i][j-1]+1,

            dp[i-1][j-1]+( A[i]!=B[j] )

        };

for( j=0 to |B| )

    ans = min(ans,dp[ |A| ][j]);

 

6.将一根木棒折成若干份,每折一次的代价是当前这段木棒的长度总代价是折这根木棒直到满足要求所需要的所有操作的代价。例如,将一根长度为10的木棒折成四段,长度分别为2, 2, 3, 3,如果先折成长度为28的两段,再将长度为8的折成长度为26的两段,最后将长度为6的折成长度为3的两段,这些操作的代价是10+8+6=24;如果先折成长度为46的两段,在分别将长度为4的折成长度为2的两段、长度为6的折成长度为3的两段,则这些操作的代价是10+4+6=20,比上一种方案更好一些。

该问题的输入是木棒的长度L和一些整数c1,…,cn, 要求将木棒折成长度为c1, …, cn的n段且操作代价最小,请设计动态规划算法解决该问题。

O(n^3)做法

dp[i][j] = min{ dp[i][k]+dp[k][j]+(ci+...+cj) } ( i<k<j )

边界处理:

如果i = j时,dp[i][j] = 0;

如果i +1 = j时,dp[i][j] = ci+cj

(ci+...+cj) 时,我们需要用到前缀和相减的方式降低复杂度:

用数组sum[i]表示c1+...+cidp方程容易想到:sum[i] = sum[i-1]+ci

所以(ci+...+cj)  = sum[j]-sum[i-1]

 

O(n^2)做法:

利用平行四边形法则可以判断该dp转移方程可以用斜率优化:当i,j确定时即可确定k,而不用逐一枚举k,从而把复杂度降到O(n^2)。(不作要求)

 

7.考虑特殊的0-1背包问题:有n个物品,每个物品i价值和重量都是wi,背包能容纳物品的最大重量是C, 选择背包能容纳的物品集合,使得这些物品价值之和最大。回答下列问题:

(1) 若物品的重量(价值)分别是1, 2, …,2n-1, 证明该0-1背包问题可以用贪心法求解并写出该贪心算法的伪代码。

(2) 请写出一个物品重量(价值)序列,使得上述贪心法无法得到最优解。

1).证明:

贪心:每次选取最大重量且满足2^k<=背包剩余重量的物品k

背包的最大容量为C,假设我们用总量为 2^k 的物品能放下,用物品2^(k+1)不能放下。如果我们不放入物品2^k,由于2^(k+1)放不下,以及2^k<=C和 

( 1+2+...+2^(k-1) ) <2^k。所以我们不选重量为2^k的物品选出的价值比选择2^k的物品要少。因此贪心法求解是正确的。

贪心伪代码:

ans = 0;

for(i=n-1 down to 0)

    if( 2^i <= C ){

        C -= 2^i;

        ans += 2^i;

    }

2).

反例:

重量:wi

价格:vi

4

4

3

3

2

2

C5时:

贪心选出的答案为4

正解为5

 

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