----------------------------------------------------- Page 1 -----------------------------------------------------
1.1 第 1 章 ─ 概 论
1.1.1 练 习 题
1. 下 列 关 于 算 法 的 说 法 中 正 确 的 有 ( ) 。
Ⅰ . 求 解 某 一 类 问 题 的 算 法 是 唯 一 的
Ⅱ . 算 法 必 须 在 有 限 步 操 作 之 后 停 止
Ⅲ . 算 法 的 每 一 步 操 作 必 须 是 明 确 的 , 不 能 有 歧 义 或 含 义 模 糊
Ⅳ . 算 法 执 行 后 一 定 产 生 确 定 的 结 果
A. 1 个 B.2 个 C.3 个 D.4 个
2. T ( n ) 表 示 当 输 入 规 模 为 n 时 的 算 法 效 率 , 以 下 算 法 效 率 最 优 的 是 ( ) 。
A. T ( n )= T ( n - 1)+1 , T (1)=1 C. T ( n )= T ( n /2)+1 , T (1)=1
B. T ( n )= 2 n
D. T ( n )=3 n log 2 n
3. 什 么 是 算 法 ? 算 法 有 哪 些 特 征 ?
4. 判 断 一 个 大 于 2 的 正 整 数 n 是 否 为 素 数 的 方 法 有 多 种 , 给 出 两 种 算 法 , 说 明 其 中 一 种 算 法 更 好 的 理 由 。
5. 证 明 以 下 关 系 成 立 :
( 1 ) 10 n - 2 n =  ( n )
( 2 ) 2 =  (2 )
6. 证 明 O( f ( n ))+O( g ( n ))=O(max{ f ( n ) , g ( n )}) 。
7. 有 一 个 含 n ( n >2 ) 个 整 数 的 数 组 a , 判 断 其 中 是 否 存 在 出 现 次 数 超 过 所 有 元 素 一 半 的 元 素 。
8. 一 个 字 符 串 采 用 string 对 象 存 储 , 设 计 一 个 算 法 判 断 该 字 符 串 是 否 为 回 文 。
9. 有 一 个 整 数 序 列 , 设 计 一 个 算 法 判 断 其 中 是 否 存 在 两 个 元 素 和 恰 好 等 于 给 定 的 整 数 k 。
10. 有 两 个 整 数 序 列 , 每 个 整 数 序 列 中 所 有 元 素 均 不 相 同 。 设 计 一 个 算 法 求 它 们 的 公 共 元 素 , 要 求 不 使 用 STL 的 集 合 算 法 。
11. 正 整 数 n ( n >1 ) 可 以 写 成 质 数 的 乘 积 形 式 , 称 为 整 数 的 质 因 数 分 解 。 例 如 , 12=2*2*3 , 18=2*3*3 , 11=11 。 设 计 一 个 算 法 求 n 这 样 分 解 后 各 个 质 因 数 出 现 的 次 数 , 采 用 vector 向 量 存 放 结 果 。
12. 有 一 个 整 数 序 列 , 所 有 元 素 均 不 相 同 , 设 计 一 个 算 法 求 相 差 最 小 的 元 素 对 的 个 数 。 如 序 列 4 、 1 、 2 、 3 的 相 差 最 小 的 元 素 对 的 个 数 是 3 , 其 元 素 对 是 ( 1 , 2 ) , ( 2 , 3 ) , ( 3 , 4 ) 。
13. 有 一 个 map<string , int> 容 器 , 其 中 已 经 存 放 了 较 多 元 素 。 设 计 一 个 算 法 求 出 其 中 重 复 的 value 并 且 返 回 重 复 value 的 个 数 。
14. 重 新 做 第 10 题 , 采 用 map 容 器 存 放 最 终 结 果 。
15. 假 设 有 一 个 含 n ( n >1 ) 个 元 素 的 stack<int> 栈 容 器 st , 设 计 一 个 算 法 出 栈 从 栈 顶 到 栈 底 的 第 k ( 1 ≤ k ≤ n ) 个 元 素 , 其 他 栈 元 素 不 变 。
2
2
2
n +1 n

----------------------------------------------------- Page 2 -----------------------------------------------------
算 法 设 计
1.1.2 练 习 题 参 考 答 案
1. 答 : 由 于 算 法 具 有 有 穷 性 、 确 定 性 和 输 出 性 , 因 而 Ⅱ 、 Ⅲ 、 Ⅳ 正 确 , 而 解 决 某 一 类 问 题 的 算 法 不 一 定 是 唯 一 的 。 答 案 为 C 。
2. 答 : 选 项 A 的 时 间 复 杂 度 为 O( n ) 。 选 项 B 的 时 间 复 杂 度 为 O( n ) 。 选 项 C 的 时 间 复 杂 度 为 O(log 2 n ) 。 选 项 D 的 时 间 复 杂 度 为 O( n log 2 n ) 。 答 案 为 C 。
3. 答 : 算 法 是 求 解 问 题 的 一 系 列 计 算 步 骤 。 算 法 具 有 有 限 性 、 确 定 性 、 可 行 性 、 输 入 性 和 输 出 性 5 个 重 要 特 征 。
4. 答 : 两 种 算 法 如 下 :
#include <stdio.h>
#include <math.h>
bool isPrime1(int n) // 方 法 1
{ for (int i=2;i<n;i++)
if (n%i==0)
return false;
return true;
}
bool isPrime2(int n) // 方 法 2
{ for (int i=2;i<=(int)sqrt(n);i++)
if (n%i==0)
return false;
return true;
}
void main()
{ int n=5;
printf("%d,%d\n",isPrime1(n),isPrime2(n));
}
方 法 1 的 时 间 复 杂 度 为 O( n ) , 方 法 2 的 时 间 复 杂 度 为 n , 所 以 方 法 2 更 好 。 5. 答 : ( 1 ) 当 n 足 够 大 时 , (10 n - 2 n )/( n )=10 , 所 以 10 n - 2 n =  ( n ) 。
( 2 ) 2 =2*2 =  (2 ) 。
6. 证 明 : 对 于 任 意 f 1 (n) ∈ O( f ( n )) , 存 在 正 常 数 c 1 和 正 常 数 n 1 , 使 得 对 所 有 n ≥ n 1 , 有 f 1 ( n ) ≤ c 1 f ( n ) 。
类 似 地 , 对 于 任 意 g 1 ( n ) ∈ O( g ( n )) , 存 在 正 常 数 c 2 和 自 然 数 n 2 , 使 得 对 所 有 n ≥ n 2 , 有 g 1 ( n ) ≤ c 2 g ( n ) 。
令 c 3 =max{ c 1 , c 2 } , n 3 =max{ n 1 , n 2 } , h ( n )= max{ f ( n ) , g ( n )} 。
则 对 所 有 的 n ≥ n 3 , 有 :
f 1 ( n ) + g 1 ( n ) ≤ c 1 f ( n ) + c 2 g ( n ) ≤ c 3 f ( n )+ c 3 g ( n )= c 3 ( f ( n )+ g ( n ))
≤ c 3 2max{ f ( n ) , g ( n )}=2 c 3 h ( n )=O(max{ f ( n ) , g ( n )}) 。
7. 解 : 先 将 a 中 元 素 递 增 排 序 , 再 求 出 现 次 数 最 多 的 次 数 maxnum , 最 后 判 断 是 否 满 足 条 件 。 对 应 的 程 序 如 下 :
#include <stdio.h>
#include <algorithm>
using namespace std;
2
2
2
2
2
2
n +1 n n

