思考题15-1
注意到此题是说有向无环图,因此存在动态规划算法计算最长加权简单路径。
设
dis[u]
表示从
u
到
采用自底向上的方法
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,其中i≤j
,包含所有的
p1,p2...pj
。从
pi
开始,严格的左走到
p1
,然后严格的右走到
pj
。记
pi,pj
两点的欧氏距离为
|pipj|
,记
b[i,j],其中1≤i≤j≤n
,为双调路径
Pi,j
的最短路径。
我们有下面的
b[i,j]
式子,其中
1≤i≤j≤n
:
我们需要计算 b[n,n]=b[n−1,n]+|pn−1pn| ,同时定义一个 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 沿着包含 pn−1 的向左打印,直到 p1 ,然后向右打印剩余的点(不包括 pn−1 )
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
。
定义
定义 c[j] 是一个最优排列的单词 1,2,...j 的代价,假设某一行以单词 i 开始到单词
还需要计算一个表 p 。当
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⌉
个单词。对于一行有
j−i+1
个单词来说,若
j−i+1>⌈M/2⌉
,则
lc[i,j]=∞
,因此我们只需要计算和存储
j−i+1≤⌈M/2⌉
的
extra[i,j]
和
lc[i,j]
。这样计算
c[j],p[j]
时为
max(1,j−⌈M/2⌉+1) to j
。当然还可以把空间降到
Θ(n)
。
最后是打印哪个单词在哪一行,GIVE-LINES(p,j)
打印一个三元组(k,i,j)
,即单词
i
到 单词
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;
}