算法设计与分析(李春保)练习题答案v1


 

 

1.11 概论

 

1.1.1练习题

 

1.下列于算法的说法中确的有()。

 

Ⅰ.求解某一类题的算法是唯一

 

Ⅱ.算法必须在限步操作之后停

 

Ⅲ.算法的每一操作必须是明确,不能有歧义或义模糊

 

Ⅳ.算法执行后定产生确定的结

 

A. 1 B.2 C.3 D.4

 

2. T(n)表示当入规模为n时的算法效率,以下法效率最优的是)。

 

 

A.T(n)= T(n-1)+1T(1)=1 C.T(n)= T(n/2)+1T(1)=1

 

B.T(n)= 2n

 

D.T(n)=3nlog2n

 

 

3.什么算法?算法有哪特征?

 

4.判断一个2的正整数n是否为素数的方有多种,给出两算法,说明其中一种算法更好的由。

 

5.证明下关系成立:

 

110n-2n=(n)

 

22=(2)

 

6.证明O(f(n))+O(g(n))=O(max{f(n)g(n)})

 

7.个含nn>2)个数的数组a,判断中是否存在出现数超过所有元素半的元素。

 

8.一个符串采用string 象存储,设计一算法判断该字符是否为回文。

 

9.有一个整序列,设计一个算法判断其中是存在两个元素和好等于给定的整k

 

10.有两个整序列,每个整数序列中所有元素不相同。设计一算法求它们的公共元素,要求不使STL 的集合算

 

11.正整nn>1)可以写成质数的乘积式,称为整数的质因数分解。如,12=2*2*318=2*3*311=11。设计一个法求n这样分解后各个质因数现的次数,采vector 向量存结果。

 

12.有一个数序列,所有元素均不相同,设一个算法求相差小的元素对的个数。如序列4123的相差最小的元素对的数是3,其元素对是(12),(23),34)。

 

13.有一个map<stringint>容器,其中已经存放了多元素。设计一算法求出其中重复的value 返回重复value 数。

 

14.做第10,采用map 容器存放终结果。

 

15.假设有一nn>1)个元素的stack<int>st,设计一个法出栈从栈顶到栈底的第k1kn)个元素,他栈元素不变。

 

 

 

 

算法设计

 

1.1.2练习题参考答

 

1.答:由于法具有有穷性、确定性和输出性因而确,而解决某一类问题的算法不定是唯一的。答C

 

2.答:A的时间复杂度为O(n)。选项B的时间复杂度为O(n)。选项C的时间复杂度为O(log2n)。选项D 的时复杂度为O(nlog2n)。答案为C

 

3.答:算法求解问题的一系列计算步骤。算具有有限性、确性、可行性、输入性和输出性5 重要特征。

 

4.答:种算法如下:

 

#include <stdio.h>

 

#include <math.h>

 

boolisPrime1(intn)//1

 

{for (int i=2;i<n;i++)

 

if (n%i==0)

 

return false;

 

return true;

 

}

 

boolisPrime2(intn)//2

 

{for (int i=2;i<=(int)sqrt(n);i++)

 

if (n%i==0)

 

return false;

 

return true;

 

}

 

voidmain()

 

{int n=5;

 

printf("%d,%d\n",isPrime1(n),isPrime2(n));

 

}

 

方法1 的时间复度为O(n),方法2的时间复杂度n,所以2 更好。5.答:1)当n 足够大(10n-2n)/( n)=10所以10n-2n=(n)

 

22=2*2=(2)

 

6.明:对于任意f1(n)O(f(n)),存在正c1 和正常数n1使得对所有nn1f1(n)c1f(n)

 

类似地,对于任g1(n)O(g(n)),存在正常数c2 自然数n2,使得所有nn2g1(n)c2g(n)

 

c3=max{c1c2}n3=max{n1n2}h(n)= max{f(n)g(n)}

 

则对所有的nn3,有:

 

f1(n) +g1(n)c1f(n) +c2g(n)c3f(n)+c3g(n)=c3(f(n)+g(n))

 

c32max{f(n)g(n)}=2c3h(n)=O(max{f(n)g(n)})

 

7.先将a 中元素递排序,再求出现数最多的次数maxnum,最后判断否满足条件。对应的序如下:

 

#include <stdio.h>

 

#include <algorithm>

 

using namespace std;

 

2

 

 

 

 

1

 

概论

 

 

boolsolve(inta[],intn,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;

 

}

 

voidmain()

 

{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;

 

boolsolve(stringstr)//断字符串str为回文{int i=0,j=str.length()-1;

 

while (i<j)

 

{if (str[i]!=str[j])

 

return false;

 

3

 

 

 

 

算法设计

 

i++; j--;

 

}

 

return true;

 

}

 

voidmain()

 