----------------------------------------------------- Page 3 -----------------------------------------------------
第 1 章
概 论
bool solve(int a[],int n,int &x)
{ sort(a,a+n);
int maxnum=0;
int num=1;
int e=a[0];
for (int i=1;i<n;i++)
{ if (a[i]==e)
{ num++;
// 递 增 排 序
// 出 现 次 数 最 多 的 次 数
if (num>maxnum)
{ maxnum=num;
x=e;
}
}
else
{ e=a[i];
num=1;
}
}
if (maxnum>n/2)
return true;
else
return false;
}
void main()
{ int a[]={2,2,2,4,5,6,2};
int n=sizeof(a)/sizeof(a[0]);
int x;
if (solve(a,n,x))
printf(" 出 现 次 数 超 过 所 有 元 素 一 半 的 元 素 为 % d\n",x); else
printf(" 不 存 在 出 现 次 数 超 过 所 有 元 素 一 半 的 元 素 \ n");
}
上 述 程 序 的 执 行 结 果 如 图 1.1 所 示 。
图 1.1
程 序 执 行 结 果
8. 解 : 采 用 前 后 字 符 判 断 方 法 , 对 应 的 程 序 如 下 : #include <iostream>
#include <string>
using namespace std;
bool solve(string str) // 判 断 字 符 串 str 是 否 为 回 文 { int i=0,j=str.length()-1;
while (i<j)
{ if (str[i]!=str[j])
return false;
3

----------------------------------------------------- Page 4 -----------------------------------------------------
算 法 设 计
i++; j--;
}
return true;
}
void main()
{ cout << " 求 解 结 果 " << endl;
string str="abcd";
cout << " " << str << (solve(str)?" 是 回 文 " :" 不 是 回 文 " ) << endl; string str1="abba";
cout << " " << str1 << (solve(str1)?" 是 回 文 " :" 不 是 回 文 " ) << endl; }
上 述 程 序 的 执 行 结 果 如 图 1.2 所 示 。
图 1.2
程 序 执 行 结 果
9. 解 : 先 将 a 中 元 素 递 增 排 序 , 然 后 从 两 端 开 始 进 行 判 断 。 对 应 的 程 序 如 下 : #include <stdio.h>
#include <algorithm>
using namespace std;
bool solve(int a[],int n,int k)
{ sort(a,a+n);
int i=0, j=n-1;
while (i<j)
{ if (a[i]+a[j]==k)
return true;
// 递 增 排 序
// 区 间 中 存 在 两 个 或 者 以 上 元 素
else if (a[i]+a[j]<k)
i++;
else
j--;
}
return false;
}
void main()
{ int a[]={1,2,4,5,3};
int n=sizeof(a)/sizeof(a[0]);
printf(" 求 解 结 果 \ n");
int k=9,i,j;
if (solve(a,n,k,i,j))
printf(" 存 在 : %d+%d=%d\n",a[i],a[j],k); else
printf(" 不 存 在 两 个 元 素 和 为 % d\n",k);
int k1=10;
if (solve(a,n,k1,i,j))
printf(" 存 在 : %d+%d=%d\n",a[i],a[j],k1);
4

----------------------------------------------------- Page 5 -----------------------------------------------------
第 1 章
概 论
else
printf("
不 存 在 两 个 元 素 和 为 % d\n",k1);
}
上 述 程 序 的 执 行 结 果 如 图 1.3 所 示 。
图 1.3
程 序 执 行 结 果
10. 解 : 采 用 集 合 set<int> 存 储 整 数 序 列 , 集 合 中 元 素 默 认 是 递 增 排 序 的 , 再 采 用 二 路 归 并 算 法 求 它 们 的 交 集 。 对 应 的 程 序 如 下 :
#include <stdio.h>
#include <set>
using namespace std;
void solve(set<int> s1,set<int> s2,set<int> &s3) { set<int>::iterator it1,it2;
it1=s1.begin(); it2=s2.begin();
while (it1!=s1.end() && it2!=s2.end())
{ if (*it1==*it2)
{ s3.insert(*it1);
++it1; ++it2;
}
else if (*it1<*it2)
++it1;
else
++it2;
}
}
// 求 交 集 s3
void dispset(set<int> s)
{ set<int>::iterator it;
// 输 出 集 合 的 元 素
for (it=s.begin();it!=s.end();++it)
printf("%d ",*it);
printf("\n");
}
void main()
{ int a[]={3,2,4,8};
int n=sizeof(a)/sizeof(a[0]);
set<int> s1(a,a+n);
int b[]={1,2,4,5,3};
int m=sizeof(b)/sizeof(b[0]);
set<int> s2(b,b+m);
set<int> s3;
solve(s1,s2,s3);
printf(" 求 解 结 果 \ n");
printf(" s1: "); dispset(s1);
5

----------------------------------------------------- Page 6 -----------------------------------------------------
算 法 设 计
printf("
printf("
s2: "); dispset(s2);
s3: "); dispset(s3);
}
上 述 程 序 的 执 行 结 果 如 图 1.4 所 示 。
图 1.4
程 序 执 行 结 果
11. 解 : 对 于 正 整 数 n , 从 i =2 开 始 查 找 其 质 因 数 , ic 记 录 质 因 数 i 出 现 的 次 数 , 当 找 到 这 样 质 因 数 后 , 将 ( i , ic ) 作 为 一 个 元 素 插 入 到 vector 容 器 v 中 。 最 后 输 出 v 。 对 应 的 算 法 如 下 :
#include <stdio.h>
#include <vector>
using namespace std;
struct NodeType
{ int p;
int pc;
//vector 向 量 元 素 类 型 // 质 因 数
// 质 因 数 出 现 次 数
};
void solve(int n,vector<NodeType> &v) // 求 n 的 质 因 数 分 解 { int i=2;
int ic=0;
NodeType e;
do
{ if (n%i==0)
{ ic++;
n=n/i;
}
else
{ if (ic>0)
{ e.p=i;
e.pc=ic;
v.push_back(e);
}
ic=0;
i++;
}
} while (n>1 || ic!=0);
}
void disp(vector<NodeType> &v) // 输 出 v
{ vector<NodeType>::iterator it;
for (it=v.begin();it!=v.end();++it)
printf(" 质 因 数 % d 出 现 % d 次 \ n",it->p,it->pc);
}
6

----------------------------------------------------- Page 7 -----------------------------------------------------
第 1 章
概 论
void main()
{ vector<NodeType> v;
int n=100;
printf("n=%d\n",n);
solve(n,v);
disp(v);
}
上 述 程 序 的 执 行 结 果 如 图 1.5 所 示 。
图 1.5
程 序 执 行 结 果
12. 解 : 先 递 增 排 序 , 再 求 相 邻 元 素 差 , 比 较 求 最 小 元 素 差 , 累 计 最 小 元 素 差 的 个 数 。 对 应 的 程 序 如 下 :
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int solve(vector<int> &myv)
// 求 myv 中 相 差 最 小 的 元 素 对 的 个 数
{ sort(myv.begin(),myv.end()); // 递 增 排 序
int ans=1;
int mindif=myv[1]-myv[0];
for (int i=2;i<myv.size();i++)
{ if (myv[i]-myv[i-1]<mindif)
{ ans=1;
mindif=myv[i]-myv[i-1];
}
else if (myv[i]-myv[i-1]==mindif)
ans++;
}
return ans;
}
void main()
{ int a[]={4,1,2,3};
int n=sizeof(a)/sizeof(a[0]);
vector<int> myv(a,a+n);
cout << " 相 差 最 小 的 元 素 对 的 个 数 : " << solve(myv) << endl; }
上 述 程 序 的 执 行 结 果 如 图 1.6 所 示 。
7

----------------------------------------------------- Page 8 -----------------------------------------------------
图 1.6
算 法 设 计
程 序 执 行 结 果
13. 解 : 对 于 map<string , int> 容 器 mymap , 设 计 另 外 一 个 map<int , int> 容 器 tmap , 将 前 者 的 value 作 为 后 者 的 关 键 字 。 遍 历 mymap , 累 计 tmap 中 相 同 关 键 字 的 次 数 。 一 个 参 考 程 序 及 其 输 出 结 果 如 下 :
#include <iostream>
#include <map>
#include <string>
using namespace std;
void main()
{ map<string,int> mymap;
mymap.insert(pair<string,int>("Mary",80));
mymap.insert(pair<string,int>("Smith",82));
mymap.insert(pair<string,int>("John",80));
mymap.insert(pair<string,int>("Lippman",95));
mymap.insert(pair<string,int>("Detial",82));
map<string,int>::iterator it;
map<int,int> tmap;
for (it=mymap.begin();it!=mymap.end();it++)
tmap[(*it).second]++;
map<int , int>::iterator it1;
cout << " 求 解 结 果 " << endl;
for (it1=tmap.begin();it1!=tmap.end();it1++)
cout << " " << (*it1).first << ": " << (*it1).second << " 次 \ n";
}
上 述 程 序 的 执 行 结 果 如 图 1.7 所 示 。
图 1.7
程 序 执 行 结 果
14. 解 : 采 用 map<int , int> 容 器 mymap 存 放 求 解 结 果 , 第 一 个 分 量 存 放 质 因 数 , 第 二 个 分 量 存 放 质 因 数 出 现 次 数 。 对 应 的 程 序 如 下 :
#include <stdio.h>
#include <map>
using namespace std;
void solve(int n,map<int,int> &mymap) // 求 n 的 质 因 数 分 解
{ int i=2;
int ic=0;
do
{
if (n%i==0)
{ ic++;
n=n/i;
}
8

----------------------------------------------------- Page 9 -----------------------------------------------------
第 1 章
概 论
else
{ if (ic>0)
mymap[i]=ic;
ic=0;
i++;
}
} while (n>1 || ic!=0);
}
void disp(map<int,int> &mymap) // 输 出 mymap
{ map<int,int>::iterator it;
for (it=mymap.begin();it!=mymap.end();++it)
printf(" 质 因 数 % d 出 现 % d 次 \ n",it->first,it->second);
}
void main()
{ map<int,int> mymap;
int n=12345;
printf("n=%d\n",n);
solve(n,mymap);
disp(mymap);
}
上 述 程 序 的 执 行 结 果 如 图 1.8 所 示 。
图 1.8
程 序 执 行 结 果
15. 解 : 栈 容 器 不 能 顺 序 遍 历 , 为 此 创 建 一 个 临 时 tmpst 栈 , 将 st 的 k 个 元 素 出 栈 并 进 栈 到 tmpst 中 , 再 出 栈 tmpst 一 次 得 到 第 k 个 元 素 , 最 后 将 栈 tmpst 的 所 有 元 素 出 栈 并 进 栈 到 st 中 。 对 应 的 程 序 如 下 :
#include <stdio.h>
#include <stack>
using namespace std;
int solve(stack<int> &st,int k)
{ stack<int> tmpst;
int e;
for (int i=0;i<k;i++)
{ e=st.top();
st.pop();
tmpst.push(e);
}
e=tmpst.top();
tmpst.pop();
while (!tmpst.empty())
{ st.push(tmpst.top());
tmpst.pop();
// 出 栈 第 k 个 元 素
// 出 栈 st 的 k 个 元 素 并 进 tmpst 栈 // 求 第 k 个 元 素
// 将 tmpst 的 所 有 元 素 出 栈 并 进 栈 st 9

----------------------------------------------------- Page 10 -----------------------------------------------------
}
return e;
}
void disp(stack<int> &st)
{ while (!st.empty())
{ printf("%d ",st.top());
st.pop();
}
printf("\n");
}
void main()
{ stack<int> st;
算 法 设 计
// 出 栈 st 的 所 有 元 素
printf(" 进 栈 元 素 1,2,3,4\n"); st.push(1);
st.push(2);
st.push(3);
st.push(4);
int k=3;
int e=solve(st,k);
printf(" 出 栈 第 % d 个 元 素 是 : %d\n",k,e); printf("st 中 元 素 出 栈 顺 序 : "); disp(st);
}
上 述 程 序 的 执 行 结 果 如 图 1.9 所 示 。
图 1.9
程 序 执 行 结 果
1.2 第 2 章 ─ 递 归 算 法 设 计 技 术
1.2.1 练 习 题
1. 什 么 是 直 接 递 归 和 间 接 递 归 ? 消 除 递 归 一 般 要 用 到 什 么 数 据 结 构 ? 2. 分 析 以 下 程 序 的 执 行 结 果 :
#include <stdio.h>
void f(int n,int &m)
{ if (n<1) return;
else
{
}
printf(" 调 用 f (%d,%d) 前 , n=%d,m=%d\n",n-1,m-1,n,m); n--; m--;
f(n-1,m);
printf(" 调 用 f (%d,%d) 后 : n=%d,m=%d\n",n-1,m-1,n,m);
10

----------------------------------------------------- Page 11 -----------------------------------------------------
第 1 章
概 论
}
void main()
{ int n=4,m=4;
f(n,m);
}
3. 采 用 直 接 推 导 方 法 求 解 以 下 递 归 方 程 : T (1)=1
T ( n )= T ( n - 1)+ n 当 n >1
4. 采 用 特 征 方 程 方 法 求 解 以 下 递 归 方 程 : H (0)=0
H (1)=1
H (2)=2
H ( n )= H ( n - 1)+9 H ( n - 2) - 9 H ( n - 3) 当 n >2 5. 采 用 递 归 树 方 法 求 解 以 下 递 归 方 程 : T (1)=1
T ( n )=4 T ( n /2)+ n 当 n >1
6. 采 用 主 方 法 求 解 以 下 题 的 递 归 方 程 。
T ( n )=1
当 n =1
T ( n )=4 T ( n /2)+ n 当 n >1
7. 分 析 求 斐 波 那 契 f(n) 的 时 间 复 杂 度 。
8. 数 列 的 首 项 a 1 =0 , 后 续 奇 数 项 和 偶 数 项 的 计 算 公 式 分 别 为 a 2 n =a 2 n -1 +2 , a 2 n +1 =a 2 n - 1 +a 2 n - 1 , 写 出 计 算 数 列 第 n 项 的 递 归 算 法 。
9. 对 于 一 个 采 用 字 符 数 组 存 放 的 字 符 串 str , 设 计 一 个 递 归 算 法 求 其 字 符 个 数 ( 长 度 ) 。
10. 对 于 一 个 采 用 字 符 数 组 存 放 的 字 符 串 str , 设 计 一 个 递 归 算 法 判 断 str 是 否 为 回 文 。
11. 对 于 不 带 头 结 点 的 单 链 表 L , 设 计 一 个 递 归 算 法 正 序 输 出 所 有 结 点 值 。
12. 对 于 不 带 头 结 点 的 单 链 表 L , 设 计 一 个 递 归 算 法 逆 序 输 出 所 有 结 点 值 。
13. 对 于 不 带 头 结 点 的 非 空 单 链 表 L , 设 计 一 个 递 归 算 法 返 回 最 大 值 结 点 的 地 址 ( 假 设 这 样 的 结 点 唯 一 ) 。
14. 对 于 不 带 头 结 点 的 单 链 表 L , 设 计 一 个 递 归 算 法 返 回 第 一 个 值 为 x 的 结 点 的 地 址 , 没 有 这 样 的 结 点 时 返 回 NULL 。
15. 对 于 不 带 头 结 点 的 单 链 表 L , 设 计 一 个 递 归 算 法 删 除 第 一 个 值 为 x 的 结 点 。
16. 假 设 二 叉 树 采 用 二 叉 链 存 储 结 构 存 放 , 结 点 值 为 int 类 型 , 设 计 一 个 递 归 算 法 求 二 叉 树 bt 中 所 有 叶 子 结 点 值 之 和 。
17. 假 设 二 叉 树 采 用 二 叉 链 存 储 结 构 存 放 , 结 点 值 为 int 类 型 , 设 计 一 个 递 归 算 法 求 二 叉 树 bt 中 所 有 结 点 值 大 于 等 于 k 的 结 点 个 数 。
18. 假 设 二 叉 树 采 用 二 叉 链 存 储 结 构 存 放 , 所 有 结 点 值 均 不 相 同 , 设 计 一 个 递 归 算 法 求 值 为 x 的 结 点 的 层 次 ( 根 结 点 的 层 次 为 1 ) , 没 有 找 到 这 样 的 结 点 时 返 回 0 。
11
2

----------------------------------------------------- Page 12 -----------------------------------------------------
算 法 设 计
1.2.2 练 习 题 参 考 答 案
1. 答 : 一 个 f 函 数 定 义 中 直 接 调 用 f 函 数 自 己 , 称 为 直 接 递 归 。 一 个 f 函 数 定 义 中 调 用 g 函 数 , 而 g 函 数 的 定 义 中 调 用 f 函 数 , 称 为 间 接 递 归 。 消 除 递 归 一 般 要 用 栈 实 现 。
2. 答 : 递 归 函 数 f ( n , m ) 中 , n 是 非 引 用 参 数 , m 是 引 用 参 数 , 所 以 递 归 函 数 的 状 态 为 ( n ) 。 程 序 执 行 结 果 如 下 :
调 用 f (3,3) 前 , n=4,m=4
调 用 f (1,2) 前 , n=2,m=3
调 用 f (0,1) 后 , n=1,m=2
调 用 f (2,1) 后 , n=3,m=2
3. 解 : 求 T ( n ) 的 过 程 如 下 :
T ( n )= T ( n - 1)+ n =[T( n - 2)+ n - 1)]+ n =T( n - 2)+ n +( n - 1)
=T( n - 3)+ n +( n - 1)+( n - 2)
= …
=T(1)+ n +( n - 1)+ … +2
= n +( n - 1)+ + … +2+1= n ( n +1)/2=O( n ) 。
4. 解 : 整 数 一 个 常 系 数 的 线 性 齐 次 递 推 式 , 用 x
n -3 3 2 3 2
代 替 H ( n ) , 有 : x = x +9 x - 9 x ,
x - x - 9 x+ 9= x ( x - 9) - ( x - 9)=( x - 1)( x - 9)=( x - 1)( x +3)( x - 3)=0 。 得 到 r 1 =1 , r 2 = - 3 , r 3 =3 则 递 归 方 程 的 通 解 为 : H ( n )= c 1 + c 2 ( - 3) + c 3 3
代 入 H (0)=0 , 有 c 1 + c 2 + c 3 =0
代 入 H (1)=1 , 有 c 1 - 3 c 2 +3 c 3 =1
代 入 H (2)=2 , 有 c 1 +9 c 2 +9 c 3 =2
求 出 : c 1 = - 1/4 , c 2 = - 1/12 , c 3 =1/3 , H ( n )= c 1 + c 2 ( - 3) + c 3 3 = (
n ‒ 1
4
+ 1 3
1
‒ 。
5. 解 : 构 造 的 递 归 树 如 图 1.10 所 示 , 第 1 层 的 问 题 规 模 为 n , 第 2 的 层 的 子 问 题 的 问 题 规 模 为 n /2 , 依 此 类 推 , 当 展 开 到 第 k +1 层 , 其 规 模 为 n /2 =1 , 所 以 递 归 树 的 高 度 为 log 2 n +1 。
第 1 层 有 1 个 结 点 , 其 时 间 为 n , 第 2 层 有 4 个 结 点 , 其 时 间 为 4 ( n /2)=2 n , 依 次 类 推 , 第 k 层 有 4 个 结 点 , 每 个 子 问 题 规 模 为 n /2 , 其 时 间 为 4 ( n /2 )=2 n 。 叶 子 结 点 的 个 数 为 n 个 , 其 时 间 为 n 。 将 递 归 树 每 一 层 的 时 间 加 起 来 , 可 得 :
log n
T ( n )= n +2 n + … + 2 n + … + n ≈ 𝑛 ∗ 2 =O( n ) 。
12
2
n
n n -1 n -2 n -3
两 边 同 时 除 以 x
, 得 到 : x
= x
+9 x - 9 , 即 x
- x
- 9 x+ 9=0 。
3 2 2 2 2
n n
(
n
n
)
‒ 1
)
n ‒ 1
4
k
k -1 k -1 k -1 k -1 k -1
2
k -1 2

----------------------------------------------------- Page 13 -----------------------------------------------------
第 1 章
概 论
n
n
( n /2)
( n /2)
( n /2)
( n /2)
2 n
高 度 h 为 log 2 n +1
( n /2 )

1
( n /2 )

1
( n /2 )

1
( n /2 )

1
2 n
n
图 1.10 一 棵 递 归 树
6. 解 : 采 用 主 方 法 求 解 , 这 里 a =4 , b =2 , f ( n )= n 。
log a log 4
因 此 , 𝑛 = 𝑛 = n , 它 与 f ( n ) 一 样 大 , 满 足 主 定 理 中 的 情 况 ( 2 ) , 所 以 T ( n )= O( log a
𝑛 log 2 n) =O( n log 2 n ) 。
7. 解 : 设 求 斐 波 那 契 f ( n ) 的 时 间 为 T( n ) , 有 以 下 递 推 式 :
T(1)=T(2)
T(n)=T( n - 1)+T( n - 2)+1
当 n >2
其 中 , T( n ) 式 中 加 1 表 示 一 次 加 法 运 算 的 时 间 。
不 妨 先 求 T 1 (1)=T 1 (2)=1 , T 1 ( n )=T 1 ( n - 1)+T 1 ( n - 2) , 按 《 教 程 》 例 2.14 的 方 法 可 以 求
出 :
T 1 ( n )=
1
5
 1  5   
 

1
5
 1  5   
 
1  1  5  ≈ =
 
所 以 T( n )=T 1 ( n )+1 ≈
1
5
 1  5   
 
+1=O( φ ) , 其 中 φ = 。
2
8. 解 : 设 f ( m ) 计 算 数 列 第 m 项 值 。
当 m 为 偶 数 时 , 不 妨 设 m =2 n , 则 2 n - 1= m - 1 , 所 以 有 f ( m )= f ( m - 1)+2 。
当 m 为 奇 数 时 , 不 妨 设 m =2 n +1 , 则 2 n - 1= m - 2 , 2 n = m - 1 , 所 以 有 f ( m )= f ( m - 2)+ f ( m - 1) - 1 。
对 应 的 递 归 算 法 如 下 :
int f(int m)
{ if (m==1) return 0;
if (m%2==0)
return f(m-1)+2;
else
return f(m-2)+f(m-1)-1;
}
9. 解 : 设 f (str) 返 回 字 符 串 str 的 长 度 , 其 递 归 模 型 如 下 :
f (str)=0
f (str)= f (str+1)+1
当 * str='\0' 时
其 他 情 况
对 应 的 递 归 程 序 如 下 :
13
2
2
2
2
2
2
b
2
2
b
2
n
 
2
n
 
2
n
 
 
5 2
n
 
2
n 1  5

----------------------------------------------------- Page 14 -----------------------------------------------------
#include <iostream>
using namespace std;
int Length(char *str)
{ if (*str=='\0')
return 0;
else
return Length(str+1)+1;
}
void main()
{ char str[]="abcd";
算 法 设 计
// 求 s tr 的 字 符 个 数
cout << str << " 的 长 度 : " << Length(str) << endl; }
上 述 程 序 的 执 行 结 果 如 图 1.11 所 示 。
图 1.11
程 序 执 行 结 果
10. 解 : 设 f (str , n ) 返 回 含 n 个 字 符 的 字 符 串 str 是 否 为 回 文 , 其 递 归 模 型 如 下 :
f (str , n )=true
f (str , n )=flase
f (str , n )= f (str+1 , n - 2)
当 n =0 或 者 n =1 时 当 str[0] ≠ str[ n - 1] 时 其 他 情 况
对 应 的 递 归 算 法 如 下 :
#include <stdio.h>
#include <string.h>
bool isPal(char *str,int n) //str 回 文 判 断 算 法 { if (n==0 || n==1)
return true;
if (str[0]!=str[n-1])
return false;
return isPal(str+1,n-2);
}
void disp(char *str)
{ int n=strlen(str);
if (isPal(str,n))
printf(" %s 是 回 文 \ n",str);
else
printf(" %s 不 是 回 文 \ n",str);
}
void main()
{ printf(" 求 解 结 果 \ n");
disp("abcba");
disp("a");
disp("abc");
}
14

