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
2
D.
T
(
n
)=3
n
log
2
n
3
4
.
什么是算法?算法有哪些特征?
.
判斷一個大於
2
的正整數
n
是否為素數的方法有多種,給出兩種算法,說明其中
一種算法更好的理由。
5
.
證明以下關系成立:
(
(
1
)
10
n
2
-
2
n
=
(
n
2
)
2
)
2
n
+
1
=
(2
n
)
6
7
.
證明
O(
f
(
n
))+O(
g
(
n
))=O(max{
f
(
n
)
,
g
(
n
)})
。
.
有一個含
n
(
n
>2
)個整數的數組
a
,判斷其中是否存在出現次數超過所有元素一
半的元素。
8
9
.
一個字符串采用
string
對象存儲,設計一個算法判斷該字符串是否為回文。
.
有一個整數序列,設計一個算法判斷其中是否存在兩個元素和恰好等於給定的整
數
k
。
0.
有兩個整數序列,每個整數序列中所有元素均不相同。設計一個算法求它們的公
共元素,要求不使用
STL
的集合算法。
1.
正整數
n
(
n
>1
)可以寫成質數的乘積形式,稱為整數的質因數分解。例如,
2=2*2*3
,
18=2*3*3
,
11=11
。設計一個算法求
n
這樣分解后各個質因數出現的次數,采
用
vector
向量存放結果。
2.
有一個整數序列,所有元素均不相同,設計一個算法求相差最小的元素對的個
數。如序列
4
、
1
、
2
、
3
的相差最小的元素對的個數是
3
,其元素對是(
1
,
2
),(
2
,
3
),
3
,
4
)。
3.
有一個
map<string
,
int>
容器,其中已經存放了較多元素。設計一個算法求出其
中重復的
value
並且返回重復
value
的個數。
1
1
1
1
(
1
1
1
4.
重新做第
10
題,采用
map
容器存放最終結果。
5.
假設有一個含
n
(
n
>1
)個元素的
stack<int>
棧容器
st
,設計一個算法出棧從棧頂
到棧底的第
k
(
1
≤
k
≤
n
)個元素,其他棧元素不變。
算法設計
1
.1.2
練習題參考答案
答:
由於算法具有有窮性、確定性和輸出性,因而Ⅱ、Ⅲ、Ⅳ正確,而解決某一
類問題的算法不一定是唯一的。答案為
C
。
答:
選項
A
的時間復雜度為
O(
n
)
。選項
B
的時間復雜度為
O(
n
2
)
。選項
C
的時間
復雜度為
O(log
2
n
)
。選項
D
的時間復雜度為
O(
n
log
2
n
)
。答案為
C
。
答:
算法是求解問題的一系列計算步驟。算法具有有限性、確定性、可行性、輸
入性和輸出性
5
個重要特征。
.
答:
兩種算法如下:
1
.
2
.
3
.
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
更好。
.
答:
(
1
)當
n
足夠大時,
(10
n
2
-
2
n
)/(
n
2
)=10
,所以
10
n
2
-
2
n
=
(
n
2
)
。
2
)
2
n
+
1
=2*2
n
=
(2
n
)
。
.
證明:
對於任意
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
,
5
(
6
有
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
)})
。
.
解:
先將
a
中元素遞增排序,再求出現次數最多的次數
maxnum
,最后判斷是否滿
足條件。對應的程序如下:
7
#
#
include <stdio.h>
include <algorithm>
using namespace std;
2
第
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
算法設計
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
第
1
章
概論
else
printf("
不存在兩個元素和為
%d\n",k1);
}
上述程序的執行結果如圖
1.3
所示。
圖
1.3
程序執行結果
1
0.
解:
采用集合
set<int>
存儲整數序列,集合中元素默認是遞增排序的,再采用二
路歸並算法求它們的交集。對應的程序如下:
#
#
include <stdio.h>
include <set>
using namespace std;
void solve(set<int> s1,set<int>
s2,set<int>
&s3)
//
求交集
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;
}
}
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
算法設計
printf(" s2:
");
dispset(s2);
printf(" s3:
");
dispset(s3);
}
上述程序的執行結果如圖
1.4
所示。
圖
1.4
程序執行結果
1
1.
解:
對於正整數
n
,從
i
=2
開始查找其質因數,
ic
記錄質因數
i
出現的次數,當找
到這樣質因數后,將(
i
,
ic
)作為一個元素插入到
vector
容器
v
中。最后輸出
v
。對應的
算法如下:
#
#
include <stdio.h>
include <vector>
using namespace std;
struct
NodeType
//vector
向量元素類型
//
質因數
{
int p;
int pc;
//
質因數出現次數
}
;
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
第
1
章
概論
void main()
{
vector<NodeType> v;
int n=100;
printf("n=%d\n",n);
solve(n,v);
disp(v);
}
上述程序的執行結果如圖
1.5
所示。
圖
1.5
程序執行結果
1
2.
解:
先遞增排序,再求相鄰元素差,比較求最小元素差,累計最小元素差的個
數。對應的程序如下:
#
#
#
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
算法設計
圖
1.6
程序執行結果
1
3.
解:
對於
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
程序執行結果
1
4.
解:
采用
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
第
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
程序執行結果
1
5.
解:
棧容器不能順序遍歷,為此創建一個臨時
tmpst
棧,將
st
的
k
個元素出棧並
進棧到
tmpst
中,再出棧
tmpst
一次得到第
k
個元素,最后將棧
tmpst
的所有元素出棧並進
棧到
st
中。對應的程序如下:
#
#
include <stdio.h>
include <stack>
using namespace std;
int solve(stack<int> &st,int k)
//
出棧第
k
個元素
{
stack<int> tmpst;
int e;
for (int i=0;i<k;i++)
//
出棧
st
的
k
個元素並進
tmpst
棧
{
e=st.top();
st.pop();
tmpst.push(e);
}
e=tmpst.top();
//
求第
k
個元素
tmpst.pop();
while (!tmpst.empty())
//
將
tmpst
的所有元素出棧並進棧
st
{
st.push(tmpst.top());
tmpst.pop();
9
算法設計
}
return e;
}
void disp(stack<int> &st)
//
出棧
st
的所有元素
{
while (!st.empty())
{
printf("%d ",st.top());
st.pop();
}
printf("\n");
}
void main()
{ stack<int>
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);
}
1
0
第
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
T
(
n
)=4
T
(
n
/2)+
n
2
當
n
=1
當
n
>1
7
8
.
分析求斐波那契
f(n)
的時間復雜度。
.
數列的首項
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
,設計一個遞歸算法求其字符個數(長
度)。
1
0.
對於一個采用字符數組存放的字符串
str
,設計一個遞歸算法判斷
str
是否為回
文。
1
1
1
1.
對於不帶頭結點的單鏈表
L
,設計一個遞歸算法正序輸出所有結點值。
2.
對於不帶頭結點的單鏈表
L
,設計一個遞歸算法逆序輸出所有結點值。
3.
對於不帶頭結點的非空單鏈表
L
,設計一個遞歸算法返回最大值結點的地址(假
設這樣的結點唯一)。
4.
對於不帶頭結點的單鏈表
L
,設計一個遞歸算法返回第一個值為
x
的結點的地
址,沒有這樣的結點時返回
NULL
。
1
1
1
5.
對於不帶頭結點的單鏈表
L
,設計一個遞歸算法刪除第一個值為
x
的結點。
6.
假設二叉樹采用二叉鏈存儲結構存放,結點值為
int
類型,設計一個遞歸算法求
二叉樹
bt
中所有葉子結點值之和。
7.
假設二叉樹采用二叉鏈存儲結構存放,結點值為
int
類型,設計一個遞歸算法求
二叉樹
bt
中所有結點值大於等於
k
的結點個數。
8.
假設二叉樹采用二叉鏈存儲結構存放,所有結點值均不相同,設計一個遞歸算法
求值為
x
的結點的層次(根結點的層次為
1
),沒有找到這樣的結點時返回
0
。
1
1
1
1
算法設計
1
.2.2
練習題參考答案
答:
一個
f
函數定義中直接調用
f
函數自己,稱為直接遞歸。一個
f
函數定義中調
用
g
函數,而
g
函數的定義中調用
f
函數,稱為間接遞歸。消除遞歸一般要用棧實現。
答:
遞歸函數
f
(
n
,
m
)
中,
n
是非引用參數,
m
是引用參數,所以遞歸函數的狀態為
1
.
2
.
(
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
2
)
。
4
.
解:
整數一個常系數的線性齊次遞推式,用
x
n
代替
H
(
n
)
,有:
x
n
=
x
n
-
1
+9
x
n
-
2
-
9
x
n
-
3
,
兩邊同時除以
x
n
-
3
,得到:
x
3
=
x
2
+9
x
-
9
,即
x
3
-
x
2
-
9
x+
9=0
。
x
3
-
x
2
-
9
x+
9=
x
(
x
2
-
9)
-
(
x
2
-
9)=(
x
-
1)(
x
2
-
9)=(
x
-
1)(
x
+3)(
x
-
3)=0
。得到
r
1
=1
,
r
2
=
-
3
,
r
3
=3
則遞歸方程的通解為:
H
(
n
)=
c
1
+
c
2
(
-
3)
n
+
c
3
3
n
代入
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
(
‒
1
)
n
‒
1
1
‒
4
。
n
‒
1
求出:
c
1
=
-
1/4
,
c
2
=
-
1/12
,
c
3
=1/3
,
H
(
n
)=
c
1
+
c
2
(
-
3)
n
+
c
3
3
n
=
(
+ 1
)
3
4
5
.
解:
構造的遞歸樹如圖
1.10
所示,第
1
層的問題規模為
n
,第
2
的層的子問題的
問題規模為
n
/2
,依此類推,當展開到第
k
+1
層,其規模為
n
/2
k
=1
,所以遞歸樹的高度為
log
2
n
+1
。
第
1
層有
1
個結點,其時間為
n
,第
2
層有
4
個結點,其時間為
4(
n
/2)=2
n
,依次類推,第
k
層有
4
k
-
1
個結點,每個子問題規模為
n
/2
k
-
1
,其時間為
4
k
-
1
(
n
/2
k
-
1
)=2
k
-
1
n
。葉子結點的個數為
n
個,其時間為
n
。將遞歸樹每一層的時間加起來,可得:
log
2
n
T
(
n
)=
n
+2
n
+
…
+ 2
k
-
1
n
+
…
+
n
≈
ꢀ ∗
2
=O(
n
2
)
。
1
2
第
1
章
概論
n
n
(
n
/2)
(
n
/2) (
n
/2)
(
n
/2)
2
n
2
高度
h
為
log
n
+1
n
/2
2
) (
n
/2
2
)
(
n
/2
2
)
(
n
/2
2
)
(
2
2
n
…
…
…
…
1
1
1
1
n
圖
1.10
一棵遞歸樹
6
.
解:
采用主方法求解,這里
a
=4
,
b
=2
,
f
(
n
)=
n
2
。
log a
log
4
b 2
=
ꢀ
l
o
g
b
a
因此,
ꢀ
=
n
2
,它與
f
(
n
)
一樣大,滿足主定理中的情況(
2
),所以
T
(
n
)=
O(
ꢀ
log
2
n)
=O(
n
2
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
的方法可以求
出:
n
n
n
1
5
1
5
T
1
(
n
)=
1
1
≈
1
1
5
2
=
5
2
5
2
5
n
所以
T(
n
)=T
1
(
n
)+1
≈
1
1
5
2
+1=O(
φ
n
)
,其中
φ
=
1
5
2
。
5
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
當
*str='\0'
時
f
(str)=
f
(str+1)+1
其他情況
對應的遞歸程序如下:
1
3
算法設計
#
include <iostream>
using namespace std;
int Length(char *str)
//
求
str
的字符個數
{
if (*str=='\0')
return 0;
else
return Length(str+1)+1;
}
void main()
{
char str[]="abcd";
cout << str << "
的長度
: " << Length(str) << endl;
}
上述程序的執行結果如圖
1.11
所示。
圖
1.11
程序執行結果
1
0.
解:
設
f
(str
,
n
)
返回含
n
個字符的字符串
str
是否為回文,其遞歸模型如下:
f
(str
,
n
)=true
當
n
=0
或者
n
=1
時
當
str[0]
≠
str[
n
-
1]
時
其他情況
f
(str
,
n
)=flase
f
(str
,
n
)=
f
(str+1
,
n
-
2)
對應的遞歸算法如下:
#
#
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");
}
1
4
第
1
章
概論
上述程序的執行結果如圖
1.12
所示。
圖
1.12
程序執行結果
1
1.
解:
設
f
(L)
正序輸出單鏈表
L
的所有結點值,其遞歸模型如下:
f
(L)
≡ 不做任何事情
當
L=NULL
f
(L)
≡ 輸出
L
-
>data;
f
(L
-
>next);
當
L
≠
NULL
時
對應的遞歸程序如下:
#
include "LinkList.cpp"
//
包含單鏈表的基本運算算法
//
正序輸出所有結點值
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
程序執行結果
1
2.
解:
設
f
(L)
逆序輸出單鏈表
L
的所有結點值,其遞歸模型如下:
f
(L)
≡ 不做任何事情
當
L=NULL
f
(L)
≡
f
(L
-
>next);
輸出
L
-
>data
當
L
≠
NULL
時
對應的遞歸程序如下:
#
include "LinkList.cpp"
void Revdisp(LinkNode *L)
if (L==NULL) return;
//
包含單鏈表的基本運算算法
//
逆序輸出所有結點值
{
1
5
算法設計
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
程序執行結果
1
3.
解:
設
f
(L)
返回單鏈表
L
中值最大結點的地址,其遞歸模型如下:
f
(L) = L
當
L
只有一個結點時
f
(L) =
MAX{
f
(L
-
>next)
,
L->data}
其他情況
對應的遞歸程序如下:
#
include "LinkList.cpp"
//
包含單鏈表的基本運算算法
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);
1
6
第
1
章
概論
}
上述程序的執行結果如圖
1.15
所示。
圖
1.15
程序執行結果
1
4.
解:
設
f
(L
,
x
)
返回單鏈表
L
中第一個值為
x
的結點的地址,其遞歸模型如下:
f
(L
,
x
) = NULL
f
(L
,
x
) = L
當
L=NULL
時
當
L
≠
NULL
且
L
-
>data=
x
時
其他情況
f
(L
,
x
) =
f
(L
-
>next
,
x
)
對應的遞歸程序如下:
#
include "LinkList.cpp"
//
包含單鏈表的基本運算算法
LinkNode *Firstxnode(LinkNode
*L,int
x)
//
返回第一個值為
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
所示。
圖
1.16
程序執行結果
1
5.
解:
設
f
(L
,
x
)
刪除單鏈表
L
中第一個值為
x
的結點,其遞歸模型如下:
f
(L
,
x
)
≡ 不做任何事情
當
L=NULL
f
(L
,
x
)
≡ 刪除
L
結點,
L=L
-
>next
f
(L
,
x
)
≡
f
(L
-
>next
,
x
)
當
L
≠
NULL
且
L
-
>data=
x
其他情況
對應的遞歸程序如下:
1
7
算法設計
#
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
程序執行結果
1
6.
解:
設
f
(bt)
返回二叉樹
bt
中所有葉子結點值之和,其遞歸模型如下:
f
(bt)=0
當
bt=NULL
f
(bt)=bt
-
>data
當
bt
≠
NULL
且
bt
結點為葉子結點
其他情況
f
(bt)=
f
(bt
-
>lchild)+
f
(bt
-
>rchild)
對應的遞歸程序如下:
#
include "Btree.cpp"
//
包含二叉樹的基本運算算法
int LeafSum(BTNode *bt)
//
二叉樹
bt
中所有葉子結點值之和
{
if (bt==NULL) return 0;
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()
1
8
第
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
構造二叉鏈
bt
printf("
二叉樹
bt:"); DispBTree(bt); printf("\n");
printf("
所有葉子結點值之和
: %d\n",LeafSum(bt));
DestroyBTree(bt);
//
銷毀樹
bt
}
上述程序的執行結果如圖
1.18
所示。
圖
1.18
程序執行結果
1
7.
解:
設
f
(bt
,
k
)
返回二叉樹
bt
中所有結點值大於等於
k
的結點個數,其遞歸模型
如下:
f
(bt
,
k
)=0
當
bt=NULL
f
(bt
,
k
)=
f
(bt
-
>lchild
,
k
)+
f
(bt
-
>rchild
,
k
)+1
f
(bt
,
k
)=
f
(bt
-
>lchild
,
k
)+
f
(bt
-
>rchild
,
k
)
當
bt
≠
NULL
且
bt
-
>data
≥
k
其他情況
對應的遞歸程序如下:
#
include "Btree.cpp"
//
包含二叉樹的基本運算算法
//
大於等於
k
的結點個數
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);
//
由
a
和
b
構造二叉鏈
bt
printf("
二叉樹
bt:"); DispBTree(bt); printf("\n");
int k=3;
printf("
大於等於
%d
的結點個數
: %d\n",k,Nodenum(bt,k));
DestroyBTree(bt);
//
銷毀樹
bt
}
上述程序的執行結果如圖
1.19
所示。
1
9
算法設計
圖
1.19
程序執行結果
1
8.
解:
設
f
(bt
,
x
,
h
)
返回二叉樹
bt
中
x
結點的層次,其中
h
表示
bt
所指結點的層
次,初始調用時,
bt
指向根結點,
h
置為
1
。其遞歸模型如下:
f
(bt
,
x
,
h
)=0
當
bt=NULL
f
(bt
,
x
,
h
)=
h
當
bt
≠
NULL
且
bt
-
>data=x
當
l
=
f
(bt
-
>lchild
,
x
,
h
+1)
≠
0
其他情況
f
(bt
,
x
,
h
) =
l
f
(bt
,
x
,
h
) =
f
(bt
-
>rchild
,
x
,
h
+1)
對應的遞歸程序如下:
#
include "Btree.cpp"
//
包含二叉樹的基本運算算法
int Level(BTNode *bt,int x,int h)
//
求二叉樹
bt
中
x
結點的層次
{
//
初始調用時:
bt
為根,
h
為
1
if (bt==NULL) return 0;
if (bt->data==x)
return h;
//
找到
x
結點,返回
h
else
{
int l=Level(bt->lchild,x,h+1);
//
在左子樹中查找
if (l!=0)
return l;
//
在左子樹中找到,返回其層次
l
else
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);
//
銷毀樹
bt
}
上述程序的執行結果如圖
1.20
所示。
圖
1.20
程序執行結果
2
0
第
1
章
概論
1
.3
第
3
章─分治法
1
.3.1
練習題
分治法的設計思想是將一個難以直接解決的大問題分割成規模較小的子問題,分
別解決子問題,最后將子問題的解組合起來形成原問題的解。這要求原問題和子問題
)。
A.
問題規模相同,問題性質相同
1
.
(
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;
2
1
算法設計
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
2
)
。
6
.
以下哪些算法采用分治策略:
(
(
(
(
1
)堆排序算法
2
)二路歸並排序算法
3
)折半查找算法
4
)順序查找算法
7
8
.
適合並行計算的問題通常表現出哪些特征?
.
設有兩個復數
x
=
a
+
bi
和
y
=
c
+
di
。復數乘積
xy
可以使用
4
次乘法來完成,即
xy
=(
ac
-
bd
)+(
ad
+
bc
)
i
。設計一個僅用
3
次乘法來計算乘積
xy
的方法。
9
1
1
1
.
有
4
個數組
a
、
b
、
c
和
d
,都已經排好序,說明找出這
4
個數組的交集的方法。
0.
設計一個算法,采用分治法求一個整數序列中的最大最小元素。
1.
設計一個算法,采用分治法求
x
n
。
2.
假設二叉樹采用二叉鏈存儲結構進行存儲。設計一個算法采用分治法求一棵二叉
樹
bt
的高度。
1
3.
假設二叉樹采用二叉鏈存儲結構進行存儲。設計一個算法采用分治法求一棵二叉
樹
bt
中度為
2
的結點個數。
1
4.
有一種二叉排序樹,其定義是空樹是一棵二叉排序樹,若不空,左子樹中所有結
點值小於根結點值,右子樹中所有結點值大於根結點值,並且左右子樹都是二叉排序樹。
現在該二叉排序樹采用二叉鏈存儲,采用分治法設計查找值為
x
的結點地址,並分析算法
的最好的平均時間復雜度。
2
2
第
1
章
概論
1
5.
設有
n
個互不相同的整數,按遞增順序存放在數組
a
[0..
n
-
1]
中,若存在一個下標
i
(
0
≤
i
<
n
),使得
a
[
i
]=
i
。設計一個算法以
O(log
2
n
)
時間找到這個下標
i
。
1
6.
請你模仿二分查找過程設計一個三分查找算法。分析其時間復雜度。
1
7.
對於大於
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
,
2=2*3*2
,
12=2*2*3
,設計一個算法求
n
的不同分解式個數。
8.
設計一個基於
BSP
模型的並行算法,假設有
p
台處理器,計算整數數組
a
[0..
n
-
1]
的所有元素之和。並分析算法的時間復雜度。
1
1
1.3.2
練習題參考答案
1
2
3
.
答:
C
。
.
答:
D
。
.
答:
以
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
2
)
。
6
7
.
答:
其中二路歸並排序和折半查找算法采用分治策略。
.
答:
適合並行計算的問題通常表現出以下特征:
(
1
)將工作分離成離散部分,有助於同時解決。例如,對於分治法設計的串行算
法,可以將各個獨立的子問題並行求解,最后合並成整個問題的解,從而轉化為並行算
法。
(
(
2
)隨時並及時地執行多個程序指令。
3
)多計算資源下解決問題的耗時要少於單個計算資源下的耗時。
8
.
答:
xy
=(
ac
-
bd
)+((
a
+
b
)(
c
+
d
)
-
ac
-
bd
)
i
。由此可見,這樣計算
xy
只需要
3
次乘法(即
ac
、
bd
和
(
a
+
b
)(
c
+
d
)
乘法運算)。
答:
采用基本的二路歸並思路,先求出
a
、
b
的交集
ab
,再求出
c
、
d
的交集
cd
,
最后求出
ab
和
cd
的交集,即為最后的結果。也可以直接采用
4
路歸並方法求解。
0.
解:
采用類似求求一個整數序列中的最大次大元素的分治法思路。對應的程序如
9
.
1
下:
#
#
#
include <stdio.h>
define max(x,y) ((x)>(y)?(x):(y))
define min(x,y) ((x)<(y)?(x):(y))
2
3
算法設計
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
程序執行結果
1
1.
解:
設
f
(
x
,
n
)=
x
n
,采用分治法求解對應的遞歸模型如下:
f
(
x
,
n
)=
x
當
n
=1
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
當
n
為偶數時
當
n
為奇數時
對應的遞歸程序如下:
#
include <stdio.h>
double solve(double x,int n)
//
求
x^n
{
double fv;
if (n==1) return x;
if (n%2==0)
{
fv=solve(x,n/2);
return fv*fv;
}
2
4
第
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
程序執行結果
1
2.
解:
設
f
(bt)
返回二叉樹
bt
的高度,對應的遞歸模型如下:
f
(bt)=0
當
bt=NULL
f
(bt)=MAX{
f
(bt
-
>lchild)
,
f
(bt
-
>rchild)}+1
其他情況
對應的程序如下:
#
include "Btree.cpp"
//
包含二叉樹的基本運算算法
//
求二叉樹
bt
的高度
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;
//
子問題
1
//
子問題
2
//
合並
}
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");
printf("bt
的高度
: %d\n",Height(bt));
DestroyBTree(bt);
//
銷毀樹
bt
}
2
5
算法設計
上述程序的執行結果如圖
1.23
所示。
圖
1.23
程序執行結果
1
3.
解:
設
f
(bt)
返回二叉樹
bt
中度為
2
的結點個數,對應的遞歸模型如下:
f
(bt)=0
當
bt=NULL
f
(bt)=
f
(bt
-
>lchild)+
f
(bt
-
>rchild)+1
f
(bt)=
f
(bt
-
>lchild)+
f
(bt
-
>rchild)
若
bt
≠
NULL
且
bt
為雙分支結點
其他情況
對應的算法如下:
#
include "Btree.cpp"
//
包含二叉樹的基本運算算法
//
求
bt
中度為
2
的結點個數
int Nodes(BTNode *bt)
{
int n=0;
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
構造二叉鏈
bt
printf("
二叉樹
bt:"); DispBTree(bt); printf("\n");
printf("bt
中度為
2
的結點個數
: %d\n",Nodes(bt));
DestroyBTree(bt);
//
銷毀樹
bt
}
上述程序的執行結果如圖
1.24
所示。
圖
1.24
程序執行結果
1
4.
解:
設
f
(bt
,
x
)
返回在二叉排序樹
bt
得到的值為
x
結點的地址,若沒有找到返回
空,對應的遞歸模型如下:
f
(bt
,
x
)=NULL
當
bt=NULL
f
(bt
,
x
)=bt
當
bt
≠
NULL
且
x
=bt
-
>data
當
x
>bt
-
>data
f
(bt
,
x
)=
f
(bt
-
>lchild
,
x
)
2
6
第
1
章
概論
f
(bt
,
x
)=
f
(bt
-
>rchild
,
x
)
當
x
<bt
-
>data
對應的程序如下:
#
include "Btree.cpp"
//
包含二叉樹的基本運算算法
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
當
n
=1
當
n
>1
T
(
n
)=
T
(
n
/2)+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
{
2
7
算法設計
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
程序執行結果
1
6.
解:
對於有序序列
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)
//
序列中只有
1
個元素
{
if (x==a[low])
return low;
else
return -1;
}
if (high-low<2)
{ if
(x==a[low])
//
序列中只有
2
個元素
return low;
else if (x==a[low+1])
return low+1;
else
2
8
第
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
程序執行結果
1
7.
解:
設
f
(
n
)
表示
n
的不同分解式個數。有:
f
(1)=1
,作為遞歸出口
f
(2)=1
,分解式為:
2=2
f
(3)=1
,分解式為:
3=3
f
(4)=2
,分解式為:
4=4
,
4=2*2
2
9
算法設計
f
(6)=3
,分解式為:
6=6
,
6=2*3
,
6=3*2
,即
f
(6)=
f
(1)+
f
(2)+
f
(3)
以此類推,可以看出
f
(
n
)
為
n
的所有因數的不同分解式個數之和,即
f
(
n
)=
∑
ꢀ
ꢁ
ꢂ
=
0
ꢃ
(
ꢀ
ꢄ
ꢂ
)
。對應的程序如下:
#
#
include <stdio.h>
define MAX 101
int solve(int n)
{ if
(n==1) return
1;
//
求
n
的不同分解式個數
else
{
int sum=0;
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
程序執行結果
1
8.
解:
對應的並行算法如下:
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++)
//for
循環的各個子問題並行執行
{
sj=Sum(a,k,k+n/p-1,p,j);
k+=n/p;
}
sum+=sj;
return sum;
}
每個處理器的執行時間為
O(
n
/
p
)
,同步開銷為
O(
p
)
,所以該算法的時間復雜度為
O(
n
/
p
+
p
)
。
3
0
第
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
.
有一個三位數,個位數字比百位數字大,而百位數字又比十位數字大,並且各位
數字之和等於各位數字相乘之積,設計一個算法用窮舉法求此三位數。
.
某年級的同學集體去公園划船,如果每只船坐
10
人,那么多出
2
個座位;如果每
只船多坐
2
人,那么可少租
1
只船,設計一個算法用蠻力法求該年級的最多人數?
已知:若一個合數的質因數分解式逐位相加之和等於其本身逐位相加之和,則稱
這個數為
Smith
數。如
4937775=3*5*5*65837
,而
3+5+5+6+5+8+3+7=42
,
+9+3+7+7+7+5=42
,所以
4937775
是
Smith
數。求給定一個正整數
N
,求大於
N
的最小
Smith
數。
輸入:若干個
case
,每個
case
一行代表正整數
N
,輸入
0
表示結束
8
9
.
4
輸出:大於
N
的最小
Smith
數
輸入樣例:
4
0
937774
樣例輸出:
3
1
算法設計
4
937775
1
0.
求解塗棋盤問題。小易有一塊
n
*
n
的棋盤,棋盤的每一個格子都為黑色或者白
色,小易現在要用他喜歡的紅色去塗畫棋盤。小易會找出棋盤中某一列中擁有相同顏色的
最大的區域去塗畫,幫助小易算算他會塗畫多少個棋格。
輸入描述:輸入數據包括
n
+1
行:第一行為一個整數
n
(
1
≤
n
≤
50
),即棋盤的大
小,接下來的
n
行每行一個字符串表示第
i
行棋盤的顏色,
'W'
表示白色,
'B'
表示黑色。
輸出描述:輸出小易會塗畫的區域大小。
輸入例子:
3
BWW
BBB
BWB
輸出例子:
3
1
1.
給定一個含
n
(
n
>1
)個整數元素的
a
,所有元素不相同,采用蠻力法求出
a
中所
有元素的全排列。
1.4.2
練習題參考答案
1
.
答:
蠻力法是一種簡單直接地解決問題的方法,適用范圍廣,是能解決幾乎所有
問題的一般性方法,常用於一些非常基本、但又十分重要的算法(排序、查找、矩陣乘法
和字符串匹配等),蠻力法主要解決一些規模小或價值低的問題,可以作為同樣問題的更
高效算法的一個標准。而分治法采用分而治之思路,把一個復雜的問題分成兩個或更多的
相同或相似的子問題,再把子問題分成更小的子問題直到問題解決。分治法在求解問題
時,通常性能比蠻力法好。
2
.
答:
如果用蠻力法求解的問題可以分解為若干個規模較小的相似子問題,此時可
以采用遞歸來實現算法。
.
解:
上述算法的時間復雜度為
O(
n
2
)
,采用的是最基本的蠻力法。可以先對
a
中元
素遞增排序,然后依次比較相鄰元素的差,求出最小差,改進后的算法如下:
3
#
#
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;
}
3
2
第
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)
//
求
n!
{
long fn=1;
for (int i=2;i<=n;i++)
fn=fn*i;
return fn;
}
long solve(int n)
//
求
1!+2!+…+n!
{
long ans=0;
for (int i=1;i<=n;i++)
ans+=f(i);
return ans;
}
實際上,
f
(
n
)=
f
(
n
-
1)*
n
,
f
(1)=1
,在求
f
(
n
)
時可以利用
f
(
n
-
1)
的結果。改進后的算法如
下:
long solve1(int n)
//
求
1!+2!+…+n!
{
long ans=0;
long fn=1;
for (int i=1;i<=n;i++)
{
fn=fn*i;
ans+=fn;
}
return ans;
}
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++)
3
3
算法設計
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()
3
4
第
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)
//
求
n
的各位數字和
{
int sum=0;
while (n>0)
3
5
算法設計
{
}
sum+=n%10;
n=n/10;
return sum;
}
bool solve(int n)
//
判斷
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;
}
void main()
{
int n;
while (true)
{
scanf("%d",&n);
if (n==0) break;
while (!solve(n))
n++;
printf("%d\n",n);
}
}
1
0.
解:
采用蠻力法,統計每一列相鄰相同顏色的棋格個數
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++;
countj=1;
else
3
6
第
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;
}
1
1.
解:
與《教程》中求全排列類似,但需要將求
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
得到
ps1
vector<int> s1;
{
vector<int>::iterator it;
for (int j=0;j<=i;j++)
//
在
s(
含
i
個整數
)
的每個位置插入
a[i]
{
s1=s;
it=s1.begin()+j;
s1.insert(it,a[i]);
ps1.push_back(s1);
//
求出插入位置
//
插入整數
a[i]
//
添加到
ps1
中
}
}
void Perm(int a[],int n)
//
求
a[0..n-1]
的所有全排列
//
臨時存放子排列
{
vector<vector<int> > ps1;
vector<vector<int> >::iterator it;
vector<int> s,s1;
//
全排列迭代器
s.push_back(a[0]);
ps.push_back(s);
//
添加
{a[0]}
集合元素
//
循環添加
a[1]
~
a[n-1]
//ps1
存放插入
a[i]
的結果
for (int i=1;i<n;i++)
{
ps1.clear();
for (it=ps.begin();it!=ps.end();++it)
Insert(*it,a,i,ps1);
ps=ps1;
//
在每個集合元素中間插入
a[i]
得到
ps1
}
}
void dispps()
vector<vector<int> >::reverse_iterator it;
//
輸出全排列
ps
{
//
全排列的反向迭代器
//
排列集合元素迭代器
vector<int>::iterator sit;
for (it=ps.rbegin();it!=ps.rend();++it)
{
for (sit=(*it).begin();sit!=(*it).end();++sit)
printf("%d",*sit);
printf(" ");
3
7
算法設計
}
printf("\n");
}
void main()
{
int a[]={2,5,8};
int n=sizeof(a)/sizeof(a[0]);
printf("a[0
~
%d]
的全排序如下
:\n ",n-1);
Perm(a,n);
dispps();
}
上述程序的執行結果如圖
1.32
所示。
圖
1.32
程序執行結果
1
.5
第
5
章─回溯法
1
.5.1
練習題
.
回溯法在問題的解空間樹中,按( )策略,從根結點出發搜索解空間樹。
A.
廣度優先
B.
活結點優先
C.
擴展結點優先
D.
深度優先
.
關於回溯法以下敘述中不正確的是( )。
1
2
A.
回溯法有“通用解題法”之稱,它可以系統地搜索一個問題的所有解或任意解
B.
回溯法是一種既帶系統性又帶有跳躍性的搜索算法
C.
回溯算法需要借助隊列這種結構來保存從根結點到當前擴展結點的路徑
D.
回溯算法在生成解空間的任一結點時,先判斷該結點是否可能包含問題的解,如果
肯定不包含,則跳過對該結點為根的子樹的搜索,逐層向祖先結點回溯
3.
回溯法的效率不依賴於下列哪些因素( )。
A.
確定解空間的時間
B.
滿足顯約束的值的個數
D.
計算限界函數的時間
C.
計算約束函數的時間
4
.
下面( )函數是回溯法中為避免無效搜索采取的策略。
A.
遞歸函數
B.
剪枝函數
C.
隨機數函數
D.
搜索函數
5
6
.
回溯法的搜索特點是什么?
.
用回溯法解
0/1
背包問題時,該問題的解空間是何種結構?用回溯法解流水作業調
度問題時,該問題的解空間是何種結構?
.
對於遞增序列
a
[]={1
,
2
,
3
,
4
,
5}
,采用例
5.4
的回溯法求全排列,以
1
、
2
開頭
的排列一定最先出現嗎?為什么?
考慮
n
皇后問題,其解空間樹為由
1
、
2
、…、
n
構成的
n
!
種排列所組成。現用回
7
8
.
3
8
第
1
章
概論
溯法求解,要求:
(
(
(
1
)通過解搜索空間說明
n
=3
時是無解的。
2
)給出剪枝操作。
3
)最壞情況下在解空間樹上會生成多少個結點?分析算法的時間復雜度。
設計一個算法求解簡單裝載問題,設有一批集裝箱要裝上一艘載重量為
W
的輪
9
.
船,其中編號為
i
(
0
≤
i
≤
n
-
1
)的集裝箱的重量為
w
i
。現要從
n
個集裝箱中選出若干裝上
輪船,使它們的重量之和正好為
W
。如果找到任一種解返回
true
,否則返回
false
。
1
0.
給定若干個正整數
a
0
、
a
0
、…、
a
n
-
1
,從中選出若干數,使它們的和恰好為
k
,
要求找選擇元素個數最少的解。
1.
設計求解有重復元素的排列問題的算法,設有
n
個元素
a
[]={
a
0
,
a
1
,…,
a
n
-
1
)
,
其中可能含有重復的元素,求這些元素的所有不同排列。如
a
[]={1
,
1
,
2}
,輸出結果是
1
,
1
,
2)
,(
1
,
2
,
1
),(
2
,
1
,
1
)。
2.
采用遞歸回溯法設計一個算法求
1
~
n
的
n
個整數中取出
m
個元素的排列,要求每個
元素最多只能取一次。例如,
n
=3
,
m
=2
的輸出結果是(
1
,
2
),(
1
,
3
),(
2
,
1
),
2
,
3
),(
3
,
1
),(
3
,
2
)。
3.
對於
n
皇后問題,有人認為當
n
為偶數時,其解具有對稱性,即
n
皇后問題的解個
數恰好為
n
/2
皇后問題的解個數的
2
倍,這個結論正確嗎?請編寫回溯法程序對
n
=4
、
6
、
、
10
的情況進行驗證。
4.
給定一個無向圖,由指定的起點前往指定的終點,途中經過所有其他頂點且只經
1
(
1
(
1
8
1
過一次,稱為哈密頓路徑,閉合的哈密頓路徑稱作哈密頓回路(
Hamiltonian cycle
)。設計
一個回溯算法求無向圖的所有哈密頓回路。
1.5.2
練習題參考答案
1
2
.
答:
D
。
.
答:
回溯算法是采用深度優先遍歷的,需要借助系統棧結構來保存從根結點到當
前擴展結點的路徑。答案為
C
。
3
4
5
.
答:
回溯法解空間是虛擬的,不必確定整個解空間。答案為
A
。
.
答:
B
。
.
答:
回溯法在解空間樹中采用深度優先遍歷方式進行解搜索,即用約束條件和限
界函數考察解向量元素
x
[
i
]
的取值,如果
x
[
i
]
是合理的就搜索
x
[
i
]
為根結點的子樹,如果
x
[
i
]
取完了所有的值,便回溯到
x
[
i
-
1]
。
6
.
答:
用回溯法解
0/1
背包問題時,該問題的解空間是子集樹結構。用回溯法解流水
作業調度問題時,該問題的解空間是排列樹結構。
答:
是的。對應的解空間是一棵排列樹,如圖
1.33
所示給出前面
3
層部分,顯然
最先產生的排列是從
G
結點擴展出來的葉子結點,它們就是以
1
、
2
開頭的排列。
7
.
3
9
算法設計
A
5
1
3
2
4
B
C
D
E
F
2
5
3
4
G
H
I
J
圖
1.33
部分解空間樹
8
.
答:
(
1
)
n
=3
時的解搜索空間如圖
1.34
所示,不能得到任何葉子結點,所有無
解。
(
(
2
)剪枝操作是任何兩個皇后不能同行、同列和同兩條對角線。
3
)最壞情況下每個結點擴展
n
個結點,共有
n
n
個結點,算法的時間復雜度為
O(
n
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()
//
判斷簡單裝載問題是否存在解
4
0
第
1
章
概論
{
count=0;
int rw=0;
for (int j=0;j<n;j++)
rw+=w[j];
//
求所有集裝箱重量和
rw
//i
從
0
開始
dfs(0,rw,0);
if (count>0)
return true;
else
return false;
}
void main()
{ printf("
求解結果
\n");
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
程序執行結果
1
0.
解:
這是一個典型的解空間為子集樹的問題,采用子集樹的回溯算法框架。當找
到一個解后通過選取的元素個數進行比較求最優解
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;
//
最多選擇
n
個元素
//
輸出一個解
void disppath()
{
}
printf("
選擇的元素
:");
for (int j=0;j<minpath.size();j++)
printf("%d ",minpath[j]);
printf("
元素個數
=%d\n",minn);
4
1
算法設計
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;
//
全部元素找完,返回
//
不選擇
a[start]
//
選擇
a[start]
dfs(path,sum,start+1);
path.push_back(a[start]);
dfs(path,sum+a[start],start+1);
}
void main()
{
vector<int> path;
//path
存放一個子集
dfs(path,0,0);
printf("
最優解
:\n");
disppath();
}
上述程序的執行結果如圖
1.36
所示。
圖
1.36
程序執行結果
1
1.
解:
在回溯法求全排列的基礎上,增加元素的重復性判斷。例如,對於
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)
//
求有重復元素的排列問題
4
2
第
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
程序執行結果
1
2.
解:
采用求全排列的遞歸框架。選取的元素個數用
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)
//x[1..m]
存放一個排列
//
求
n
個元素中
m
個元素的全排列
{
if (i>m)
for (int j=1;j<=m;j++)
printf(" %d",x[j]);
printf("\n");
{
//
輸出一個排列
4
3
算法設計
}
else
{
for (int j=1;j<=n;j++)
{
if (!used[j])
{
used[j]=true;
//
修改
used[i]
x[i]=j;
//x[i]
選擇
j
dfs(i+1);
//
繼續搜索排列的下一個元素
//
回溯:恢復
used[i]
used[j]=false;
}
}
}
}
void main()
{
n=4,m=2;
memset(used,0,sizeof(used));
printf("n=%d,m=%d
的求解結果
\n",n,m);
dfs(1);
//
初始化為
0
//i
從
1
開始
}
上述程序的執行結果如圖
1.38
所示。
圖
1.38
程序執行結果
1
3.
解:
這個結論不正確。驗證程序如下:
#
#
#
include <stdio.h>
include <stdlib.h>
define MAXN 10
int q[MAXN];
bool place(int i)
//
測試第
i
行的
q[i]
列上能否擺放皇后
//j=1
~
i-1
是已放置了皇后的行
{
int j=1;
if (i==1) return true;
while (j<i)
{
if ((q[j]==q[i]) || (abs(q[j]-q[i])==abs(j-i)))
/
該皇后是否與以前皇后同列,位置
(j,q[j])
與
(i,q[i])
是否同對角線
return false;
/
j++;
}
return true;
}
4
4
第
1
章
概論
int Queens(int n)
//
求
n
皇后問題的解個數
{
int count=0,k;
int i=1;
//
計數器初始化
//i
為當前行
q[1]=0;
//q[i]
為皇后
i
的列號
while (i>0)
{
q[i]++;
//
移到下一列
while (q[i]<=n && !place(i))
q[i]++;
if (q[i]<=n)
{
if (i==n)
count++;
//
找到一個解計數器
count
加
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
程序執行結果
1
4.
解:
假設給定的無向圖有
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
4
5
算法設計
/
/
求解問題表示
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]
的有效性
//x[i-1]
到
x[i]
沒有邊,返回
false
{
if (a[x[i-1]][x[i]]!=1)
return false;
for (int j=0;j<=i-1;j++)
if (x[i]==x[j])
return false;
//
頂點
i
重復出現,返回
false
return true;
}
void Hamiltonian(int v)
//
求從頂點
v
出發的哈密頓回路
//
存放起點
{
x[0]=v;
int i=1;
x[i]=-1;
while (i>0)
//
從頂點
-1+1=0
開始試探
//
尚未回溯到頭,循環
{
x[i]++;
while (!valid(i) && x[i]<n)
x[i]++;
//
試探一個頂點
x[i]
if (x[i]<n)
//
找到一個有效的頂點
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;
4
6
第
1
章
概論
Hamiltonian(v);
//
從頂點
v
出發
}
}
上述程序對如圖
1.40
所示的無向圖求從每個頂點出發的哈密頓回路,程序執行結果
如圖
1.41
所示。
1
3
2
0
4
圖
1.40
一個無向圖
圖
1.41
程序執行結果
1
.6
第
6
章─分枝限界法
1
.6.1
練習題
.
分枝限界法在問題的解空間樹中,按( )策略,從根結點出發搜索解空間樹。
A.
廣度優先
B.
活結點優先
C.
擴展結點優先
D.
深度優先
.
常見的兩種分枝限界法為( )。
A.
廣度優先分枝限界法與深度優先分枝限界法
1
2
4
7
算法設計
B.
隊列式(
FIFO
)分枝限界法與堆棧式分枝限界法
C.
排列樹法與子集樹法
D.
隊列式(
FIFO
)分枝限界法與優先隊列式分枝限界法
3
.
分枝限界法求解
0/1
背包問題時,活結點表的組織形式是(
)。
A.
小根堆
B.
大根堆
C.
棧
D.
數組
4
.
采用最大效益優先搜索方式的算法是( )。
A.
分支界限法
B.
動態規划法
C.
貪心法
D.
回溯法
D.
隨機
5
.
優先隊列式分枝限界法選取擴展結點的原則是( )。
A.
先進先出
B.
后進先出
C.
結點的優先級
.
簡述分枝限界法的搜索策略。
有一個
0/1
背包問題,其中
n
=4
,物品重量為(
4
,
7
,
5
,
3
),物品價值為(
40
,
6
7
.
4
2
,
25
,
12
),背包最大載重量
W
=10
,給出采用優先隊列式分枝限界法求最優解的過程。
.
有一個流水作業調度問題,
n
=4
,
a
[]={5
,
10
,
9
,
7}
,
b
[]={7
,
5
,
9
,
8}
,給出采
用優先隊列式分枝限界法求一個解的過程。
有一個含
n
個頂點(頂點編號為
0
~
n
-
1
)的帶權圖,采用鄰接矩陣數組
A
表示,
8
9
.
采用分枝限界法求從起點
s
到目標點
t
的最短路徑長度,以及具有最短路徑長度的路徑條
數。
1
0.
采用優先隊列式分枝限界法求解最優裝載問題。給出以下裝載問題的求解過程和
結果:
n
=5
,集裝箱重量為
w
=
(
5
,
2
,
6
,
4
,
3
),限重為
W
=10
。在裝載重量相同時,最
優裝載方案是集裝箱個數最少的方案。
1.6.2
練習題參考答案
1
2
3
4
5
6
.
答:
A
。
.
答:
D
。
.
答:
B
。
.
答:
A
。
.
答:
C
。
.
答:
分枝限界法的搜索策略是廣度優先遍歷,通過限界函數可以快速找到一個解
或者最優解。
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]
。
4
8
第
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]={
//
一個帶權有向圖
4
9
算法設計
{
{
{
{
{
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;
}
//length
越小越優先
}
;
void solve(int s
,
int t)
//
求最短路徑問題
//
定義
2
個結點
{
NodeType e
,
e1;
priority_queue<NodeType> qu;
e.vno=s;
//
定義一個優先隊列
qu
//
構造根結點
e.length=0;
qu.push(e);
//
根結點進隊
while (!qu.empty())
//
隊不空循環
{
e=qu.top(); qu.pop();
if (e.vno==t)
//
出隊結點
e
作為當前結點
//e
是一個葉子結點
//
比較找最優解
{
if (e.length<bestlen)
{
bestcount=1;
bestlen=e.length;
//
保存最短路徑長度
}
else if (e.length==bestlen)
bestcount++;
}
else
{
//e
不是葉子結點
for (int j=0; j<n; j++)
//
檢查
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);
//
有效子結點
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);
5
0
第
1
章
概論
printf("
最短路徑長度
=%d
,條數
=%d\n"
,
bestlen
,
bestcount);
/
/
輸出:
5 3
}
}
上述程序的執行結果如圖
1.39
所示。
圖
1.39
程序執行結果
1
0.
解:
采用優先隊列式分枝限界法求解。設計優先隊列
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};
//
集裝箱重量
,
不計下標
0
的元素
/
/
求解結果表示
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
//
隊列中關系比較函數
{
bool operator()(const NodeType &s,const NodeType &t)
{
return (s.ub<t.ub) || (s.ub==t.ub && s.x[0]>t.x[0]);
/
/ub
越大越優先
,
當
ub
相同時
x[0]
越小越優先
}
}
;
void bound(NodeType &e)
//
計算分枝結點
e
的上界
//r
為剩余集裝箱的重量
{
int i=e.i+1;
int r=0;
while (i<=n)
{
r+=w[i];
i++;
}
e.ub=e.w+r;
5
1
算法設計
}
void Loading()
//
求裝載問題的最優解
{
NodeType e,e1,e2;
//
定義
3
個結點
priority_queue<NodeType,vector<NodeType>,Cmp >
qu;
//
定義一個優先隊列
qu
e.no=Count++;
e.i=0;
//
設置結點編號
//
根結點置初值
,
其層次計為
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)
//
出隊結點
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
{
//e
不是葉子結點
if (e.w+w[e.i+1]<=W)
//
檢查左孩子結點
//
設置結點編號
//
建立左孩子結點
{
e1.no=Count++;
e1.i=e.i+1;
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;
//
選擇集裝箱
i
e1.x[0]++;
bound(e1);
qu.push(e1);
//
裝入集裝箱數增
1
//
求左孩子結點的上界
//
左孩子結點進隊
}
e2.no=Count++;
e2.i=e.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);
//
不選擇集裝箱
i
//
求右孩子結點的上界
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=[");
5
2
第
1
章
概論
disparr(bestx,n);
printf("],
裝入總價值為
%d\n",bestw);
}
void main()
{
Loading();
printf("
求解結果
:\n");
dispLoading();
//
輸出最優解
}
上述程序的執行結果如圖
1.40
所示。
圖
1.40
程序執行結果
1
.7
第
7
章─貪心法
1
.7.1
練習題
.
下面是貪心算法的基本要素的是( )。
A.
重疊子問題
B.
構造最優解
C.
貪心選擇性質
.
下面問題( )不能使用貪心法解決。
A.
單源最短路徑問題
B.
n
皇后問題
采用貪心算法的最優裝載問題的主要計算量在於將集裝箱依其重量從小到大排
序,故算法的時間復雜度為( )。
A.O(
n
)
B.O(
n
2
)
.
關於
0/ 1
背包問題以下描述正確的是(
)。
1
D.
定義最優解
2
C.
最小花費生成樹問題
D.
背包問題
3
.
C.O(
n
3
)
D.O(
n
log
2
n
)
4
A.
可以使用貪心算法找到最優解
B.
能找到多項式時間的有效算法
C.
使用教材介紹的動態規划方法可求解任意
0
-
1
背包問題
D.
對於同一背包與相同的物品,做背包問題取得的總價值一定大於等於做
0/1
背包問
題
5
.
一棵哈夫曼樹共有
215
個結點,對其進行哈夫曼編碼,共能得到(
)個不同的碼
字。