{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;

 

boolsolve(inta[],intn,intk)

 

 

{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;

 

}

 

voidmain()

 

{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

 

 

 

 

1

 

概论

 

 

else

 

 

printf("

 

不存在两个元素和%d\n",k1);

 

 

}

 

上述程序的执行果如图1.3 所示

 

 

1.3

 

程序执行结果

 

 

10.解:采用集合set<int>储整数序列,集中元素默认是递增排序的,再用二路归并算法求它的交集。对应的序如下:

 

#include <stdio.h>

 

#include <set>

 

using namespace std;

 

 

voidsolve(set<int>s1,set<int>s2,set<int>&s3){set<int>::iteratorit1,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

 

 

voiddispset(set<int>s)

 

{set<int>::iteratorit;

 

//集合的元素

 

 

for (it=s.begin();it!=s.end();++it)

 

printf("%d ",*it);

 

printf("\n");

 

}

 

voidmain()

 

{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

 

 

 

 

算法设计

 

 

printf("

 

printf("

 

s2: "); dispset(s2);

 

s3: "); dispset(s3);

 

 

}

 

上述程序的执行果如图1.4 所示

 

 

1.4

 

程序执行结果

 

 

11.解:正整数n,从i=2 始查找其质因数ic 记录质因数i 的次数,当找到这样质因数后将(iic)作为一个素插入到vector v 中。最后输出v对应的算法如下:

 

#include <stdio.h>

 

#include <vector>

 

using namespace std;

 

 

struct NodeType

 

{int p;

 

int pc;

 

//vector向量元素 //质因数

 

//质因数出现次数

 

 

};

 

voidsolve(intn,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);

 

}

 

voiddisp(vector<NodeType>&v)//v

 

{vector<NodeType>::iteratorit;

 

for (it=v.begin();it!=v.end();++it)

 

printf("质因数%d出现%d\n",it->p,it->pc);

 

}

 

6

 

 

 

 

1

 

概论

 

 

voidmain()

 

{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;

 

 

intsolve(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;

 

}

 

voidmain()

 

{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

 

 

 

 

 

1.6


算法设计

 

程序执行结果

 

 

13.解:对于map<stringint>容器mymap,设计另外一map<intint>容器tmap将前者的value 后者的关键字。mymap,累计tmap 中相同关键字次数。一个参考程序及其输结果如下

 

#include <iostream>

 

#include <map>

 

#include <string>

 

using namespace std;

 

voidmain()

 

{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>::iteratorit;

 

map<int,int> tmap;

 

for (it=mymap.begin();it!=mymap.end();it++)

 

tmap[(*it).second]++;

 

map<intint>::iteratorit1;

 

cout << "求解结"<<endl;

 

for (it1=tmap.begin();it1!=tmap.end();it1++)

 

cout << "" << (*it1).first<< ":" << (*it1).second<< "\n";

 

}

 

上述程序的执行果如图1.7 所示

 

 

1.7

 

程序执行结果

 

 

14.解:采用map<intint>容器mymap 存放解结果,第一个量存放质因数,二个分量存放质数出现次数。对的程序如下:

 

#include <stdio.h>

 

#include <map>

 

using namespace std;

 

voidsolve(intn,map<int,int>&mymap)//n分解

 

{int i=2;

 

int ic=0;

 

do

 

 

{

 

if (n%i==0)

 

{ic++;

 

n=n/i;

 

}

 

 

8

 

 

 

 

1

 

概论

 

 

else

 

{if (ic>0)

 

mymap[i]=ic;

 

ic=0;

 

i++;

 

}

 

} while (n>1 || ic!=0);

 

}

 

voiddisp(map<int,int>&mymap)//mymap

 

{map<int,int>::iteratorit;

 

for (it=mymap.begin();it!=mymap.end();++it)

 

printf("质因数%d出现%d\n",it->first,it->second);

 

}

 

voidmain()

 

{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;

 

 

intsolve(stack<int>&st,intk)

 

{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个元

 

//出栈stk素并进tmpst//求第k个元素

 

//tmpst的所有素出栈并进栈st9

 

 

 

 

 

}

 

return e;

 

}

 

voiddisp(stack<int>&st)

 

{while (!st.empty())

 

{printf("%d ",st.top());

 

st.pop();

 

}

 

printf("\n");

 

}

 

voidmain()

 

{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.22 递归算法设计技术

 

1.2.1练习题

 

1.什么直接递归和间接归?消除递归一要用到什么数据构?2.分析下程序的执行结

 

#include <stdio.h>

 

voidf(intn,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

 

 

 

 

1

 

概论

 

 

}

 

voidmain()

 

{int n=4,m=4;

 

f(n,m);

 

}

 

3.采用接推导方法求解下递归方程:T(1)=1

 

T(n)=T(n-1)+nn>1

 

4.采用征方程方法求解下递归方程:H(0)=0

 

H(1)=1

 

H(2)=2

 

H(n)=H(n-1)+9H(n-2)-9H(n-3)n>2 5.采用归树方法求解以递归方程:T(1)=1

 

T(n)=4T(n/2)+nn>1

 

6.采用方法求解以下题递归方程。

 

 

T(n)=1

 

n=1

 

 

T(n)=4T(n/2)+nn>1

 

7.分析斐波那契f(n)间复杂度。

 

8.的首项a1=0,后奇数项和偶数项计算公式分别为a2n=a2n-1+2a2n+1=a2n- 1+a2n-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

 

 

 

 

算法设计

 

1.2.2练习题参考答

 

1.答:一个f 数定义中直接调用f 函数自己,为直接递归。一f 函数定义中调g 函数,而g 数的定义中调用f 数,称为间接递。消除递归一般用栈实现。

 

2.答:归函数f(nm)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-33232

 

代替H(n),有x=x+9x-9x

 

 

x-x-9x+9=x(x-9)-(x-9)=(x-1)(x-9)=(x-1)(x+3)(x-3)=0得到r1=1r2=-3r3=3则递归方程的通为:H(n)=c1+c2(-3)+c33

 

代入H(0)=0c1+c2+c3=0

 

代入H(1)=1c1-3c2+3c3=1

 

 

代入H(2)=2c1+9c2+9c3=2

 

求出:c1=-1/4c2=-1/12c3=1/3H(n)=c1+c2(-3)+c33=(

 

n1

 

4

 

+ 13

 

1

 

 

5.解:构造递归树如图1.10 所示,第1 问题规模为n,第2 的层的子问问题规模为n/2此类推,当展开k+1层,其规模为n/2=1,所以递归的高度为log2n+1

 

1层有1个结点其时间为n,第24个结点,其间为4(n/2)=2n,依次类推,第k 层有4点,每个子问题规模为n/2其时间为4(n/2)=2n。叶子结点个数为n 个,其时间为n将递归树每一层时间加起来,可

 

logn

T(n)=n+2n++ 2n++n𝑛2=O(n)

 

 

12

 

 

 

 

1

 

概论

 

 

n

 

n

 

 

(n/2)

 

(n/2)

 

(n/2)

 

(n/2)

 

2n

 

 

高度h log2n+1

 

(n/2)

 

 

1

 

(n/2)

 

 

1

 

(n/2)

 

 

1

 

(n/2)

 

 

1

 

2n

 

n

 

 

1.10一棵递归

 

6.解:用主方法求解,a=4b=2f(n)=n

logalog4

因此,𝑛=𝑛=n,它与f(n)大,满足主定理的情况(2),所以T(n)=O( loga

𝑛log2n)=O(nlog2n)

 

7.解:求斐波那契f(n)时间为T(n),有以下递推式:

 

T(1)=T(2)

 

 

T(n)=T(n-1)+T(n-2)+1

 

n>2

 

 

其中,T(n)式中1表示一次加法运算的时间。

 

不妨先求T1(1)=T1(2)=1T1(n)=T1(n-1)+T1(n-2),按《教程》例2.14的方法可以求

 

 

出:

 

 

T1(n)=

 

1

 

5

 

15

 

 

1

 

5

 

15

 

115=

 

 

T(n)=T1(n)+1

 

1

 

5

 

15

 

+1=O(φ),其中φ=

2

 

 

8.解:f(m)计算数列第m项值。

 

m 为偶数时不妨设m=2n2n-1=m-1,所f(m)=f(m-1)+2

 

m 为奇数时不妨设m=2n+12n-1=m-22n=m-1,所以有f(m)=f(m-2)+f(m-1)-1

 

对应的递归算法下:

 

intf(int m)

 

{if (m==1) return0;

 

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

 

 

 

 

 

#include <iostream>

 

using namespace std;

 

intLength(char*str)

 

{if (*str=='\0')

 

return 0;

 

else

 

return Length(str+1)+1;

 

}

 

voidmain()

 

{char str[]="abcd";


算法设计

 

//str的字个数

 

 

cout << str << "长度:"<<Length(str) << endl; }

 

上述程序的执行果如图1.11 所示

 

 

1.11

 

程序执行结果

 

 

10.f(strn)返回含n 个字符的字str是否为回文,其递归模型下:

 

 

f(strn)=true

 

f(strn)=flase

 

f(strn)=f(str+1n-2)

 

n=0 或者n=1 str[0]str[n-1]其他情况

 

 

对应的递归算法下:

 

#include <stdio.h>

 

#include <string.h>

 

boolisPal(char*str,intn)//str判断算法{if (n==0 ||n==1)

 

return true;

 

if (str[0]!=str[n-1])

 

return false;

 

return isPal(str+1,n-2);

 

}

 

voiddisp(char*str)

 

{int n=strlen(str);

 

if (isPal(str,n))

 

printf("%s是回\n",str);

 

else

 

printf("%s不是\n",str);

 

}

 

voidmain()

 

{printf("结果\n");

 

disp("abcba");

 

disp("a");

 

disp("abc");

 

}

 

14

 

 

 

 

1

 

概论

 

 

上述程序的执行果如图1.12 所示

 

 

1.12

 

程序执行结果

 

 

11.f(L)正序输出单链表L 的所有结值,其递归模型下:

 

 

f(L)不做任何事

 

f(L)输出L->data; f(L->next);对应的递归程序下:#include "LinkList.cpp"

 

L=NULL

 

LNULL

 

//包含单链表的基运算算法

 

 

voiddispLink(LinkNode*L)//序输出所有结点值{if (L==NULL)return;

 

else

 

{printf("%d ",L->data);

 

dispLink(L->next);

 

}

 

}

 

voidmain()

 

{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"

 

voidRevdisp(LinkNode*L)

 

{if (L==NULL)return;

 

L=NULL

 

LNULL

 

//包含单链表的基运算算法//输出所有结点值

 

15

 

 

 

 

 

else

 

{Revdisp(L->next);

 

printf("%d ",L->data);

 

}

 

}

 

voidmain()

 

{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;

 

}

 

}

 

voidmain()

 

{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

 

 

 

 

1

 

概论

 

 

}

 

上述程序的执行果如图1.15 所示

 

1.15

 

程序执行结果

 

 

14.f(Lx)返回单链表L 中第一个值x 的结点的地址其递归模型如下

 

 

f(Lx) = NULL

 

f(Lx) = L

 

f(Lx) =f(L->nextx) 对应的递归程序下:

 

L=NULL

 

LNULL L->data=x 其他情况

 

 

#include "LinkList.cpp"

 

LinkNode*Firstxnode(LinkNode*L,intx) {if (L==NULL)return NULL;

 

if (L->data==x)

 

return L;

 

else

 

return Firstxnode(L->next,x);

 

}

 

voidmain()

 

{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(Lx)删除单链表L 中第一个值x 的结点,其递模型如下:

 

 

f(Lx)不做任何

 

f(Lx)删除L L=L->nextf(Lx)f(L->nextx)

 

对应的递归程序下:

 

L=NULL

 

LNULL L->data=x 其他情况

 

17

 

 

 

 

 

#include "LinkList.cpp"


算法设计

 

//包含单链表的基运算算法

 

 

voidDelfirstx(LinkNode*&L,intx)//除单链表L中第个值为x{if (L==NULL)return;

 

if (L->data==x)

 

{LinkNode *p=L;

 

L=L->next;

 

free(p);

 

}

 

else

 

Delfirstx(L->next,x);

 

}

 

voidmain()

 

{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"

 

intLeafSum(BTNode*bt)

 

{if (bt==NULL)return 0;

 

bt=NULL

 

btNULL 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;

 

}

 

voidmain()

 

18

 

 

 

 

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);//ab构造二叉链bt printf("二叉树bt:");DispBTree(bt);printf("\n"); printf("所有叶子点值之和:%d\n",LeafSum(bt));

 

 

DestroyBTree(bt);

 

//销毁树bt

 

 

}

 

上述程序的执行果如图1.18 所示

 

 

1.18

 

程序执行结果

 

 

17.解:f(btk)返回二叉树bt 中所有结值大于等于k 点个数,其递归模型如下:

 

 

f(btk)=0

 

f(btk)=f(bt->lchildk)+f(bt->rchildk)+1f(btk)=f(bt->lchildk)+f(bt->rchildk)

 

对应的递归程序下:

 

#include "Btree.cpp"

 

intNodenum(BTNode*bt,intk)

 

{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;

 

}

 

voidmain()

 

{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

 

btNULL bt->datak其他情况

 

//包含二叉树的基运算算法//等于k的结个数

 

//ab构造二叉bt

 

 

printf("二叉树bt:");DispBTree(bt);printf("\n"); int k=3;

 

printf("大于等于%d的结点个数:%d\n",k,Nodenum(bt,k));

 

 

DestroyBTree(bt);

 

//销毁树bt

 

 

}

 

上述程序的执行果如图1.19 所示

 

 

19

 

 

 

 

算法设计

 

 

1.19

 

程序执行结果

 

 

18.解:f(btxh)返回二叉树btx 结点的层次其中h 表示bt 结点的层次,初始调用时bt 指向根结点,h 1。其递归模如下:

 

 

f(btxh)=0

 

f(btxh)=h

 

f(btxh) =l

 

f(btxh) =f(bt->rchildxh+1) 对应的递归程序下:

 

bt=NULL

 

btNULL bt->data=xl=f(bt->lchildxh+1)0其他情况

 

 

#include "Btree.cpp"

 

intLevel(BTNode*bt,intx,int h) {//初始调用bt为根,h1

 

if (bt==NULL) return0;

 

if (bt->data==x)

 

return h;

 

//包含二叉树的基运算算法//叉树btx

 

//找到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);//回在右子树的查找

 

 

}

 

}

 

voidmain()

 

{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);

 

//ab构造二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

 

 

 

 

1

 

概论

 

 

1.33 分治法

 

1.3.1练习题

 

1.分治法的计思想是将一个难以直接解决的问题分割成规模小的子问题,分别解决子问题,后将子问题的解合起来形成原的解。这要求原题和子问题)。

 

A.问题规模相,问题性质相同

 

B.问题规模相同问题性质不同

 

C.问题规模不同问题性质相同

 

D.问题规模不,问题性质不同

 

2.在寻找n 个元中第k 小元素问中,如快速排序法思想,运用分算法对n个元素进行划分如何选择划分基?下面()答解释最合理。

 

A.随机选择一元素作为划分基准

 

B.取子序列的第个元素作为划分

 

C.用中位数的中数方法寻找划分

 

D.以上皆可行但不同方法,算法复杂度上界可不同

 

3.对于列二分查找算法以下正确的是()。

 

A.

 

intbinarySearch(inta[],intn,int x)

 

{int low=0, high=n-1;

 

while(low<=high)

 

{int mid=(low+high)/2;

 

if(x==a[mid]) returnmid;

 

if(x>a[mid]) low=mid;

 

else high=mid;

 

}

 

return 1;

 

}

 

B.

 

intbinarySearch(inta[],intn,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]) returnlow;

 

else return 1;

 

}

 

C.

 

intbinarySearch(inta[],intn,intx)

 

{int low=0, high=n-1;

 

while(low<high-1)

 

{int mid=(low+high)/2;

 

21

 

 

 

 

算法设计

 

if(x<a[mid])

 

high=mid;

 

else low=mid;

 

}

 

if(x==a[low]) returnlow;

 

else return 1;

 

}

 

D.

 

intbinarySearch(inta[],intn,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]) returnlow;

 

}

 

return 1;

 

}

 

4.快速序算法是根据分策略来设计的,述其基本思想。

 

5.假设含n 个元素的待排的数据a 恰好是减排列的,说明QuickSort(a0n-1)递增排时间复杂度为O(n)

 

6.以下些算法采用分治略:

 

1)堆排序算

 

2)二路归并序算法

 

3)折半查找

 

4)顺序查找

 

7.适合行计算的问题通表现出哪些特征

 

8.设有两个复数x=a+biy=c+di。复数乘积xy 可以使4 次乘法来完成xy=(ac-bd)+(ad+bc)i计一个仅用3 法来计算乘积xy 的方法。

 