----------------------------------------------------- Page 15 -----------------------------------------------------
第 1 章
概 论
上 述 程 序 的 执 行 结 果 如 图 1.12 所 示 。
图 1.12
程 序 执 行 结 果
11. 解 : 设 f (L) 正 序 输 出 单 链 表 L 的 所 有 结 点 值 , 其 递 归 模 型 如 下 :
f (L) ≡ 不 做 任 何 事 情
f (L) ≡ 输 出 L - >data; f (L - >next); 对 应 的 递 归 程 序 如 下 : #include "LinkList.cpp"
当 L=NULL
当 L ≠ NULL 时
// 包 含 单 链 表 的 基 本 运 算 算 法
void dispLink(LinkNode *L) // 正 序 输 出 所 有 结 点 值 { if (L==NULL) return;
else
{ printf("%d ",L->data);
dispLink(L->next);
}
}
void main()
{ int a[]={1,2,5,2,3,2};
int n=sizeof(a)/sizeof(a[0]);
LinkNode *L;
CreateList(L,a,n); printf(" 正 向 L : "); dispLink(L); printf("\n");
Release(L);
// 由 a [0..n-1] 创 建 不 带 头 结 点 的 单 链 表 // 销 毁 单 链 表
}
上 述 程 序 的 执 行 结 果 如 图 1.13 所 示 。
图 1.13
程 序 执 行 结 果
12. 解 : 设 f (L) 逆 序 输 出 单 链 表 L 的 所 有 结 点 值 , 其 递 归 模 型 如 下 :
f (L) ≡ 不 做 任 何 事 情
f (L) ≡ f (L - >next); 输 出 L - >data 对 应 的 递 归 程 序 如 下 :
#include "LinkList.cpp"
void Revdisp(LinkNode *L)
{ if (L==NULL) return;
当 L=NULL
当 L ≠ NULL 时
// 包 含 单 链 表 的 基 本 运 算 算 法 // 逆 序 输 出 所 有 结 点 值
15

----------------------------------------------------- Page 16 -----------------------------------------------------
else
{ Revdisp(L->next);
printf("%d ",L->data);
}
}
void main()
{ int a[]={1,2,5,2,3,2};
int n=sizeof(a)/sizeof(a[0]);
LinkNode *L;
CreateList(L,a,n);
printf(" 反 向 L : ");
Revdisp(L); printf("\n");
Release(L);
}
上 述 程 序 的 执 行 结 果 如 图 1.14 所 示 。
图 1.14
算 法 设 计
程 序 执 行 结 果
13. 解 : 设 f (L) 返 回 单 链 表 L 中 值 最 大 结 点 的 地 址 , 其 递 归 模 型 如 下 :
f (L) = L
f (L) = MAX{ f (L - >next) , L->data} 对 应 的 递 归 程 序 如 下 : #include "LinkList.cpp"
当 L 只 有 一 个 结 点 时 其 他 情 况
// 包 含 单 链 表 的 基 本 运 算 算 法
LinkNode *Maxnode(LinkNode *L) // 返 回 最 大 值 结 点 的 地 址 { if (L->next==NULL)
return L;
else
{ LinkNode *maxp;
maxp=Maxnode(L->next);
if (L->data>maxp->data)
return L;
else
return maxp;
}
}
void main()
{ int a[]={1,2,5,2,3,2};
// 只 有 一 个 结 点 时
int n=sizeof(a)/sizeof(a[0]);
LinkNode *L,*p;
CreateList(L,a,n);
p=Maxnode(L);
printf(" 最 大 结 点 值 : %d\n",p->data); Release(L);
16

----------------------------------------------------- Page 17 -----------------------------------------------------
第 1 章
概 论
}
上 述 程 序 的 执 行 结 果 如 图 1.15 所 示 。
图 1.15
程 序 执 行 结 果
14. 解 : 设 f (L , x ) 返 回 单 链 表 L 中 第 一 个 值 为 x 的 结 点 的 地 址 , 其 递 归 模 型 如 下 :
f (L , x ) = NULL
f (L , x ) = L
f (L , x ) = f (L - >next , x ) 对 应 的 递 归 程 序 如 下 :
当 L=NULL 时
当 L ≠ NULL 且 L - >data= x 时 其 他 情 况
#include "LinkList.cpp"
LinkNode *Firstxnode(LinkNode *L,int x) { if (L==NULL) return NULL;
if (L->data==x)
return L;
else
return Firstxnode(L->next,x);
}
void main()
{ int a[]={1,2,5,2,3,2};
int n=sizeof(a)/sizeof(a[0]);
LinkNode *L,*p;
CreateList(L,a,n);
int x=2;
p=Firstxnode(L,x);
printf(" 结 点 值 : %d\n",p->data); Release(L);
}
上 述 程 序 的 执 行 结 果 如 图 1.16 所 示 。
// 包 含 单 链 表 的 基 本 运 算 算 法
// 返 回 第 一 个 值 为 x 的 结 点 的 地 址
图 1.16
程 序 执 行 结 果
15. 解 : 设 f (L , x ) 删 除 单 链 表 L 中 第 一 个 值 为 x 的 结 点 , 其 递 归 模 型 如 下 :
f (L , x ) ≡ 不 做 任 何 事 情
f (L , x ) ≡ 删 除 L 结 点 , L=L - >next f (L , x ) ≡ f (L - >next , x )
对 应 的 递 归 程 序 如 下 :
当 L=NULL
当 L ≠ NULL 且 L - >data= x 其 他 情 况
17

