CLRS第十五章思考题1-4

思考题15-1

注意到此题是说有向无环图,因此存在动态规划算法计算最长加权简单路径。
dis[u] 表示从 u t 的最长加权简单路径,可以得到如下式子:

dis[u]={0max(u,v)E{w(u,v)+dis[v]}if u=totherwise

采用自底向上的方法

LONGEST-PATH(G, s, t)
    let dist[1...n] and next[1...n] be new arrays
    topologically sort the vertices of G
    for i = 1 to |G.V| 
        dist[i] = ∞
    dist[s] = 0
    for each u in topological order, starting from s
        for each edge(u,v)∈ G.Adj[u]
            if dist[u] + w(u,v) > dist[v] 
                dist[v]  = dist[u] + w(u,v)
                next[u] = v
print “The longest distance is ” dist[t] 
PRINT-PATH(s, t, next)

时间复杂度是 Θ(V+E)

思考题15-2

首先说一下,这题和一般的最长回文子序列好像不一样,因为它给的 character 得到的是 carac,这个子串在原字符串中并不连续,一般的应该返回是 ara。
针对这题,很简单的一个思路就是求最长公共子序列,先把字符串逆转,然后求最长公共子序列就可以了。

#include <iostream>
#include <string>
using std::cout;
using std::cin;
using std::endl;
using std::string;

void LCS_LENGTH(string &x,string &y,int **b,int **c)
{
    int m = x.size(),n = y.size();
    for(int i = 0; i <= n; i++)
        c[i][0] = 0;
    for(int i = 1; i <= n; i++)
        c[0][i] = 0;
    for(int i = 1; i <= m; i++)
    {
        for(int j = 1; j <= n; j++)
        {
            if(x[i-1] == y[j-1])
            {
                c[i][j] = c[i-1][j-1] + 1;
                b[i][j] = 0;
            }
            else if(c[i-1][j] >= c[i][j-1])
            {
                c[i][j] = c[i-1][j];
                b[i][j] = 1;//up
            }
            else
            {
                c[i][j] = c[i][j-1];
                b[i][j] = 2;//left
            }
        }
    }
}

void PRINT_LCS(int **b,string &x,int i,int j)
{
    if(i == 0 || j == 0)
        return;
    if(b[i][j] == 0)
    {
        PRINT_LCS(b,x,i-1,j-1);
        cout << x[i-1];
    }
    else if(b[i][j] == 1)
        PRINT_LCS(b,x,i-1,j);
    else PRINT_LCS(b,x,i,j-1);
}


int main()
{
    string x = "character";
    string y = x;
    for(int i = 0; i < y.size()/2; i++)
    {
        char temp = y[i];
        y[i]  = y[y.size() - i - 1];
        y[y.size() - i - 1] = temp;
    }
    int m = x.size(),n = y.size();
    int **b = new int *[m+1];
    int **c = new int *[m+1];
    for(int i = 0; i <= m; i++)
    {
        b[i] = new int[n+1];
        c[i] = new int[n+1];
    }

    LCS_LENGTH(x,y,b,c);
    PRINT_LCS(b,x,m,n);
    cout << endl;
    for(int i = 0; i <= m; i++)
    {
        delete []b[i];
        delete []c[i];
    }
    delete []b;
    delete []c;
    return 0;
}

思考题15-3

首先按照 x 坐标进行排序,需要 O(nlgn) 时间,设排序后的点从左到右为 (p1,p2...pn) ,其中 p1 是最左点, pn 是最右点。
定义双调路径 Pi,j,ij ,包含所有的 p1,p2...pj 。从 pi 开始,严格的左走到 p1 ,然后严格的右走到 pj 。记 pi,pj 两点的欧氏距离为 |pipj| ,记 b[i,j]1ijn ,为双调路径 Pi,j 的最短路径。
我们有下面的 b[i,j] 式子,其中 1ijn

b[1,2]=|p1p2|b[i,j]=b[i,j1]+|pj1pj|,i<j1b[j1,j]=min1k<j1{b[k,j1]+|pkpj|}

我们需要计算 b[n,n]=b[n1,n]+|pn1pn| ,同时定义一个 r[i,j] 来记录最短双调路径 Pi,j pj 相邻连接点的下标。

EUCLIDEAN-TSP(p)
    sort the points so that <p1, p2, p3...pn> are in order of increasing x-coordinate
    let b[1...n,2...n] and r[1...n-2,3...n] be new arrays
    b[1,2] = |p1p2|
    for j = 3 to n
        for i = 1 to j - 2
            b[i,j] = b[i,j-1] + |pj-1pj|
            r[i, j]  = j - 1
        b[j-1,j] = ∞
        for k = 1 to j - 2
            q = b[k, j-1] + |pkpj|
            if q < b[j-1, j] 
                b[j-1,j] = q
                r[j-1,j] = k
    b[n,n] = b[n-1, n] + |pn-1pn|
    return b and r

然后从 pn 沿着包含 pn1 的向左打印,直到 p1 ,然后向右打印剩余的点(不包括 pn1

PRINT-TOUR(r, n)
    print pn
    print pn-1
    k = r[n-1, n]
    PRINT-PATH(r, k, n-1)
    print pk

PRINT-PATH(r, i, j)
    if i < j
        k = r[i, j]
        if k ≠ i
            print pk
        if k > 1
            PRINT-PATH(r, i, k)
    else k = r[j,i]
        if k > 1
            PRINT-PATH(r, k, j)
            print pk

在调用PRINT-PATH时,当 i<j 表示从右向左的路径,当 i>j 则是从左到右的路径。
最后总的时间复杂度是 O(n2)

思考题15-4

先假设最长的单词长度小于 M
定义 extra[i,j]=Mj+ik=ijlk 表示包含单词 i 到单词 j 的这一行的额外空格数,注意 extra[i,j] 可能是负数。接着定义包含单词 i 到单词 j 的这一行的代价 lc

lc[i,j]=0(extra[i,j])3 extra[i,j]<0j=nextra[i,j]≥0()otherwise

定义 c[j] 是一个最优排列的单词 1,2,...j 的代价,假设某一行以单词 i 开始到单词 j 结束,则 c[j]=c[i1]+lc[i,j] 。其中 c[0]=0,c[1]=lc[1,1] 。然后有:
c[j]={0min1ij{c[i1]+lc[i,j]} j=0 j>0

还需要计算一个表 p 。当 c[j] 计算出来后,若 c[j]=c[k1]+lc[k,j] ,则 p[j]=k ,表示最后一行是以单词 k 开头到单词 j 结尾。计算的伪代码如下:

PRINT-NEATLY(l, n, M)
    let extra[1..n, 1..n], lc[1..n, 1..n], and c[0..n] be new arrays
    // Compute extra[i, j]  for 1 ≤i ≤j ≤n.
    for i = 1 to n
        extra[i, i]  = M-li
        for j = i + 1 to n
            extra[i, j]  = extra[i, j-1]-lj-1
    // Compute lc[i, j]  for 1 ≤i≤ j≤n.
    for i = 1 to n
        for j = i to n
            if extra[i, j]  < 0
                lc[i, j] = ∞ 
            elseif j == n and extra[i, j] ≥ 0
                lc[i, j] = 0
            else lc[i, j]  = (extra[i, j])^3
    // Compute c[j]  and p[j]  for 1 ≤j≤ n.
    c[0] = 0
    for j = 1 to n
        c[j]  = ∞
        for i = 1 to j
            if c[i-1] + lc[i, j]  < c[j]
                c[j] = c[i-1] + lc[i, j] 
                p[j] = i
    return c and p

显然时间和空间复杂度都是 O(n2) ,实际上可以减少时间和空间复杂度,我们知道每个单词长度必定大于等于1,并且每个单词后面有一个空格,所以一行最多可以有 M/2 个单词。对于一行有 ji+1 个单词来说,若 ji+1>M/2 ,则 lc[i,j]= ,因此我们只需要计算和存储 ji+1M/2 extra[i,j] lc[i,j] 。这样计算 c[j],p[j] 时为 max(1,jM/2+1) to j 。当然还可以把空间降到 Θ(n)
最后是打印哪个单词在哪一行,GIVE-LINES(p,j) 打印一个三元组(k,i,j),即单词 i 到 单词 j 在第 k 行,最后函数返回行数 k

GIVE-LINES(p, j)
    i = p[j] 
    if i == 1
        k = 1
    else k = GIVE-LINES(p, i-1) + 1
    print(k, i, j)
    return k

对应的代码如下:

#include <iostream>
#include <string>
#include <algorithm>
using std::cout;
using std::cin;
using std::endl;
using std::string;

void PRINT_NEATLY(int *l,int n,int M,int *c,int *p)
{
    int **extra = new int *[n];
    int **lc = new int *[n];
    for(int i = 0; i < n; i++)
    {
        extra[i] = new int[n];
        lc[i] = new int[n];
    }
    for(int i = 0; i < n; i++)
    {
        extra[i][i] = M - l[i];
        for(int j = i + 1; j < n; j++)
            extra[i][j] = extra[i][j-1] - l[j] - 1;
    }
    for(int i = 0; i < n; i++)
    {
        for(int j = i; j < n; j++)
        {
            if(extra[i][j] < 0)
                lc[i][j] = INT_MAX/2;//这里做个特别处理是为了防止后面c[i]>c[j-1]+lc[j-1][i-1]溢出
            else if(j == n - 1 && extra[i][j] >= 0)
                lc[i][j] = 0;
            else lc[i][j] = pow(static_cast<double>(extra[i][j]),3);
        }
    }
    c[0] = 0;
    for(int i = 1; i <= n; i++)
    {
        c[i] = INT_MAX;
        for(int j = 1; j <= i; j++)
        {
            if(c[i] > c[j-1] + lc[j-1][i-1])
            {
                c[i] = c[j-1] + lc[j-1][i-1];
                p[i] = j;
            }
        }
    }
    for(int i = 0; i < n; i++)
    {
        delete []extra[i];
        delete []lc[i];
    }
    delete []extra;
    delete []lc;
}

int GIVE_LINES(int *p,int j)
{
    int i = p[j];
    int k;
    if(i == 1)
        k = 1;
    else k = GIVE_LINES(p,i-1) + 1;
    cout <<"第 " << k << " 行," << "第 " << i << " 个单词到第 " << j << " 个单词" << endl;
    return k;
}

int main()
{
    int l[] = {3,7,2,5,1,8,5,1,4};//一共9个单词
    int *c = new int[10];
    int *p = new int[10];
    int M = 10;//每行的最多有 M 个字符
    PRINT_NEATLY(l,9,M,c,p);
    GIVE_LINES(p,9);
    delete []c;
    delete []p;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值