9.4 数组abc d,都已经排好序说明找出这4 组的交集的方法。10.一个算法,采用分治法求一个数序列中的最大最小元素。

 

11.一个算法,采用分治法求x

 

12.假设二叉采用二叉链存储结构进行存储。计一个算法采用治法求一棵二叉bt 的高度。

 

13.假设二叉采用二叉链存储结构进行存储。计一个算法采用治法求一棵二叉bt 中度为2 点个数。

 

14.有一种二排序树,其定义是空树是一棵二排序树,若不空左子树中所有结点值小于根结点,右子树中所有点值大于根结点,并且左右子树是二叉排序树。现在该二叉排序采用二叉链存储采用分治法设计找值为x 的结点址,并分析算法的最好的平均时复杂度。

 

22

 

 

 

 

1

 

概论

 

 

15.设有n 不相同的整数,递增顺序存放在数组a[0..n-1],若存在一个下标i0i<n),使得a[i]=i。设计一个算法以O(log2n)时间找到这个i

 

16.模仿二分查找过程设计一个三查找算法。分析其时间复杂度。

 

17.对于1 的正整数n可以分解为n=x1*x2**xm,其中xi2例如,n=12 8 种不同的分式:12=1212=6*212=4*312=3*412=3*2*212=2*612=2*3*212=2*2*3,设计一个算法求n 不同分解式个数

 

18.设计一个基BSP 模型的并算法,假设有p 处理器,计算整数组a[0..n-1]的所有元素之和并分析算法的时复杂度。

 

1.3.2练习题参考答

 

1.答:C

 

2.答:D

 

3.答:a[]={12345}为例说明。选项A中在查找5 现死循环。选项B 中在查找5 时返-1。选项C 中在5 时返回-1D 正确。

 

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次乘法(即acbd (a+b)(c+d)乘法运算)。

 

9.采用基本的二路并思路,先求ab 的交集ab求出cd 的交集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

 

 

 

 

算法设计

 

voidMaxMin(inta[],intlow,inthigh,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);

 

}

 

}

 

voidmain()

 

{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(xn)=x,采用分治法解对应的递归模如下:

 

 

f(xn)=x

 

f(xn)=f(xn/2)*f(xn/2)

 

f(xn)=f(x(n-1)/2)*f(x(n-1)/2)*x对应的递归程序下:#include <stdio.h>

 

doublesolve(doublex,intn)

 

{double fv;

 

if (n==1) returnx;

 

if (n%2==0)

 

{fv=solve(x,n/2);

 

return fv*fv;

 

}

 

n=1 n 为偶数时n 为奇数时

 

//x^n

 

24

 

 

 

 

1

 

else

 

{fv=solve(x,(n-1)/2);

 

return fv*fv*x;

 

}

 

}

 

voidmain()

 

{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"

 

intHeight(BTNode*bt)

 

{if (bt==NULL)return 0;

 

int lh=Height(bt->lchild);

 

int rh=Height(bt->rchild);

 

if (lh>rh) returnlh+1;

 

else return rh+1;

 

}

 

voidmain()

 

{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

 

//子问题1

 

//子问题2

 

//合并

 

//ab构造二叉bt

 

 

printf("二叉树bt:");DispBTree(bt);printf("\n"); printf("bt的高度:%d\n",Height(bt));

 

 

DestroyBTree(bt);

 

//销毁树bt

 

 

}

 

 

25

 

 

 

 

算法设计

 

 

上述程序的执行果如图1.23 所示

 

 

1.23

 

程序执行结果

 

 

13.f(bt)返回二叉树bt 中度为2 的结个数,对应的递模型如下:

 

 

f(bt)=0

 

f(bt)=f(bt->lchild)+f(bt->rchild)+1f(bt)=f(bt->lchild)+f(bt->rchild)

 

对应的算法如下

 

#include "Btree.cpp"

 

intNodes(BTNode*bt)

 

{int n=0;

 

bt=NULL

 

btNULL bt 分支结点其他情况

 

//包含二叉树的基运算算法//bt2

 

 

if (bt==NULL) return0;

 

if (bt->lchild!=NULL&& bt->rchild!=NULL)

 

n=1;

 

return Nodes(bt->lchild)+Nodes(bt->rchild)+n;

 

}

 

voidmain()

 

{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);

 

//ab构造二叉bt

 

 

printf("二叉树bt:");DispBTree(bt);printf("\n"); printf("bt中度为2的结点个数:%d\n",Nodes(bt));

 

 

DestroyBTree(bt);

 

//销毁树bt

 

 

}

 

上述程序的执行果如图1.24 所示

 

 

1.24

 

程序执行结果

 

 

14.解:f(btx)返回在二叉排序树bt 的值为x 结点的址,若没有找到返空,对应的递归型如下:

 

 

f(btx)=NULL

 

f(btx)=bt

 

f(btx)=f(bt->lchildx)

 

bt=NULL

 

btNULL x=bt->datax>bt->data

 

26

 

 

 

 

1

 

概论

 

 

f(btx)=f(bt->rchildx) 对应的程序如下

 

#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) returnSearch(bt->lchild,x);

 

else return Search(bt->rchild,x);

 

}

 

voidmain()

 

{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(btx)算法采的是减治法,最的情况是某个结左右子树高度大致相同,其平均执行时间T(n)如下:

 

 

T(n)=1

 

T(n)=T(n/2)+1

 

n=1

 

n>1

 

 

可以推出T(n)=O(log2n),其中n 为二排序树的结点个

 

15.解:二分查找方法。a[i]=i 时表示该元在有序非重复序a 中恰好第i 对于序列a[low..high]mid=(low+high)/2a[mid]=mid 表示找该元素;若a[mid]>mid说明右区间的所元素都大于其位,只能在左区间查找;若a[mid]<mid说明左区间的所有元素都小其位置,只能在间中查找。对的程序如下:

 

#include <stdio.h>

 

 

intSearch(inta[],intn)

 

{int low=0,high=n-1,mid;

 

//使得a[i]=i

 

27

 

 

 

 

算法设计

 

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;

 

}

 

voidmain()

 

{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(log3n),属于O(log2n)级别。对应的算法如下:

 

#include <stdio.h>

 

 

intSearch(inta[],intlow,inthigh,intx) {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

 

 

 

 

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);

 

}

 

voidmain()

 

{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=44=2*2

 

29

 

 

 

 

算法设

 

f(6)=3,分解式为6=66=2*36=3*2,即f(6)=f(1)+f(2)+f(3)

 

以此类推,可以f(n)n 的所有数的不同分解式数之和,即f(n)= 𝑓(𝑛/𝑖)。对应的程序如

 

#include <stdio.h>

 

#define MAX 101

 

 

intsolve(int n)

 

{if (n==1) return1; else

 

{int sum=0;

 

//n分解式个数

 

 

for (int i=2;i<=n;i++)

 

if (n%i==0)

 

sum+=solve(n/i);

 

return sum;

 

}

 

}

 

voidmain()

 

{int n=12;

 

int ans=solve(n);

 

printf("结果: %d\n",ans);

 

}

 

上述程序的执行果如图1.28 所示

 

1.28

 

程序执行结果

 

 

18.对应的并行算法下:

 

intSum(inta[],ints,intt,intp,int i)//理器i执行{int j,s=0;

 

for (j=s;j<=t;j++)

 

s+=a[j];

 

return s;

 

}

 

intParaSum(inta[],ints,intt,intp,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

 

 

 

 

1

 

概论

 

 

1.44 蛮力法

 

1.4.1练习题

 

1.简要较蛮力法和分治

 

2.在采蛮力法求解时什情况下使用递归

 

3.考虑下面个算法,它求的是数组a 中大小差最小的两个元的差。请对这个算法做尽可能多改进。

 

#define INF 99999

 

 

#define abs(x) (x)<0?-(x):(x)

 

intMindif(inta[],intn)

 

{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=(a0a1an-1),若i<jai>aj,则<aiaj>就为个逆序对。例如数组31452逆序对有<31><32><42><52>。设计一个算法采用蛮力A 中逆序对的数即逆序数。

 

5.于给定的正整数nn>1,采用蛮力1!+2!++n!改进该算法提高率。

 

6.有一群鸡一群兔,它们的只数相同,它们脚数都是三位数且这两个三位数的各位数字只能012345。设计一个算法用蛮力法求鸡兔的只数各是多少?它们的脚数是多少?

 

7.有一个三数,个位数字比百位数字大,而位数字又比十位字大,并且各位数字之和等于各数字相乘之积,计一个算法用穷法求此三位数。

 

8.某年级的同集体去公园划船如果每只船坐10 ,那么多出2 位;如果每只船多坐2 人,么可少租1 只船设计一个算法用力法求该年级的多人数?

 

9.已知:若个合数的质因数分解式逐位相加和等于其本身逐相加之和,则称这个数为Smith 。如4937775=3*5*5*65837,而3+5+5+6+5+8+3+7=424+9+3+7+7+7+5=42,所以4937775 Smith 求给定一个正整N,求大于N Smith 数。

 

输入:若干个case每个case 一行代正整数N,输入0 表示结束

 

输出:大于N 最小Smith

 

输入样例:

 

4937774

 

0

 

样例输出:

 

31

 

 

 

 

算法设计

 

4937775

 

10.求解涂盘问题。小易有一块n*n 的棋盘棋盘的每一个格都为黑色或者白色,小易现在要他喜欢的红色去画棋盘。小易会出棋盘中某一列拥有相同颜色的最大的区域去涂,帮助小易算算会涂画多少个棋

 

输入描述:输入据包括n+1 行:一行为一个整数n1n50),即棋盘的小,接下来的n 每行一个字符串示第i 行棋盘的色,'W'表示白色,'B'表示黑色

 

输出描述:输出易会涂画的区域小。

 

输入例子:

 

3

 

BWW

 

BBB

 

BWB

 

输出例子:

 

3

 

11.一个含nn>1整数元素的a,所元素不相同,采蛮力法求出a 有元素的全排列

 

1.4.2练习题参考答

 

1.答:蛮力是一种简单直接地解决问题的方,适用范围广,能解决几乎所有问题的一般性方,常用于一些非基本、但又十分要的算法(排序查找、矩阵乘法和字符串匹配等),蛮力法主解决一些规模小或值低的问题,可作为同样问题的高效算法的一个准。而分治法采分而治之思路,一个复杂的问题成两个或更多的相同或相似的子题,再把子问题成更小的子问题到问题解决。分法在求解问题时,通常性能比力法好。

 

2.答:如果蛮力法求解的问题可以分解为若个规模较小的相子问题,此时可以采用递归来实算法。

 

3.解:上述算的时间复杂度为O(n),采的是最基本的蛮力法。可以先a中元素递增排序,然依次比较相邻元的差,求出最小,改进后的算法下:

 

#include <stdio.h>

 

#include <algorithm>

 

using namespace std;

 

intMindif1(inta[],intn)

 

 

{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

 

 

 

 

1

 

概论

 

 

上述算法的主要间花费在排序上算法的时间复杂O(nlog2n)

 

4.解:用两循环直接判断是否为逆序对,法的时间复杂度为O(n2),比第3实验3 算法的性差。对应的算法下:

 

 

intsolve(inta[],intn)

 

{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.解:接采用蛮力法求算法如下:

 

 

longf(intn)

 

{long fn=1;

 

for (int i=2;i<=n;i++)

 

fn=fn*i;

 

return fn;

 

}

 

longsolve(intn)

 

{long ans=0;

 

for (int i=1;i<=n;i++)

 

ans+=f(i);

 

return ans;

 

//n!

 

//1!+2!+…+n!

 

 

}

 

实际上,f(n)=f(n-1)*nf(1)=1,在求f(n)时可以利用f(n-1)的结果。改后的算法如

 

 

下:

 

 

longsolve1(intn)

 

{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,有1ad50bcef5,采6 重循环,求鸡只数x1=y/2y 2的倍数),兔只数x2=z/4z 4 的倍数),当x1=x2 时输出结。对应的程序如

 

#include <stdio.h>

 

voidsolve()

 

{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

 

 

 

 

算法设计

 

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);

 

}

 

}

 

voidmain()

 

{printf("结果\n");

 

solve();

 

}

 

上述程序的执行果如图1.29 所示

 

 

1.29程序执行

 

7.设该三数为x=abc,有1a90bc9满足c>aa>ba+b+c=a*b*c。对的程序如下:

 

#include <stdio.h>

 

voidsolve()

 

{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);

 

}

 

}

 