----------------------------------------------------- Page 18 -----------------------------------------------------
#include "LinkList.cpp"
算 法 设 计
// 包 含 单 链 表 的 基 本 运 算 算 法
void Delfirstx(LinkNode *&L,int x) // 删 除 单 链 表 L 中 第 一 个 值 为 x 的 结 点 { if (L==NULL) return;
if (L->data==x)
{ LinkNode *p=L;
L=L->next;
free(p);
}
else
Delfirstx(L->next,x);
}
void main()
{ int a[]={1,2,5,2,3,2};
int n=sizeof(a)/sizeof(a[0]);
LinkNode *L;
CreateList(L,a,n);
printf(" 删 除 前 L : "); DispList(L);
int x=2;
printf(" 删 除 第 一 个 值 为 % d 的 结 点 \ n",x);
Delfirstx(L,x);
printf(" 删 除 后 L : "); DispList(L);
Release(L);
}
上 述 程 序 的 执 行 结 果 如 图 1.17 所 示 。
图 1.17
程 序 执 行 结 果
16. 解 : 设 f (bt) 返 回 二 叉 树 bt 中 所 有 叶 子 结 点 值 之 和 , 其 递 归 模 型 如 下 :
f (bt)=0
f (bt)=bt - >data
f (bt)= f (bt - >lchild)+ f (bt - >rchild) 对 应 的 递 归 程 序 如 下 : #include "Btree.cpp"
int LeafSum(BTNode *bt)
{ if (bt==NULL) return 0;
当 bt=NULL
当 bt ≠ NULL 且 bt 结 点 为 叶 子 结 点 其 他 情 况
// 包 含 二 叉 树 的 基 本 运 算 算 法
// 二 叉 树 bt 中 所 有 叶 子 结 点 值 之 和
if (bt->lchild==NULL && bt->rchild==NULL)
return bt->data;
int lsum=LeafSum(bt->lchild);
int rsum=LeafSum(bt->rchild);
return lsum+rsum;
}
void main()
18

