#define MAX 101
int solve(int n)
{ if (n==1) return 1; else
{ int sum=0;
// 求 n 的 不 同 分 解 式 个 数
for (int i=2;i<=n;i++)
if (n%i==0)
sum+=solve(n/i);
return sum;
}
}
void main()
{ int n=12;
int ans=solve(n);
printf(" 结 果 : %d\n",ans);
}
上 述 程 序 的 执 行 结 果 如 图 1.28 所 示 。
图 1.28
程 序 执 行 结 果
18. 解 : 对 应 的 并 行 算 法 如 下 :
int Sum(int a[],int s,int t,int p,int i) // 处 理 器 i 执 行 求 和 { int j,s=0;
for (j=s;j<=t;j++)
s+=a[j];
return s;
}
int ParaSum(int a[],int s,int t,int p,int i)
{ int sum=0,j,k=0,sj;
for (j=0;j{ sj=Sum(a,k,k+n/p-1,p,j);
k+=n/p;
}
sum+=sj;
return sum;
//for 循 环 的 各 个 子 问 题 并 行 执 行
}
每 个 处 理 器 的 执 行 时 间 为 O ( n / p ) , 同 步 开 销 为 O ( p ) , 所 以 该 算 法 的 时 间 复 杂 度 为 O( n / p + p ) 。
30
?% ? = 0
----------------------------------------------------- Page 31 -----------------------------------------------------
第 1 章
概 论
1.4 第 4 章 ─ 蛮 力 法
1.4.1 练 习 题
1. 简 要 比 较 蛮 力 法 和 分 治 法 。
2. 在 采 用 蛮 力 法 求 解 时 什 么 情 况 下 使 用 递 归 ?
3. 考 虑 下 面 这 个 算 法 , 它 求 的 是 数 组 a 中 大 小 相 差 最 小 的 两 个 元 素 的 差 。 请 对 这 个 算 法 做 尽 可 能 多 的 改 进 。
#define INF 99999
#define abs(x) (x)<0?-(x):(x)
int Mindif(int a[],int n)
{ int dmin=INF;
for (int i=0;i<=n-2;i++)
// 求 绝 对 值 宏
for (int j=i+1;j<=n-1;j++)
{ int temp=abs(a[i]-a[j]);
if (tempdmin=temp;
}
return dmin;
}
4. 给 定 一 个 整 数 数 组 A =( a 0 , a 1 , … a n -1 ) , 若 i < j 且 a i > a j , 则 < a i , a j > 就 为 一 个 逆 序 对 。 例 如 数 组 ( 3 , 1 , 4 , 5 , 2 ) 的 逆 序 对 有 < 3 , 1> , <3 , 2> , <4 , 2> , <5 , 2> 。 设 计 一 个 算 法 采 用 蛮 力 法 求 A 中 逆 序 对 的 个 数 即 逆 序 数 。
5. 对 于 给 定 的 正 整 数 n ( n >1 ) , 采 用 蛮 力 法 求 1!+2!+ … + n ! , 并 改 进 该 算 法 提 高 效 率 。
6. 有 一 群 鸡 和 一 群 兔 , 它 们 的 只 数 相 同 , 它 们 的 脚 数 都 是 三 位 数 , 且 这 两 个 三 位 数 的 各 位 数 字 只 能 是 0 、 1 、 2 、 3 、 4 、 5 。 设 计 一 个 算 法 用 蛮 力 法 求 鸡 和 兔 的 只 数 各 是 多 少 ? 它 们 的 脚 数 各 是 多 少 ?
7. 有 一 个 三 位 数 , 个 位 数 字 比 百 位 数 字 大 , 而 百 位 数 字 又 比 十 位 数 字 大 , 并 且 各 位 数 字 之 和 等 于 各 位 数 字 相 乘 之 积 , 设 计 一 个 算 法 用 穷 举 法 求 此 三 位 数 。
8. 某 年 级 的 同 学 集 体 去 公 园 划 船 , 如 果 每 只 船 坐 10 人 , 那 么 多 出 2 个 座 位 ; 如 果 每 只 船 多 坐 2 人 , 那 么 可 少 租 1 只 船 , 设 计 一 个 算 法 用 蛮 力 法 求 该 年 级 的 最 多 人 数 ?
9. 已 知 : 若 一 个 合 数 的 质 因 数 分 解 式 逐 位 相 加 之 和 等 于 其 本 身 逐 位 相 加 之 和 , 则 称 这 个 数 为 Smith 数 。 如 4937775=3*5*5*65837 , 而 3+5+5+6+5+8+3+7=42 , 4+9+3+7+7+7+5=42 , 所 以 4937775 是 Smith 数 。 求 给 定 一 个 正 整 数 N , 求 大 于 N 的 最 小 Smith 数 。
输 入 : 若 干 个 case , 每 个 case 一 行 代 表 正 整 数 N , 输 入 0 表 示 结 束
输 出 : 大 于 N 的 最 小 Smith 数
输 入 样 例 :
4937774
0
样 例 输 出 :
31
----------------------------------------------------- Page 32 -----------------------------------------------------
算 法 设 计
4937775
10. 求 解 涂 棋 盘 问 题 。 小 易 有 一 块 n * n 的 棋 盘 , 棋 盘 的 每 一 个 格 子 都 为 黑 色 或 者 白 色 , 小 易 现 在 要 用 他 喜 欢 的 红 色 去 涂 画 棋 盘 。 小 易 会 找 出 棋 盘 中 某 一 列 中 拥 有 相 同 颜 色 的 最 大 的 区 域 去 涂 画 , 帮 助 小 易 算 算 他 会 涂 画 多 少 个 棋 格 。
输 入 描 述 : 输 入 数 据 包 括 n +1 行 : 第 一 行 为 一 个 整 数 n ( 1 ≤ n ≤ 50 ) , 即 棋 盘 的 大 小 , 接 下 来 的 n 行 每 行 一 个 字 符 串 表 示 第 i 行 棋 盘 的 颜 色 , 'W' 表 示 白 色 , 'B' 表 示 黑 色 。
输 出 描 述 : 输 出 小 易 会 涂 画 的 区 域 大 小 。
输 入 例 子 :
3
BWW
BBB
BWB
输 出 例 子 :
3
11. 给 定 一 个 含 n ( n >1 ) 个 整 数 元 素 的 a , 所 有 元 素 不 相 同 , 采 用 蛮 力 法 求 出 a 中 所 有 元 素 的 全 排 列 。
1.4.2 练 习 题 参 考 答 案
1. 答 : 蛮 力 法 是 一 种 简 单 直 接 地 解 决 问 题 的 方 法 , 适 用 范 围 广 , 是 能 解 决 几 乎 所 有 问 题 的 一 般 性 方 法 , 常 用 于 一 些 非 常 基 本 、 但 又 十 分 重 要 的 算 法 ( 排 序 、 查 找 、 矩 阵 乘 法 和 字 符 串 匹 配 等 ) , 蛮 力 法 主 要 解 决 一 些 规 模 小 或 价 值 低 的 问 题 , 可 以 作 为 同 样 问 题 的 更 高 效 算 法 的 一 个 标 准 。 而 分 治 法 采 用 分 而 治 之 思 路 , 把 一 个 复 杂 的 问 题 分 成 两 个 或 更 多 的 相 同 或 相 似 的 子 问 题 , 再 把 子 问 题 分 成 更 小 的 子 问 题 直 到 问 题 解 决 。 分 治 法 在 求 解 问 题 时 , 通 常 性 能 比 蛮 力 法 好 。
2. 答 : 如 果 用 蛮 力 法 求 解 的 问 题 可 以 分 解 为 若 干 个 规 模 较 小 的 相 似 子 问 题 , 此 时 可 以 采 用 递 归 来 实 现 算 法 。
3. 解 : 上 述 算 法 的 时 间 复 杂 度 为 O( n ) , 采 用 的 是 最 基 本 的 蛮 力 法 。 可 以 先 对 a 中 元 素 递 增 排 序 , 然 后 依 次 比 较 相 邻 元 素 的 差 , 求 出 最 小 差 , 改 进 后 的 算 法 如 下 :
#include
#include
using namespace std;
int Mindif1(int a[],int n)
{ sort(a,a+n);
int dmin=a[1]-a[0];
for (int i=2;i{ int temp=a[i]-a[i-1];
if (tempdmin=temp;
}
return dmin;
}
// 递 增 排 序
32
2
----------------------------------------------------- Page 33 -----------------------------------------------------
第 1 章
概 论
上 述 算 法 的 主 要 时 间 花 费 在 排 序 上 , 算 法 的 时 间 复 杂 度 为 O( n log 2 n ) 。
4. 解 : 采 用 两 重 循 环 直 接 判 断 是 否 为 逆 序 对 , 算 法 的 时 间 复 杂 度 为 O(n2) , 比 第 3 章 实 验 3 算 法 的 性 能 差 。 对 应 的 算 法 如 下 :
int solve(int a[],int n)
{ int ans=0;
for (int i=0;ifor (int j=i+1;jif (a[i]>a[j])
ans++;
return ans;
// 求 逆 序 数
}
5. 解 : 直 接 采 用 蛮 力 法 求 解 算 法 如 下 :
long f(int n)
{ long fn=1;
for (int i=2;i<=n;i++)
fn=fn*i;
return fn;
}
long solve(int n)
{ long ans=0;
for (int i=1;i<=n;i++)
ans+=f(i);
return ans;
// 求 n !
// 求 1 !+2!+ …+n!
}
实 际 上 , f ( n )= f ( n - 1)* n , f (1)=1 , 在 求 f ( n ) 时 可 以 利 用 f ( n - 1) 的 结 果 。 改 进 后 的 算 法 如
下 :
long solve1(int n)
{ long ans=0;
long fn=1;
for (int i=1;i<=n;i++)
{ fn=fn*i;
ans+=fn;
}
return ans;
// 求 1 !+2!+ …+n!
}
6. 解 : 设 鸡 脚 数 为 y = abc , 兔 脚 数 为 z = def , 有 1 ≤ a , d ≤ 5 , 0 ≤ b , c , e , f ≤ 5 , 采 用 6 重 循 环 , 求 出 鸡 只 数 x1= y /2 ( y 是 2 的 倍 数 ) , 兔 只 数 x2= z /4 ( z 是 4 的 倍 数 ) , 当 x1=x2 时 输 出 结 果 。 对 应 的 程 序 如 下 :
#include
void solve()
{ int a,b,c,d,e,f;
int x1,x2,y,z;
for (a=1;a<=5;a++)
for (b=0;b<=5;b++)
for (c=0;c<=5;c++)
33
----------------------------------------------------- Page 34 -----------------------------------------------------
算 法 设 计
for (d=1;d<=5;d++)
for (e=0;e<=5;e++)
for (f=0;f<=5;f++)
{ y=a*100+b*10+c;
z=d*100+e*10+f;
if (y%2!=0 || z%4!=0)
continue;
x1=y/2;
x2=z/4;
if (x1==x2)
// 鸡 脚 数
// 兔 脚 数
// 鸡 只 数
// 兔 只 数
printf(" 鸡 只 数 : %d, 兔 只 数 : %d, 鸡 脚 数 : %d, 兔 脚 数 : %d\n",x1,x2,y,z);
}
}
void main()
{ printf(" 求 解 结 果 \ n");
solve();
}
上 述 程 序 的 执 行 结 果 如 图 1.29 所 示 。
图 1.29 程 序 执 行 结 果
7. 解 : 设 该 三 位 数 为 x = abc , 有 1 ≤ a ≤ 9 , 0 ≤ b , c ≤ 9 , 满 足 c > a , a > b , a + b + c = a * b * c 。 对 应 的 程 序 如 下 :
#include
void solve()
{ int a,b,c;
for (a=1;a<=9;a++)
for (b=0;b<=9;b++)
for (c=0;c<=9;c++)
{ if (c>a && a>b && a+b+c==a*b*c)
printf(" %d%d%d\n",a,b,c);
}
}
void main()
34
----------------------------------------------------- Page 35 -----------------------------------------------------
第 1 章
概 论
{
printf(" 求 解 结 果 \ n"); solve();
}
上 述 程 序 的 执 行 结 果 如 图 1.30 所 示 。
图 1.30 程 序 执 行 结 果
8. 解 : 设 该 年 级 的 人 数 为 x , 租 船 数 为 y 。 因 为 每 只 船 坐 10 人 正 好 多 出 2 个 座 位 , 则 x =10* y - 2 ; 因 为 每 只 船 多 坐 2 人 即 12 人 时 可 少 租 1 只 船 ( 没 有 说 恰 好 全 部 座 位 占 满 ) , 有 x + z =12*( y - 1) , z 表 示 此 时 空 出 的 座 位 , 显 然 z <12 。 让 y 从 1 到 100 ( 实 际 上 y 取 更 大 范 围 的 结 果 是 相 同 的 ) 、 z 从 0 到 11 枚 举 , 求 出 最 大 的 x 即 可 。 对 应 的 程 序 如 下 :
#include
int solve()
{ int x,y,z;
for (y=1;y<=100;y++)
for (z=0;z<12;z++)
if (10*y-2==12*(y-1)-z)
x=10*y-2;
return x;
}
void main()
{ printf(" 求 解 结 果 \ n");
printf(" 最 多 人 数 : %d\n",solve());
}
上 述 程 序 的 执 行 结 果 如 图 1.31 所 示 。
图 1.31 程 序 执 行 结 果
9. 解 : 采 用 蛮 力 法 求 出 一 个 正 整 数 n 的 各 位 数 字 和 sum1 , 以 及 n 的 所 有 质 因 数 的 数 字 和 sum2 , 若 sum1=sum2 , 即 为 Smitch 数 。 从 用 户 输 入 的 n 开 始 枚 举 , 若 是 Smitch 数 , 输 出 , 本 次 结 束 , 否 则 n ++ 继 续 查 找 大 于 n 的 最 小 Smitch 数 。 对 应 的 完 整 程 序 如 下 :
#include
int Sum(int n)
{ int sum=0;
while (n>0)
// 求 n 的 各 位 数 字 和
35
----------------------------------------------------- Page 36 -----------------------------------------------------
算 法 设 计
{ sum+=n%10;
n=n/10;
}
return sum;
}
bool solve(int n) // 判 断 n 是 否 为 S mitch 数
{ int m=2;
int sum1=Sum(n);
int sum2=0;
while (n>=m)
{ if (n%m==0) // 找 到 一 个 质 因 数 m
{ n=n/m;
sum2+=Sum(m);
}
else
m++;
}
if (sum1==sum2)
return true;
else
return false;
}
void main()
{ int n;
while (true)
{ scanf("%d",&n);
if (n==0) break;
while (!solve(n))
n++;
printf("%d\n",n);
}
}
10. 解 : 采 用 蛮 力 法 , 统 计 每 一 列 相 邻 相 同 颜 色 的 棋 格 个 数 countj , 在 countj 中 求 最 大 值 。 对 应 的 程 序 如 下 :
#include
#define MAXN 51
// 问 题 表 示
int n;
char board[MAXN][MAXN];
int getMaxArea()
{ int maxArea=0;
for (int j=0; j// 蛮 力 法 求 解 算 法
for (int i=1; icountj++;
else
countj=1;
}
36
----------------------------------------------------- Page 37 -----------------------------------------------------
第 1 章
概 论
if (countj>maxArea)
maxArea=countj;
}
return maxArea;
}
int main()
{ scanf("%d",&n);
for (int i=0;iscanf("%s",board[i]);
printf("%d\n",getMaxArea());
return 0;
}
11. 解 : 与 《 教 程 》 中 求 全 排 列 类 似 , 但 需 要 将 求 1 ~ n 的 全 排 列 改 为 按 下 标 0 ~ n - 1 求 a 的 全 排 列 ( 下 标 从 0 开 始 ) 。 采 用 非 递 归 的 程 序 如 下 :
#include
#include
using namespace std;
vector > ps;
// 存 放 全 排 列
void Insert(vector s,int a[],int i,vector > &ps1) // 在 每 个 集 合 元 素 中 间 插 入 i 得 到 p s1
{ vector s1;
vector::iterator it;
for (int j=0;j<=i;j++)
{ s1=s;
it=s1.begin()+j;
s1.insert(it,a[i]);
ps1.push_back(s1);
}
}
void Perm(int a[],int n)
{ vector > ps1;
vector >::iterator it;
vector s,s1;
s.push_back(a[0]);
ps.push_back(s);
for (int i=1;i{ ps1.clear();
for (it=ps.begin();it!=ps.end();++it)
Insert(*it,a,i,ps1);
ps=ps1;
}
}
void dispps()
// 在 s ( 含 i 个 整 数 ) 的 每 个 位 置 插 入 a [i] // 求 出 插 入 位 置
// 插 入 整 数 a [i]
// 添 加 到 p s1 中
// 求 a [0..n-1] 的 所 有 全 排 列
// 临 时 存 放 子 排 列
// 全 排 列 迭 代 器
// 添 加 { a[0]} 集 合 元 素
// 循 环 添 加 a [1] ~ a[n-1]
//ps1 存 放 插 入 a [i] 的 结 果
// 在 每 个 集 合 元 素 中 间 插 入 a [i] 得 到 p s1 // 输 出 全 排 列 ps
{ vector >::reverse_iterator it; // 全 排 列 的 反 向 迭 代 器
vector::iterator sit;
// 排 列 集 合 元 素 迭 代 器
for (it=ps.rbegin();it!=ps.rend();++it)
{ for (sit=(*it).begin();sit!=(*it).end();++sit)
printf("%d",*sit);
printf(" ");
37
----------------------------------------------------- Page 38 -----------------------------------------------------
}
printf("\n");
}
void main()
{ int a[]={2,5,8};
int n=sizeof(a)/sizeof(a[0]); printf("a[0 ~ %d] 的 全 排 序 如 下 : \n Perm(a,n);
dispps();
算 法 设 计
",n-1);
}
上 述 程 序 的 执 行 结 果 如 图 1.32 所 示 。
图 1.32 程 序 执 行 结 果
1.5 第 5 章 ─ 回 溯 法
1.5.1 练 习 题
1. 回 溯 法 在 问 题 的 解 空 间 树 中 , 按 ( ) 策 略 , 从 根 结 点 出 发 搜 索 解 空 间 树 。
A. 广 度 优 先 B. 活 结 点 优 先 C. 扩 展 结 点 优 先 D. 深 度 优 先
2. 关 于 回 溯 法 以 下 叙 述 中 不 正 确 的 是 ( ) 。
A. 回 溯 法 有 “ 通 用 解 题 法 ” 之 称 , 它 可 以 系 统 地 搜 索 一 个 问 题 的 所 有 解 或 任 意 解
B. 回 溯 法 是 一 种 既 带 系 统 性 又 带 有 跳 跃 性 的 搜 索 算 法
C. 回 溯 算 法 需 要 借 助 队 列 这 种 结 构 来 保 存 从 根 结 点 到 当 前 扩 展 结 点 的 路 径
D. 回 溯 算 法 在 生 成 解 空 间 的 任 一 结 点 时 , 先 判 断 该 结 点 是 否 可 能 包 含 问 题 的 解 , 如 果 肯 定 不 包 含 , 则 跳 过 对 该 结 点 为 根 的 子 树 的 搜 索 , 逐 层 向 祖 先 结 点 回 溯
3. 回 溯 法 的 效 率 不 依 赖 于 下 列 哪 些 因 素 ( ) 。
A. 确 定 解 空 间 的 时 间 C. 计 算 约 束 函 数 的 时 间
B. 满 足 显 约 束 的 值 的 个 数 D. 计 算 限 界 函 数 的 时 间
4. 下 面 ( ) 函 数 是 回 溯 法 中 为 避 免 无 效 搜 索 采 取 的 策 略 。
A. 递 归 函 数 B. 剪 枝 函 数 C. 随 机 数 函 数 D. 搜 索 函 数
5. 回 溯 法 的 搜 索 特 点 是 什 么 ?
6. 用 回 溯 法 解 0/1 背 包 问 题 时 , 该 问 题 的 解 空 间 是 何 种 结 构 ? 用 回 溯 法 解 流 水 作 业 调 度 问 题 时 , 该 问 题 的 解 空 间 是 何 种 结 构 ?
7. 对 于 递 增 序 列 a []={1 , 2 , 3 , 4 , 5} , 采 用 例 5.4 的 回 溯 法 求 全 排 列 , 以 1 、 2 开 头 的 排 列 一 定 最 先 出 现 吗 ? 为 什 么 ?
8. 考 虑 n 皇 后 问 题 , 其 解 空 间 树 为 由 1 、 2 、 … 、 n 构 成 的 n ! 种 排 列 所 组 成 。 现 用 回
38
----------------------------------------------------- Page 39 -----------------------------------------------------
第 1 章
概 论
溯 法 求 解 , 要 求 :
( 1 ) 通 过 解 搜 索 空 间 说 明 n =3 时 是 无 解 的 。
( 2 ) 给 出 剪 枝 操 作 。
( 3 ) 最 坏 情 况 下 在 解 空 间 树 上 会 生 成 多 少 个 结 点 ? 分 析 算 法 的 时 间 复 杂 度 。 9. 设 计 一 个 算 法 求 解 简 单 装 载 问 题 , 设 有 一 批 集 装 箱 要 装 上 一 艘 载 重 量 为 W 的 轮
船 , 其 中 编 号 为 i ( 0 ≤ i ≤ n - 1 ) 的 集 装 箱 的 重 量 为 w i 。 现 要 从 n 个 集 装 箱 中 选 出 若 干 装 上 轮 船 , 使 它 们 的 重 量 之 和 正 好 为 W 。 如 果 找 到 任 一 种 解 返 回 true , 否 则 返 回 false 。
10. 给 定 若 干 个 正 整 数 a 0 、 a 0 要 求 找 选 择 元 素 个 数 最 少 的 解 。
、 … 、 a n -1
, 从 中 选 出 若 干 数 , 使 它 们 的 和 恰 好 为 k ,
11. 设 计 求 解 有 重 复 元 素 的 排 列 问 题 的 算 法 , 设 有 n 个 元 素 a []={ a 0 , a 1 , … , a n -1 ) , 其 中 可 能 含 有 重 复 的 元 素 , 求 这 些 元 素 的 所 有 不 同 排 列 。 如 a []={1 , 1 , 2} , 输 出 结 果 是 ( 1 , 1 , 2) , ( 1 , 2 , 1 ) , ( 2 , 1 , 1 ) 。
12. 采 用 递 归 回 溯 法 设 计 一 个 算 法 求 1 ~ n 的 n 个 整 数 中 取 出 m 个 元 素 的 排 列 , 要 求 每 个 元 素 最 多 只 能 取 一 次 。 例 如 , n =3 , m =2 的 输 出 结 果 是 ( 1 , 2 ) , ( 1 , 3 ) , ( 2 , 1 ) , ( 2 , 3 ) , ( 3 , 1 ) , ( 3 , 2 ) 。
13. 对 于 n 皇 后 问 题 , 有 人 认 为 当 n 为 偶 数 时 , 其 解 具 有 对 称 性 , 即 n 皇 后 问 题 的 解 个 数 恰 好 为 n /2 皇 后 问 题 的 解 个 数 的 2 倍 , 这 个 结 论 正 确 吗 ? 请 编 写 回 溯 法 程 序 对 n =4 、 6 、 8 、 10 的 情 况 进 行 验 证 。
14. 给 定 一 个 无 向 图 , 由 指 定 的 起 点 前 往 指 定 的 终 点 , 途 中 经 过 所 有 其 他 顶 点 且 只 经 过 一 次 , 称 为 哈 密 顿 路 径 , 闭 合 的 哈 密 顿 路 径 称 作 哈 密 顿 回 路 ( Hamiltonian cycle ) 。 设 计 一 个 回 溯 算 法 求 无 向 图 的 所 有 哈 密 顿 回 路 。
1.5.2 练 习 题 参 考 答 案
1. 答 : D 。
2. 答 : 回 溯 算 法 是 采 用 深 度 优 先 遍 历 的 , 需 要 借 助 系 统 栈 结 构 来 保 存 从 根 结 点 到 当 前 扩 展 结 点 的 路 径 。 答 案 为 C 。
3. 答 : 回 溯 法 解 空 间 是 虚 拟 的 , 不 必 确 定 整 个 解 空 间 。 答 案 为 A 。
4. 答 : B 。
5. 答 : 回 溯 法 在 解 空 间 树 中 采 用 深 度 优 先 遍 历 方 式 进 行 解 搜 索 , 即 用 约 束 条 件 和 限 界 函 数 考 察 解 向 量 元 素 x [ i ] 的 取 值 , 如 果 x [ i ] 是 合 理 的 就 搜 索 x [ i ] 为 根 结 点 的 子 树 , 如 果 x [ i ] 取 完 了 所 有 的 值 , 便 回 溯 到 x [ i - 1] 。
6. 答 : 用 回 溯 法 解 0/1 背 包 问 题 时 , 该 问 题 的 解 空 间 是 子 集 树 结 构 。 用 回 溯 法 解 流 水 作 业 调 度 问 题 时 , 该 问 题 的 解 空 间 是 排 列 树 结 构 。
7. 答 : 是 的 。 对 应 的 解 空 间 是 一 棵 排 列 树 , 如 图 1.33 所 示 给 出 前 面 3 层 部 分 , 显 然 最 先 产 生 的 排 列 是 从 G 结 点 扩 展 出 来 的 叶 子 结 点 , 它 们 就 是 以 1 、 2 开 头 的 排 列 。
39
----------------------------------------------------- Page 40 -----------------------------------------------------
算 法 设 计
A
1
2
3
4
5
B
C
D
E
F
2
3
4
5
G
H
I
图 1.33
J
部 分 解 空 间 树
8. 答 : ( 1 ) n =3 时 的 解 搜 索 空 间 如 图 1.34 所 示 , 不 能 得 到 任 何 叶 子 结 点 , 所 有 无 解 。
( 2 ) 剪 枝 操 作 是 任 何 两 个 皇 后 不 能 同 行 、 同 列 和 同 两 条 对 角 线 。
( 3 ) 最 坏 情 况 下 每 个 结 点 扩 展 n 个 结 点 , 共 有 n 个 结 点 , 算 法 的 时 间 复 杂 度 为 O( n ) 。
(*,*,*)
(1,*,*)
(1,3,*)
(2,*,*)
(3,*,*)
(3,1,*)
图 1.34
3 皇 后 问 题 的 解 搜 索 空 间
9. 解 : 用 数 组 w [0.. n - 1] 存 放 n 个 集 装 箱 的 重 量 , 采 用 类 似 判 断 子 集 和 是 否 存 在 解 的 方 法 求 解 。 对 应 完 整 的 求 解 程 序 如 下 :
#include
#define MAXN 20
// 问 题 表 示
int n=5,W;
int w[]={2,9,5,6,3};
int count;
void dfs(int tw,int rw,int i) { if (i>=n)
{ if (tw==W)
count++;
}
else
// 最 多 集 装 箱 个 数
// 全 局 变 量 , 累 计 解 个 数
// 求 解 简 单 装 载 问 题
// 找 到 一 个 叶 子 结 点
// 找 到 一 个 满 足 条 件 的 解 , 输 出 它 // 尚 未 找 完
{
}
rw-=w[i];
if (tw+w[i]<=W)
dfs(tw+w[i],rw,i+1);
if (tw+rw>=W)
dfs(tw,rw,i+1);
// 求 剩 余 的 集 装 箱 重 量 和
// 左 孩 子 结 点 剪 枝 : 选 取 满 足 条 件 的 集 装 箱 w [i] // 选 取 第 i 个 集 装 箱
// 右 孩 子 结 点 剪 枝 : 剪 除 不 可 能 存 在 解 的 结 点 // 不 选 取 第 i 个 集 装 箱 , 回 溯
}
bool solve()
// 判 断 简 单 装 载 问 题 是 否 存 在 解 40
n
n
----------------------------------------------------- Page 41 -----------------------------------------------------
第 1 章
概 论
{ count=0;
int rw=0;
for (int j=0;jrw+=w[j];
dfs(0,rw,0);
if (count>0)
return true;
else
return false;
}
void main()
{ printf(" 求 解 结 果 \ n");
// 求 所 有 集 装 箱 重 量 和 r w //i 从 0 开 始
W=4;
printf(" W=%d 时 % s\n",W,(solve()?" 存 在 解 " :" 没 有 解 " )); W=10;
printf(" W=%d 时 % s\n",W,(solve()?" 存 在 解 " :" 没 有 解 " )); W=12;
printf(" W=%d 时 % s\n",W,(solve()?" 存 在 解 " :" 没 有 解 " )); W=21;
printf(" W=%d 时 % s\n",W,(solve()?" 存 在 解 " :" 没 有 解 " )); }
本 程 序 执 行 结 果 如 图 1.35 所 示 。
图 1.35
程 序 执 行 结 果
10. 解 : 这 是 一 个 典 型 的 解 空 间 为 子 集 树 的 问 题 , 采 用 子 集 树 的 回 溯 算 法 框 架 。 当 找 到 一 个 解 后 通 过 选 取 的 元 素 个 数 进 行 比 较 求 最 优 解 minpath 。 对 应 的 完 整 程 序 如 下 :
#include
#include
using namespace std;
// 问 题 表 示
int a[]={1,2,3,4,5};
int n=5,k=9;
vector minpath;
// 求 解 结 果 表 示
int minn=n;
void disppath()
{ printf(" 选 择 的 元 素 : ");
// 设 置 为 全 局 变 量 // 存 放 最 优 解
// 最 多 选 择 n 个 元 素 // 输 出 一 个 解
for (int j=0;jprintf("%d ",minpath[j]); printf(" 元 素 个 数 = %d\n",minn);
}
41
----------------------------------------------------- Page 42 -----------------------------------------------------
算 法 设 计
void dfs(vector path,int sum,int start) // 求 解 算 法
{ if (sum==k)
{ if (path.size(){ minn=path.size();
minpath=path;
}
return;
}
if (start>=n) return;
dfs(path,sum,start+1);
// 如 果 找 到 一 个 解 , 不 一 定 到 叶 子 结 点 // 全 部 元 素 找 完 , 返 回
// 不 选 择 a [start]
path.push_back(a[start]); // 选 择 a [start] dfs(path,sum+a[start],start+1);
}
void main()
{ vector path;
dfs(path,0,0); printf(" 最 优 解 : \n"); disppath();
//path 存 放 一 个 子 集
}
上 述 程 序 的 执 行 结 果 如 图 1.36 所 示 。
图 1.36
程 序 执 行 结 果
11. 解 : 在 回 溯 法 求 全 排 列 的 基 础 上 , 增 加 元 素 的 重 复 性 判 断 。 例 如 , 对 于 a []={1 , 1 , 2} , 不 判 断 重 复 性 时 输 出 ( 1 , 1 , 2 ) , ( 1 , 2 , 1 ) , ( 1 , 1 , 2 ) , ( 1 , 2 , 1 ) , ( 2 , 1 , 1 ) , ( 2 , 1 , 1 ) , 共 6 个 , 有 3 个 是 重 复 的 。 重 复 性 判 断 是 这 样 的 , 对 于 在 扩 展 a [ i ] 时 , 仅 仅 将 与 a [ i .. j - 1] 没 有 出 现 的 元 素 a [ j ] 交 换 到 a [ i ] 的 位 置 , 如 果 出 现 , 对 应 的 排 列 已 经 在 前 面 求 出 了 。 对 应 的 完 整 程 序 如 下 :
#include
bool ok(int a[],int i,int j) //ok 用 于 判 别 重 复 元 素
{ if (j>i)
{ for(int k=i;kif (a[k]==a[j])
return false;
}
return true;
}
void swap(int &x,int &y)
{ int tmp=x;
x=y; y=tmp;
// 交 换 两 个 元 素
}
void dfs(int a[],int n,int i) // 求 有 重 复 元 素 的 排 列 问 题 { if (i==n)
42
----------------------------------------------------- Page 43 -----------------------------------------------------
第 1 章
概 论
{ for(int j=0;jprintf("%3d",a[j]);
printf("\n");
}
else
{ for (int j=i;jif (ok(a,i,j)) // 选 取 与 a [i..j-1] 不 重 复 的 元 素 a [j] { swap(a[i],a[j]);
dfs(a,n,i+1);
swap(a[i],a[j]);
}
}
}
void main()
{ int a[]={1,2,1,2};
int n=sizeof(a)/sizeof(a[0]);
printf(" 序 列 ( ");
for (int i=0;iprintf("%d ",a[i]);
printf("%d) 的 所 有 不 同 排 列 : \n",a[n-1]);
dfs(a,n,0);
}
上 述 程 序 的 执 行 结 果 如 图 1.37 所 示 。
图 1.37
程 序 执 行 结 果
12. 解 : 采 用 求 全 排 列 的 递 归 框 架 。 选 取 的 元 素 个 数 用 i 表 示 ( i 从 1 开 始 ) , 当 i > m 时 达 到 一 个 叶 子 结 点 , 输 出 一 个 排 列 。 为 了 避 免 重 复 , 用 used 数 组 实 现 , used[ i ]=0 表 示 没 有 选 择 整 数 i , used[ i ]=1 表 示 已 经 选 择 整 数 i 。 对 应 的 完 整 程 序 如 下 :
#include
#include
#define MAXN 20
#define MAXM 10
int m,n;
int x[MAXM];
bool used[MAXN];
void dfs(int i)
{ if (i>m)
{ for (int j=1;j<=m;j++)
printf(" %d",x[j]);
printf("\n");
//x[1..m] 存 放 一 个 排 列
// 求 n 个 元 素 中 m 个 元 素 的 全 排 列 // 输 出 一 个 排 列
43
----------------------------------------------------- Page 44 -----------------------------------------------------
算 法 设 计
}
else
{ for (int j=1;j<=n;j++)
{ if (!used[j])
{ used[j]=true;
x[i]=j;
dfs(i+1);
used[j]=false;
}
}
}
}
void main()
{ n=4,m=2;
memset(used,0,sizeof(used)); printf("n=%d,m=%d 的 求 解 结 果 \ n",n,m); dfs(1);
}
上 述 程 序 的 执 行 结 果 如 图 1.38 所 示 。
// 修 改 u sed[i]
//x[i] 选 择 j
// 继 续 搜 索 排 列 的 下 一 个 元 素 // 回 溯 : 恢 复 u sed[i]
// 初 始 化 为 0
//i 从 1 开 始
图 1.38
程 序 执 行 结 果
13. 解 : 这 个 结 论 不 正 确 。 验 证 程 序 如 下 : #include
#include
#define MAXN 10
int q[MAXN];
bool place(int i)
{ int j=1;
if (i==1) return true;
while (j// 测 试 第 i 行 的 q [i] 列 上 能 否 摆 放 皇 后 //j=1 ~ i-1 是 已 放 置 了 皇 后 的 行
{ if ((q[j]==q[i]) || (abs(q[j]-q[i])==abs(j-i)))
// 该 皇 后 是 否 与 以 前 皇 后 同 列 , 位 置 ( j,q[j]) 与 ( i,q[i]) 是 否 同 对 角 线 return false;
j++;
}
return true;
}
44
----------------------------------------------------- Page 45 -----------------------------------------------------
第 1 章
概 论
int Queens(int n)
{ int count=0,k;
int i=1;
q[1]=0;
while (i>0)
{ q[i]++;
// 求 n 皇 后 问 题 的 解 个 数 // 计 数 器 初 始 化
//i 为 当 前 行
//q[i] 为 皇 后 i 的 列 号 // 移 到 下 一 列
while (q[i]<=n && !place(i))
q[i]++;
if (q[i]<=n)
{ if (i==n)
count++; // 找 到 一 个 解 计 数 器 c ount 加 1 else
{
i++;; q[i]=0;
}
}
else i--;
}
return count;
}
void main()
{ printf(" 验 证 结 果 如 下 : \n"); for (int n=4;n<=10;n+=2)
// 回 溯
if (Queens(n)==2*Queens(n/2))
printf(" n=%d: 正 确 \ n",n);
else
printf(" n=%d: 错 误 \ n",n);
}
上 述 程 序 的 执 行 结 果 如 图 1 .39 所 示 。 从 执 行 结 果 看 出 结 论 是 不 正 确 的 。
图 1.39
程 序 执 行 结 果
14. 解 : 假 设 给 定 的 无 向 图 有 n 个 顶 点 ( 顶 点 编 号 从 0 到 n - 1 ) , 采 用 邻 接 矩 阵 数 组 a ( 0/1 矩 阵 ) 存 放 , 求 从 顶 点 v 出 发 回 到 顶 点 v 的 哈 密 顿 回 路 。 采 用 回 溯 法 , 解 向 量 为 x [0.. n ] , x [ i ] 表 示 第 i 步 找 到 的 顶 点 编 号 ( i = n - 1 时 表 示 除 了 起 点 v 外 其 他 顶 点 都 查 找 了 ) , 初 始 时 将 起 点 v 存 放 到 x [0] , i 从 1 开 始 查 找 , i >0 时 循 环 : 为 x [ i ] 找 到 一 个 合 适 的 顶 点 , 当 i = n - 1 时 , 若 顶 点 x [ i ] 到 顶 点 v 有 边 对 应 一 个 解 ; 否 则 继 续 查 找 下 一 个 顶 点 。 如 果 不 能 为 x [ i ] 找 到 一 个 合 适 的 顶 点 , 则 回 溯 。 采 用 非 递 归 回 溯 框 架 ( 与 《 教 程 》 中 求 解 n 皇 后 问 题 的 非 递 归 回 溯 框 架 类 似 ) 的 完 整 程 序 如 下 :
#include
#define MAXV 10
45
----------------------------------------------------- Page 46 -----------------------------------------------------
// 求 解 问 题 表 示 int n=5;
算 法 设 计
// 图 中 顶 点 个 数
int a[MAXV][MAXV]={{0,1,1,1,0},{1,0,0,1,1},{1,0,0,0,1},{1,1,0,0,1},{0,1,1,1,0}};
// 邻 接 矩 阵 数 组
// 求 解 结 果 表 示
int x[MAXV];
int count;
void dispasolution()
{ for (int i=0;i<=n-1;i++)
// 输 出 一 个 解 路 径
printf("(%d,%d) ",x[i],x[i+1]);
printf("\n");
}
bool valid(int i)
// 判 断 顶 点 第 i 个 顶 点 x [i] 的 有 效 性
{ if (a[x[i-1]][x[i]]!=1) //x[i-1] 到 x [i] 没 有 边 , 返 回 f alse
return false;
for (int j=0;j<=i-1;j++)
if (x[i]==x[j]) // 顶 点 i 重 复 出 现 , 返 回 f alse return false;
return true;
}
void Hamiltonian(int v)
{ x[0]=v;
int i=1;
x[i]=-1;
while (i>0)
{ x[i]++;
// 求 从 顶 点 v 出 发 的 哈 密 顿 回 路 // 存 放 起 点
// 从 顶 点 - 1+1=0 开 始 试 探
// 尚 未 回 溯 到 头 , 循 环
while (!valid(i) && x[i]x[i]++;
if (x[i]// 试 探 一 个 顶 点 x [i]
// 找 到 一 个 有 效 的 顶 点 x [i]
{ if (i==n-1) // 达 到 叶 子 结 点
{ if (a[x[i]][v]==1)
{ x[n]=v; // 找 到 一 个 解
printf(" 第 % d 个 解 : ",count++); dispasolution();
}
}
else
{
i++; x[i]=-1;
}
}
else
i--;
// 回 溯
}
}
void main()
{ printf(" 求 解 结 果 \ n");
for (int v=0;v{ printf(" 从 顶 点 % d 出 发 的 哈 密 顿 回 路 : \n",v); count=1;
46
----------------------------------------------------- Page 47 -----------------------------------------------------
第 1 章
概 论
Hamiltonian(v);
// 从 顶 点 v 出 发
}
}
上 述 程 序 对 如 图 1.40 所 示 的 无 向 图 求 从 每 个 顶 点 出 发 的 哈 密 顿 回 路 , 程 序 执 行 结 果 如 图 1.41 所 示 。
1
0
3
2
4
图 1.40 一 个 无 向 图
图 1.41
程 序 执 行 结 果
1.6 第 6 章 ─ 分 枝 限 界 法
1.6.1 练 习 题
1. 分 枝 限 界 法 在 问 题 的 解 空 间 树 中 , 按 ( ) 策 略 , 从 根 结 点 出 发 搜 索 解 空 间 树 。 A. 广 度 优 先 B. 活 结 点 优 先 C. 扩 展 结 点 优 先 D. 深 度 优 先
2. 常 见 的 两 种 分 枝 限 界 法 为 ( ) 。
A. 广 度 优 先 分 枝 限 界 法 与 深 度 优 先 分 枝 限 界 法
47
----------------------------------------------------- Page 48 -----------------------------------------------------
算 法 设 计
B. 队 列 式 ( FIFO ) 分 枝 限 界 法 与 堆 栈 式 分 枝 限 界 法
C. 排 列 树 法 与 子 集 树 法
D. 队 列 式 ( FIFO ) 分 枝 限 界 法 与 优 先 队 列 式 分 枝 限 界 法
3. 分 枝 限 界 法 求 解 0/1 背 包 问 题 时 , 活 结 点 表 的 组 织 形 式 是 ( ) 。
A. 小 根 堆 B. 大 根 堆 C. 栈
4. 采 用 最 大 效 益 优 先 搜 索 方 式 的 算 法 是 ( ) 。 A. 分 支 界 限 法 B. 动 态 规 划 法 C. 贪 心 法
D. 数 组
D. 回 溯 法
5. 优 先 队 列 式 分 枝 限 界 法 选 取 扩 展 结 点 的 原 则 是 ( ) 。
A. 先 进 先 出 B. 后 进 先 出 C. 结 点 的 优 先 级 D. 随 机
6. 简 述 分 枝 限 界 法 的 搜 索 策 略 。
7. 有 一 个 0/1 背 包 问 题 , 其 中 n =4 , 物 品 重 量 为 ( 4 , 7 , 5 , 3 ) , 物 品 价 值 为 ( 40 , 42 , 25 , 12 ) , 背 包 最 大 载 重 量 W =10 , 给 出 采 用 优 先 队 列 式 分 枝 限 界 法 求 最 优 解 的 过 程 。
8. 有 一 个 流 水 作 业 调 度 问 题 , n =4 , a []={5 , 10 , 9 , 7} , b []={7 , 5 , 9 , 8} , 给 出 采 用 优 先 队 列 式 分 枝 限 界 法 求 一 个 解 的 过 程 。
9. 有 一 个 含 n 个 顶 点 ( 顶 点 编 号 为 0 ~ n - 1 ) 的 带 权 图 , 采 用 邻 接 矩 阵 数 组 A 表 示 , 采 用 分 枝 限 界 法 求 从 起 点 s 到 目 标 点 t 的 最 短 路 径 长 度 , 以 及 具 有 最 短 路 径 长 度 的 路 径 条 数 。
10. 采 用 优 先 队 列 式 分 枝 限 界 法 求 解 最 优 装 载 问 题 。 给 出 以 下 装 载 问 题 的 求 解 过 程 和 结 果 : n =5 , 集 装 箱 重 量 为 w = ( 5 , 2 , 6 , 4 , 3 ) , 限 重 为 W =10 。 在 装 载 重 量 相 同 时 , 最 优 装 载 方 案 是 集 装 箱 个 数 最 少 的 方 案 。
1.6.2 练 习 题 参 考 答 案
1. 答 : A 。
2. 答 : D 。
3. 答 : B 。
4. 答 : A 。
5. 答 : C 。
6. 答 : 分 枝 限 界 法 的 搜 索 策 略 是 广 度 优 先 遍 历 , 通 过 限 界 函 数 可 以 快 速 找 到 一 个 解 或 者 最 优 解 。
7. 答 : 求 解 过 程 如 下 :
( 1 ) 根 结 点 1 进 队 , 对 应 结 点 值 : e.i=0 , e.w=0 , e.v=0 , e.ub=76 , x :[0 , 0 , 0 , 0] 。 ( 2 ) 出 队 结 点 1 : 左 孩 子 结 点 2 进 队 , 对 应 结 点 值 : e.no=2 , e.i=1 , e.w=4 ,
e.v=40 , e.ub=76 , x :[1 , 0 , 0 , 0] ; 右 孩 子 结 点 3 进 队 , 对 应 结 点 值 : e.no=3 , e.i=1 , e.w=0 , e.v=0 , e.ub=57 , x :[0 , 0 , 0 , 0] 。
( 3 ) 出 队 结 点 2 : 左 孩 子 超 重 ; 右 孩 子 结 点 4 进 队 , 对 应 结 点 值 : e.no=4 , e.i=2 , e.w=4 , e.v=40 , e.ub=69 , x :[1 , 0 , 0 , 0] 。
( 4 ) 出 队 结 点 4 : 左 孩 子 结 点 5 进 队 , 对 应 结 点 值 : e.no=5 , e.i=3 , e.w=9 , e.v=65 , e.ub=69 , x :[1 , 0 , 1 , 0] ; 右 孩 子 结 点 6 进 队 , 对 应 结 点 值 : e.no=6 , e.i=3 , e.w=4 , e.v=40 , e.ub=52 , x :[1 , 0 , 0 , 0] 。
48
----------------------------------------------------- Page 49 -----------------------------------------------------
第 1 章
概 论
( 5 ) 出 队 结 点 5 : 产 生 一 个 解 , maxv= 65 , bestx:[1 , 0 , 1 , 0] 。
( 6 ) 出 队 结 点 3 : 左 孩 子 结 点 8 进 队 , 对 应 结 点 值 : e.no=8 , e.i=2 , e.w=7 , e.v=42 , e.ub=57 , x :[0 , 1 , 0 , 0] ; 右 孩 子 结 点 9 被 剪 枝 。
( 7 ) 出 队 结 点 8 : 左 孩 子 超 重 ; 右 孩 子 结 点 10 被 剪 枝 。
( 8 ) 出 队 结 点 6 : 左 孩 子 结 点 11 超 重 ; 右 孩 子 结 点 12 被 剪 枝 。
( 9 ) 队 列 空 , 算 法 结 束 , 产 生 的 最 优 解 : maxv= 65 , bestx:[1 , 0 , 1 , 0] 。
8. 答 : 求 解 过 程 如 下 :
( 1 ) 根 结 点 1 进 队 , 对 应 结 点 值 : e.i=0 , e.f1=0 , e.f2=0 , e.lb=29 , x : [0 , 0 , 0 , 0] 。
( 2 ) 出 队 结 点 1 : 扩 展 结 点 如 下 :
进 队 ( j =1 ) : 结 点 2 , e.i=1 , e.f1=5 , e.f2=12 , e.lb=27 , x : [1 , 0 , 0 , 0] 。
进 队 ( j =2 ) : 结 点 3 , e.i=1 , e.f1=10 , e.f2=15 , e.lb=34 , x : [2 , 0 , 0 , 0] 。 进 队 ( j =3 ) : 结 点 4 , e.i=1 , e.f1=9 , e.f2=18 , e.lb=29 , x : [3 , 0 , 0 , 0] 。
进 队 ( j =4 ) : 结 点 5 , e.i=1 , e.f1=7 , e.f2=15 , e.lb=28 , x : [4 , 0 , 0 , 0] 。
( 3 ) 出 队 结 点 2 : 扩 展 结 点 如 下 :
进 队 ( j =2 ) : 结 点 6 , e.i=2 , e.f1=15 , e.f2=20 , e.lb=32 , x : [1 , 2 , 0 , 0] 。 进 队 ( j =3 ) : 结 点 7 , e.i=2 , e.f1=14 , e.f2=23 , e.lb=27 , x : [1 , 3 , 0 , 0] 。 进 队 ( j =4 ) : 结 点 8 , e.i=2 , e.f1=12 , e.f2=20 , e.lb=26 , x : [1 , 4 , 0 , 0] 。 ( 4 ) 出 队 结 点 8 : 扩 展 结 点 如 下 :
进 队 ( j =2 ) : 结 点 9 , e.i=3 , e.f1=22 , e.f2=27 , e.lb=31 , x : [1 , 4 , 2 , 0] 。 进 队 ( j =3 ) : 结 点 10 , e.i=3 , e.f1=21 , e.f2=30 , e.lb=26 , x : [1 , 4 , 3 , 0] 。 ( 5 ) 出 队 结 点 10 , 扩 展 一 个 j =2 的 子 结 点 , 有 e.i=4 , 到 达 叶 子 结 点 , 产 生 的 一 个 解
是 e.f1=31 , e.f2=36 , e.lb=31 , x =[1 , 4 , 3 , 2] 。
该 解 对 应 的 调 度 方 案 是 : 第 1 步 执 行 作 业 1 , 第 2 步 执 行 作 业 4 , 第 3 步 执 行 作 业 3 , 第 4 步 执 行 作 业 2 , 总 时 间 = 36 。
9. 解 : 采 用 优 先 队 列 式 分 枝 限 界 法 求 解 , 队 列 中 结 点 的 类 型 如 下 :
struct NodeType
{ int vno;
int length;
bool operator<(const NodeType &s) const { return length>s.length; }
// 顶 点 的 编 号
// 当 前 结 点 的 路 径 长 度 // 重 载 < 关 系 函 数 //length 越 小 越 优 先
};
从 顶 点 s 开 始 广 度 优 先 搜 索 , 找 到 目 标 点 t 后 比 较 求 最 短 路 径 长 度 及 其 路 径 条 数 。 对 应 的 完 整 程 序 如 下 :
#include
#include
using namespace std;
#define MAX 11
#define INF 0x3f3f3f3f
// 问 题 表 示
int A[MAX][MAX]={
// 一 个 带 权 有 向 图
49
----------------------------------------------------- Page 50 -----------------------------------------------------
{0 , 1 , 4 , INF , INF} ,
{INF , 0 , INF , 1 , 5} ,
{INF , INF , 0 , INF , 1} , {INF , INF , 2 , 0 , 3} ,
{INF , INF , INF , INF , INF} };
int n=5;
// 求 解 结 果 表 示
int bestlen=INF;
int bestcount=0;
struct NodeType
{ int vno;
int length;
算 法 设 计
// 最 优 路 径 的 路 径 长 度 // 最 优 路 径 的 条 数
// 顶 点 的 编 号
// 当 前 结 点 的 路 径 长 度
bool operator<(const NodeType &s) const // 重 载 > 关 系 函 数
{ return length>s.length; }
};
void solve(int s , int t)
{ NodeType e , e1; priority_queue qu;
e.vno=s;
e.length=0;
qu.push(e);
while (!qu.empty())
{ e=qu.top(); qu.pop();
if (e.vno==t)
{ if (e.length{ bestcount=1;
bestlen=e.length;
}
else if (e.length==bestlen)
bestcount++;
}
else
{ for (int j=0; j//length 越 小 越 优 先
// 求 最 短 路 径 问 题
// 定 义 2 个 结 点
// 定 义 一 个 优 先 队 列 q u // 构 造 根 结 点
// 根 结 点 进 队
// 队 不 空 循 环
// 出 队 结 点 e 作 为 当 前 结 点 //e 是 一 个 叶 子 结 点
// 比 较 找 最 优 解
// 保 存 最 短 路 径 长 度
//e 不 是 叶 子 结 点
// 检 查 e 的 所 有 相 邻 顶 点
if (A[e.vno][j]!=INF && A[e.vno][j]!=0) // 顶 点 e .vno 到 顶 点 j 有 边 { if (e.length+A[e.vno][j]{ e1.vno=j;
e1.length=e.length+A[e.vno][j];
qu.push(e1);
}
}
}
}
}
void main()
{ int s=0 , t=4;
solve(s , t);
if (bestcount==0)
printf(" 顶 点 % d 到 % d 没 有 路 径 \ n" , s , t); else
{ printf(" 顶 点 % d 到 % d 存 在 路 径 \ n" , s , t);
50
// 有 效 子 结 点 e 1 进 队
----------------------------------------------------- Page 51 -----------------------------------------------------
第 1 章
概 论
printf(" 最 短 路 径 长 度 = %d , 条 数 = %d\n" , bestlen , bestcount); // 输 出 : 5 3
}
}
上 述 程 序 的 执 行 结 果 如 图 1.39 所 示 。
图 1.39
程 序 执 行 结 果
10. 解 : 采 用 优 先 队 列 式 分 枝 限 界 法 求 解 。 设 计 优 先 队 列 priority_queue , 并 设 计 优 先 队 列 的 关 系 比 较 函 数 Cmp , 指 定 按 结 点 的 ub 值 进 行 比 较 , 即 ub 值 越 大 的 结 点 越 先 出 队 。 对 应 的 完 整 程 序 如 下 :
#include
#include
using namespace std;
#define MAXN 21
// 问 题 表 示
int n=5;
int W=10;
int w[]={0,5,2,6,4,3}; // 求 解 结 果 表 示
int bestw=0;
int bestx[MAXN];
int Count=1;
typedef struct
{ int no;
int i;
int w;
int x[MAXN];
int ub;
} NodeType;
struct Cmp
// 最 多 的 集 装 箱 数
// 集 装 箱 重 量 , 不 计 下 标 0 的 元 素 // 存 放 最 大 重 量 , 全 局 变 量
// 存 放 最 优 解 , 全 局 变 量
// 搜 索 空 间 中 结 点 数 累 计 , 全 局 变 量 // 结 点 编 号
// 当 前 结 点 在 解 空 间 中 的 层 次
// 当 前 结 点 的 总 重 量
// 当 前 结 点 包 含 的 解 向 量
// 上 界
// 队 列 中 关 系 比 较 函 数
{ bool operator()(const NodeType &s,const NodeType &t)
{ return (s.ubt.x[0]); //ub 越 大 越 优 先 , 当 u b 相 同 时 x [0] 越 小 越 优 先
}
};
void bound(NodeType &e)
{ int i=e.i+1;
int r=0;
while (i<=n)
{ r+=w[i];
i++;
}
e.ub=e.w+r;
// 计 算 分 枝 结 点 e 的 上 界
//r 为 剩 余 集 装 箱 的 重 量 51
----------------------------------------------------- Page 52 -----------------------------------------------------
}
void Loading()
{ NodeType e,e1,e2;
算 法 设 计
// 求 装 载 问 题 的 最 优 解 // 定 义 3 个 结 点
priority_queue,Cmp > qu; // 定 义 一 个 优 先 队 列 q u
e.no=Count++;
e.i=0;
e.w=0;
for (int j=0; j<=n; j++) e.x[j]=0;
bound(e);
qu.push(e);
while (!qu.empty())
{ e=qu.top(); qu.pop();
if (e.i==n)
// 设 置 结 点 编 号
// 根 结 点 置 初 值 , 其 层 次 计 为 0 // 初 始 化 根 结 点 的 解 向 量
// 求 根 结 点 的 上 界
// 根 结 点 进 队
// 队 不 空 循 环
// 出 队 结 点 e 作 为 当 前 结 点 //e 是 一 个 叶 子 结 点
{ if ((e.w>bestw) || (e.w==bestw && e.x[0]{ bestw=e.w;
// 更 新 b estw
for (int j=0;j<=e.i;j++)
bestx[j]=e.x[j]; // 复 制 解 向 量 e .x->bestx
}
}
else
{ if (e.w+w[e.i+1]<=W)
{ e1.no=Count++;
e1.i=e.i+1;
//e 不 是 叶 子 结 点 // 检 查 左 孩 子 结 点 // 设 置 结 点 编 号 // 建 立 左 孩 子 结 点
e1.w=e.w+w[e1.i];
for (int j=0; j<=e.i; j++)
e1.x[j]=e.x[j]; // 复 制 解 向 量 e .x->e1.x
e1.x[e1.i]=1;
e1.x[0]++;
bound(e1);
qu.push(e1);
}
e2.no=Count++;
e2.i=e.i+1;
// 选 择 集 装 箱 i
// 装 入 集 装 箱 数 增 1 // 求 左 孩 子 结 点 的 上 界 // 左 孩 子 结 点 进 队
// 设 置 结 点 编 号
// 建 立 右 孩 子 结 点
e2.w=e.w;
for (int j=0; j<=e.i; j++) // 复 制 解 向 量 e .x->e2.x e2.x[j]=e.x[j];
e2.x[e2.i]=0;
bound(e2);
if (e2.ub>bestw)
qu.push(e2);
}
}
}
void disparr(int x[],int len)
{ for (int i=1;i<=len;i++)
printf("%2d",x[i]);
}
void dispLoading()
{ printf(" X=[");
// 不 选 择 集 装 箱 i
// 求 右 孩 子 结 点 的 上 界
// 若 右 孩 子 结 点 可 行 , 则 进 队 , 否 则 被 剪 枝 // 输 出 一 个 解 向 量
// 输 出 最 优 解
52
----------------------------------------------------- Page 53 -----------------------------------------------------
第 1 章
概 论
disparr(bestx,n);
printf("], 装 入 总 价 值 为 % d\n",bestw); }
void main()
{ Loading();
printf(" 求 解 结 果 : \n"); dispLoading();
}
上 述 程 序 的 执 行 结 果 如 图 1.40 所 示 。
// 输 出 最 优 解
图 1.40
程 序 执 行 结 果
1.7 第 7 章 ─ 贪 心 法
1.7.1 练 习 题
1. 下 面 是 贪 心 算 法 的 基 本 要 素 的 是 ( ) 。
A. 重 叠 子 问 题 B. 构 造 最 优 解 C. 贪 心 选 择 性 质 D. 定 义 最 优 解
2. 下 面 问 题 ( ) 不 能 使 用 贪 心 法 解 决 。
A. 单 源 最 短 路 径 问 题 B. n 皇 后 问 题 C. 最 小 花 费 生 成 树 问 题 D. 背 包 问 题
3. 采 用 贪 心 算 法 的 最 优 装 载 问 题 的 主 要 计 算 量 在 于 将 集 装 箱 依 其 重 量 从 小 到 大 排 序 , 故 算 法 的 时 间 复 杂 度 为 ( ) 。
A.O( n )
B.O( n )
C.O( n )
D.O( n log 2 n )
4. 关 于 0/ 1 背 包 问 题 以 下 描 述 正 确 的 是 ( ) 。
A. 可 以 使 用 贪 心 算 法 找 到 最 优 解
B. 能 找 到 多 项 式 时 间 的 有 效 算 法
C. 使 用 教 材 介 绍 的 动 态 规 划 方 法 可 求 解 任 意 0 - 1 背 包 问 题
D. 对 于 同 一 背 包 与 相 同 的 物 品 , 做 背 包 问 题 取 得 的 总 价 值 一 定 大 于 等 于 做 0/1 背 包 问
题
5. 一 棵 哈 夫 曼 树 共 有 215 个 结 点 , 对 其 进 行 哈 夫 曼 编 码 , 共 能 得 到 ( ) 个 不 同 的 码
字 。
A.107
B.108
C.214
D.215
6. 求 解 哈 夫 曼 编 码 中 如 何 体 现 贪 心 思 路 ?
7. 举 反 例 证 明 0/1 背 包 问 题 若 使 用 的 算 法 是 按 照 v i / w i 的 非 递 减 次 序 考 虑 选 择 的 物 品 , 即 只 要 正 在 被 考 虑 的 物 品 装 得 进 就 装 入 背 包 , 则 此 方 法 不 一 定 能 得 到 最 优 解 ( 此 题 说 明 0/1 背 包 问 题 与 背 包 问 题 的 不 同 ) 。
53
2
3
----------------------------------------------------- Page 54 -----------------------------------------------------
算 法 设 计
8. 求 解 硬 币 问 题 。 有 1 分 、 2 分 、 5 分 、 10 分 、 50 分 和 100 分 的 硬 币 各 若 干 枚 , 现 在 要 用 这 些 硬 币 来 支 付 W 元 , 最 少 需 要 多 少 枚 硬 币 。
9. 求 解 正 整 数 的 最 大 乘 积 分 解 问 题 。 将 正 整 数 n 分 解 为 若 干 个 互 不 相 同 的 自 然 数 之 和 , 使 这 些 自 然 数 的 乘 积 最 大 。
10. 求 解 乘 船 问 题 。 有 n 个 人 , 第 i 个 人 体 重 为 w i ( 0 ≤ i < n ) 。 每 艘 船 的 最 大 载 重 量 均 为 C , 且 最 多 只 能 乘 两 个 人 。 用 最 少 的 船 装 载 所 有 人 。
11. 求 解 会 议 安 排 问 题 。 有 一 组 会 议 A 和 一 组 会 议 室 B , A [ i ] 表 示 第 i 个 会 议 的 参 加 人 数 , B [ j ] 表 示 第 j 个 会 议 室 最 多 可 以 容 纳 的 人 数 。 当 且 仅 当 A [ i ] ≤ B [ j ] 时 , 第 j 个 会 议 室 可 以 用 于 举 办 第 i 个 会 议 。 给 定 数 组 A 和 数 组 B , 试 问 最 多 可 以 同 时 举 办 多 少 个 会 议 。 例 如 , A []={1 , 2 , 3} , B []={3 , 2 , 4} , 结 果 为 3 ; 若 A []={3 , 4 , 3 , 1} , B []={1 , 2 , 2 , 6} , 结 果 为 2.
12. 假 设 要 在 足 够 多 的 会 场 里 安 排 一 批 活 动 , n 个 活 动 编 号 为 1 ~ n , 每 个 活 动 有 开 始
时 间 b i
和 结 束 时 间 e i ( 1 ≤ i ≤ n ) 。 设 计 一 个 有 效 的 贪 心 算 法 求 出 最 少 的 会 场 个 数 。
13. 给 定 一 个 m × n 的 数 字 矩 阵 , 计 算 从 左 到 右 走 过 该 矩 阵 且 经 过 的 方 格 中 整 数 最 小 的 路 径 。 一 条 路 径 可 以 从 第 1 列 的 任 意 位 置 出 发 , 到 达 第 n 列 的 任 意 位 置 , 每 一 步 为 从 第 i 列 走 到 第 i +1 列 相 邻 行 ( 水 平 移 动 或 沿 45 度 斜 线 移 动 ) , 如 图 1.41 所 示 。 第 1 行 和 最 后 一 行 看 作 是 相 邻 的 , 即 应 当 把 这 个 矩 阵 看 成 是 一 个 卷 起 来 的 圆 筒 。
图 1.41 每 一 步 的 走 向
两 个 略 有 不 同 的 5×6 的 数 字 矩 阵 的 最 小 路 径 如 图 1.42 所 示 , 只 有 最 下 面 一 行 的 数 不 同 。 右 边 矩 阵 的 路 径 利 用 了 第 一 行 与 最 后 一 行 相 邻 的 性 质 。
输 入 : 包 含 多 个 矩 阵 , 每 个 矩 阵 的 第 一 行 为 两 个 数 m 和 n , 分 别 表 示 矩 阵 的 行 数 和 列 数 , 接 下 来 的 m × n 个 整 数 按 行 优 先 的 顺 序 排 列 , 即 前 n 个 数 组 成 第 一 行 , 接 下 的 n 个 数 组 成 第 2 行 , 依 此 类 推 。 相 邻 整 数 间 用 一 个 或 多 个 空 格 分 隔 。 注 意 这 些 数 不 一 定 是 正 数 。 输 入 中 可 能 有 一 个 或 多 个 矩 阵 描 述 , 直 到 输 入 结 束 。 每 个 矩 阵 的 行 数 在 1 到 10 之 间 , 列 数 在 1 到 100 之 间 。
输 出 : 对 每 个 矩 阵 输 出 两 行 , 第 一 行 为 最 小 整 数 之 和 的 路 径 , 路 径 由 n 个 整 数 组 成 , 表 示 路 径 经 过 的 行 号 , 如 果 这 样 的 路 径 不 止 一 条 , 输 出 字 典 序 最 小 一 条 。
54
输 入 样 本 :
图 1.42 个 字 阵 最
2
----------------------------------------------------- Page 55 -----------------------------------------------------
第 1 章
概 论
6 18 2 7 4
5 9 3 9 9 5
8 4 1 326
37 2 8 64
输 出 结 果 :
1 2 3 4 4 5
16
1.7.2 练 习 题 参 考 答 案
1. 答 : C 。
2. 答 : n 皇 后 问 题 的 解 不 满 足 贪 心 选 择 性 质 。 答 案 为 B 。
3. 答 : D 。
4. 答 : 由 于 背 包 问 题 可 以 取 物 品 的 一 部 分 , 所 以 总 价 值 一 定 大 于 等 于 做 0/1 背 包 问 题 。 答 案 为 D 。
5. 答 : 这 里 n =215 , 哈 夫 曼 树 中 n 1 =0 , 而 n 0 = n 2 +1 , n = n 0 + n 1 + n 2 =2n 0 - 1 , n 0 =( n +1)/2=108 。 答 案 为 B 。
6. 答 : 在 构 造 哈 夫 曼 树 时 每 次 都 是 将 两 棵 根 结 点 最 小 的 树 合 并 , 从 而 体 现 贪 心 的 思 路 。
7. 证 明 : 例 如 , n =3 , w ={3 , 2 , 2} , v ={7 , 4 , 4} , W =4 时 , 由 于 7/3 最 大 , 若 按 题 目 要 求 的 方 法 , 只 能 取 第 一 个 , 收 益 是 7 。 而 此 实 例 的 最 大 的 收 益 应 该 是 8 , 取 第 2 、 3 个 物 品 。
8. 解 : 用 结 构 体 数 组 A 存 放 硬 币 数 据 , A [ i ]. v 存 放 硬 币 i 的 面 额 , A [ i ]. c 存 放 硬 币 i 的 枚 数 。 采 用 贪 心 思 路 , 首 先 将 数 组 A 按 面 额 递 减 排 序 , 再 兑 换 硬 币 , 每 次 尽 可 能 兑 换 面 额 大 的 硬 币 。 对 应 的 完 整 程 序 如 下 :
#include
#include
using namespace std;
#define min(x,y) ((x)<(y)?(x):(y))
#define MAX 21
// 问 题 表 示
int n=7;
struct NodeType
{ int v;
int c;
// 面 额
// 枚 数
bool operator<(const NodeType &s)
{
// 用 于 按 面 额 递 减 排 序
return s.v}
};
NodeType A[]={{1,12},{2,8},{5,6},{50,10},{10,8},{200,1},{100,4}};
int W;
// 求 解 结 果 表 示
int ans=0;
void solve()
// 兑 换 的 硬 币 枚 数 // 兑 换 硬 币
55
----------------------------------------------------- Page 56 -----------------------------------------------------
{ sort(A,A+n);
算 法 设 计
// 按 面 额 递 减 排 序
for (int i=0;i{ int t=min(W/A[i].v,A[i].c); // 使 用 硬 币 i 的 枚 数 if (t!=0)
printf(" 支 付 %3d 面 额 : %3d 枚 \ n",A[i].v,t);
W-=t*A[i].v;
ans+=t;
if (W==0) break;
}
}
void main()
{ W=325;
// 剩 余 的 金 额
// 支 付 的 金 额
printf(" 支 付 % d 分 : \n",W);
solve();
printf(" 最 少 硬 币 的 个 数 : %d 枚 \ n",ans); }
上 述 程 序 的 执 行 结 果 如 图 1.43 所 示 。
图 1.43
程 序 执 行 结 果
9. 解 : 采 用 贪 心 方 法 求 解 。 用 a [0.. k ] 存 放 n 的 分 解 结 果 :
( 1 ) n ≤ 4 时 可 以 验 证 其 分 解 成 几 个 正 整 数 的 和 的 乘 积 均 小 于 n , 没 有 解 。
( 2 ) n >4 时 , 把 n 分 拆 成 若 干 个 互 不 相 等 的 自 然 数 的 和 , 分 解 数 的 个 数 越 多 乘 积 越 大 。 为 此 让 n 的 分 解 数 个 数 尽 可 能 多 ( 体 现 贪 心 的 思 路 ) , 把 n 分 解 成 从 2 开 始 的 连 续 的 自 然 数 之 和 。 例 如 , 分 解 n 为 a [0]=2 , a [1]=3 , a [2]=4 , … , a [ k ]= k +2 ( 共 有 k +1 个 分 解 数 ) , 用 m 表 示 剩 下 数 , 这 样 的 分 解 直 到 m ≤ a [ k ] 为 止 , 即 m ≤ k +2 。 对 剩 下 数 m 的 处 理 分 为 如 下 两 种 情 况 :
① m < k +2 : 将 m 平 均 分 解 到 a [ k .. i ] ( 对 应 的 分 解 数 个 数 为 m ) 中 , 即 从 a [ k ] 开 始 往 前 的 分 解 数 增 加 1 ( 也 是 贪 心 的 思 路 , 分 解 数 越 大 加 1 和 乘 积 也 越 大 ) 。
② m = k +2 : 将 a [0.. k - 1] ( 对 应 的 分 解 数 个 数 为 k ) 的 每 个 分 解 数 增 加 1 , 剩 下 的 2 增 加 到 a [ k ] 中 , 即 a [ k ] 增 加 2 。
对 应 的 完 整 程 序 如 下 :
#include
#include
#define MAX 20
// 问 题 表 示
int n;
// 求 解 结 果 表 示
56
----------------------------------------------------- Page 57 -----------------------------------------------------
第 1 章
概 论
int a[MAX];
int k=0;
void solve()
{ int i;
int sum=1;
if (n<4)
return;
else
{ int m=n;
a[0]=2;
m-=a[0];
k=0;
while (m>a[k])
{ k++;
// 存 放 被 分 解 的 数
//a[0..k] 存 放 被 分 解 的 数
// 求 解 n 的 最 大 乘 积 分 解 问 题
// 不 存 在 最 优 方 案 , 直 接 返 回
//m 表 示 剩 下 数
// 第 一 个 数 从 2 开 始
// 减 去 已 经 分 解 的 数
// 若 剩 下 数 大 于 最 后 一 个 分 解 数 , 则 继 续 分 解 //a 数 组 下 标 + 1
a[k]=a[k-1]+1; // 按 2 、 3 、 4 递 增 顺 序 分 解
m-=a[k];
}
if (m// 减 去 最 新 分 解 的 数
// 若 剩 下 数 小 于 a [k], 从 a [k] 开 始 往 前 的 数 + 1
{ for (i=0; ia[k-i]+=1;
}
if (m==a[k])
{ a[k]+=2;
// 若 剩 下 数 等 于 a [k], 则 a [k] 的 值 + 2, 之 前 的 数 + 1
for (i=0; ia[i]+=1;
}
}
}
void main()
{ n=23;
memset(a,0,sizeof(a));
solve();
printf("%d 的 最 优 分 解 方 案 \ n",n); int mul=1;
printf(" 分 解 的 数 : ");
for (int i=0;i<=k;i++)
if (a[i]!=0)
{ printf("%d ",a[i]);
mul*=a[i];
}
printf("\n 乘 积 最 大 值 : %d\n",mul); }
上 述 程 序 的 执 行 结 果 如 图 1.44 所 示 。
57
----------------------------------------------------- Page 58 -----------------------------------------------------
图 1.44
算 法 设 计
程 序 执 行 结 果
10. 解 : 采 用 贪 心 思 路 , 首 先 按 体 重 递 增 排 序 ; 再 考 虑 前 后 的 两 个 人 ( 最 轻 者 和 最 重 者 ) , 分 别 用 i 、 j 指 向 : 若 w [ i ]+ w [ j ] ≤ C , 说 明 这 两 个 人 可 以 同 乘 ( 执 行 i ++ , j -- ) , 否 则 w [ j ] 单 乘 ( 执 行 j -- ) , 若 最 后 只 剩 余 一 个 人 , 该 人 只 能 单 乘 。
对 应 的 完 整 程 序 如 下 :
#include
#include
using namespace std;
#define MAXN 101
// 问 题 表 示
int n=7;
int w[]={50,65,58,72,78,53,82};
int C=150;
// 求 解 结 果 表 示
int bests=0;
void Boat()
{ sort(w,w+n);
int i=0;
int j=n - 1;
while (i<=j)
{ if(i==j)
// 求 解 乘 船 问 题 // 递 增 排 序
// 剩 下 最 后 一 个 人
{ printf(" 一 艘 船 : %d\n",w[i]); bests++;
break;
}
if (w[i]+w[j]<=C) // 前 后 两 个 人 同 乘
{ printf(" 一 艘 船 : %d %d\n",w[i],w[j]); bests++;
i++;
j--;
}
else
//w[j] 单 乘
{ printf(" 一 艘 船 : %d\n",w[j]); bests++;
j--;
}
}
}
void main()
{ printf(" 求 解 结 果 : \n");
Boat();
printf(" 最 少 的 船 数 = %d\n",bests);
}
上 述 程 序 的 执 行 结 果 如 图 1.45 所 示 。
58
----------------------------------------------------- Page 59 -----------------------------------------------------
第 1 章
概 论
图 1.45
程 序 执 行 结 果
11. 解 : 采 用 贪 心 思 路 。 每 次 都 在 还 未 安 排 的 容 量 最 大 的 会 议 室 安 排 尽 可 能 多 的 参 会 人 数 , 即 对 于 每 个 会 议 室 , 都 安 排 当 前 还 未 安 排 的 会 议 中 , 参 会 人 数 最 多 的 会 议 。 若 能 容 纳 下 , 则 选 择 该 会 议 , 否 则 找 参 会 人 数 次 多 的 会 议 来 安 排 , 直 到 找 到 能 容 纳 下 的 会 议 。
对 应 的 完 整 程 序 如 下 :
#include
#include
using namespace std;
// 问 题 表 示
int n=4;
int m=4;
int A[]={3,4,3,1};
int B[]={1,2,2,6}; // 求 解 结 果 表 示 int ans=0;
void solve()
{ sort(A,A+n);
sort(B,B+m);
// 会 议 个 数 // 会 议 室 个 数
// 求 解 算 法 // 递 增 排 序 // 递 增 排 序
int i=n-1,j=m-1; // 从 最 多 人 数 会 议 和 最 多 容 纳 人 数 会 议 室 开 始
for(i;i>=0;i--)
{ if(A[i]<=B[j] && j>=0)
{ ans++; // 不 满 足 条 件 , 增 加 一 个 会 议 室
j--;
}
}
}
void main()
{ solve();
printf("%d\n",ans); // 输 出 2
}
12. 解 : 与 《 教 程 》 例 7.2 类 似 , 会 场 对 应 蓄 栏 , 只 是 这 里 仅 仅 求 会 场 个 数 , 即 最 大 兼 容 活 动 子 集 的 个 数 。 对 应 的 完 整 程 序 如 下 :
#include
#include
#include
using namespace std;
#define MAX 51
// 问 题 表 示
struct Action
// 活 动 的 类 型 声 明 59
----------------------------------------------------- Page 60 -----------------------------------------------------
{ int b;
int e;
算 法 设 计
// 活 动 起 始 时 间 // 活 动 结 束 时 间
bool operator<(const Action &s) const // 重 载 < 关 系 函 数
{ if (e==s.e)
return b<=s.b;
else
return e<=s.e;
}
// 结 束 时 间 相 同 按 开 始 时 间 递 增 排 序 // 否 则 按 结 束 时 间 递 增 排 序
};
int n=5;
Action A[]={{0},{1,10},{2,4},{3,6},{5,8},{4,7}}; // 下 标 0 不 用 // 求 解 结 果 表 示
int ans;
void solve()
{ bool flag[MAX];
memset(flag,0,sizeof(flag));
sort(A+1,A+n+1);
ans=0;
for (int j=1;j<=n;j++)
{ if (!flag[j])
{ flag[j]=true;
int preend=j;
// 最 少 会 场 个 数
// 求 解 最 大 兼 容 活 动 子 集 // 活 动 标 志
//A[1..n] 按 指 定 方 式 排 序 // 会 场 个 数
// 前 一 个 兼 容 活 动 的 下 标
for (int i=preend+1;i<=n;i++)
{ if (A[i].b>=A[preend].e && !flag[i]) { preend=i;
flag[i]=true;
}
}
ans++;
// 增 加 一 个 最 大 兼 容 活 动 子 集
}
}
}
void main()
{ solve();
printf(" 求 解 结 果 \ n");
printf(" 最 少 会 场 个 数 : %d\n",ans); // 输 出 4
}
13. 解 : 采 用 贪 心 思 路 。 从 第 1 列 开 始 每 次 查 找 a [ i ][ j ] 元 素 上 、 中 、 下 3 个 对 应 数 中 的 最 小 数 。 对 应 的 程 序 如 下 :
#include
#define M 12
#define N 110
int m=5, n=6;
int a[M][N]={{3,4,1,2,8,6},{6,1,8,2,7,4},{5,9,3,9,9,5},{8,4,1,3,2,6},{3,7,2,8,6,4}};
int minRow,minCol;
int minValue(int i, int j)
// 求 a [i][j] 有 方 上 、 中 、 下 3 个 数 的 最 小 数 , 同 时 要 把 行 标 记 录 下 来
{
int s = (i == 0) ? m - 1 : i - 1; int x = (i == m - 1) ? 0 : i + 1;
60
----------------------------------------------------- Page 61 -----------------------------------------------------
第 1 章
概 论
minRow = s;
minRow = a[i][j+1] < a[minRow][j+1] ? i : minRow;
minRow = a[x][j+1] < a[minRow][j+1] ? x : minRow;
minRow = a[minRow][j+1] == a[s][j+1] && minRow > s ? s : minRow; minRow = a[minRow][j+1] == a[i][j+1] && minRow > i ? i : minRow; minRow = a[minRow][j+1] == a[x][j+1] && minRow > x ? x : minRow; return a[minRow][j+1];
}
void solve()
{ int i,j,min;
for (j=n-2; j>=0; j--)
for (i=0; ia[i][j]+= minValue(i,j);
min=a[0][0];
minRow=0;
for (i=1; iif (a[i][0]{ min=a[i][0];
minRow=i;
}
for (j=0; j{ printf("%d",minRow+1);
if (jminValue(minRow, j);
}
printf("\n%d\n",min);
}
void main()
{
solve();
}
// 在 第 一 列 查 找 最 小 代 价 的 行
1.8 第 8 章 ─ 动 态 规 划
1.8.1 练 习 题
1. 下 列 算 法 中 通 常 以 自 底 向 上 的 方 式 求 解 最 优 解 的 是 ( ) 。
A. 备 忘 录 法 B. 动 态 规 划 法 C. 贪 心 法 D. 回 溯 法 2. 备 忘 录 方 法 是 ( ) 算 法 的 变 形 。
A. 分 治 法
B. 回 溯 法
C. 贪 心 法 D. 动 态 规 划 法
3. 下 列 是 动 态 规 划 算 法 基 本 要 素 的 是 ( ) 。
A. 定 义 最 优 解 B. 构 造 最 优 解 C. 算 出 最 优 解 D. 子 问 题 重 叠 性 质 4. 一 个 问 题 可 用 动 态 规 划 算 法 或 贪 心 算 法 求 解 的 关 键 特 征 是 问 题 的 ( ) 。
A. 贪 心 选 择 性 质 B. 重 叠 子 问 题 C. 最 优 子 结 构 性 质 D. 定 义 最 优 解 5. 简 述 动 态 规 划 法 的 基 本 思 路 。
6. 简 述 动 态 规 划 法 与 贪 心 法 的 异 同 。
61
----------------------------------------------------- Page 62 -----------------------------------------------------
算 法 设 计
7. 简 述 动 态 规 划 法 与 分 治 法 的 异 同 。
8. 下 列 算 法 中 哪 些 属 于 动 态 规 划 算 法 ?
( 1 ) 顺 序 查 找 算 法
( 2 ) 直 接 插 入 排 序 算 法
( 3 ) 简 单 选 择 排 序 算 法
( 4 ) 二 路 归 并 排 序 算 法
9. 某 个 问 题 对 应 的 递 归 模 型 如 下 :
f (1)=1
f (2)=2
f ( n )= f ( n - 1)+ f ( n - 2)+ … + f (1)+1
可 以 采 用 如 下 递 归 算 法 求 解 : long f(int n)
{ if (n==1) return 1;
if (n==2) return 2;
long sum=1;
for (int i=1;i<=n-1;i++)
sum+=f(i);
return sum;
当 n >2 时
}
但 其 中 存 在 大 量 的 重 复 计 算 , 请 采 用 备 忘 录 方 法 求 解 。
10. 第 3 章 中 的 实 验 4 采 用 分 治 法 求 解 半 数 集 问 题 , 如 果 直 接 递 归 求 解 会 存 在 大 量 重 复 计 算 , 请 改 进 该 算 法 。
11. 设 计 一 个 时 间 复 杂 度 为 O( n ) 的 算 法 来 计 算 二 项 式 系 数 C n ( k ≤ n ) 。 二 项 式 系 数 C n 的 求 值 过 程 如 下 :
? = 1
? = 1
? ? ‒ 1 ?
? ? ‒ 1 ? ‒ 1
当 i ≥ j
12. 一 个 机 器 人 只 能 向 下 和 向 右 移 动 , 每 次 只 能 移 动 一 步 , 设 计 一 个 算 法 求 它 从 ( 0 , 0 ) 移 动 到 ( m , n ) 有 多 少 条 路 径 。
13. 两 种 水 果 杂 交 出 一 种 新 水 果 , 现 在 给 新 水 果 取 名 , 要 求 这 个 名 字 中 包 含 了 以 前 两 种 水 果 名 字 的 字 母 , 并 且 这 个 名 字 要 尽 量 短 。 也 就 是 说 以 前 的 一 种 水 果 名 字 arr1 是 新 水 果 名 字 arr 的 子 序 列 , 另 一 种 水 果 名 字 arr2 也 是 新 水 果 名 字 arr 的 子 序 列 。 设 计 一 个 算 法 求 arr 。
例 如 : 输 入 以 下 3 组 水 果 名 称 :
apple peach
ananas banana
pear peach
输 出 的 新 水 果 名 称 如 下 :
62
2
k
n
k
0
?
?
?
? = ?
+ ?
----------------------------------------------------- Page 63 -----------------------------------------------------
第 1 章
概 论
appleach
bananas
pearch
1.8.2 练 习 题 参 考 答 案
1. 答 : B 。
2. 答 : D 。
3. 答 : D 。
4. 答 : C 。
5. 答 : 动 态 规 划 法 的 基 本 思 路 是 将 待 求 解 问 题 分 解 成 若 干 个 子 问 题 , 先 求 子 问 题 的 解 , 然 后 从 这 些 子 问 题 的 解 得 到 原 问 题 的 解 。
6. 答 : 动 态 规 划 法 的 3 个 基 本 要 素 是 最 优 子 结 构 性 质 、 无 后 效 性 和 重 叠 子 问 题 性 质 , 而 贪 心 法 的 两 个 基 本 要 素 是 贪 心 选 择 性 质 和 最 优 子 结 构 性 质 。 所 以 两 者 的 共 同 点 是 都 要 求 问 题 具 有 最 优 子 结 构 性 质 。
两 者 的 不 同 点 如 下 :
( 1 ) 求 解 方 式 不 同 , 动 态 规 划 法 是 自 底 向 上 的 , 有 些 具 有 最 优 子 结 构 性 质 的 问 题 只 能 用 动 态 规 划 法 , 有 些 可 用 贪 心 法 。 而 贪 心 法 是 自 顶 向 下 的 。
( 2 ) 对 子 问 题 的 依 赖 不 同 , 动 态 规 划 法 依 赖 于 各 子 问 题 的 解 , 所 以 应 使 各 子 问 题 最 优 , 才 能 保 证 整 体 最 优 ; 而 贪 心 法 依 赖 于 过 去 所 作 过 的 选 择 , 但 决 不 依 赖 于 将 来 的 选 择 , 也 不 依 赖 于 子 问 题 的 解 。
7. 答 : 两 者 的 共 同 点 是 将 待 求 解 的 问 题 分 解 成 若 干 子 问 题 , 先 求 解 子 问 题 , 然 后 再 从 这 些 子 问 题 的 解 得 到 原 问 题 的 解 。
两 者 的 不 同 点 是 : 适 合 于 用 动 态 规 划 法 求 解 的 问 题 , 分 解 得 到 的 各 子 问 题 往 往 不 是 相 互 独 立 的 ( 重 叠 子 问 题 性 质 ) , 而 分 治 法 中 子 问 题 相 互 独 立 ; 另 外 动 态 规 划 法 用 表 保 存 已 求 解 过 的 子 问 题 的 解 , 再 次 碰 到 同 样 的 子 问 题 时 不 必 重 新 求 解 , 而 只 需 查 询 答 案 , 故 可 获 得 多 项 式 级 时 间 复 杂 度 , 效 率 较 高 , 而 分 治 法 中 对 于 每 次 出 现 的 子 问 题 均 求 解 , 导 致 同 样 的 子 问 题 被 反 复 求 解 , 故 产 生 指 数 增 长 的 时 间 复 杂 度 , 效 率 较 低 。
8. 答 : 判 断 算 法 是 否 具 有 最 优 子 结 构 性 质 、 无 后 效 性 和 重 叠 子 问 题 性 质 。 ( 2 ) 、 ( 3 ) 和 ( 4 ) 均 属 于 动 态 规 划 算 法 。
9. 解 : 设 计 一 个 dp 数 组 , dp[ i ] 对 应 f ( i ) 的 值 , 首 先 dp 的 所 有 元 素 初 始 化 为 0 , 在 计 算 f ( i ) 时 , 若 dp[0]>0 表 示 f ( i ) 已 经 求 出 , 直 接 返 回 dp[ i ] 即 可 , 这 样 避 免 了 重 复 计 算 。 对 应 的 算 法 如 下 :
long dp[MAX]; //dp[n] 保 存 f (n) 的 计 算 结 果
long f1(int n)
{ if (n==1)
{ dp[n]=1;
return dp[n];
}
if (n==2)
{ dp[n]=2;
return dp[n];
63
----------------------------------------------------- Page 64 -----------------------------------------------------
算 法 设 计
}
if (dp[n]>0) return dp[n];
long sum=1;
for (int i=1;i<=n-1;i++)
sum+=f1(i);
dp[n]=sum;
return dp[n];
}
10. 解 : 设 计 一 个 数 组 a , 其 中 a [ i ]= f ( i ) , 首 先 将 a 的 所 有 元 素 初 始 化 为 0 , 当 a [ i ]>0 时 表 示 对 应 的 f ( i ) 已 经 求 出 , 直 接 返 回 就 可 以 了 。 对 应 的 完 整 程 序 如 下 :
#include
#include
#define MAXN 201
// 问 题 表 示
int n;
int a[MAXN];
int fa(int i)
{ int ans=1;
if (a[i]>0)
return a[i];
for(int j=1;j<=i/2;j++)
ans+=fa(j);
a[i]=ans;
return ans;
}
int solve(int n)
{ memset(a,0,sizeof(a));
a[1]=1;
return fa(n);
}
void main()
{ n=6;
// 求 a [i]
// 求 s et(n) 的 元 素 个 数
printf(" 求 解 结 果 \ n");
printf(" n=%d 时 半 数 集 元 素 个 数 = %d\n",n,solve(n));
}
11. 解 : 定 义 C ( i , j )= C i
, i ≥ j 。 则 有 如 下 递 推 计 算 公 式 : C ( i , n j )= C ( i - 1 , j - 1)+C( i -
1 , j ) , 初 始 条 件 为 C ( i , 0)=1 , C ( i , i )=1 。 可 以 根 据 初 始 条 件 由 此 递 推 关 系 计 算 C ( n , k ) ,
n
即 C n 。 对 应 的 程 序 如 下 :
#include
#define MAXN 51
#define MAXK 31
// 问 题 表 示
int n,k;
// 求 解 结 果 表 示
int C[MAXN][MAXK];
void solve()
{ int i,j;
64
j
k
----------------------------------------------------- Page 65 -----------------------------------------------------
第 1 章
概 论
for (i=0;i<=n;i++)
{ C[i][i]=1;
C[i][0]=1;
}
for (i=1;i<=n;i++)
for (j=1;j<=k;j++)
C[i][j]=C[i-1][j-1]+C[i-1][j];
}
void main()
{ n=5,k=3;
solve();
printf("%d\n",C[n][k]); // 输 出 1 0
}
显 然 , solve() 算 法 的 时 间 复 杂 度 为 O( n ) 。
12. 解 : 设 从 ( 0 , 0 ) 移 动 到 ( i , j ) 的 路 径 条 数 为 dp[ i ][ j ] , 由 于 机 器 人 只 能 向 下 和 向 右 移 动 , 不 同 于 迷 宫 问 题 ( 迷 宫 问 题 由 于 存 在 后 退 , 不 满 足 无 后 效 性 , 不 适 合 用 动 态 规 划 法 求 解 ) 。 对 应 的 状 态 转 移 方 程 如 下 :
dp[0][ j ]=1
dp[ i ][0]=1
dp[ i ][ j ]=dp[ i ][ j - 1]+dp[ i - 1][ j ]
i 、 j >0
最 后 结 果 是 dp[ m ][ n ] 。 对 应 的 程 序 如 下 :
#include
#include
#define MAXX 51
#define MAXY 51
// 问 题 表 示
int m,n;
// 求 解 结 果 表 示
int dp[MAXX][MAXY];
void solve()
{ int i,j;
dp[0][0]=0;
memset(dp,0,sizeof(dp));
for (i=1;i<=m;i++)
dp[i][0]=1;
for (j=1;j<=n;j++)
dp[0][j]=1;
for (i=1;i<=m;i++)
for (j=1;j<=n;j++)
dp[i][j]=dp[i][j-1]+dp[i-1][j];
}
void main()
{ m=5,n=3;
solve();
printf("%d\n",dp[m][n]);
}
13. 解 : 本 题 目 的 思 路 是 求 arr1 和 arr2 字 符 串 的 最 长 公 共 子 序 列 , 基 本 过 程 参 见 《 教
65
2
----------------------------------------------------- Page 66 -----------------------------------------------------
程 》 第 8 章 8.5 节 。 对 应 的 完 整 程 序 如 下 : #include
#include
#include
#include
using namespace std;
#define max(x,y) ((x)>(y)?(x):(y))
#define MAX 51
// 问 题 表 示
int m,n;
string arr1,arr2;
// 求 解 结 果 表 示
int dp[MAX][MAX];
vector subs;
void LCSlength()
{ int i,j;
for (i=0;i<=m;i++)
dp[i][0]=0;
for (j=0;j<=n;j++)
dp[0][j]=0;
for (i=1;i<=m;i++)
for (j=1;j<=n;j++)
算 法 设 计
// 序 列 中 最 多 的 字 符 个 数
// 动 态 规 划 数 组
// 存 放 L CS
// 求 d p
// 将 d p[i][0] 置 为 0 , 边 界 条 件
// 将 d p[0][j] 置 为 0 , 边 界 条 件
// 两 重 f or 循 环 处 理 arr1 、 arr2 的 所 有 字 符
{ if (arr1[i-1]==arr2[j-1]) // 比 较 的 字 符 相 同
dp[i][j]=dp[i-1][j-1]+1;
else
// 比 较 的 字 符 不 同
dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
}
}
void Buildsubs()
{ int k=dp[m][n];
int i=m;
int j=n;
while (k>0)
// 由 d p 构 造 从 s ubs
//k 为 a rr1 和 a rr2 的 最 长 公 共 子 序 列 长 度
// 在 s ubs 中 放 入 最 长 公 共 子 序 列 ( 反 向 )
if (dp[i][j]==dp[i-1][j]) i--;
else if (dp[i][j]==dp[i][j-1]) j--;
else
{ subs.push_back(arr1[i-1]); //subs 中 添 加 a rr1[i-1] i--; j--; k--;
}
}
void main()
{ cin >> arr1 >> arr2; m=arr1.length();
n=arr2.length();
LCSlength();
Buildsubs();
cout << " 求 解 结 果 " << endl;
cout << " arr: "; vector::reverse_iterator rit;
// 输 入 a rr1 和 a rr2 //m 为 a rr1 的 长 度 //n 为 a rr2 的 长 度 // 求 出 d p
// 求 出 L CS
66
----------------------------------------------------- Page 67 -----------------------------------------------------
第 1 章
for (rit=subs.rbegin();rit!=subs.rend();++rit)
cout << *rit;
cout << endl;
cout << " 长 度 : " << dp[m][n] << endl;
概 论
}
改 为 如 下 :
13. 解 : 本 题 目 的 思 路 是 先 求 arr1 和 arr2 字 符 串 的 最 长 公 共 子 序 列 , 基 本 过 程 参 见 《 教 程 》 第 8 章 8.5 节 , 再 利 用 递 归 输 出 新 水 果 取 名 。
算 法 中 设 置 二 维 动 态 规 划 数 组 dp , dp[i][j] 表 示 arr1[0..i-1] ( i 个 字 母 ) 和 arr2[0..j-1] ( j 个 字 母 ) 中 最 长 公 共 子 序 列 的 长 度 。 另 外 设 置 二 维 数 组 b , b[i][j] 表 示 arr1 和 arr2 比 较 的 3 种 情 况 : b[i][j]=0 表 示 arr1[i-1]=arr2[j-1] , b[i][j]=1 表 示 arr1[i-1] ≠ arr2[j-1] 并 且 dp[i- 1][j]>dp[i][j-1] , b[i][j]=2 表 示 arr1[i-1] ≠ arr2[j-1] 并 且 dp[i-1][j] ≤ dp[i][j-1] 。
对 应 的 完 整 程 序 如 下 :
#include
#include
#define MAX 51
// 问 题 表 示
int m,n;
char arr1[MAX],arr2[MAX];
// 求 解 结 果 表 示
int dp[MAX][MAX];
int b[MAX][MAX];
void Output(int i,int j)
{ if (i==0 && j==0)
return;
if(i==0)
{ Output(i,j-1);
printf("%c",arr2[j-1]);
return;
}
else if(j==0)
{ Output(i-1,j);
printf("%c",arr1[i-1]);
return;
}
if (b[i][j]==0)
{ Output(i-1,j-1);
printf("%c",arr1[i-1]);
return;
}
else if(b[i][j]==1)
{ Output(i-1,j);
printf("%c",arr1[i-1]);
return;
}
else
{ Output(i,j-1);
// 序 列 中 最 多 的 字 符 个 数
// 动 态 规 划 数 组
// 存 放 a rr1 与 a rr2 比 较 的 3 种 情 况 // 利 用 递 归 输 出 新 水 果 取 名
// 输 出 完 毕
//arr1 完 毕 , 输 出 arr2 的 剩 余 部 分
//arr2 完 毕 , 输 出 a rr1 的 剩 余 部 分
//arr1[i-1]=arr2[j-1] 的 情 况
67
----------------------------------------------------- Page 68 -----------------------------------------------------
算 法 设 计
printf("%c",arr2[j-1]);
return;
}
}
void LCSlength()
{ int i,j;
for (i=0;i<=m;i++)
dp[i][0]=0;
for (j=0;j<=n;j++)
dp[0][j]=0;
for (i=1;i<=m;i++)
for (j=1;j<=n;j++)
{ if (arr1[i-1]==arr2[j-1])
{ dp[i][j]=dp[i-1][j-1]+1;
b[i][j]=0;
}
else if (dp[i-1][j]>dp[i][j-1])
{ dp[i][j]=dp[i-1][j];
b[i][j]=1;
}
else
{ dp[i][j]=dp[i][j-1];
b[i][j]=2;
}
}
}
void main()
{ int t;
printf(" 测 试 用 例 个 数 : "); scanf("%d",&t);
while(t--)
{ scanf("%s",arr1);
scanf("%s",arr2);
memset(b,-1,sizeof(b));
m=strlen(arr1);
n=strlen(arr2);
LCSlength();
printf(" 结 果 : "); Output(m,n); printf("\n");
}
}
上 述 程 序 的 一 次 执 行 结 果 如 图 1.46 所 示 。
// 求 d p
// 将 d p[i][0] 置 为 0 , 边 界 条 件
// 将 d p[0][j] 置 为 0 , 边 界 条 件
// 两 重 f or 循 环 处 理 a rr1 、 arr2 的 所 有 字 符 // 比 较 的 字 符 相 同 : 情 况 0
// 情 况 1
//dp[i-1][j]<=dp[i][j-1]: 情 况 2
// 输 入 测 试 用 例 个 数
//m 为 a rr1 的 长 度
//n 为 a rr2 的 长 度
// 求 出 d p
// 输 出 新 水 果 取 名
68
----------------------------------------------------- Page 69 -----------------------------------------------------
第 1 章
概 论
图 1.46
程 序 的 一 次 执 行 结 果
13. 解 : 本 题 目 的 思 路 是 求 arr1 和 arr2 字 符 串 的 最 长 公 共 子 序 列 , 基 本 过 程 参 见 《 教 程 》 第 8 章 8.5 节 。 对 应 的 完 整 程 序 如 下 :
然 后 再 用 递 归 思 想 , 逐 一 输 出 , 得 到 的 就 是 最 后 答 案 。
#include
#include
#include
#include
using namespace std;
#define max(x,y) ((x)>(y)?(x):(y))
#define MAX 51
// 问 题 表 示
int m,n;
string arr1,arr2;
// 求 解 结 果 表 示
int dp[MAX][MAX];
vector subs;
void LCSlength()
{ int i,j;
for (i=0;i<=m;i++)
dp[i][0]=0;
for (j=0;j<=n;j++)
dp[0][j]=0;
for (i=1;i<=m;i++)
for (j=1;j<=n;j++)
// 序 列 中 最 多 的 字 符 个 数
// 动 态 规 划 数 组
// 存 放 L CS
// 求 d p
// 将 d p[i][0] 置 为 0 , 边 界 条 件
// 将 d p[0][j] 置 为 0 , 边 界 条 件
// 两 重 f or 循 环 处 理 a rr1 、 arr2 的 所 有 字 符
{ if (arr1[i-1]==arr2[j-1]) // 比 较 的 字 符 相 同
dp[i][j]=dp[i-1][j-1]+1;
else
// 比 较 的 字 符 不 同
dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
}
}
void Buildsubs()
{ int k=dp[m][n];
int i=m;
int j=n;
while (k>0)
// 由 d p 构 造 从 s ubs
//k 为 a rr1 和 a rr2 的 最 长 公 共 子 序 列 长 度
// 在 s ubs 中 放 入 最 长 公 共 子 序 列 ( 反 向 )
if (dp[i][j]==dp[i-1][j]) i--;
else if (dp[i][j]==dp[i][j-1]) j--;
69
----------------------------------------------------- Page 70 -----------------------------------------------------
算 法 设 计
else
{ subs.push_back(arr1[i-1]); //subs 中 添 加 a rr1[i-1] i--; j--; k--;
}
}
void main()
{ cin >> arr1 >> arr2; m=arr1.length();
n=arr2.length();
LCSlength();
Buildsubs();
// 输 入 a rr1 和 arr2 //m 为 a rr1 的 长 度 //n 为 a rr2 的 长 度 // 求 出 d p
// 求 出 L CS
cout << " 求 解 结 果 " << endl;
cout << " arr: ";
vector::reverse_iterator rit;
for (rit=subs.rbegin();rit!=subs.rend();++rit)
cout << *rit;
cout << endl;
cout << " 长 度 : " << dp[m][n] << endl;
}
上 述 程 序 的 一 次 执 行 结 果 如 图 1.46 所 示 。
图 1.46
程 序 的 一 次 执 行 结 果
1.9 第 9 章 ─ 图 算 法 设 计
1.9.1 练 习 题
1. 以 下 不 属 于 贪 心 算 法 的 是 ( ) 。
A.Prim 算 法 B.Kruskal 算 法 C.Dijkstra 算 法 D. 深 度 优 先 遍 历 2. 一 个 有 n 个 顶 点 的 连 通 图 的 生 成 树 是 原 图 的 最 小 连 通 子 图 , 且 包 含 原 图 中 所 有 n
个 顶 点 , 并 且 有 保 持 图 联 通 的 最 少 的 边 。 最 大 生 成 树 就 是 权 和 最 大 生 成 树 , 现 在 给 出 一 个 无 向 带 权 图 的 邻 接 矩 阵 为 { {0 , 4 , 5 , 0 , 3} , {4 , 0 , 4 , 2 , 3} , {5 , 4 , 0 , 2 , 0} , {0 , 2 , 2 , 0 , 1} , {3 , 3 , 0 , 1 , 0}} , 其 中 权 为 0 表 示 没 有 边 。 一 个 图 为 求 这 个 图 的 最 大 生 成 树 的 权 和 是 ( ) 。
A.11
B.12
C.13
D.14
E.15
3. 某 个 带 权 连 通 图 有 4 个 以 上 的 顶 点 , 其 中 恰 好 有 2 条 权 值 最 小 的 边 , 尽 管 该 图 的 最 小 生 成 树 可 能 有 多 个 , 而 这 2 条 权 值 最 小 的 边 一 定 包 含 在 所 有 的 最 小 生 成 树 中 吗 ? 如 果 有 3 条 权 值 最 小 的 边 呢 ?
70
----------------------------------------------------- Page 71 -----------------------------------------------------
第 1 章
概 论
4. 为 什 么 TSP 问 题 采 用 贪 心 算 法 求 解 不 一 定 得 到 最 优 解 ?
5. 求 最 短 路 径 的 4 种 算 法 适 合 带 权 无 向 图 吗 ?
6. 求 单 源 最 短 路 径 的 算 法 有 Dijkstra 算 法 、 Bellman - Ford 算 法 和 SPFA 算 法 , 比 较 这 些 算 法 的 不 同 点 。
7. 有 人 这 样 修 改 Dijkstra 算 法 以 便 求 一 个 带 权 连 通 图 的 单 源 最 长 路 径 , 将 每 次 选 择 dist 最 小 的 顶 点 u 改 为 选 择 最 大 的 顶 点 u , 将 按 路 径 长 度 小 进 行 调 整 改 为 按 路 径 长 度 大 调 整 。 这 样 可 以 求 单 源 最 长 路 径 吗 ?
8. 给 出 一 种 方 法 求 无 环 带 权 连 通 图 ( 所 有 权 值 非 负 ) 中 从 顶 点 s 到 顶 点 t 的 一 条 最 长 简 单 路 径 。
9. 一 个 运 输 网 络 如 图 1.47 所 示 , 边 上 数 字 为 ( c ( i , j ) , b ( i , j ) ) , 其 中 c ( i , j ) 表 示 容 量 , b ( i , j ) 表 示 单 位 运 输 费 用 。 给 出 从 1 、 2 、 3 位 置 运 输 货 物 到 位 置 6 的 最 小 费 用 最 大 流 的 过 程 。
10. 本 教 程 中 的 Dijkstra 算 法 采 用 邻 接 矩 阵 存 储 图 , 算 法 时 间 复 杂 度 为 O( n ) 。 请 你 从 各 个 方 面 考 虑 优 化 该 算 法 , 用 于 求 源 点 v 到 其 他 顶 点 的 最 短 路 径 长 度 。
11. 有 一 个 带 权 有 向 图 G ( 所 有 权 为 正 整 数 ) , 采 用 邻 接 矩 阵 存 储 。 设 计 一 个 算 法 求 其 中 的 一 个 最 小 环 。
1
(4,2)
(6,4)
4
(12,3)
2
(3,5)
6
(3,4)
(1,6)
5
(9,12)
3
(2,3)
图 1.47
一 个 运 输 网 络
1.9.2 练 习 题 参 考 答 案
1. 答 : D 。
2. 答 : 采 用 类 似 Kurskal 算 法 来 求 最 大 生 成 树 , 第 1 步 取 最 大 边 ( 0 , 2 ) , 第 2 步 取 边 ( 0 , 1 ) , 第 3 步 取 边 ( 0 , 4 ) , 第 4 步 取 最 大 边 ( 1 , 3 ) , 得 到 的 权 和 为 14 。 答 案 为 D 。
3. 答 : 这 2 条 权 值 最 小 的 边 一 定 包 含 在 所 有 的 最 小 生 成 树 中 , 因 为 按 Kurskal 算 法 一 定 首 先 选 中 这 2 条 权 值 最 小 的 边 。 如 果 有 3 条 权 值 最 小 的 边 , 就 不 一 定 了 , 因 为 首 先 选 中 这 3 条 权 值 最 小 的 边 有 可 能 出 现 回 路 。
4. 答 : TSP 问 题 不 满 足 最 优 子 结 构 性 质 , 如 ( 0 , 1 , 2 , 3 , 0 ) 是 整 个 问 题 的 最 优 解 , 但 ( 0 , 1 , 2 , 0 ) 不 一 定 是 子 问 题 的 最 优 解 。
5. 答 : 都 适 合 带 权 无 向 图 求 最 短 路 径 。
6. 答 : Dijkstra 算 法 不 适 合 存 在 负 权 边 的 图 求 单 源 最 短 路 径 , 其 时 间 复 杂 度 为 O( n ) 。 Bellman - Ford 算 法 和 SPFA 算 法 适 合 存 在 负 权 边 的 图 求 单 源 最 短 路 径 , 但 图 中 不 能
71
2
2
----------------------------------------------------- Page 72 -----------------------------------------------------
算 法 设 计
存 在 权 值 和 为 负 的 环 。 Bellman - Ford 算 法 的 时 间 复 杂 度 为 O( ne ) , 而 SPFA 算 法 的 时 间 复 杂 度 为 O( e ) , 所 以 SPFA 算 法 更 优 。
7. 答 : 不 能 。 Dijkstra 算 法 本 质 上 是 一 种 贪 心 算 法 , 而 求 单 源 最 长 路 径 不 满 足 贪 心 选 择 性 质 。
8. 答 : Bellman - Ford 算 法 和 SPFA 算 法 适 合 存 在 负 权 边 的 图 求 单 源 最 短 路 径 。 可 以 将 图 中 所 有 边 权 值 改 为 负 权 值 , 求 出 从 顶 点 s 到 顶 点 t 的 一 条 最 短 简 单 路 径 , 它 就 是 原 来 图 中 从 顶 点 s 到 顶 点 t 的 一 条 最 长 简 单 路 径 。
9. 答 : 为 该 运 输 网 络 添 加 一 个 虚 拟 起 点 0 , 它 到 1 、 2 、 3 位 置 运 输 费 用 为 0 , 容 量 分 别 为 到 1 、 2 、 3 位 置 运 输 容 量 和 , 如 图 1.48 所 示 , 起 点 s =0 , 终 点 t =6 。
1
(4,2)
(10,0)
(6,4)
4
(12,3)
0
(6,0)
2
(3,5)
6
(3,0)
(3,4)
(1,6)
5
(9,12)
3
(2,3)
图 1.48
添 加 一 个 虚 拟 起 点 的 运 输 网 络
首 先 初 始 化 f 为 零 流 , 最 大 流 量 maxf=0 , 最 小 费 用 mincost=0 , 采 用 最 小 费 用 最 大 流 算 法 求 解 过 程 如 下 :
( 1 ) k =0 , 求 出 w 如 下 :
求 出 从 起 点 0 到 终 点 6 的 最 短 路 径 为 0→1 →4 →6 , 求 出 最 小 调 整 量 =4 , f [4][6] 调 整 为 4 , f [1][4] 调 整 为 4 , f [0][1] 调 整 为 4 , mincost=20 , maxf=4 。
( 2 ) k =1 , 求 出 w 如 下 :
求 出 从 起 点 0 到 终 点 6 的 最 短 路 径 为 0 →2 →4 →6 , 求 出 最 小 调 整 量 =3 , f [4][6] 调 整
72
----------------------------------------------------- Page 73 -----------------------------------------------------
第 1 章
概 论
为 7 , f [2][4] 调 整 为 3 , f [0][2] 调 整 为 3 , mincost=44 , maxf=4+3=7 。 ( 3 ) k =2 , 求 出 w 如 下 :
求 出 从 起 点 0 到 终 点 6 的 最 短 路 径 为 0 →3 →4 →6 , 求 出 最 小 调 整 量 =1 , f [4][6] 调 整 为 8 , f [3][4] 调 整 为 1 , f [0][3] 调 整 为 1 , mincost=53 , maxf=7+1=8 。
( 4 ) k =3 , 求 出 w 如 下 :
求 出 从 起 点 0 到 终 点 6 的 最 短 路 径 为 0 →3 →5 →6 , 求 出 最 小 调 整 量 =2 , f [5][6] 调 整 为 2 , f [3][5] 调 整 为 2 , f [0][3] 调 整 为 3 , mincost=83 , maxf=8+2=10 。
( 5 ) k =4 , 求 出 w 如 下 :
求 出 从 起 点 0 到 终 点 6 的 最 短 路 径 为 0 →1→5 →6 , 求 出 最 小 调 整 量 =6 , f [5][6] 调 整 为 8 , f [1][5] 调 整 为 6 , f [0][1] 调 整 为 10 , mincost=179 , maxf=10+6=16 。
( 6 ) k =5 , 求 出 w 如 下 :
求 出 从 起 点 0 到 终 点 6 的 最 短 路 径 为 0 →1 →5→6 , 求 出 最 小 调 整 量 =1 , f [5][6] 调 整
73
----------------------------------------------------- Page 74 -----------------------------------------------------
算 法 设 计
为 9 , f [2][5] 调 整 为 1 , f [0][2] 调 整 为 4 , mincost=195 , maxf=16+1=17 。 ( 7 ) k =6 , 求 出 的 w 中 没 有 增 广 路 径 , 调 整 结 束 。 对 应 的 最 大 流 如 下 :
最 终 结 果 , maxf=17 , mincost=195 。 即 运 输 的 最 大 货 物 量 为 17 , 对 应 的 最 小 总 运 输 费 用 为 195 。
10. 解 : 从 两 个 方 面 考 虑 优 化 :
( 1 ) 在 Dijkstra 算 法 中 , 当 求 出 源 点 v 到 顶 点 u 的 最 短 路 径 长 度 后 , 仅 仅 调 整 从 顶 点 u 出 发 的 邻 接 点 的 最 短 路 径 长 度 , 而 教 程 中 的 Dijkstra 算 法 由 于 采 用 邻 接 矩 阵 存 储 图 , 需 要 花 费 O( n ) 的 时 间 来 调 整 顶 点 u 出 发 的 邻 接 点 的 最 短 路 径 长 度 , 如 果 采 用 邻 接 表 存 储 图 , 可 以 很 快 查 找 到 顶 点 u 的 所 有 邻 接 点 并 进 行 调 整 , 时 间 为 O(MAX( 图 中 顶 点 的 出 度 ) ) 。
( 2 ) 求 目 前 一 个 最 短 路 径 长 度 的 顶 点 u 时 , 教 科 书 上 的 Dijkstra 算 法 采 用 简 单 比 较 方 法 , 可 以 改 为 采 用 优 先 队 列 ( 小 根 堆 ) 求 解 。 由 于 最 多 e 条 边 对 应 的 顶 点 进 队 , 对 应 的 时 间 为 O(log 2 e ) 。
对 应 的 完 整 程 序 和 测 试 数 据 算 法 如 下 :
#include "Graph.cpp"
#include
#include
using namespace std;
ALGraph *G;
struct Node
{ int i;
int v;
// 包 含 图 的 基 本 运 算 算 法
// 图 的 邻 接 表 存 储 结 构 , 作 为 全 局 变 量 // 声 明 堆 中 结 点 类 型
// 顶 点 编 号
//dist[i] 值
friend bool operator<(const Node &a , const Node &b) // 定 义 比 较 运 算 符 { return a.v > b.v; }
};
void Dijkstra(int v , int dist[]) // 改 进 的 D ijkstra 算 法
{ ArcNode *p;
priority_queue qu;
Node e;
int S[MAXV];
// 创 建 小 根 堆
//S[i]=1 表 示 顶 点 i 在 S 中 , S[i]=0 表 示 顶 点 i 在 U 中
int i , j , u , w;
memset(S , 0 , sizeof(S));
p=G->adjlist[v].firstarc;
for (i=0;in;i++) dist[i]=INF;
while (p!=NULL)
{ w=p->adjvex;
74
----------------------------------------------------- Page 75 -----------------------------------------------------
第 1 章
概 论
dist[w]=p->weight;
e.i=w; e.v=dist[w];
qu.push(e);
p=p->nextarc;
}
S[v]=1;
for (i=0;in-1;i++)
// 距 离 初 始 化
// 将 v 的 出 边 顶 点 进 队 q u
// 源 点 编 号 v 放 入 S 中
// 循 环 直 到 所 有 顶 点 的 最 短 路 径 都 求 出
{ e=qu.top(); qu.pop(); // 出 队 e
u=e.i;
S[u]=1;
p=G->adjlist[u].firstarc;
while (p!=NULL)
{ w=p->adjvex;
if (S[w]==0)
// 选 取 具 有 最 小 最 短 路 径 长 度 的 顶 点 u
// 顶 点 u 加 入 S 中
// 考 察 从 顶 点 u 出 发 的 所 有 相 邻 点
// 考 虑 修 改 不 在 S 中 的 顶 点 w 的 最 短 路 径 长 度
if (dist[u]+p->weight{ dist[w]=dist[u]+p->weight; // 修 改 最 短 路 径 长 度 e.i=w; e.v=dist[w];
qu.push(e); // 修 改 最 短 路 径 长 度 的 顶 点 进 队
}
p=p->nextarc;
}
}
}
void Disppathlength(int v , int dist[]) // 输 出 最 短 路 径 长 度
{ printf(" 从 % d 顶 点 出 发 的 最 短 路 径 长 度 如 下 : \n" , v);
for (int i=0;in;++i)
if (i!=v)
printf(" 到 顶 点 % d: %d\n" , i , dist[i]);
}
void main()
{ int A[MAXV][MAXV]={
{0 , 4 , 6 , 6 , INF , INF , INF} ,
{INF , 0 , 1 , INF , 7 , INF , INF} ,
{INF , INF , 0 , INF , 6 , 4 , INF} ,
{INF , INF , 2 , 0 , INF , 5 , INF} ,
{INF , INF , INF , INF , 0 , INF , 6} ,
{INF , INF , INF , INF , 1 , 0 , 8} ,
{INF , INF , INF , INF , INF , INF , 0}};
int n=7 , e=12;
CreateAdj(G , A , n , e); printf(" 图 G 的 邻 接 表 : \n"); DispAdj(G);
int v=0;
int dist[MAXV];
Dijkstra(v , dist);
Disppathlength(v , dist);
DestroyAdj(G);
// 建 立 图 的 邻 接 表 // 输 出 邻 接 表
// 调 用 Dijkstra 算 法 // 输 出 结 果
// 销 毁 图 的 邻 接 表
}
上 述 程 序 的 执 行 结 果 如 图 1.49 所 示 。
75
----------------------------------------------------- Page 76 -----------------------------------------------------
算 法 设 计
图 1.49 程 序 执 行 结 果
其 中 Dijkstra 算 法 的 时 间 复 杂 度 为 O( n (log 2 e +MAX( 顶 点 的 出 度 ) ) , 一 般 图 中 最 大 顶 点 出 度 远 小 于 e , 所 以 进 一 步 简 化 时 间 复 杂 度 为 O( n log 2 e ) 。
11. 有 一 个 带 权 有 向 图 G ( 所 有 权 为 正 整 数 ) , 采 用 邻 接 矩 阵 存 储 。 设 计 一 个 算 法 求 其 中 的 一 个 最 小 环 。
解 : 利 用 Floyd 算 法 求 出 所 有 顶 点 对 之 间 的 最 短 路 径 , 若 顶 点 i 到 j 有 最 短 路 径 , 而 图 中 又 存 在 顶 点 j 到 i 的 边 , 则 构 成 一 个 环 , 在 所 有 环 中 比 较 找 到 一 个 最 小 环 并 输 出 。 对 应 的 程 序 如 下 :
#include "Graph.cpp"
// 包 含 图 的 基 本 运 算 算 法
#include
using namespace std;
void Dispapath(int path[][MAXV],int i,int j) // 输 出 顶 点 i 到 j 的 一 条 最 短 路 径
{ vector apath;
int k=path[i][j];
apath.push_back(j);
while (k!=-1 && k!=i)
{ apath.push_back(k);
k=path[i][k];
}
apath.push_back(i);
// 存 放 一 条 最 短 路 径 中 间 顶 点 ( 反 向 ) // 路 径 上 添 加 终 点
// 路 径 上 添 加 中 间 点
// 路 径 上 添 加 起 点
for (int s=apath.size()-1;s>=0;s--) // 输 出 路 径 上 的 中 间 顶 点 printf("%d → ",apath[s]);
}
int Mincycle(MGraph g,int A[MAXV][MAXV],int &mini,int &minj) // 在 图 g 和 A 中 的 查 找 一 个 最 小 环
{ int i,j,min=INF;
for (i=0;ifor (j=0;jif (i!=j && g.edges[j][i]{ if (A[i][j]+g.edges[j][i]{ min=A[i][j]+g.edges[j][i];
mini=i; minj=j;
76
----------------------------------------------------- Page 77 -----------------------------------------------------
第 1 章
概 论
}
}
return min;
}
void Floyd(MGraph g)
{ int A[MAXV][MAXV],path[MAXV][MAXV];
int i,j,k,min,mini,minj;
for (i=0;ifor (j=0;j{ A[i][j]=g.edges[i][j];
//Floyd 算 法 求 图 g 中 的 一 个 最 小 环
if (i!=j && g.edges[i][j]path[i][j]=i;
else
path[i][j]=-1;
}
for (k=0;k{ for (i=0;ifor (j=0;j// 顶 点 i 到 j 有 边 时
// 顶 点 i 到 j 没 有 边 时
// 依 次 考 察 所 有 顶 点
if (A[i][j]>A[i][k]+A[k][j])
{ A[i][j]=A[i][k]+A[k][j]; // 修 改 最 短 路 径 长 度 path[i][j]=path[k][j]; // 修 改 最 短 路 径
}
}
min=Mincycle(g,A,mini,minj);
if (min!=INF)
{ printf(" 图 中 最 小 环 : ");
Dispapath(path,mini,minj); printf("%d, 长 度 : %d\n",mini,min);
}
else printf(" 图 中 没 有 任 何 环 \ n");
}
void main()
{ MGraph g;
// 输 出 一 条 最 短 路 径
int A[MAXV][MAXV]={ {0,5,INF,INF},{INF,0,1,INF},
{3,INF,0,2}, {INF,4,INF,0}};
int n=4, e=5;
CreateMat(g,A,n,e); printf(" 图 G 的 邻 接 矩 阵 : \n"); DispMat(g);
Floyd(g);
// 建 立 图 的 邻 接 矩 阵 // 输 出 邻 接 矩 阵
}
上 述 程 序 的 执 行 结 果 如 图 1.50 所 示 。
77
----------------------------------------------------- Page 78 -----------------------------------------------------
算 法 设 计
图 1.50 程 序 执 行 结 果
1.10 第 10 章 ─ 计 算 几 何
1.10.1 练 习 题
1. 对 如 图 1.51 所 示 的 点 集 A , 给 出 采 用 Graham 扫 描 算 法 求 凸 包 的 过 程 及 结 果 。
10
9
8
7
6
5
4
3
2
1
a 8
a 11
a 9
a 0
a 6
a 7
a 5
a 3
a 10
a 4
a 1
a 2
1 2 3 4 5
6 7 8 9 10
图 1.51 一 个 点 集 A
2. 对 如 图 1.51 所 示 的 点 集 A , 给 出 采 用 分 治 法 求 最 近 点 对 的 过 程 及 结 果 。
3. 对 如 图 1.51 所 示 的 点 集 A , 给 出 采 用 旋 转 卡 壳 法 求 最 远 点 对 的 结 果 。
4. 对 应 3 个 点 向 量 p 1 、 p 2 、 p 3 , 采 用 S (p 1 , p 2 , p 3 )=(p 2 - p 1 ) (p 3 - p 1 )/2 求 它 们 构 成 的 三 角 形 面 积 , 请 问 什 么 情 况 下 计 算 结 果 为 正 ? 什 么 情 况 下 计 算 结 果 为 负 ?
5. 已 知 坐 标 为 整 数 , 给 出 判 断 平 面 上 一 点 p 是 否 在 一 个 逆 时 针 三 角 形 p 1 -p 2 -p 3 的 算 法 。
1.10.2 练 习 题 参 考 答 案
内 部
1. 答 : 采 用 Graham 扫 描 算 法 求 凸 包 的 过 程 及 结 果 如 下 :
求 出 起 点 a 0 (1 , 1) 。
排 序 后 : a 0 (1 , 1) a 1 (8 , 1) a 2 (9 , 4) a 3 (5 , 4) a 4 (8 , 7) a 5 (5 , 6) a 10 (7 , 10) a 9 (3 , 5) a 6 (3 , 7) a 7 (4 , 10) a 8 (1 , 6) a 11 (0 , 3) 。
先 将 a 0 (1 , 1) 进 栈 , a 1 (8 , 1) 进 栈 , a 2 (9 , 4) 进 栈 。
处 理 点 a 3 (5 , 4) : a 3 (5 , 4) 进 栈 。
处 理 点 a 4 (8 , 7) : a 3 (5 , 4) 存 在 右 拐 关 系 , 退 栈 , a 4 (8 , 7) 进 栈 。
78
----------------------------------------------------- Page 79 -----------------------------------------------------
第 1 章
概 论
处 理 点 a 5 (5 , 6) : a 5 (5 , 6) 进 栈 。
处 理 点 a 10 (7 , 10) : a 5 (5 , 6) 存 在 右 拐 关 系 , 退 栈 , a 10 (7 , 10) 进 栈 。
处 理 点 a 9 (3 , 5) : a 9 (3 , 5) 进 栈 。
处 理 点 a 6 (3 , 7) : a 9 (3 , 5) 存 在 右 拐 关 系 , 退 栈 , a 6 (3 , 7) 进 栈 。
处 理 点 a 7 (4 , 10) : a 6 (3 , 7) 存 在 右 拐 关 系 , 退 栈 , a 7 (4 , 10) 进 栈 。
处 理 点 a 8 (1 , 6) : a 8 (1 , 6) 进 栈 。
处 理 点 a 11 (0 , 3) : a 11 (0 , 3) 进 栈 。
结 果 : n =8 , 凸 包 的 顶 点 : a 0 (1 , 1) a 1 (8 , 1) a 2 (9 , 4) a 4 (8 , 7) a 10 (7 , 10) a 7 (4 , 10) a 8 (1 , 6) a 11 (0 , 3) 。
2. 答 : 求 解 过 程 如 下 :
排 序 前 : (1 , 1) (8 , 1) (9 , 4) (5 , 4) (8 , 7) (5 , 6) (3 , 7) (4 , 10) (1 , 6) (3 , 5) (7 , 10) (0 , 3) 。 按 x 坐 标 排 序 后 : (0 , 3) (1 , 1) (1 , 6) (3 , 7) (3 , 5) (4 , 10) (5 , 4) (5 , 6) (7 , 10) (8 , 1) (8 , 7) (9 , 4) 。 按 y 坐 标 排 序 后 : (1 , 1) (8 , 1) (0 , 3) (5 , 4) (9 , 4) (3 , 5) (1 , 6) (5 , 6) (3 , 7) (8 , 7) (4 , 10) (7 , 10) 。
( 1 ) 中 间 位 置 midindex=5 , 左 部 分 : (0 , 3) (1 , 1) (1 , 6) (3 , 7) (3 , 5) (4 , 10) ; 右 部 分 : (5 , 4) (5 , 6) (7 , 10) (8 , 1) (8 , 7) (9 , 4) ; 中 间 部 分 点 集 为 (0 , 3) (3 , 7) (4 , 10) (5 , 4) (5 , 6) (7 , 10) (8 , 7) 。
( 2 ) 求 解 左 部 分 : (0 , 3) (1 , 1) (1 , 6) (3 , 7) (3 , 5) (4 , 10) 。
中 间 位 置 = 2 , 划 分 为 左 部 分 1 : (0 , 3) (1 , 1) (1 , 6) , 右 部 分 1 : (3 , 7) (3 , 5) (4 , 10) 处 理 左 部 分 1 : 点 数 少 于 4 : 求 出 最 近 距 离 = 2.23607 , 即 ( 0 , 3) 和 ( 1 , 1) 之 间 的 距 离 。 处 理 右 部 分 1 : 点 数 少 于 4 : 求 出 最 近 距 离 =2 , 即 ( 3 , 7) 和 ( 3 , 5) 之 间 的 距 离 。 再 考 虑 中 间 部 分 ( 中 间 部 分 最 近 距 离 = 2.23 ) 求 出 左 部 分 d1=2 。
( 3 ) 求 解 右 部 分 : (5 , 4) (5 , 6) (7 , 10) (8 , 1) (8 , 7) (9 , 4) 。
中 间 位 置 = 8 , 划 分 为 左 部 分 2 : (5 , 4) (5 , 6) (7 , 10) , 右 部 分 2 : (8 , 1) (8 , 7) (9 , 4) 。
处 理 左 部 分 2 : 点 数 少 于 4 , 求 出 最 近 距 离 = 2 , 即 (5 , 4) 和 ( 5 , 6) 之 间 的 距 离 。 处 理 右 部 分 2 : 点 数 少 于 4 , 求 出 最 近 距 离 = 3.16228 , 即 ( 8 , 1) 和 ( 9 , 4) 之 间 的 距 离 。 再 考 虑 中 间 部 分 ( 中 间 部 分 为 空 ) 求 出 右 部 分 d2=2 。
( 4 ) 求 解 中 间 部 分 点 集 : (0 , 3) (3 , 7) (4 , 10) (5 , 4) (5 , 6) (7 , 10) (8 , 7) 。 求 出 最 近 距 离 d3=5 。
最 终 结 果 为 : d=MIN{d1 , d2 , d3)=2 。
3. 答 : 采 用 旋 转 卡 壳 法 求 出 两 个 最 远 点 对 是 ( 1 , 1 ) 和 ( 7 , 10 ) , 最 远 距 离 为 10.82 。
4. 答 : 当 三 角 形 p 1 - p 2 - p 3
逆 时 针 方 向 时 , 如 图 1.52 所 示 , p 2 - p 1
在 p 3 - p 1
的 顺 时 针 方
向 上 , (p 2 - p 1 ) (p 3 - p 1 )>0 , 对 应 的 面 积 ( p 2 - p 1 ) (p 3 - p 1 )/2 为 正 。
当 三 角 形 p 1 - p 2 - p 3
顺 时 针 方 向 时 , 如 图 1.53 所 示 , p 2 - p 1
在 p 3 - p 1
的 逆 时 针 方 向 上 ,
(p 2 - p 1 ) (p 3 - p 1 )<0 , 对 应 的 面 积 ( p 2 - p 1 ) (p 3 - p 1 )/2 为 负 。
79
----------------------------------------------------- Page 80 -----------------------------------------------------
算 法 设 计
p 1
p 3 - p 1
p 2 - p 1
p 1
p 2 - p 1
p 3 - p 1
图 1.52
p 1 - p 2 - p 3
逆 时 针 方 向 图
图 1.53 p 1 - p 2 - p 3
逆 时 针 方 向
5. 答 : 用 S (p 1 , p 2 , p 3 )=(p 2 - p 1 ) (p 3 - p 1 )/2 求 三 角 形 p 1 、 p 2 、 p 3 带 符 号 的 的 面 积 。 如 图 1.54 所 示 , 若 S (p , p 2 , p 3 ) 和 S (p , p 3 , p 1 ) 和 S (p , p 1 , p 2 ) ( 3 个 三 角 形 的 方 向 均 为 逆 时 针 方 向 ) 均 大 于 0 , 表 示 p 在 该 三 角 形 内 部 。
p 3
p 1
p
p 2
图 1.54
一 个 点 p 和 一 个 三 角 形
对 应 的 程 序 如 下 :
#include "Fundament.cpp"
// 包 含 向 量 基 本 运 算 算 法
double getArea(Point p1,Point p2,Point p3) // 求 带 符 号 的 面 积
{
return Det(p2-p1,p3-p1);
}
bool Intrig(Point p,Point p1,Point p2,Point p3) // 判 断 p 是 否 在 三 角 形 p1p2p3 的 内 部 { double area1=getArea(p,p2,p3);
double area2=getArea(p,p3,p1);
double area3=getArea(p,p1,p2);
if (area1>0 && area2>0 && area3>0)
return true;
else
return false;
}
void main()
{ printf(" 求 解 结 果 \ n");
Point p1(0,0);
Point p2(5,-4);
Point p3(4,3);
Point p4(3,1);
Point p5(-1,1);
printf(" p1:"); p1.disp(); printf("\n");
printf(" p2:"); p2.disp(); printf("\n");
printf(" p3:"); p3.disp(); printf("\n");
printf(" p4:"); p4.disp(); printf("\n");
80
----------------------------------------------------- Page 81 -----------------------------------------------------
第 1 章
概 论
printf("
printf("
printf("
printf("
p5:"); p5.disp(); printf("\n");
p1p2p3 三 角 形 面 积 : %g\n",getArea(p1,p2,p3));
p4 在 p 1p2p3 三 角 形 内 部 : %s\n",Intrig(p4,p1,p2,p3)?" 是 " :" 不 是 " ); p5 在 p 1p2p3 三 角 形 内 部 : %s\n",Intrig(p5,p1,p2,p3)?" 是 " :" 不 是 " );
}
上 述 程 序 的 执 行 结 果 如 图 1.55 所 示 。
图 1.55 程 序 执 行 结 果
1.11 第 11 章 ─ 计 算 复 杂 性 理 论
1.11.1 练 习 题
1. 旅 行 商 问 题 是 NP 问 题 吗 ?
A. 否
B. 是
C. 至 今 尚 无 定 论
2. 下 面 有 关 P 问 题 , NP 问 题 和 NPC 问 题 , 说 法 错 误 的 是 ( ) 。
A. 如 果 一 个 问 题 可 以 找 到 一 个 能 在 多 项 式 的 时 间 里 解 决 它 的 算 法 , 那 么 这 个 问 题 就 属 于 P 问 题
B.NP 问 题 是 指 可 以 在 多 项 式 的 时 间 里 验 证 一 个 解 的 问 题
C. 所 有 的 P 类 问 题 都 是 NP 问 题
D.NPC 问 题 不 一 定 是 个 NP 问 题 , 只 要 保 证 所 有 的 NP 问 题 都 可 以 约 化 到 它 即 可
3. 对 于 《 教 程 》 例 11.2 设 计 的 图 灵 机 , 分 别 给 出 执 行 f (3 , 2) 和 f (2 , 3) 的 瞬 像 演 变 过 程 。
4. 什 么 是 P 类 问 题 ? 什 么 是 NP 类 问 题 ?
5. 证 明 求 两 个 m 行 n 列 的 二 维 矩 阵 相 加 的 问 题 属 于 P 类 问 题 。
6. 证 明 求 含 有 n 个 元 素 的 数 据 序 列 中 求 最 大 元 素 的 问 题 属 于 P 类 问 题 。
7. 设 计 一 个 确 定 性 图 灵 机 M , 用 于 计 算 后 继 函 数 S ( n )= n +1 ( n 为 一 个 二 进 制 数 ) , 并 给 出 求 1010001 的 后 继 函 数 值 的 瞬 像 演 变 过 程 。
1.11.2 练 习 题 参 考 答 案
1. 答 : B 。
2. 答 : D 。
3. 答 : ( 1 ) 执 行 f (3 , 2) 时 , 输 入 带 上 的 初 始 信 息 为 000100B , 其 瞬 像 演 变 过 程 如
81
----------------------------------------------------- Page 82 -----------------------------------------------------
算 法 设 计
下 :
q 0 000100B B0 q 3 0110B BB01 q 2 10B
B q 1 00100B B q 3 00110B BB011 q 2 0B
B0 q 1 0100B q 3 B00110B BB01 q 3 11B
B00 q 1 100B
B001 q 2 00B
B00 q 3 110B
B q 0 00110B
BB q 1 0110B
BB0 q 1 110B
BB0 q 3 111B
BB q 3 0111B
BB q 0 0111B
BBB1 q 2 11B
BBB11 q 2 1B
BBB111 q 2 B
BBB11 q 4 1B
BBB1 q 4 1BB
BBB q 4 1BBB
BBB q 4 BBBB
BBB0 q 6 BBB
最 终 带 上 有 一 个 0 , 计 算 结 果 为 1 。
( 2 ) 执 行 f (2 , 3) 时 , 输 入 带 上 的 初 始 信 息 为 001000B , 其 瞬 像 演 变 过 程 如 下 :
q 0 001000B
B q 0 01000B
B0 q 1 1000B
B01 q 2 000B
B0 q 3 1100B
B q 3 01100B
q 3 B01100B BB q 3 1100B
B q 0 01100B B q 3 B1100B
BB q 1 1100B BB q 0 1100B
BB1 q 2 100B BBB q 5 100B
BB11 q 2 00B BBBB q 5 00B
BB1 q 3 100B
BBBBB q 5 0B
BBBBBB q 5 B
BBBBBBB q 6
最 终 带 上 有 零 个 0 , 计 算 结 果 为 0 。
4. 答 : 用 确 定 性 图 灵 机 以 多 项 式 时 间 界 可 解 的 问 题 称 为 P 类 问 题 。 用 非 确 定 性 图 灵 机 以 多 项 式 时 间 界 可 解 的 问 题 称 为 NP 类 问 题 。
5. 答 : 求 两 个 m 行 n 列 的 二 维 矩 阵 相 加 的 问 题 对 应 的 算 法 时 间 复 杂 度 为 O( mn ) , 所 以 属 于 P 类 问 题 。
6. 答 : 求 含 有 n 个 元 素 的 数 据 序 列 中 最 大 元 素 的 问 题 的 算 法 时 间 复 杂 度 为 O( n ) , 所 以 属 于 P 类 问 题 。
7. 解 : q 0 为 初 始 状 态 , q 3 为 终 止 状 态 , 读 写 头 初 始 时 注 视 最 右 边 的 格 。 δ 动 作 函 数 如 下 :
δ ( q 0 , 0) → ( q 1 , 1 , L )
δ ( q 0 , 1) → ( q 2 , 0 , L )
δ ( q 0 , B ) → ( q 3 , B , R )
δ ( q 1 , 0) → ( q 1 , 0 , L )
δ ( q 1 , 1) → ( q 1 , 1 , L )
δ ( q 1 , B ) → ( q 3 , B , L )
δ ( q 2 , 0) → ( q 1 , 1 , L )
δ ( q 2 , 1) → ( q 2 , 0 , L )
δ ( q 2 , B ) → ( q 3 , B , L )
求 10100010 的 后 继 函 数 值 的 瞬 像 演 变 过 程 如 下 :
B 1010001 q 0 0 B
B 101000 q 1 11 B
B 10100 q 1 011 B
B 1010 q 1 0011 B
B 101 q 1 00011 B
B 10 q 1 100011 B
B 1 q 1 0100011 B
Bq 1 10100011 B
q 1 B10100011 B
q 3 BB 10100011 B
其 结 果 为 10100011 。
82
----------------------------------------------------- Page 83 -----------------------------------------------------
第 1 章
概 论
1.12 第 12 章 ─ 概 率 算 法 和 近 似 算 法
1.12.1 练 习 题
1. 蒙 特 卡 罗 算 法 是 ( ) 的 一 种 。
A. 分 枝 限 界 算 法 B. 贪 心 算 法 C. 概 率 算 法 D. 回 溯 算 法
2. 在 下 列 算 法 中 有 时 找 不 到 问 题 解 的 是 ( ) 。
A. 蒙 特 卡 罗 算 法 B. 拉 斯 维 加 斯 算 法 C. 舍 伍 德 算 法 D. 数 值 概 率 算 法 3. 在 下 列 算 法 中 得 到 的 解 未 必 正 确 的 是 ( ) 。
A. 蒙 特 卡 罗 算 法 B. 拉 斯 维 加 斯 算 法 C. 舍 伍 德 算 法 D. 数 值 概 率 算 法 4. 总 能 求 得 非 数 值 问 题 的 一 个 解 , 且 所 求 得 的 解 总 是 正 确 的 是 ( ) 。
A. 蒙 特 卡 罗 算 法 B. 拉 斯 维 加 斯 算 法 C. 数 值 概 率 算 法 D. 舍 伍 德 算 法 5. 目 前 可 以 采 用 ( ) 在 多 项 式 级 时 间 内 求 出 旅 行 商 问 题 的 一 个 近 似 最 优 解 。
A. 回 溯 法
B. 蛮 力 法
C. 近 似 算 法 D. 都 不 可 能
6. 下 列 叙 述 错 误 的 是 ( ) 。
A. 概 率 算 法 的 期 望 执 行 时 间 是 指 反 复 解 同 一 个 输 入 实 例 所 花 的 平 均 执 行 时 间
B. 概 率 算 法 的 平 均 期 望 时 间 是 指 所 有 输 入 实 例 上 的 平 均 期 望 执 行 时 间
C. 概 率 算 法 的 最 坏 期 望 事 件 是 指 最 坏 输 入 实 例 上 的 期 望 执 行 时 间
D. 概 率 算 法 的 期 望 执 行 时 间 是 指 所 有 输 入 实 例 上 的 所 花 的 平 均 执 行 时 间
7. 下 列 叙 述 错 误 的 是 ( ) 。
A. 数 值 概 率 算 法 一 般 是 求 数 值 计 算 问 题 的 近 似 解
B.Monte Carlo 总 能 求 得 问 题 的 一 个 解 , 但 该 解 未 必 正 确
C.Las Vegas 算 法 的 一 定 能 求 出 问 题 的 正 确 解
D.Sherwood 算 法 的 主 要 作 用 是 减 少 或 是 消 除 好 的 和 坏 的 实 例 之 间 的 差 别
8. 近 似 算 法 和 贪 心 法 有 什 么 不 同 ?
9. 给 定 能 随 机 生 成 整 数 1 到 5 的 函 数 rand5() , 写 出 能 随 机 生 成 整 数 1 到 7 的 函 数 rand7() 。
1.12.2 练 习 题 参 考 答 案
1. 答 : C 。
2. 答 : B 。
3. 答 : A 。
4. 答 : D 。
5. 答 : C 。
6. 答 : 对 概 率 算 法 通 常 讨 论 平 均 的 期 望 时 间 和 最 坏 的 期 望 时 间 , 前 者 指 所 有 输 入 实 例 上 平 均 的 期 望 执 行 时 间 , 后 者 指 最 坏 的 输 入 实 例 上 的 期 望 执 行 时 间 。 答 案 为 D 。
7. 答 : 一 旦 用 拉 斯 维 加 斯 算 法 找 到 一 个 解 , 那 么 这 个 解 肯 定 是 正 确 的 , 但 有 时 用 拉 斯 维 加 斯 算 法 可 能 找 不 到 解 。 答 案 为 C 。
8. 答 : 近 似 算 法 不 能 保 证 得 到 最 优 解 。 贪 心 算 法 不 一 定 是 近 似 算 法 , 如 果 可 以 证 明
83
----------------------------------------------------- Page 84 -----------------------------------------------------
算 法 设 计
决 策 既 不 受 之 前 决 策 的 影 响 , 也 不 影 响 后 续 决 策 , 则 贪 心 算 法 就 是 确 定 的 最 优 解 算 法 。 9. 解 : 通 过 rand5()*5+rand5() 产 生 6 , 7 , 8 , 9 , … , 26 , 27 , 28 , 29 , 30 这 25 个 整
数 , 每 个 整 数 x 出 现 的 概 率 相 等 , 取 前 面 3*7=21 个 整 数 , 舍 弃 后 面 的 4 个 整 数 , 将 { 6 , 7 , 8} 转 化 成 1 , {9 , 10 , 11} 转 化 成 2 , 以 此 类 推 , 即 由 y = ( x - 3)/3 为 最 终 结 果 。 对 应 的 程 序 如 下 :
#include
#include
#include
int rand5()
{ int a=1,b=5;
return rand()%(b-a+1)+a;
}
int rand7()
{ int x;
do
{
x=rand5()*5+rand5();
} while (x>26);
int y=(x-3)/3;
return y;
}
void main()
{ srand((unsigned)time(NULL));
for (int i=1;i<=20;i++)
printf("%d ",rand7());
printf("\n");
// 包 含 产 生 随 机 数 的 库 函 数
// 产 生 一 个 [ 1,5] 的 随 机 数
// 产 生 一 个 [ 1,7] 的 随 机 数 // 随 机 种 子
// 输 出 20 个 [ 1,7] 的 随 机 数
}
上 述 程 序 的 一 次 执 行 结 果 如 图 1.56 所 示 。
图 1.56
程 序 执 行 结 果
84