voidmain()

 

34

 

 

 

 

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<12y 1 100实际上y取更大范围的结果是相同的)、z011 ,求出最大的x 。对应的程序如

 

#include <stdio.h>

 

intsolve()

 

{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;

 

}

 

voidmain()

 

{printf("结果\n");

 

printf("最多人:%d\n",solve());

 

}

 

上述程序的执行果如图1.31 所示

 

 

1.31程序执行

 

9.解:用蛮力法求出一正整数n的各位数字和sum1n的所有质因数的数字和sum2,若sum1=sum2,即为Smitch数。从用户输入的n开始枚举,若是Smitch 数,输出,本次束,否则n++继续找大于n 的最小Smitch数。对应的整程序如下:

 

#include <stdio.h>

 

 

intSum(intn)

 

{int sum=0;

 

while (n>0)

 

//n数字

 

 

35

 

 

 

 

算法设计

 

{sum+=n%10;

 

n=n/10;

 

}

 

return sum;

 

}

 

boolsolve(intn)//n是否Smitch

 

{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;

 

}

 

voidmain()

 

{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];

 

 

intgetMaxArea()

 

{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

 

 

 

 

1

 

概论

 

 

if (countj>maxArea)

 

maxArea=countj;

 

}

 

return maxArea;

 

}

 

intmain()

 

{scanf("%d",&n);

 

for (int i=0;i<n;i++)

 

scanf("%s",board[i]);

 

printf("%d\n",getMaxArea());

 

return 0;

 

}

 

11.解:与《程》中求全排列类似,但需要将1n 的全排列改按下标0n-1 a 的全排列(标从0 开始)。采用非递的程序如下:

 

#include <stdio.h>

 

#include <vector>

 

using namespace std;

 

 

vector<vector<int>> ps;

 

//存放全排列

 

 

voidInsert(vector<int>s,inta[],inti,vector<vector<int>> &ps1)//个集合元素中间插入i得到ps1

 

{vector<int>s1;

 

vector<int>::iteratorit;

 

 

for (int j=0;j<=i;j++)

 

{s1=s;

 

it=s1.begin()+j;

 

s1.insert(it,a[i]);

 

ps1.push_back(s1);

 

}

 

}

 

voidPerm(inta[],intn)

 

{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;

 

}

 

}

 

voiddispps()

 

//s(i个整数)每个位置插入a[i] //求出插入位置

 

//插入整数a[i]

 

//添加到ps1

 

//a[0..n-1]全排

 

//临时存放子排列

 

//全排列迭代器

 

//添加{a[0]}集合

 

//循环添加a[1]a[n-1]

 

//ps1存放插入a[i]的结果

 

//在每个集合元素间插入a[i]得到ps1 //全排列ps

 

 

{vector<vector<int>>::reverse_iteratorit;//全排列的反向代器

 

 

vector<int>::iteratorsit;

 

//排列集合元素迭

 

 

for (it=ps.rbegin();it!=ps.rend();++it)

 

{for (sit=(*it).begin();sit!=(*it).end();++sit)

 

printf("%d",*sit);

 

printf("");

 

37

 

 

 

 

 

}

 

printf("\n");

 

}

 

voidmain()

 

{int a[]={2,5,8};

 

int n=sizeof(a)/sizeof(a[0]);printf("a[0%d]全排序如下:\nPerm(a,n);

 

dispps();


算法设计

 

",n-1);

 

 

}

 

上述程序的执行果如图1.32 所示

 

 

1.32程序执行

 

1.55 回溯法

 

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[]={12345},采用5.4的回溯法求全排列,以12开头的排列一定最先现吗?为什么?

 

8.n 皇后问题,其空间树为由12n 构成的n!排列所组成。现用回

 

38

 

 

 

 

1

 

概论

 

 

溯法求解,要求

 

1)通过解搜空间说明n=3 无解的。

 

2)给出剪枝

 

3)最坏情况在解空间树上会成多少个结点?析算法的时间复度。9.设计一算法求解简单装载问题,设有一集装箱要装上一载重量为W

 

船,其中编号为i0in-1)的集箱的重量为wi。现要从n 个集装中选出若干装上轮船,使它们的量之和正好为W如果找到任一种返回true,否则返false

 

 

10.给定若干个正整a0a0 要求找选择元素数最少的解。

 

an-1

 

,从中选出若干,使它们的和恰k

 

 

11.设计求解有复元素的排列问题的算法,设有n个元素a[]={a0a1an-1)其中可能含有重的元素,求这些素的所有不同排。如a[]={112},输出结果是112),(121),(211)。

 

12.采用归回溯法设计一个算法求1nn个整数中取出m个元素的排列要求每个元素最多只能取次。例如,n=3m=2的输出结果是(12),(13),(2123),(31),(32

 

13.对于n问题,有人认为当n为偶数时,解具有对称性,即n皇后问题的数恰好为n/2皇后题的解个数的2,这个结论正确吗?请编写回法程序对n=46810的情况进验证。

 

14.给定一个向图,由指定的起点前往指定的点,途中经过所其他顶点且只经过一次,称为哈顿路径,闭合的密顿路径称作哈顿回路(Hamiltoniancycle)。设计一个回溯算法求向图的所有哈密回路。

 

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 结点扩展出的叶子结点,它就是以12 的排列。

 

 

39

 

 

 

 

算法设计

 

A

 

 

1

 

2

 

3

 

4

 

5

 

 

B

 

C

 

D

 

E

 

F

 

 

2

 

3

 

4

 

5

 

 

G

 

H

 

I

 

1.33

 

J

 

部分解空间树

 

 

8.1n=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;

 

voiddfs(inttw,intrw,inti) {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个集,回溯

 

 

}

 

boolsolve()

 

//简单装载问题是否存在解40

 

 

 

 

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;

 

}

 

voidmain()

 

{printf("结果\n");

 

//求所有集装箱重rw//i0开始

 

 

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;

 

voiddisppath()

 

{printf("择的元素:");

 

//设置为全局变量 //存放最优解

 

//最多选择n个元//一个解

 

 

for (int j=0;j<minpath.size();j++)

 

printf("%d ",minpath[j]);printf("元素个数=%d\n",minn);

 

 

}

 

 

41

 

 

 

 

算法设计

 

voiddfs(vector<int>path,intsum,intstart)//解算

 

 

{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);

 

}

 

voidmain()

 

 

{vector<int>path;

 

dfs(path,0,0); printf("最优解:\n");disppath();

 

//path存放一个子

 

 

}

 

上述程序的执行果如图1.36 所示

 

 

1.36

 

程序执行结果

 

 

11.解:回溯法求全排列基础上,增加元的重复性判断。如,对于a[]={112},不判断复性时输出(112),(121),(112),(121211),211),共6个,有3 个是重的。重复性判断这样的,对于在a[i]时,仅仅a[i..j-1]没有出现的元素a[j]交换a[i]的位置,如果现,对应的排列已经在前面求了。对应的完整序如下:

 

#include <stdio.h>

 

boolok(inta[],inti,intj)//ok

 

{if (j>i)

 

{for(int k=i;k<j;k++)

 

if (a[k]==a[j])

 

return false;

 

}

 

return true;

 

}

 

 

voidswap(int&x,int&y)

 

{int tmp=x;

 

x=y; y=tmp;

 

//两个元素

 

 

}

 

voiddfs(inta[],intn,inti)//元素的排列问题{if (i==n)

 

42

 

 

 

 

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]);

 

}

 

}

 

}

 

voidmain()

 

{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表示没有选择整数iused[i]=1 表示已经择整数i。对应的完整程序如下:

 

#include <stdio.h>

 

#include <string.h>

 

#define MAXN 20

 

#define MAXM 10

 

int m,n;

 

 

int x[MAXM];

 

bool used[MAXN];

 

voiddfs(inti)

 

{if (i>m)

 

{for (int j=1;j<=m;j++)

 

printf("%d",x[j]);

 

printf("\n");

 

//x[1..m]存放一排列

 

//nm素的全排列//输出一个排列

 

43

 

 

 

 

算法设计

 

 

}

 

else

 

{for (int j=1;j<=n;j++)

 

{if (!used[j])

 

{used[j]=true;

 

x[i]=j;

 

dfs(i+1);

 

used[j]=false;

 

}

 

}

 

}

 

}

 

voidmain()

 

{n=4,m=2;

 

memset(used,0,sizeof(used));printf("n=%d,m=%d求解结果\n",n,m); dfs(1);

 

}

 

上述程序的执行果如图1.38 所示

 

//修改used[i]

 

//x[i]选择j

 

//继续搜索排列的一个元素//回溯:恢复used[i]

 

//初始化为0

 

//i1开始

 

 

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) returntrue;

 

while (j<i)

 

//测试第i行的q[i]列上能否摆放皇后 //j=1i-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

 

 

 

 

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++;//到一个解计数器count1 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),采用邻接阵数组a0/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

 

 

 

 

 

//求解问题表示 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;

 

 

voiddispasolution()

 

{for (int i=0;i<=n-1;i++)

 

//一个解路径

 

 

printf("(%d,%d) ",x[i],x[i+1]);

 

printf("\n");

 

 

}

 