----------------------------------------------------- Page 19 -----------------------------------------------------
第 1 章
概 论
{
BTNode *bt;
Int a[]={5,2,3,4,1,6}; // 先 序 序 列
Int b[]={2,3,5,1,4,6}; // 中 序 序 列
int n=sizeof(a)/sizeof(a[0]); bt=CreateBTree(a,b,n); // 由 a 和 b 构 造 二 叉 链 b t printf(" 二 叉 树 b t:"); DispBTree(bt); printf("\n"); printf(" 所 有 叶 子 结 点 值 之 和 : %d\n",LeafSum(bt));
DestroyBTree(bt);
// 销 毁 树 b t
}
上 述 程 序 的 执 行 结 果 如 图 1.18 所 示 。
图 1.18
程 序 执 行 结 果
17. 解 : 设 f (bt , k ) 返 回 二 叉 树 bt 中 所 有 结 点 值 大 于 等 于 k 的 结 点 个 数 , 其 递 归 模 型 如 下 :
f (bt , k )=0
f (bt , k )= f (bt - >lchild , k )+ f (bt - >rchild , k )+1 f (bt , k )= f (bt - >lchild , k )+ f (bt - >rchild , k )
对 应 的 递 归 程 序 如 下 :
#include "Btree.cpp"
int Nodenum(BTNode *bt,int k)
{ if (bt==NULL) return 0;
int lnum=Nodenum(bt->lchild,k);
int rnum=Nodenum(bt->rchild,k);
if (bt->data>=k)
return lnum+rnum+1;
else
return lnum+rnum;
}
void main()
{ BTNode *bt;
Int a[]={5,2,3,4,1,6};
Int b[]={2,3,5,1,4,6};
int n=sizeof(a)/sizeof(a[0]);
bt=CreateBTree(a,b,n);
当 bt=NULL
当 bt ≠ NULL 且 bt - >data ≥ k 其 他 情 况
// 包 含 二 叉 树 的 基 本 运 算 算 法 // 大 于 等 于 k 的 结 点 个 数
// 由 a 和 b 构 造 二 叉 链 b t
printf(" 二 叉 树 b t:"); DispBTree(bt); printf("\n"); int k=3;
printf(" 大 于 等 于 % d 的 结 点 个 数 : %d\n",k,Nodenum(bt,k));
DestroyBTree(bt);
// 销 毁 树 b t
}
上 述 程 序 的 执 行 结 果 如 图 1.19 所 示 。
19

----------------------------------------------------- Page 20 -----------------------------------------------------
算 法 设 计
图 1.19
程 序 执 行 结 果
18. 解 : 设 f (bt , x , h ) 返 回 二 叉 树 bt 中 x 结 点 的 层 次 , 其 中 h 表 示 bt 所 指 结 点 的 层 次 , 初 始 调 用 时 , bt 指 向 根 结 点 , h 置 为 1 。 其 递 归 模 型 如 下 :
f (bt , x , h )=0
f (bt , x , h )= h
f (bt , x , h ) = l
f (bt , x , h ) = f (bt - >rchild , x , h +1) 对 应 的 递 归 程 序 如 下 :
当 bt=NULL
当 bt ≠ NULL 且 bt - >data=x 当 l = f (bt - >lchild , x , h +1) ≠ 0 其 他 情 况
#include "Btree.cpp"
int Level(BTNode *bt,int x,int h) { // 初 始 调 用 时 : bt 为 根 , h 为 1
if (bt==NULL) return 0;
if (bt->data==x)
return h;
// 包 含 二 叉 树 的 基 本 运 算 算 法 // 求 二 叉 树 bt 中 x 结 点 的 层 次
// 找 到 x 结 点 , 返 回 h
else
{ int l=Level(bt->lchild,x,h+1); // 在 左 子 树 中 查 找
if (l!=0)
return l;
else
// 在 左 子 树 中 找 到 , 返 回 其 层 次 l
return Level(bt->rchild,x,h+1);// 返 回 在 右 子 树 的 查 找 结 果
}
}
void main()
{ BTNode *bt;
Int a[]={5,2,3,4,1,6};
Int b[]={2,3,5,1,4,6};
int n=sizeof(a)/sizeof(a[0]);
bt=CreateBTree(a,b,n);
// 由 a 和 b 构 造 二 叉 链 bt
printf(" 二 叉 树 bt:"); DispBTree(bt); printf("\n"); int x=1;
printf("%d 结 点 的 层 次 : %d\n",x,Level(bt,x,1));
DestroyBTree(bt);
}
上 述 程 序 的 执 行 结 果 如 图 1.20 所 示 。
// 销 毁 树 bt
图 1.20
程 序 执 行 结 果 20