boolvalid(inti)

 

//顶点第i个顶x[i]

 

 

{if (a[x[i-1]][x[i]]!=1)//x[i-1]x[i]没有边,返回false

 

return false;

 

for (int j=0;j<=i-1;j++)

 

if (x[i]==x[j])//顶点i出现,返回falsereturn false;

 

return true;

 

}

 

 

voidHamiltonian(intv)

 

{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

 

 

 

 

1

 

概论

 

 

Hamiltonian(v);

 

//从顶点v出发

 

 

}

 

}

 

上述程序对如图1.40所示的无向图求从每个顶点出发哈密顿回路,程序执行结果如图1.41 所示

 

1

 

 

0

 

3

 

2

 

4

 

 

1.40一个无向

 

 

1.41

 

程序执行结果

 

 

1.66 分枝限界法

 

1.6.1练习题

 

1.分枝界法在问题的解间树中,按(略,从根结点出搜索解空间树。A.广度优先B.活结点优先C.扩展点优先D.深度优先

 

2.常见两种分枝限界法)。

 

A.广度优先限界法与深度优先分枝限界法

 

47

 

 

 

 

算法设计

 

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品重量为(4753),物品价值40422512),背包最大重量W=10,给出采用先队列式分枝限法求最优解的过

 

8.有一个流水作业度问题,n=4a[]={51097}b[]={7598},给出用优先队列式分限界法求一个解过程。

 

9.有一个n个顶点(顶点编号为0n-1的带权图,采用邻接矩阵数组A表示,采用分枝限界法从起点s 到目标t 的最短路径长,以及具有最短路径长度的路径数。

 

10.采用优先列式分枝限界法求解最优装载问。给出以下装载题的求解过程和结果:n=5,集箱重量为w=52643),限重为W=10。在装载重量同时,最优装载方案是集箱个数最少的方

 

1.6.2练习题参考答

 

1.答:A

 

2.答:D

 

3.答:B

 

4.答:A

 

5.答:C

 

6.答:分枝界法的搜索策略是广度优先遍历通过限界函数可快速找到一个解或者最优解。

 

7.答:解过程如下:

 

1)根结点1 队,对应结点值e.i=0e.w=0e.v=0e.ub=76x:[0000]2)出队结点1:左孩子结点2 进队,对应结点e.no=2e.i=1e.w=4

 

e.v=40e.ub=76x:[1000];右孩子结点3进队,对应结点值:e.no=3e.i=1e.w=0e.v=0e.ub=57x:[0000]

 

3)出队结点2:左孩子超重;右孩子结点4 ,对应结点值:e.no=4e.i=2e.w=4e.v=40e.ub=69x:[1000]

 

4)出队结点4:左孩子结点5 进队,对应结点e.no=5e.i=3e.w=9e.v=65e.ub=69x:[1010];右孩子结点6进队,对应结点值:e.no=6e.i=3e.w=4e.v=40e.ub=52x:[1000]

 

48

 

 

 

 

1

 

概论

 

 

5)出队结点5:产生一个解,maxv= 65bestx:[1010]

 

6)出队结点3:左孩子结点8 进队,对应结点e.no=8e.i=2e.w=7e.v=42e.ub=57x:[0100];右孩子结点9被剪枝。

 

7)出队结点8:左孩子超重;右孩子结点10 剪枝。

 

8)出队结点6:左孩子结点11 超重;右孩子12 被剪枝。

 

9)队列空,法结束,产生的优解:maxv= 65bestx:[1010]

 

8.答:解过程如下:

 

1)根结点1 队,对应结点值e.i=0e.f1=0e.f2=0e.lb=29x[0000]

 

2)出队结点1:扩展结点如下:

 

进队(j=1):结2e.i=1e.f1=5e.f2=12e.lb=27x[1000]

 

进队(j=2):结3e.i=1e.f1=10e.f2=15e.lb=34x[2000]进队(j=3):结4e.i=1e.f1=9e.f2=18e.lb=29x[3000]

 

进队(j=4):结5e.i=1e.f1=7e.f2=15e.lb=28x[4000]

 

3)出队结点2:扩展结点如下:

 

进队(j=2):结6e.i=2e.f1=15e.f2=20e.lb=32x[1200]进队(j=3):结7e.i=2e.f1=14e.f2=23e.lb=27x[1300]进队(j=4):结8e.i=2e.f1=12e.f2=20e.lb=26x[1400]4)出队结点8:扩展结点如下:

 

进队(j=2):结9e.i=3e.f1=22e.f2=27e.lb=31x[1420]进队(j=3):结10e.i=3e.f1=21e.f2=30e.lb=26x[1430]5)出队结点10,扩展一个j=2的子结点,有e.i=4到达叶子结点,产生的一个解

 

e.f1=31e.f2=36e.lb=31x=[1432]

 

该解对应的调度案是:第1 步执作业1,第2 行作业4,第3步执行作业3,第4 步执行2,总时间=36

 

9.采用优先队列式分枝限界法求,队列中结点的类型如下:

 

struct NodeType

 

 

{int vno;

 

int length;

 

bool operator<(constNodeType &s)const {return length>s.length;}

 

//顶点的编号

 

//当前结点的路径//重载<关系函数 //length越小越优

 

 

};

 

从顶点s 开始广度优先搜索,找到目标点t 后比求最短路径长度及其路径条数应的完整程序如

 

#include <stdio.h>

 

#include <queue>

 

using namespacestd;

 

#define MAX 11

 

#define INF 0x3f3f3f3f

 

//问题表示

 

 

int A[MAX][MAX]={

 

//一个带权有向图

 

 

49

 

 

 

 

 

{014INFINF}

 

{INF0INF15}

 

{INFINF0INF1}{INFINF203}

 

{INFINFINFINFINF} };

 

int n=5;

 

//求解结果表示

 

int bestlen=INF;

 

int bestcount=0;

 

struct NodeType

 

{int vno;

 

int length;


算法设计

 

//最优路径的路径//最优路径的条数

 

//顶点的编号

 

//当前结点的路径

 

 

bool operator<(constNodeType &s)const//重载>系函数

 

 

{return length>s.length;}

 

};

 

voidsolve(intsintt)

 

{NodeType ee1;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个结点

 

//定义一个优先队qu//构造根结点

 

//根结点进队

 

//队不空循环

 

//出队结点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);

 

}

 

}

 

}

 

}

 

}

 

voidmain()

 

{int s=0t=4;

 

solve(st);

 

if (bestcount==0)

 

printf("顶点%d%d没有路径\n"st); else

 

{printf("%d%d存在路径\n"st);

 

50

 

//有效子结点e1

 

 

 

 

1

 

概论

 

 

printf("最短径长度=%d,条数=%d\n" bestlenbestcount);//输出: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()(constNodeType&s,const NodeType&t)

 

{return (s.ub<t.ub)|| (s.ub==t.ub&& s.x[0]>t.x[0]);//ub越大越优先,ub相同时x[0]越小越优先

 

}

 

};

 

 

voidbound(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

 

 

 

 

 

}

 

voidLoading()

 

{NodeType e,e1,e2;


算法设计

 

//载问题的最优解//定义3个结点

 

 

priority_queue<NodeType,vector<NodeType>,Cmp> qu;//定义个优先队列qu

 

 

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;

 

//更新bestw

 

 

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.xe2.x[j]=e.x[j];

 

 

e2.x[e2.i]=0;

 

bound(e2);

 

if (e2.ub>bestw)

 

qu.push(e2);

 

}

 

}

 

}

 

voiddisparr(intx[],intlen)

 

{for (int i=1;i<=len;i++)

 

printf("%2d",x[i]);

 

}

 

voiddispLoading()

 

{printf("X=[");

 

//不选择集装箱i

 

//求右孩子结点的

 

//若右孩子结点可,则进队,否则被剪枝//一个解向量

 

//最优解

 

52

 

 

 

 

1

 

概论

 

 

disparr(bestx,n);

 

printf("],装入总值为%d\n",bestw); }

 

voidmain()

 

{Loading();

 

printf("求解结果:\n");dispLoading();

 

}

 

上述程序的执行果如图1.40 所示

 

//输出最优解

 

 

1.40

 

程序执行结果

 

 

1.77 贪心法

 

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(nlog2n)

 

 

4.关于0/1背包问题以下描正确的是()。

 

A.可以使用贪算法找到最优解

 

B.能找到多项式间的有效算法

 

C.使用教材介绍动态规划方法可解任意01 背包

 

D.对于同一背与相同的物品,做背包问题取得总价值一定大于于做0/1 背包问

 

 

 

 

5.一棵哈曼树共有215个结点,对其进行夫曼编码,共能到()个不同的

 

 

字。

 

 

A.107

 

B.108

 

C.214

 

D.215

 

 

6.求解夫曼编码中如何现贪心思路?

 

7.举反例证明0/1背包问题若使用的算是按照vi/wi 的非递减序考虑选择的物品,即只要正在考虑的物品装得就装入背包,则方法不一定能得最优解(此题说0/1 背包问题背包问题的不同)。

 

53

 

 

 

 

算法设计

 

8.求解硬币题。有1分、2分、5 分、10 50 分和100 硬币各若干枚,现在要用这些硬币支付W 元,最少要多少枚硬币。

 

9.求解正整的最大乘积分解问题。将正整数n分解为若干个不相同的自然数之和,使这些自然的乘积最大。

 

10.求解船问题。有n ,第i个人体重为wi0i<n)。每艘船的大载重量均C,且最多只乘两个人。用最的船装载所有人

 

11.求解会议排问题。有一组A 和一组会议BA[i]表示第i 个会的参加人数,B[j]表示第j 会议室最多可以容纳的人数。当仅当A[i]B[j]时,j个会议室可以用于举办第i 会议。给定数组A和数组B,试问最多可以同时举多少个会议。例如,A[]={123}B[]={324},结果为3;若A[]={3431}B[]={1226},结果为2.

 

12.假设要在够多的会场里安排一批活动,n 活动编号为1n,每个活动有开始

 

 

时间bi

 

和结束时间ei1in)。设计一个效的贪心算法求最少的会场个数

 

 

13.给定一个m×n的数字矩阵,算从左到右走过该矩阵且经过的格中整数最小的路径。一条路径以从第1 列的任位置出发,到达n 列的任意位置每一步为从第i 列走到第i+1 邻行(水平移动或沿45 度斜线),如图1.41所示。第1 行和后一行看作是相邻的即应当把这个矩看成是一个卷起的圆筒。

 

 

1.41每一步的

 

两个略有不同的5×6的数字矩阵的最小径如图1.42 所示只有最下面一行数不同。右边矩阵的径利用了第一行最后一行相邻的质。

 