----------------------------------------------------- Page 21 -----------------------------------------------------
第 1 章
概 论
1.3 第 3 章 ─ 分 治 法
1.3.1 练 习 题
1. 分 治 法 的 设 计 思 想 是 将 一 个 难 以 直 接 解 决 的 大 问 题 分 割 成 规 模 较 小 的 子 问 题 , 分 别 解 决 子 问 题 , 最 后 将 子 问 题 的 解 组 合 起 来 形 成 原 问 题 的 解 。 这 要 求 原 问 题 和 子 问 题 ( ) 。
A. 问 题 规 模 相 同 , 问 题 性 质 相 同
B. 问 题 规 模 相 同 , 问 题 性 质 不 同
C. 问 题 规 模 不 同 , 问 题 性 质 相 同
D. 问 题 规 模 不 同 , 问 题 性 质 不 同
2. 在 寻 找 n 个 元 素 中 第 k 小 元 素 问 题 中 , 如 快 速 排 序 算 法 思 想 , 运 用 分 治 算 法 对 n 个 元 素 进 行 划 分 , 如 何 选 择 划 分 基 准 ? 下 面 ( ) 答 案 解 释 最 合 理 。
A. 随 机 选 择 一 个 元 素 作 为 划 分 基 准
B. 取 子 序 列 的 第 一 个 元 素 作 为 划 分 基 准
C. 用 中 位 数 的 中 位 数 方 法 寻 找 划 分 基 准
D. 以 上 皆 可 行 。 但 不 同 方 法 , 算 法 复 杂 度 上 界 可 能 不 同
3. 对 于 下 列 二 分 查 找 算 法 , 以 下 正 确 的 是 ( ) 。
A.
int binarySearch(int a[], int n, int x)
{ int low=0, high=n-1;
while(low<=high)
{ int mid=(low+high)/2;
if(x==a[mid]) return mid;
if(x>a[mid]) low=mid;
else high=mid;
}
return – 1;
}
B.
int binarySearch(int a[], int n, int x)
{ int low=0, high=n-1;
while(low+1!=high)
{ int mid=(low+high)/2;
if(x>=a[mid]) low=mid;
else high=mid;
}
if(x==a[low]) return low;
else return – 1;
}
C.
int binarySearch (int a[], int n, int x)
{ int low=0, high=n-1;
while(low<high-1)
{ int mid=(low+high)/2;
21

----------------------------------------------------- Page 22 -----------------------------------------------------
算 法 设 计
if(x<a[mid])
high=mid;
else low=mid;
}
if(x==a[low]) return low;
else return – 1;
}
D.
int binarySearch(int a[], int n, int x)
{ if(n > 0 && x >= a[0])
{ int low = 0, high = n-1;
while(low < high)
{ int mid=(low+high+1)/2;
if(x < a[mid])
high=mid-1;
else low=mid;
}
if(x==a[low]) return low;
}
return – 1;
}
4. 快 速 排 序 算 法 是 根 据 分 治 策 略 来 设 计 的 , 简 述 其 基 本 思 想 。
5. 假 设 含 有 n 个 元 素 的 待 排 序 的 数 据 a 恰 好 是 递 减 排 列 的 , 说 明 调 用 QuickSort( a , 0 , n - 1) 递 增 排 序 的 时 间 复 杂 度 为 O( n ) 。
6. 以 下 哪 些 算 法 采 用 分 治 策 略 :
( 1 ) 堆 排 序 算 法
( 2 ) 二 路 归 并 排 序 算 法
( 3 ) 折 半 查 找 算 法
( 4 ) 顺 序 查 找 算 法
7. 适 合 并 行 计 算 的 问 题 通 常 表 现 出 哪 些 特 征 ?
8. 设 有 两 个 复 数 x = a + bi 和 y = c + di 。 复 数 乘 积 xy 可 以 使 用 4 次 乘 法 来 完 成 , 即 xy =( ac - bd )+( ad + bc ) i 。 设 计 一 个 仅 用 3 次 乘 法 来 计 算 乘 积 xy 的 方 法 。
9. 有 4 个 数 组 a 、 b 、 c 和 d , 都 已 经 排 好 序 , 说 明 找 出 这 4 个 数 组 的 交 集 的 方 法 。 10. 设 计 一 个 算 法 , 采 用 分 治 法 求 一 个 整 数 序 列 中 的 最 大 最 小 元 素 。
11. 设 计 一 个 算 法 , 采 用 分 治 法 求 x 。
12. 假 设 二 叉 树 采 用 二 叉 链 存 储 结 构 进 行 存 储 。 设 计 一 个 算 法 采 用 分 治 法 求 一 棵 二 叉 树 bt 的 高 度 。
13. 假 设 二 叉 树 采 用 二 叉 链 存 储 结 构 进 行 存 储 。 设 计 一 个 算 法 采 用 分 治 法 求 一 棵 二 叉 树 bt 中 度 为 2 的 结 点 个 数 。
14. 有 一 种 二 叉 排 序 树 , 其 定 义 是 空 树 是 一 棵 二 叉 排 序 树 , 若 不 空 , 左 子 树 中 所 有 结 点 值 小 于 根 结 点 值 , 右 子 树 中 所 有 结 点 值 大 于 根 结 点 值 , 并 且 左 右 子 树 都 是 二 叉 排 序 树 。 现 在 该 二 叉 排 序 树 采 用 二 叉 链 存 储 , 采 用 分 治 法 设 计 查 找 值 为 x 的 结 点 地 址 , 并 分 析 算 法 的 最 好 的 平 均 时 间 复 杂 度 。
22
2
n

----------------------------------------------------- Page 23 -----------------------------------------------------
第 1 章
概 论
15. 设 有 n 个 互 不 相 同 的 整 数 , 按 递 增 顺 序 存 放 在 数 组 a [0.. n - 1] 中 , 若 存 在 一 个 下 标 i ( 0 ≤ i < n ) , 使 得 a [ i ]= i 。 设 计 一 个 算 法 以 O(log 2 n ) 时 间 找 到 这 个 下 标 i 。
16. 请 你 模 仿 二 分 查 找 过 程 设 计 一 个 三 分 查 找 算 法 。 分 析 其 时 间 复 杂 度 。
17. 对 于 大 于 1 的 正 整 数 n , 可 以 分 解 为 n = x 1 * x 2 * … * x m , 其 中 x i ≥ 2 。 例 如 , n =12 时 有 8 种 不 同 的 分 解 式 : 12=12 , 12=6*2 , 12=4*3 , 12=3*4 , 12=3*2*2 , 12=2*6 , 12=2*3*2 , 12=2*2*3 , 设 计 一 个 算 法 求 n 的 不 同 分 解 式 个 数 。
18. 设 计 一 个 基 于 BSP 模 型 的 并 行 算 法 , 假 设 有 p 台 处 理 器 , 计 算 整 数 数 组 a [0.. n - 1] 的 所 有 元 素 之 和 。 并 分 析 算 法 的 时 间 复 杂 度 。
1.3.2 练 习 题 参 考 答 案
1. 答 : C 。
2. 答 : D 。
3. 答 : 以 a []={1 , 2 , 3 , 4 , 5} 为 例 说 明 。 选 项 A 中 在 查 找 5 时 出 现 死 循 环 。 选 项 B 中 在 查 找 5 时 返 回 - 1 。 选 项 C 中 在 查 找 5 时 返 回 - 1 。 选 项 D 正 确 。
4. 答 : 对 于 无 序 序 列 a [low..high] 进 行 快 速 排 序 , 整 个 排 序 为 “ 大 问 题 ” 。 选 择 其 中 的 一 个 基 准 base= a [ i ] ( 通 常 以 序 列 中 第 一 个 元 素 为 基 准 ) , 将 所 有 小 于 等 于 base 的 元 素 移 动 到 它 的 前 面 , 所 有 大 于 等 于 base 的 元 素 移 动 到 它 的 后 面 , 即 将 基 准 归 位 到 a [ i ] , 这 样 产 生 a [low.. i - 1] 和 a [ i +1..high] 两 个 无 序 序 列 , 它 们 的 排 序 为 “ 小 问 题 ” 。 当 a [low..high] 序 列 只 有 一 个 元 素 或 者 为 空 时 对 应 递 归 出 口 。
所 以 快 速 排 序 算 法 就 是 采 用 分 治 策 略 , 将 一 个 “ 大 问 题 ” 分 解 为 两 个 “ 小 问 题 ” 来 求 解 。 由 于 元 素 都 是 在 a 数 组 中 , 其 合 并 过 程 是 自 然 产 生 的 , 不 需 要 特 别 设 计 。
5. 答 : 此 时 快 速 排 序 对 应 的 递 归 树 高 度 为 O( n ) , 每 一 次 划 分 对 应 的 时 间 为 O( n ) , 所 以 整 个 排 序 时 间 为 O( n ) 。
6. 答 : 其 中 二 路 归 并 排 序 和 折 半 查 找 算 法 采 用 分 治 策 略 。
7. 答 : 适 合 并 行 计 算 的 问 题 通 常 表 现 出 以 下 特 征 :
( 1 ) 将 工 作 分 离 成 离 散 部 分 , 有 助 于 同 时 解 决 。 例 如 , 对 于 分 治 法 设 计 的 串 行 算 法 , 可 以 将 各 个 独 立 的 子 问 题 并 行 求 解 , 最 后 合 并 成 整 个 问 题 的 解 , 从 而 转 化 为 并 行 算 法 。
( 2 ) 随 时 并 及 时 地 执 行 多 个 程 序 指 令 。
( 3 ) 多 计 算 资 源 下 解 决 问 题 的 耗 时 要 少 于 单 个 计 算 资 源 下 的 耗 时 。
8. 答 : xy =( ac - bd )+(( a + b )( c + d ) - ac - bd ) i 。 由 此 可 见 , 这 样 计 算 xy 只 需 要 3 次 乘 法 ( 即 ac 、 bd 和 ( a + b )( c + d ) 乘 法 运 算 ) 。
9. 答 : 采 用 基 本 的 二 路 归 并 思 路 , 先 求 出 a 、 b 的 交 集 ab , 再 求 出 c 、 d 的 交 集 cd , 最 后 求 出 ab 和 cd 的 交 集 , 即 为 最 后 的 结 果 。 也 可 以 直 接 采 用 4 路 归 并 方 法 求 解 。
10. 解 : 采 用 类 似 求 求 一 个 整 数 序 列 中 的 最 大 次 大 元 素 的 分 治 法 思 路 。 对 应 的 程 序 如 下 :
#include <stdio.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
23
2