输入:包含多个阵,每个矩阵的一行为两个数mn,分别表示阵的行数和列数,接下来的m×n 个整数按行优的顺序排列,即前n 个数组成第行,接下的n 组成第2 行,依类推。相邻整数用一个或多个空分隔。注意这些不一定是正数。输入中可能有一或多个矩阵描述直到输入结束。个矩阵的行数在110 之间,列数在1 100

 

输出:对每个矩输出两行,第一为最小整数之和路径,路径由n 整数组成,表示路径经过的号,如果这样的径不止一条,输字典序最小一条

 

 

3

4

1

2

8

6

 

6

1

8

2

7

4

5

5

9

6

3

9

9

5

83

44 1

18 6

3

2

6

 

3

7

2

8

6

4

 

 

 

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

1

2

3

 

 

 

54

 

 

 

 

1

 

概论

 

 

6 1 8 2 7 4

 

5 9 3 9 9 5

 

8 4 1 3 2 6

 

3 7 2 8 6 4

 

输出结果:

 

1 2 3 4 4 5

 

16

 

1.7.2练习题参考答

 

1.答:C

 

2.答:n皇后问题的解满足贪心选择性质。答案为B

 

3.答:D

 

4.由于背包问题以取物品的一部,所以总价值一大于等于做0/1题。答案为D

 

5.这里n=215夫曼树中n1=0,而n0=n2+1n=n0+n1+n2=2n0-1n0=(n+1)/2=108案为B

 

6.答:在构哈夫曼树时每次都是将两棵根结最小的树合并,而体现贪心的思路。

 

7.证明:例如n=3w={322}v={744}W=4 时,由于7/3最大,若按题目要求的方法,能取第一个,收7。而此实例最大的收益应该是8,取第23个物品。

 

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<(constNodeType &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;

 

voidsolve()

 

//兑换的硬币枚数//硬币

 

55

 

 

 

 

 

{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;

 

}

 

}

 

voidmain()

 

{W=325;

 

//剩余的金额

 

//支付的金额

 

 

printf("支付%d:\n",W);

 

solve();

 

printf("最少硬币个数:%d\n",ans);}

 

上述程序的执行果如图1.43 所示

 

 

1.43

 

程序执行结果

 

 

9.解:用贪心方法求解a[0..k]存放n 解结果:

 

1n4 时可验证其分解成几正整数的和的乘均小于n,没有解。

 

2n>4 时,n分拆成若干个互不相等的自数的和,分解数的个数越多乘积大。为此让n 解数个数尽可能(体现贪心的思),把n分解成从2开始的连续的自然数之和。例,分解n a[0]=2a[1]=3a[2]=4a[k]=k+2(共有k+1 个分解),用m表示下数,这样的分直到ma[k],即mk+2。对剩下数m 的处为如下两种情况

 

m<k+2:将m平均分解到a[k..i]对应的分解数个m)中,即从a[k]开始往前的分解数增加1也是贪心的思路分解数越大加1 乘积也越大)。

 

m=k+2a[0..k-1](对应的分数个数为k)的每分解数增加1下的2 加到a[k]中,即a[k]增加2

 

对应的完整程序下:

 

#include <stdio.h>

 

#include <string.h>

 

#define MAX 20

 

//问题表示

 

int n;

 

//求解结果表示

 

56

 

 

 

 

1

 

概论

 

 

int a[MAX];

 

int k=0;

 

voidsolve()

 

{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;//234顺序分解

 

 

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;

 

}

 

}

 

}

 

voidmain()

 

{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

 

 

 

 

 

1.44


算法设计

 

程序执行结果

 

 

10.解:采用心思路,首先按体重递增排序;考虑前后的两个(最轻者和最重),分别用ij 向:若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;

 

 

voidBoat()

 

{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--;

 

}

 

}

 

}

 

voidmain()

 

{printf("结果:\n");

 

Boat();

 

printf("最少的船=%d\n",bests);

 

}

 

上述程序的执行果如图1.45 所示

 

58

 

 

 

 

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;

 

voidsolve()

 

{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--;

 

}

 

}

 

}

 

voidmain()

 

{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

 

 

 

 

 

{int b;

 

int e;


算法设计

 

//活动起始时间 //活动结束时间

 

 

bool operator<(constAction &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;

 

voidsolve()

 

{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++;

 

//增加一个最大兼活动子集

 

 

}

 

}

 

}

 

voidmain()

 

{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;

 

intminValue(inti,intj)

 

//a[i][j]方上、中、3的最小数,同时把行

 

 

{

 

int s = (i == 0)? m - 1 : i - 1; int x = (i == m -1)?0:i+1;

 

 

60

 

 

 

 

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];

 

}

 

voidsolve()

 

{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);

 

}

 

voidmain()

 

{

 

solve();

 

}

 

//在第一列查找最代价的行

 

 

1.88 动态规划

 

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

 

 

 

 

算法设计

 

7.简述态规划法与分治的异同。

 

8.下列法中哪些属于动规划算法?

 

1)顺序查找

 

2)直接插入序算法

 

3)简单选择序算法

 

4)二路归并序算法

 

9.某个题对应的递归模如下:

 

f(1)=1

 

f(2)=2

 

 

f(n)=f(n-1)+f(n-2)++f(1)+1

 

可以采用如下递算法求解:longf(intn)

 

{if (n==1) return1;

 

if (n==2) return2;

 

long sum=1;

 

for (int i=1;i<=n-1;i++)

 

sum+=f(i);

 

return sum;

 

n>2

 

 

}

 

但其中存在大量重复计算,请采备忘录方法求解

 

10.3 章中实验4采用分治法求解半数集问,如果直接递归解会存在大量重复计算,请改进算法。

 

11.设计一个时复杂度为O(n)的算法计算二项式系数Cnkn)。二项式系Cn的求值过如下:

 

𝐶=1

 

𝐶=1

 

 

𝑗𝑗1𝑗

 

𝑖𝑖1𝑖1

 

ij

 

 

12.一个器人只能向下和向右移动,每次能移动一步,设一个算法求它从00)移动到mn)有多少条径。

 

13.两种水果交出一种新水果,现在给新水果名,要求这个名中包含了以前两种水果名字的字,并且这个名字尽量短。也就是以前的一种水果arr1 是新水果名字arr 的子序,另一种水果名arr2 也是新水果arr 的子序列。计一个算法求arr

 

例如:输入以下3组水果名称:

 

apple peach

 

ananas banana

 

pear peach

 

输出的新水果名如下:

 

62

 

 

 

 

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)的计算结果

 

longf1(intn)

 

{if (n==1)

 

{dp[n]=1;

 

return dp[n];

 

}

 

if (n==2)

 

{dp[n]=2;

 

return dp[n];

 

63

 

 

 

 

算法设计

 

}

 

if (dp[n]>0) returndp[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];

 

 

intfa(inti)

 

{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;

 

}

 

intsolve(int n)

 

{memset(a,0,sizeof(a));

 

a[1]=1;

 

return fa(n);

 

}

 

voidmain()

 

{n=6;

 

//a[i]

 

//set(n)

 

 

printf("求解结果\n");

 

printf("n=%d数集元素个数=%d\n",n,solve(n));

 

 

}

 

11.定义C(ij)=Ci

 

ij。则有如下推计算公式:C(inj)=C(i-1j-1)+C(i-

 

 

1j),初始条件C(i0)=1C(ii)=1可以根据初始由此递推关系计C(nk)

n

Cn。对应的序如下:

 

#include <stdio.h>

 

#define MAXN 51

 

#define MAXK 31

 

//问题表示

 

int n,k;

 

//求解结果表示

 

int C[MAXN][MAXK];

 

voidsolve()

 

{int i,j;

 

64

 

 

 

 

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];

 

}

 

voidmain()

 

{n=5,k=3;

 

solve();

 

printf("%d\n",C[n][k]);//输出10

 

}

 

显然,solve()的时间复杂度为O(n)

 

12.设从(00)移动到(ij)的路条数为dp[i][j],由机器人只能向下向右移动,不同迷宫问题(迷宫题由于存在后退不满足无后效性不适合用动态规划法求解)。对应的状转移方程如下:

 

dp[0][j]=1

 

dp[i][0]=1

 

 

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

 

ij>0

 

 

最后结果是dp[m][n]。对应的程如下:

 

#include <stdio.h>

 

#include <string.h>

 

#define MAXX 51

 

#define MAXY 51

 

//问题表示

 

int m,n;

 

//求解结果表示

 

int dp[MAXX][MAXY];

 

voidsolve()

 

{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];

 

}

 

voidmain()

 

{m=5,n=3;

 

solve();

 

printf("%d\n",dp[m][n]);

 

}

 

13.解:本题目的路是求arr1 arr2字符串的最长公共子序,基本过程参见

 

65

 

 

 

 

 

程》第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;

 

voidLCSlength()

 

{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++)


算法设计

 

//序列中最多的字个数

 

//动态规划数组

 

//存放LCS

 

//dp

 

//dp[i][0]置为0,边界条件

 

//dp[0][j]置为0,边界条件

 

//两重for循环处arr1arr2的所有字符

 

 

{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]);

 

 

}

 

}

 

voidBuildsubs()

 

{int k=dp[m][n];

 

int i=m;

 

int j=n;

 

while (k>0)

 

//dpsubs

 

//karr1arr2最长公共子序列长度

 

//subs中放入最公共子序列(反向)

 

 

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添加arr1[i-1] i--; j--; k--;

 

}

 

}

 

voidmain()

 

 

{cin >> arr1>> arr2; m=arr1.length();

 

n=arr2.length();

 

LCSlength();

 

Buildsubs();

 

cout << "求解结"<<endl;

 

cout << "arr:";vector<char>::reverse_iteratorrit;

 

//输入arr1arr2 //marr1的长度 //narr2的长度 //求出dp

 

//求出LCS

 

66

 

 

 

 

1

 

for (rit=subs.rbegin();rit!=subs.rend();++rit)

 

cout << *rit;

 

cout << endl;

 

cout << ":"<<dp[m][n]<< endl;

 

概论

 

 

}

 

改为如下:

 

13.解:本题目思路是先求arr1 arr2 字符串的最公共子序列,基本过程参见《教程》第8 8.5节,再利用递归输新水果取名。

 

算法中设置二维态规划数组dpdp[i][j]表示arr1[0..i-1]i 个字母)和arr2[0..j-1]j 个字母)中长公共子序列的长度。另外设置维数组bb[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,intj)

 

{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);

 

//序列中最多的字个数

 

//动态规划数组

 

//存放arr1arr2较的3种情况//利用递归输出新果取名

 

//输出完毕

 

//arr1完毕,输出arr2的剩余部分

 

//arr2完毕,输arr1的剩余部分

 

//arr1[i-1]=arr2[j-1]情况

 

 

67

 

 

 

 

算法设计

 

 

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 示。

 