----------------------------------------------------- Page 24 -----------------------------------------------------
算 法 设 计
void MaxMin(int a[],int low,int high,int &maxe,int &mine)
// 求 a 中 最 大 最 小 元 素
{ if (low==high)
{ maxe=a[low];
mine=a[low];
}
else if (low==high-1)
{ maxe=max(a[low],a[high]);
mine=min(a[low],a[high]);
}
else
{ int mid=(low+high)/2;
// 只 有 一 个 元 素
// 只 有 两 个 元 素
// 有 两 个 以 上 元 素
int lmaxe,lmine;
MaxMin(a,low,mid,lmaxe,lmine);
int rmaxe,rmine;
MaxMin(a,mid+1,high,rmaxe,rmine);
maxe=max(lmaxe,rmaxe);
mine=min(lmine,rmine);
}
}
void main()
{ int a[]={4,3,1,2,5};
int n=sizeof(a)/sizeof(a[0]);
int maxe,mine;
MaxMin(a,0,n-1,maxe,mine);
printf("Max=%d, Min=%d\n",maxe,mine);
}
上 述 程 序 的 执 行 结 果 如 图 1.21 所 示 。
图 1.21
程 序 执 行 结 果
11. 解 : 设 f ( x , n )= x , 采 用 分 治 法 求 解 对 应 的 递 归 模 型 如 下 :
f ( x , n )= x
f ( x , n )= f ( x , n /2)* f ( x , n /2)
f ( x , n )= f ( x , ( n - 1)/2)* f ( x , ( n - 1)/2)* x 对 应 的 递 归 程 序 如 下 : #include <stdio.h>
double solve(double x,int n)
{ double fv;
if (n==1) return x;
if (n%2==0)
{ fv=solve(x,n/2);
return fv*fv;
}
当 n =1 当 n 为 偶 数 时 当 n 为 奇 数 时
// 求 x ^n
24
n

----------------------------------------------------- Page 25 -----------------------------------------------------
第 1 章
else
{ fv=solve(x,(n-1)/2);
return fv*fv*x;
}
}
void main()
{ double x=2.0;
printf(" 求 解 结 果 : \n");
for (int i=1;i<=10;i++)
printf(" %g^%d=%g\n",x,i,solve(x,i));
}
上 述 程 序 的 执 行 结 果 如 图 1.22 所 示 。
概 论
图 1.22
程 序 执 行 结 果
12. 解 : 设 f (bt) 返 回 二 叉 树 bt 的 高 度 , 对 应 的 递 归 模 型 如 下 :
f (bt)=0
f (bt)=MAX{ f (bt - >lchild) , f (bt - >rchild)}+1 对 应 的 程 序 如 下 :
#include "Btree.cpp"
int Height(BTNode *bt)
{ if (bt==NULL) return 0;
int lh=Height(bt->lchild);
int rh=Height(bt->rchild);
if (lh>rh) return lh+1;
else return rh+1;
}
void main()
{ BTNode *bt;
Int a[]={5,2,3,4,1,6};
Int b[]={2,3,5,1,4,6};
int n=sizeof(a)/sizeof(a[0]);
bt=CreateBTree(a,b,n);
当 bt=NULL
其 他 情 况
// 包 含 二 叉 树 的 基 本 运 算 算 法 // 求 二 叉 树 b t 的 高 度
// 子 问 题 1
// 子 问 题 2
// 合 并
// 由 a 和 b 构 造 二 叉 链 b t
printf(" 二 叉 树 b t:"); DispBTree(bt); printf("\n"); printf("bt 的 高 度 : %d\n",Height(bt));
DestroyBTree(bt);
// 销 毁 树 b t
}
25

----------------------------------------------------- Page 26 -----------------------------------------------------
算 法 设 计
上 述 程 序 的 执 行 结 果 如 图 1.23 所 示 。
图 1.23
程 序 执 行 结 果
13. 解 : 设 f (bt) 返 回 二 叉 树 bt 中 度 为 2 的 结 点 个 数 , 对 应 的 递 归 模 型 如 下 :
f (bt)=0
f (bt)= f (bt - >lchild)+ f (bt - >rchild)+1 f (bt)= f (bt - >lchild)+ f (bt - >rchild)
对 应 的 算 法 如 下 :
#include "Btree.cpp"
int Nodes(BTNode *bt)
{ int n=0;
当 bt=NULL
若 bt ≠ NULL 且 bt 为 双 分 支 结 点 其 他 情 况
// 包 含 二 叉 树 的 基 本 运 算 算 法 // 求 bt 中 度 为 2 的 结 点 个 数
if (bt==NULL) return 0;
if (bt->lchild!=NULL && bt->rchild!=NULL)
n=1;
return Nodes(bt->lchild)+Nodes(bt->rchild)+n;
}
void main()
{ BTNode *bt;
Int a[]={5,2,3,4,1,6};
Int b[]={2,3,5,1,4,6};
int n=sizeof(a)/sizeof(a[0]);
bt=CreateBTree(a,b,n);
// 由 a 和 b 构 造 二 叉 链 b t
printf(" 二 叉 树 b t:"); DispBTree(bt); printf("\n"); printf("bt 中 度 为 2 的 结 点 个 数 : %d\n",Nodes(bt));
DestroyBTree(bt);
// 销 毁 树 b t
}
上 述 程 序 的 执 行 结 果 如 图 1.24 所 示 。
图 1.24
程 序 执 行 结 果
14. 解 : 设 f (bt , x ) 返 回 在 二 叉 排 序 树 bt 得 到 的 值 为 x 结 点 的 地 址 , 若 没 有 找 到 返 回 空 , 对 应 的 递 归 模 型 如 下 :
f (bt , x )=NULL
f (bt , x )=bt
f (bt , x )= f (bt - >lchild , x )
当 bt=NULL
当 bt ≠ NULL 且 x =bt - >data 当 x >bt - >data
26

----------------------------------------------------- Page 27 -----------------------------------------------------
第 1 章
概 论
f (bt , x )= f (bt - >rchild , x ) 对 应 的 程 序 如 下 :
#include "Btree.cpp"
当 x <bt - >data
// 包 含 二 叉 树 的 基 本 运 算 算 法
BTNode *Search(BTNode *bt,Int x) // 在 二 叉 排 序 树 bt 查 找 的 值 为 x 结 点 { if (bt==NULL) return NULL;
if (x==bt->data) return bt;
if (x<bt->data) return Search(bt->lchild,x);
else return Search(bt->rchild,x);
}
void main()
{ BTNode *bt;
Int a[]={4,3,2,8,6,7,9};
Int b[]={2,3,4,6,7,8,9};
int n=sizeof(a)/sizeof(a[0]);
bt=CreateBTree(a,b,n);
// 构 造 一 棵 二 叉 排 序 树 bt
printf(" 二 叉 排 序 树 bt:"); DispBTree(bt); printf("\n"); int x=6;
BTNode *p=Search(bt,x);
if (p!=NULL)
printf(" 找 到 结 点 : %d\n",p->data);
else
printf(" 没 有 找 到 结 点 \ n",x);
DestroyBTree(bt);
// 销 毁 树 bt
}
上 述 程 序 的 执 行 结 果 如 图 1.25 所 示 。
图 1.25
程 序 执 行 结 果
Search(bt , x ) 算 法 采 用 的 是 减 治 法 , 最 好 的 情 况 是 某 个 结 点 左 右 子 树 高 度 大 致 相 同 , 其 平 均 执 行 时 间 T ( n ) 如 下 :
T ( n )=1
T ( n )= T ( n /2)+1
当 n =1
当 n >1
可 以 推 出 T ( n )=O(log 2 n ) , 其 中 n 为 二 叉 排 序 树 的 结 点 个 数 。
15. 解 : 采 用 二 分 查 找 方 法 。 a [ i ]= i 时 表 示 该 元 素 在 有 序 非 重 复 序 列 a 中 恰 好 第 i 大 。 对 于 序 列 a [low..high] , mid=(low+high)/2 , 若 a [mid]=mid 表 示 找 到 该 元 素 ; 若 a [mid]>mid 说 明 右 区 间 的 所 有 元 素 都 大 于 其 位 置 , 只 能 在 左 区 间 中 查 找 ; 若 a [mid]<mid 说 明 左 区 间 的 所 有 元 素 都 小 于 其 位 置 , 只 能 在 右 区 间 中 查 找 。 对 应 的 程 序 如 下 :
#include <stdio.h>
int Search(int a[],int n)
{ int low=0,high=n-1,mid;
// 查 找 使 得 a[i]=i
27

----------------------------------------------------- Page 28 -----------------------------------------------------
算 法 设 计
while (low<=high)
{ mid=(low+high)/2;
if (a[mid]==mid) // 查 找 到 这 样 的 元 素
return mid;
else if (a[mid]<mid) // 这 样 的 元 素 只 能 在 右 区 间 中 出 现 low=mid+1;
else
// 这 样 的 元 素 只 能 在 左 区 间 中 出 现
high=mid-1;
}
return -1;
}
void main()
{ int a[]={-2,-1,2,4,6,8,9};
int n=sizeof(a)/sizeof(a[0]);
int i=Search(a,n);
printf(" 求 解 结 果 \ n");
if (i!=-1)
printf(" 存 在 a [%d]=%d\n",i,i); else
printf(" 不 存 在 \ n");
}
上 述 程 序 的 执 行 结 果 如 图 1.26 所 示 。
图 1.26
程 序 执 行 结 果
16. 解 : 对 于 有 序 序 列 a [low..high] , 若 元 素 个 数 少 于 3 个 , 直 接 查 找 。 若 含 有 更 多 的 元 素 , 将 其 分 为 a [low..mid1 - 1] 、 a [mid1+1..mid2 - 1] 、 a [mid2+1..high] 子 序 列 , 对 每 个 子 序 列 递 归 查 找 , 算 法 的 时 间 复 杂 度 为 O(log 3 n ) , 属 于 O(log 2 n ) 级 别 。 对 应 的 算 法 如 下 :
#include <stdio.h>
int Search(int a[],int low,int high,int x) { if (high<low)
return -1;
else if (high==low)
{ if (x==a[low])
return low;
else
return -1;
}
if (high-low<2)
{ if (x==a[low])
return low;
else if (x==a[low+1])
return low+1;
else
// 三 分 查 找
// 序 列 中 没 有 元 素
// 序 列 中 只 有 1 个 元 素
// 序 列 中 只 有 2 个 元 素
28

----------------------------------------------------- Page 29 -----------------------------------------------------
第 1 章
概 论
return -1;
}
int length=(high-low+1)/3;
int mid1=low+length;
int mid2=high-length;
if (x==a[mid1])
return mid1;
else if (x<a[mid1])
return Search(a,low,mid1-1,x);
else if (x==a[mid2])
return mid2;
else if (x<a[mid2])
return Search(a,mid1+1,mid2-1,x);
else
return Search(a,mid2+1,high,x);
}
void main()
{ int a[]={1,3,5,7,9,11,13,15};
int n=sizeof(a)/sizeof(a[0]); printf(" 求 解 结 果 \ n");
int x=13;
int i=Search(a,0,n-1,x);
if (i!=-1)
printf(" a[%d]=%d\n",i,x);
else
printf(" 不 存 在 % d\n",x);
int y=10;
int j=Search(a,0,n-1,y);
if (j!=-1)
printf(" a[%d]=%d\n",j,y);
else
printf(" 不 存 在 % d\n",y);
}
上 述 程 序 的 执 行 结 果 如 图 1.27 所 示 。
// 每 个 子 序 列 的 长 度
图 1.27
程 序 执 行 结 果
17. 解 : 设 f ( n ) 表 示 n 的 不 同 分 解 式 个 数 。 有 : f (1)=1 , 作 为 递 归 出 口
f (2)=1 , 分 解 式 为 : 2=2
f (3)=1 , 分 解 式 为 : 3=3
f (4)=2 , 分 解 式 为 : 4=4 , 4=2*2
29