//dp

 

//dp[i][0]置为0,边界条件

 

//dp[0][j]置为0,边界条件

 

//两重for循环处arr1arr2的所有字符 //比较的字符相同情况0

 

//情况1

 

//dp[i-1][j]<=dp[i][j-1]:2

 

//输入测试用例个

 

//marr1的长度

 

//narr2的长度

 

//求出dp

 

//输出新水果取名

 

 

68

 

 

 

 

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;

 

voidLCSlength()

 

{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++)

 

//序列中最多的字个数

 

//动态规划数组

 

//存放LCS

 

//dp

 

//dp[i][0]置为0,边界条件

 

//dp[0][j]置为0,边界条件

 

//两重for循环处arr1arr2的所有字符

 

 

{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]);

 

 

}

 

}

 

voidBuildsubs()

 

{int k=dp[m][n];

 

int i=m;

 

int j=n;

 

while (k>0)

 

//dpsubs

 

//karr1arr2最长公共子序列长度

 

//subs中放入最公共子序列(反向)

 

 

if (dp[i][j]==dp[i-1][j])i--;

 

else if (dp[i][j]==dp[i][j-1])j--;

 

69

 

 

 

 

算法设

 

else

 

{subs.push_back(arr1[i-1]);//subs添加arr1[i-1] i--; j--; k--;

 

}

 

}

 

voidmain()

 

 

{cin >> arr1>> arr2; m=arr1.length();

 

n=arr2.length();

 

LCSlength();

 

Buildsubs();

 

//输入arr1arr2 //marr1的长度 //narr2的长度 //求出dp

 

//求出LCS

 

 

cout << "求解结"<<endl;

 

cout << "arr:";

 

vector<char>::reverse_iteratorrit;

 

for (rit=subs.rbegin();rit!=subs.rend();++rit)

 

cout << *rit;

 

cout << endl;

 

cout << ":"<<dp[m][n]<< endl;

 

}

 

上述程序的一次行结果如图1.46示。

 

 

1.46

 

程序的一次执行结

 

 

1.99 图算法设计

 

1.9.1练习题

 

1.以下属于贪心算法的)。

 

A.Prim 算法B.Kruskal C.Dijkstra算法D.深度优遍历2.一个有n 顶点的连通图的生成树是原图的小连通子图,且含原图中所有n

 

个顶点,并且有持图联通的最少边。最大生成树是权和最大生成,现在给出一个无向带权图的邻矩阵为{{04503}{40423}{54020}{02201}{33010}},其中权为0 没有边。一个图求这个图的最大生成树的权和是()。

 

 

A.11

 

B.12

 

C.13

 

D.14

 

E.15

 

 

3.某个带权通图有4个以上的顶点,其中恰2 条权值最小边,尽管该图的最小生成树可能多个,而这2 值最小的边一定含在所有的最小成树中吗?如果3 条权值最小边呢?

 

70

 

 

 

 

1

 

概论

 

 

4.为什TSP 问题采用心算法求解不一定得到最优解

 

5.求最路径的4 种算法合带权无向图吗

 

6.单源最短路径的算法有Dijkstra 法、Bellman-Ford 算法和SPFA 法,比较这些算法的不同点

 

7.有人这样修Dijkstra 算法以便求一个带权连通图的单源最长路,将每次选择dist 最小的顶点u改为选择最大的顶点u,将按路长度小进行调整为按路径长度大整。这样可以求源最长路径吗?

 

8.给出一种方法无环带权连通图所有权值非负)从顶点s 到顶点t的一条最长简单路径。

 

9.一个运输网如图1.47 所示,上数字为(c(ij)b(ij)),其中c(ij)表示容量,b(ij)表示单运输费用。给出123 位置运货物到位置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 步取最大边02),第2步取边(01),第3取边(04),第4取最大边(13),得到的权14。答案为D

 

3.答:2 权值最小的边一包含在所有的最生成树中,因为Kurskal算法一定首先选中这2 权值最小的边。果有3 条权值最的边,就不一定,因为首先选中3 条权值最小边有可能出现回

 

4.答:TSP 问题满足最优子结构性质,如(01230)是整个问题的最优解,但(0120)不一定是问题的最优解。

 

5.答:适合带权无向图最短路径。

 

6.答:Dijkstra 不适合存在负权的图求单源最短径,其时间复杂O(n)Bellman-Ford 算法SPFA 算法适存在负权边的图单源最短路径,图中不能

 

71

 

 

 

 

算法设计

 

存在权值和为负环。Bellman-Ford 法的时间复杂度O(ne),而SPFA算法的时间复杂度为O(e),所SPFA 算法更

 

7.答:不能。Dijkstra算法本质上是一种贪心法,而求单源最长路径不满足心选择性质。

 

8.答:Bellman-Ford 算法和SPFA算法适合存在负权边的图求源最短路径。将图中所有边权改为负权值,求从顶点s 到顶点t的一条最短简单路径,它就是原图中从顶点s 顶点t的一条最长简单路径。

 

9.答:为该运网络添加一个虚起点0,它到123 位置运输费0,容量分别为到123 置运输容量和,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,采最小费用最大流算法求解过程如

 

1k=0,求出w如下:

 

 

0

0

0

0

0

2

4

0

5

4

0

6

3

0

3

0

12

0

 

 

求出从起点0 6 的最短路径0→1→4→6,求最小调整量=4f[4][6]调整4f[1][4]调整4f[0][1]调整为4mincost=20maxf=4

 

2k=1,求出w如下:

 

 

0

0

0

0

0

0

4

0

5

4

0

6

3

-2

0

3

0

12

-3

0

 

 

求出从起点0 6 的最短路径0→2→4→6,求最小调整量=3f[4][6]调整

 

72

 

 

 

 

1

 

概论

 

 

7f[2][4]调整3f[0][2]调整为3mincost=44maxf=4+3=73k=2,求出w如下:

 

 

0

0

0

0

0

0

4

0

0

4

0

6

3

-2

-5

0

3

0

12

-3

0

 

 

求出从起点0 6 的最短路径0→3→4→6,求最小调整量=1f[4][6]调整8f[3][4]调整1f[0][3]调整为1mincost=53maxf=7+1=8

 

4k=3,求出w如下:

 

 

0

0

0

0

0

0

4

0

0

4

0

0

3

-2

-5

-6

0

3

0

12

-3

0

 

 

求出从起点0 6 的最短路径0→3→5→6,求最小调整量=2f[5][6]调整2f[3][5]调整2f[0][3]调整为3mincost=83maxf=8+2=10

 

5k=4,求出w如下:

 

 

0

0

0

0

0

4

0

0

4

0

0

-2

-5

-6

0

3

-3

0

12

-3

-12

0

 

 

求出从起点0 6 的最短路径0→1→5→6,求最小调整量=6f[5][6]调整8f[1][5]调整6f[0][1]调整为10mincost=179maxf=10+6=16

 

6k=5,求出w如下:

 

 

0

0

0

0

0

0

4

0

0

-2

-5

-6

0

3

-4

-3

0

12

-3

-12

0

 

 

求出从起点0 6 的最短路径0→1→5→6,求最小调整量=1f[5][6]调整

 

73

 

 

 

 

算法设计

 

9f[2][5]调整1f[0][2]调整为4mincost=195maxf=16+1=177k=6,求出w中没有增广路径,调整结束对应的最大流如下:

 

 

0

10

4

3

0

0

0

0

0

0

0

4

6

0

0

0

0

0

3

1

0

0

0

0

0

1

2

0

0

0

0

0

0

0

8

0

0

0

0

0

0

9

0

0

0

0

0

0

0

 

 

最终结果,maxf=17mincost=195。即输的最大货物量17,对应的最总运输费用为195

 

10.从两个方面考虑优化:

 

1)在Dijkstra 法中,当求出源v 到顶点u 的最路径长度后,仅仅调整从顶u 出发的邻接的最短路径长度而教程中的Dijkstra算法由于采用邻接矩阵存储需要花费O(n)时间来调整顶点u 出发的邻接的最短路径长度,如果采用邻接存储图,可以很快查到顶点u 的所有接点并进行调整时间为O(MAX(图中顶点的出))

 

2)求目前一最短路径长度的u 时,教科书Dijkstra 算法采简单比较方法,可以改为用优先队列(小堆)求解。由于e 条边对应的点进队,对应的时间为O(log2e)

 

对应的完整程序测试数据算法如

 

 

#include "Graph.cpp"

 

#include <queue>

 

#include <string.h>

 

using namespace std;

 

ALGraph *G;

 

struct Node

 

{int i;

 

int v;

 

//包含图的基本运算法

 

//图的邻接表存储构,作为全局变量 //声明堆中结点类

 

//顶点编号

 

//dist[i]

 

 

friend bool operator<(constNode &aconst Node &b)//定义比较运算符 {returna.v >b.v;}

 

};

 

voidDijkstra(intvintdist[])//Dijkstra

 

{ArcNode *p;

 

 

priority_queue<Node>qu;

 

Node e;

 

int S[MAXV];

 

//创建小根堆

 

//S[i]=1表示顶点iS中,S[i]=0表示顶点iU

 

 

int ijuw;

 

memset(S0sizeof(S));

 

p=G->adjlist[v].firstarc;

 

for (i=0;i<G->n;i++)dist[i]=INF;

 

while (p!=NULL)

 

{w=p->adjvex;

 

 

74

 

 

 

 

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的出边顶点qu

 

//源点编号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;

 

}

 

}

 

}

 

voidDisppathlength(intvintdist[])//出最短

 

{printf("%d顶点出发的最短路径长度如下:\n"v);

 

for (int i=0;i<G->n;++i)

 

if (i!=v)

 

printf("到顶点%d:%d\n"idist[i]);

 

}

 

voidmain()

 

{int A[MAXV][MAXV]={

 

{0466INFINFINF}

 

{INF01INF7INFINF}

 

{INFINF0INF64INF}

 

{INFINF20INF5INF}

 

{INFINFINFINF0INF6}

 

{INFINFINFINF108}

 

{INFINFINFINFINFINF0}};

 

int n=7 e=12;

 

 

CreateAdj(GAne);printf("G的邻:\n");DispAdj(G);

 

int v=0;

 

int dist[MAXV];

 

Dijkstra(vdist);

 

Disppathlength(vdist);

 

DestroyAdj(G);

 

//建立图的邻接表 //输出邻接表

 

//调用Dijkstra//输出结果

 

//销毁图的邻接表

 

 

}

 

上述程序的执行果如图1.49 所示

 

 

75

 

 

 

 

算法设计

 

 

1.49程序执行

 