----------------------------------------------------- Page 30 -----------------------------------------------------
算 法 设 计
f (6)=3 , 分 解 式 为 : 6=6 , 6=2*3 , 6=3*2 , 即 f (6)= f (1)+ f (2)+ f (3)
以 此 类 推 , 可 以 看 出 f ( n ) 为 n 的 所 有 因 数 的 不 同 分 解 式 个 数 之 和 , 即 f ( n )= ∑ 𝑓 ( 𝑛/ 𝑖 ) 。 对 应 的 程 序 如 下 :
#include <stdio.h>
#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<p;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 (temp<dmin)
dmin=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 <stdio.h>
#include <algorithm>
using namespace std;
int Mindif1(int a[],int n)
{ sort(a,a+n);
int dmin=a[1]-a[0];
for (int i=2;i<n;i++)
{ int temp=a[i]-a[i-1];
if (temp<dmin)
dmin=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;i<n-1;i++)
for (int j=i+1;j<n;j++)
if (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 <stdio.h>
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 <stdio.h>
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 <stdio.h>
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 <stdio.h>
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 <stdio.h>
#define MAXN 51
// 问 题 表 示
int n;
char board[MAXN][MAXN];
int getMaxArea()
{ int maxArea=0;
for (int j=0; j<n; j++) { int countj=1;
// 蛮 力 法 求 解 算 法
for (int i=1; i<n; i++) // 统 计 第 j 列 中 相 同 颜 色 相 邻 棋 格 个 数 { if (board[i][j]==board[i-1][j])
countj++;
else
countj=1;
}
36

----------------------------------------------------- Page 37 -----------------------------------------------------
第 1 章
概 论
if (countj>maxArea)
maxArea=countj;
}
return maxArea;
}
int main()
{ scanf("%d",&n);
for (int i=0;i<n;i++)
scanf("%s",board[i]);
printf("%d\n",getMaxArea());
return 0;
}
11. 解 : 与 《 教 程 》 中 求 全 排 列 类 似 , 但 需 要 将 求 1 ~ n 的 全 排 列 改 为 按 下 标 0 ~ n - 1 求 a 的 全 排 列 ( 下 标 从 0 开 始 ) 。 采 用 非 递 归 的 程 序 如 下 :
#include <stdio.h>
#include <vector>
using namespace std;
vector<vector<int> > ps;
// 存 放 全 排 列
void Insert(vector<int> s,int a[],int i,vector<vector<int> > &ps1) // 在 每 个 集 合 元 素 中 间 插 入 i 得 到 p s1
{ vector<int> s1;
vector<int>::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<vector<int> > ps1;
vector<vector<int> >::iterator it;
vector<int> s,s1;
s.push_back(a[0]);
ps.push_back(s);
for (int i=1;i<n;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<vector<int> >::reverse_iterator it; // 全 排 列 的 反 向 迭 代 器
vector<int>::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 <stdio.h>
#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;j<n;j++)
rw+=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 <stdio.h>
#include <vector>
using namespace std;
// 问 题 表 示
int a[]={1,2,3,4,5};
int n=5,k=9;
vector<int> minpath;
// 求 解 结 果 表 示
int minn=n;
void disppath()
{ printf(" 选 择 的 元 素 : ");
// 设 置 为 全 局 变 量 // 存 放 最 优 解
// 最 多 选 择 n 个 元 素 // 输 出 一 个 解
for (int j=0;j<minpath.size();j++)
printf("%d ",minpath[j]); printf(" 元 素 个 数 = %d\n",minn);
}
41

----------------------------------------------------- Page 42 -----------------------------------------------------
算 法 设 计
void dfs(vector<int> path,int sum,int start) // 求 解 算 法
{ if (sum==k)
{ if (path.size()<minn)
{ 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<int> 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 <stdio.h>
bool ok(int a[],int i,int j) //ok 用 于 判 别 重 复 元 素
{ if (j>i)
{ for(int k=i;k<j;k++)
if (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;j<n;j++)
printf("%3d",a[j]);
printf("\n");
}
else
{ for (int j=i;j<n;j++)
if (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;i<n-1;i++)
printf("%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 <stdio.h>
#include <string.h>
#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 <stdio.h>
#include <stdlib.h>
#define MAXN 10
int q[MAXN];
bool place(int i)
{ int j=1;
if (i==1) return true;
while (j<i)
// 测 试 第 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 <stdio.h>
#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]<n)
x[i]++;
if (x[i]<n)
// 试 探 一 个 顶 点 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<n;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 <stdio.h>
#include <queue>
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<NodeType> 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<bestlen)
{ bestcount=1;
bestlen=e.length;
}
else if (e.length==bestlen)
bestcount++;
}
else
{ for (int j=0; j<n; 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]<bestlen) // 剪 枝
{ 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<NodeType> , 并 设 计 优 先 队 列 的 关 系 比 较 函 数 Cmp , 指 定 按 结 点 的 ub 值 进 行 比 较 , 即 ub 值 越 大 的 结 点 越 先 出 队 。 对 应 的 完 整 程 序 如 下 :
#include <stdio.h>
#include <queue>
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.ub<t.ub) || (s.ub==t.ub && s.x[0]>t.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<NodeType,vector<NodeType>,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]<bestx[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 <stdio.h>
#include <algorithm>
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<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<n;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 <stdio.h>
#include <string.h>
#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], 从 a [k] 开 始 往 前 的 数 + 1
{ for (i=0; i<m; i++)
a[k-i]+=1;
}
if (m==a[k])
{ a[k]+=2;
// 若 剩 下 数 等 于 a [k], 则 a [k] 的 值 + 2, 之 前 的 数 + 1
for (i=0; i<k; i++)
a[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 <stdio.h>
#include <algorithm>
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 <stdio.h>
#include <algorithm>
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 <stdio.h>
#include <string.h>
#include <algorithm>
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 <stdio.h>
#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; i<m; i++)
a[i][j]+= minValue(i,j);
min=a[0][0];
minRow=0;
for (i=1; i<m; i++)
if (a[i][0]<min)
{ min=a[i][0];
minRow=i;
}
for (j=0; j<n; j++)
{ printf("%d",minRow+1);
if (j<n-1) printf(" ");
minValue(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 <stdio.h>
#include <string.h>
#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 <stdio.h>
#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 <stdio.h>
#include <string.h>
#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 <iostream>
#include <string.h>
#include <vector>
#include <string>
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<char> 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<char>::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 <stdio.h>
#include <string.h>
#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 <iostream>
#include <string.h>
#include <vector>
#include <string>
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<char> 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<char>::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 <queue>
#include <string.h>
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<Node> 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;i<G->n;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;i<G->n-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[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;i<G->n;++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 <vector>
using namespace std;
void Dispapath(int path[][MAXV],int i,int j) // 输 出 顶 点 i 到 j 的 一 条 最 短 路 径
{ vector<int> 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;i<g.n;i++)
for (j=0;j<g.n;j++)
if (i!=j && g.edges[j][i]<INF)
{ if (A[i][j]+g.edges[j][i]<min)
{ 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;i<g.n;i++)
for (j=0;j<g.n;j++)
{ A[i][j]=g.edges[i][j];
//Floyd 算 法 求 图 g 中 的 一 个 最 小 环
if (i!=j && g.edges[i][j]<INF)
path[i][j]=i;
else
path[i][j]=-1;
}
for (k=0;k<g.n;k++)
{ for (i=0;i<g.n;i++)
for (j=0;j<g.n;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 <stdio.h>
#include <stdlib.h>
#include <time.h>
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