其中Dijkstra 的时间复杂度为O(n(log2e+MAX(点的出度)),一图中最大顶点出度远小于e以进一步简化时间复杂度为O(nlog2e)

 

11.有一个带权向图G(所有权正整数),采用邻接阵存储。设计一个算法求其中的一个最小

 

解:利用Floyd 法求出所有顶点之间的最短路径,若顶点i j 最短路径,而图中又存在顶点ji的边,则构成一个环,在所环中比较找到一最小环并输出。应的程序如下:

 

 

#include "Graph.cpp"

 

//包含图的基本运算法

 

 

#include <vector>

 

using namespace std;

 

voidDispapath(intpath[][MAXV],inti,intj) //顶点ij的一

 

 

{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]);

 

}

 

intMincycle(MGraphg,intA[MAXV][MAXV],int&mini,int&minj)//gA中的查找

 

{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

 

 

 

 

1

 

概论

 

 

}

 

}

 

return min;

 

}

 

voidFloyd(MGraphg)

 

{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];

 

//Floydg

 

 

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++)

 

//顶点ij有边时

 

//顶点ij没有边

 

//依次考察所有顶

 

 

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");

 

}

 

voidmain()

 

{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

 

 

 

 

算法设计

 

 

1.50程序执行

 

1.1010计算

 

1.10.1练习题

 

1.对如1.51 所示的点A,给出采用Graham 扫描算法求包的过程及结果

 

 

10

9

8

7

6

5

4

3

2

 

1

 

a8

 

a11

 

a9

 

a0

 

a6

 

a7

 

a5

 

a3

 

a10

 

a4

 

a1

 

a2

 

 

12345

 

678910

 

 

1.51一个点集A

 

2.对如1.51 所示的点A,给出采用分治法求最近点对过程及结果。

 

3.对如1.51 所示的点A,给出采用旋转卡壳法求最远对的结果。

 

4.对应3 个点向p1p2p3,采S(p1p2p3)=(p2-p1)(p3-p1)/2求它们构成的三角形面积,请问么情况下计算结为正?什么情况计算结果为负?

 

 

5.知坐标为整数,出判断平面上一p 是否在一个逆针三角形p1-p2-p3的算法。

 

1.10.2练习题参考答

 

内部

 

 

1.答:Graham 扫描法求凸包的过程及结果如下:

 

求出起点a0(11)

 

排序后:a0(11)a1(81)a2(94)a3(54)a4(87)a5(56)a10(710)a9(35) a6(37) a7(410)a8(16)a11(03)

 

先将a0(11)a1(81)进栈,a2(94)进栈。

 

处理点a3(54)a3(54)进栈。

 

处理点a4(87)a3(54)存在右拐系,退栈,a4(87)进栈。

 

78

 

 

 

 

1

 

概论

 

 

处理点a5(56)a5(56)进栈。

 

处理点a10(710)a5(56)存在右拐关系,退a10(710)进栈。

 

处理点a9(35)a9(35)进栈。

 

处理点a6(37)a9(35)存在右拐系,退栈,a6(37)进栈。

 

处理点a7(410)a6(37)存在右拐关系,退栈a7(410)进栈。

 

处理点a8(16)a8(16)进栈。

 

处理点a11(03)a11(03)进栈。

 

结果:n=8,凸的顶点:a0(11)a1(81)a2(94)a4(87)a10(710)a7(410) a8(16) a11(03)

 

2.答:解过程如下:

 

排序前:(11)(81)(94)(54)(87)(56)(37)(410)(16)(35)(710)(03)。按x 排序后:(03)(11)(16)(37)(35)(410)(54)(56)(710) (81)(87)(94)。按y 坐标排序后(11)(81)(03)(54)(94)(35)(16) (56) (37) (87) (410) (710)

 

1)中间位置midindex=5左部分:(03)(11)(16)(37)(35)(410);右部分:(54)(56)(710)(81)(87)(94);中间部分点(03)(37)(410)(54) (56) (710) (87)

 

2)求解左部(03) (11) (16) (37) (35)(410)

 

中间位置=2,划为左部分1(03)(11)(16),右部分1(37) (35) (410)处理左部分1数少于4:求出近距离=2.23607(03)(11)之间的距离。处理右部分1数少于4:求出近距离=2,即(37)(35)之间的离。再考虑中间部分中间部分最近距=2.23)求出左部d1=2

 

3)求解右部(54) (56) (710) (81) (87)(94)

 

中间位置=8,划为左部分2(54)(56)(710),右部分2(81)(87)(94)

 

处理左部分2数少于4,求出近距离=2,即(54)(56)之间距离。处理右部分2数少于4,求出近距离=3.16228(81)(94)之间的距离。再考虑中间部分中间部分为空)出右部分d2=2

 

4)求解中间分点集:(03)(37)(410)(54)(56)(710)(87)。求出最近距离d3=5

 

最终结果为:d=MIN{d1d2d3)=2

 

3.答:采用旋转壳法求出两个最远点对是(11)和(710),最远距离10.82

 

 

4.当三角形p1-p2-p3

 

逆时针方向时,1.52 所示,p2-p1

 

p3-p1

 

的顺时针方

 

 

向上,(p2-p1)(p3-p1)>0,对应的面(p2-p1)(p3-p1)/2为正。

 

 

当三角形p1-p2-p3

 

顺时针方向时,1.53 所示,p2-p1

 

p3-p1

 

的逆时针方向上

 

 

(p2-p1)(p3-p1)<0,对应的面积(p2-p1)(p3-p1)/2为负。

 

79

 

 

 

 

算法设计

 

 

p1

 

p3-p1

 

p2-p1

 

p1

 

p2-p1

 

p3-p1

 

 

1.52

 

p1-p2-p3

 

逆时针方向图

 

1.53p1-p2-p3

 

逆时针方向

 

 

5.答:S(p1p2p3)=(p2-p1)(p3-p1)/2求三角形p1p2p3 号的的面积。如图1.54 所示,若S(pp2p3)S(pp3p1)S(pp1p2)3 个三角的方向均为逆时方向)均大于0表示p 在该三角内部。

 

p3

 

 

p1

 

p

 

p2

 

 

1.54

 

一个点p 和一个三

 

 

对应的程序如下

 

#include "Fundament.cpp"

 

//包含向量基本运算法

 

 

doublegetArea(Pointp1,Pointp2,Pointp3)//

 

{

 

return Det(p2-p1,p3-p1);

 

}

 

boolIntrig(Pointp,Pointp1,Pointp2,Pointp3)//pp1p2p3{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;

 

}

 

voidmain()

 

{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

 

 

 

 

1

 

概论

 

 

printf("

 

printf("

 

printf("

 

printf("

 

p5:"); p5.disp();printf("\n");

 

p1p2p3三角形面积:%g\n",getArea(p1,p2,p3));

 

p4p1p2p3三角形:%s\n",Intrig(p4,p1,p2,p3)?"":"不是");p5p1p2p3三角形:%s\n",Intrig(p5,p1,p2,p3)?"":"不是");

 

 

}

 

上述程序的执行果如图1.55 所示

 

 

1.55程序执行

 

1.1111计算杂性理论

 

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(32)f(23)像演变过程。

 

4.什么P 类问题?什NP类问题?

 

5.证明两个m n 二维矩阵相加的题属于P 类问

 

6.证明含有n 个元素的据序列中求最大素的问题属于P 问题。

 

7.设计一个确定图灵机M,用于算后继函数S(n)=n+1n为一个二进制数),并给出求1010001 后继函数值的瞬演变过程。

 

1.11.2练习题参考答

 

1.答:B

 

2.答:D

 

3.答:1)执行f(32)时,输入带上初始信息为000100B,其瞬像演变过程

 

81

 

 

 

 

算法设计

 

 

下:

 

 

q0000100BB0q30110BBB01q210B

 

Bq100100BBq300110BBB011q20B

 

B0q10100Bq3B00110BBB01q311B

 

B00q1100B

B001q200B

B00q3110B

 

Bq000110B

BBq10110B

BB0q1110B

 

BB0q3111B

BBq30111B

BBq00111B

 

 

BBB1q211B

 

BBB11q21B

 

BBB111q2B

 

BBB11q41B

 

BBB1q41BB

 

 

BBBq41BBB

 

BBBq4BBBB

 

BBB0q6BBB

 

 

最终带上有一个0,计算结果为1

 

2)执行f(23)时,输入带上的初信息为001000B,其瞬像演变过如下:

 

 

q0001000B

 

Bq001000B

 

B0q11000B

 

B01q2000B

 

B0q31100B

 

Bq301100B

 

 

q3B01100BBBq31100B

 

Bq001100BBq3B1100B

 

BBq11100BBBq01100B

 

BB1q2100BBBBq5100B

 

BB11q200BBBBBq500B

 

BB1q3100B

 

 

BBBBBq50B

 

BBBBBBq5B

 

BBBBBBBq6

 

 

最终带上有零个0,计算结果为0

 

4.答:定性图灵机以多式时间界可解的题称为P 类问题用非确定性图灵机以多项式时间可解的问题称为NP类问题。

 

5.求两个m n列的二维矩阵相加的问题对应算法时间复杂度为O(mn),所以属于P 类问

 

6.答:求含有n个元素的数据列中最大元素的问题的算法时复杂度为O(n),所以属于P 类问

 

7.解:q0 为初状态,q3为终止状态,读写头始时注视最右边的格。δ 动作函如下:

 

δ(q00)(q11L)

 

δ(q01)(q20L)

 

δ(q0B)(q3BR)

 

δ(q10)(q10L)

 

δ(q11)(q11L)

 

δ(q1B)(q3BL)

 

δ(q20)(q11L)

 

δ(q21)(q20L)

 

δ(q2B)(q3BL)

 

10100010 继函数值的瞬像变过程如下:

 

 

B1010001q00B

 

B101000q111B

 

B10100q1011B

 

B1010q10011B

 

B101q100011B

 

 

 

B10q1100011B

 

B1q10100011B

 

Bq110100011B

 

q1B10100011B

 

 

 

q3BB10100011B

 

 

其结果为10100011

 

 

82

 

 

 

 

1

 

概论

 

 

1.1212概率法和近似算法

 

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

 

 

 

 

算法设计

 

决策既不受之前策的影响,也不响后续决策,则心算法就是确定最优解算法。9.解:通过rand5()*5+rand5()产生67892627282930 25个整

 

数,每个整数x 现的概率相等,前面3*7=21 个整,舍弃后面的4 整数,将{678}转化成1{91011}转化成2,以此类,即由y=(x-3)/3 最终结果。对应序如下:

 

#include <stdio.h>

 

 

#include <stdlib.h>

 

#include <time.h>

 

intrand5()

 

{int a=1,b=5;

 

return rand()%(b-a+1)+a;

 

}

 

intrand7()

 

{int x;

 

do

 

{

 

x=rand5()*5+rand5();

 

} while (x>26);

 

int y=(x-3)/3;

 

return y;

 

}

 

voidmain()

 

{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


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM