實(shí)驗(yàn)二 背包問題和最小生成樹算法實(shí)現(xiàn)(用貪心法)
一、實(shí)驗(yàn)?zāi)康?br />
1.掌握能用貪心法求解的問題應(yīng)滿足的條件;
2.加深對(duì)貪心法算法設(shè)計(jì)方法的理解與應(yīng)用;
3.鍛煉學(xué)生對(duì)程序跟蹤調(diào)試能力;
4.通過(guò)本次實(shí)驗(yàn)的練習(xí)培養(yǎng)學(xué)生應(yīng)用所學(xué)知識(shí)解決實(shí)際問題的能力。
二.實(shí)驗(yàn)內(nèi)容
有一個(gè)背包,背包容量是M=150。有7個(gè)物品,物品可以分割成任意大小。要求盡可能讓裝入背包中的物品總價(jià)值最大,但不能超過(guò)總?cè)萘俊?br />
三.實(shí)驗(yàn)要求
(1)用貪心法求解背包問題;
(2)上機(jī)實(shí)現(xiàn)所設(shè)計(jì)的算法;
四.實(shí)驗(yàn)過(guò)程設(shè)計(jì)(算法設(shè)計(jì)過(guò)程)
貪心算法(又稱貪婪算法)是指,在對(duì)問題求解時(shí),總是做出在當(dāng)前看來(lái)是最好的選擇。也就是說(shuō),不從整體最優(yōu)上加以考慮,他所做出的是在某種意義上的局部最優(yōu)解。
貪心算法不是對(duì)所有問題都能得到整體最優(yōu)解,關(guān)鍵是貪心策略的選擇,選擇的貪心策略必須具備無(wú)后效性,即某個(gè)狀態(tài)以前的過(guò)程不會(huì)影響以后的狀態(tài),只與當(dāng)前狀態(tài)有關(guān)。
基本思路
建立數(shù)學(xué)模型來(lái)描述問題;
把求解的問題分成若干個(gè)子問題;
對(duì)每一子問題求解,得到子問題的局部最優(yōu)解;
把子問題的解局部最優(yōu)解合成原來(lái)解問題的一個(gè)解。
算法實(shí)現(xiàn)
從問題的某個(gè)初始解出發(fā)。
采用循環(huán)語(yǔ)句,當(dāng)可以向求解目標(biāo)前進(jìn)一步時(shí),就根據(jù)局部最優(yōu)策略,得到一個(gè)部分解,縮小問題的范圍或規(guī)模。
將所有部分解綜合起來(lái),得到問題的最終解。
實(shí)例分析
實(shí)例1 背包問題
問題描述
有一個(gè)背包,背包容量是M=150。有7個(gè)物品,物品可以分割成任意大小。要求盡可能讓裝入背包中的物品總價(jià)值最大,但不能超過(guò)總?cè)萘俊?/div>
- 問題分析
1.目標(biāo)函數(shù): ∑pi最大,使得裝入背包中的所有物品pi的價(jià)值加起來(lái)最大。
2.約束條件:裝入的物品總重量不超過(guò)背包容量:∑wi<=M( M=150)
3.貪心策略:
- 選擇價(jià)值最大的物品
- 選擇價(jià)值最大的物品
- 選擇單位重量?jī)r(jià)值最大的物品
有三個(gè)物品A,B,C,其重量分別為{30,10,20},價(jià)值分別為{60,30,80},背包的容量為50,分別應(yīng)用三種貪心策略裝入背包的物品和獲得的價(jià)值如下圖所示:
三種策略
- 計(jì)算出每個(gè)物品單位重量的價(jià)值
- 按單位價(jià)值從大到小將物品排序
- 根據(jù)背包當(dāng)前所剩容量選取物品
- 如果背包的容量大于當(dāng)前物品的重量,那么就將當(dāng)前物品裝進(jìn)去。否則,那么就將當(dāng)前物品舍去,然后跳出循環(huán)結(jié)束。
五.源代碼
(1)背包問題
- #include <iostream>
- #include<algorithm>
- using namespace std;
- /*定義物品的結(jié)構(gòu)體*/
- struct Object{
- double weight;//物品重量
- double value;//物品價(jià)值
- double avg;//單位重量?jī)r(jià)值
- double prop;//放入比例:0表示沒有放入,1表示全部放入
- };
-
- /*定義物品排序的標(biāo)準(zhǔn)*/
- bool cmp(Object a,Object b){
- return a.avg>b.avg;
- }
-
- int main()
- {
- Object *ob;
- double volume;//背包容量
- int num;//物品種類
- double maxValue = 0;//背包中的最大價(jià)值
- cout<<"請(qǐng)輸入所有物品數(shù)量和背包容量\n";
- cin>>num>>volume;
- ob = new Object[num];
- /*初始化物品的重量和價(jià)值,并計(jì)算單位重量?jī)r(jià)值*/
- for(int i = 0;i<num;i++){
- cout<<"輸入第"<<i+1<<"個(gè)物品的重量和價(jià)值\n";
- cin>>ob[i].weight>>ob[i].value;
- ob[i].avg = ob[i].value/ob[i].weight;
- }
- sort(ob,ob+num,cmp);//物品按單位重量?jī)r(jià)值降序
- int i;
- /*開始裝入物品*/
- for(i = 0;i<num;i++){
- if(ob[i].weight <= volume){//如果這個(gè)物品能全部裝入
- volume = volume -ob[i].weight;
- ob[i].prop = 1;//表示全部裝入
- maxValue = maxValue + ob[i].value;
- } else{//這個(gè)物品已經(jīng)不能全部裝人
- break;
- }
- }
- if(i<num){//有一個(gè)物品不能全部裝入
- ob[i].prop = volume/ob[i].weight;//裝入的比例
- volume = 0;
- maxValue = maxValue + ob[i].prop*ob[i].value;
- }
- cout<<"放入的物品的重量,價(jià)值,比例\n";
- for(int i = 0;i<num;i++){
- cout<<ob[i].weight<<" "<<ob[i].value<<" "<<ob[i].prop<<"\n";
- }
- cout<<"放入物品的總價(jià)值:"<<maxValue;
- return 0;
- }
復(fù)制代碼
程序運(yùn)行結(jié)果:
(2)最小生成樹
- /*
- S:當(dāng)前已經(jīng)在聯(lián)通塊中的所有點(diǎn)的集合
- 1. dist[i] = inf
- 2. for n 次
- t<-S外離S最近的點(diǎn)
- 利用t更新S外點(diǎn)到S的距離
- st[t] = true
- n次迭代之后所有點(diǎn)都已加入到S中
- 聯(lián)系:Dijkstra算法是更新到起始點(diǎn)的距離,Prim是更新到集合S的距離
- */
- #include <iostream>
- #include <cstring>
- using namespace std;
- const int N = 510, INF = 0x3f3f3f3f;
-
- int n, m;//n=|V|,m=|E|
- int g[N][N], dist[N];
- //鄰接矩陣存儲(chǔ)所有邊
- //dist存儲(chǔ)其他點(diǎn)到S的距離
- bool st[N];
-
- int prim() {
- //如果圖不連通返回INF, 否則返回res
- memset(dist, INF, sizeof dist);
- int res = 0;
-
- for(int i = 0; i < n; i++) {
- int t = -1;
- for(int j = 1; j <= n; j++)
- if(!st[j] && (t == -1 || dist[t] > dist[j]))
- t = j;
- //尋找離集合S最近的點(diǎn)
- if(i && dist[t] == INF) return INF;
- //判斷是否連通,有無(wú)最小生成樹
-
- if(i) res += dist[t];
- //cout << i << ' ' << res << endl;
- st[t] = true;
- //更新最新S的權(quán)值和
-
- for(int j = 1; j <= n; j++) dist[j] = min(dist[j], g[t][j]);
- }
-
- return res;
- }
-
- int main() {
- cout << "頂點(diǎn)數(shù)和邊數(shù):" << endl;
- cin >> n >> m;//n=|V|,m=|E|
- int u, v, w;//u,v,w,表示點(diǎn)u和點(diǎn)v之間存在一條權(quán)值為w的邊
-
- for(int i = 1; i <= n; i++)
- for(int j = 1; j <= n; j++)
- if(i ==j) g[i][j] = 0;
- else g[i][j] = INF;
-
- while(m--) {
- cout<< "兩頂點(diǎn)和邊長(zhǎng):" << endl;
- cin >> u >> v >> w;
- g[u][v] = g[v][u] = min(g[u][v], w);
- }
- int t = prim();
- cout <<"最小生成樹的樹邊權(quán)重之和:"<< t << endl;
- }
復(fù)制代碼
程序運(yùn)行結(jié)果:

實(shí)驗(yàn)三 多段圖和貨郎擔(dān)問題算法實(shí)現(xiàn)(用動(dòng)態(tài)規(guī)劃方法)
一.實(shí)驗(yàn)?zāi)康?br />
1.掌握能用動(dòng)態(tài)規(guī)劃方法求解的問題應(yīng)滿足的條件;
2.加深對(duì)動(dòng)態(tài)規(guī)劃方法的理解與應(yīng)用;
3.鍛煉學(xué)生對(duì)程序跟蹤調(diào)試能力;
4.通過(guò)本次實(shí)驗(yàn)的練習(xí)培養(yǎng)學(xué)生應(yīng)用所學(xué)知識(shí)解決實(shí)際問題的能力。
二.實(shí)驗(yàn)內(nèi)容
(1)多段圖:對(duì)于任意數(shù)目的n個(gè)節(jié)點(diǎn),分別用1~n編號(hào),對(duì)于k段,分別用1~k編號(hào),則這個(gè)問題歸結(jié)為在k段n個(gè)節(jié)點(diǎn)的帶權(quán)圖中尋找從節(jié)點(diǎn)1到節(jié)點(diǎn)n的最短路徑。(自己添加的內(nèi)容)
(2)貨郎擔(dān):對(duì)于任意數(shù)目的n個(gè)城市,分別用1~n編號(hào),則這個(gè)問題歸結(jié)為在有向帶權(quán)圖中,尋找一條路徑最短的哈密爾頓回路問題。
三.實(shí)驗(yàn)要求
(1)用動(dòng)態(tài)規(guī)劃方法貨郎擔(dān)問題;實(shí)現(xiàn)多段圖問題
(2)上機(jī)實(shí)現(xiàn)所設(shè)計(jì)的算法;
四.實(shí)驗(yàn)過(guò)程設(shè)計(jì)(算法設(shè)計(jì)過(guò)程)
動(dòng)態(tài)規(guī)劃的思想實(shí)質(zhì)是分治思想和解決冗余。 與分治法類似的是,將原問題分解成若干個(gè)子問題,先求解子問題,再?gòu)倪@些子問題的解得到原問題的解。與分治法不同的是,經(jīng)分解的子問題往往不是互相獨(dú)立的。若用分治法來(lái)解,有些共同部分(子問題或子子問題)被重復(fù)計(jì)算了很多次。如果能夠保存已解決的子問題的答案,在需要時(shí)再查找,這樣就可以避免重復(fù)計(jì)算、節(jié)省時(shí)間。動(dòng)態(tài)規(guī)劃法用一個(gè)表來(lái)記錄所有已解的子問題的答案。
動(dòng)態(tài)規(guī)劃方法的基本思想是,把求解的問題分成許多階段或多個(gè)子問題,然后按順序求解各子問題。最后一個(gè)子問題就是初始問題的解。
由于動(dòng)態(tài)規(guī)劃的問題有重疊子問題的特點(diǎn),為了減少重復(fù)計(jì)算,對(duì)每一個(gè)子問題只解一次,將其不同階段的不同狀態(tài)保存在一個(gè)二維數(shù)組中。
五.源代碼
(1)多段圖
- #include <iostream>
- using namespace std;
- #define INFI 65535 //定義兩節(jié)點(diǎn)之間的最大路徑
- int c[12][12]; //兩節(jié)點(diǎn)之間路徑長(zhǎng)度的數(shù)組
-
- /**/
- void FGRAPH(int n,int k){
- int cost[n];//第n個(gè)節(jié)點(diǎn)到最后一個(gè)節(jié)點(diǎn)的最端路徑
- for(int i = 0;i<n;i++){
- cost[i] = INFI;//初始化所有節(jié)點(diǎn)的最短路徑為無(wú)窮大
- }
- cost[n-1] = 0;
- int j,r;
- int p[k],d[n];//p為段決策,d為節(jié)點(diǎn)決策
- int min;
- /*尋找所有節(jié)點(diǎn)到最后一個(gè)節(jié)點(diǎn)的最短路徑和節(jié)點(diǎn)決策)*/
- for(j = n-2;j>=0;j--){
- min = INFI;
- for(r = 0;r<=n-1;r++){
- if(c[j][r]+cost[r]<min){
- min = c[j][r] + cost[r];
- d[j] = r; //r即為j節(jié)點(diǎn)的節(jié)點(diǎn)的決策
- }
- }
- cost[j] = min; //j節(jié)點(diǎn)的最短路徑
- }
- p[0] = 0;
- p[k-1] = n-1;
- for(int j = 1;j<=k-1;j++){
- p[j] = d[p[j-1]];//上一個(gè)段的段決策的節(jié)點(diǎn)決策即為下一個(gè)段的段決策
- }
- for(int j = 0;j<=k-1;j++){
- cout<<p[j]<<"\n";
- }
- }
- int main()
- {
- int N = 12;
- /*節(jié)點(diǎn)之間距離初始化*/
- for(int i = 0;i<N;i++){
- for(int j = 0;j<N;j++){
- if(i == j)
- c[i][j] = 0;
- else{
- c[i][j] = INFI;
- c[j][i] = INFI;
- }
-
- }
- }
- c[0][1] = 9;
- c[0][2] = 7;
- c[0][3] = 3;
- c[0][4] = 2;
- c[1][5] = 4;
- c[1][6] = 2;
- c[1][7] = 1;
- c[2][5] = 2;
- c[2][6] = 7;
- c[3][7] = 11;
- c[4][6] = 11;
- c[4][7] = 8;
- c[5][8] = 6;
- c[5][9] = 5;
- c[6][8] = 4;
- c[6][9] = 3;
- c[7][9] = 5;
- c[7][10] = 6;
- c[8][11] = 4;
- c[9][11] = 2;
- c[10][11] = 5;
- FGRAPH(N,5);//調(diào)用多段圖函數(shù)
- return 0;
- }
復(fù)制代碼
程序運(yùn)行結(jié)果如下圖所示:
(2) 貨郎擔(dān)
- #include<iostream>
- #include<iomanip>
- using namespace std;
-
- int n;
- int cost[20][20];
- bool done[20]={1};
- int start = 0; //從城市0開始
- int imin(int num, int cur)
- {
- if(num==1) //遞歸調(diào)用的出口
- return cost[cur][start]; //所有節(jié)點(diǎn)的最后一個(gè)節(jié)
- //最后返回 最后一個(gè)節(jié)點(diǎn)到起點(diǎn)的路徑
- int mincost = 10000;
- for(int i=0; i<n; i++)
- {
- cout<<i<<" i:"<<done[i]<<endl;
- if(!done[i] && i!=start) //該結(jié)點(diǎn)沒加入且非起始點(diǎn)
- {
- done[i] = 1; //遞歸調(diào)用時(shí),防止重復(fù)調(diào)用
- int value = cost[cur][i] + imin(num-1, i);
- if(mincost > value)
- {
- mincost = value;
- cout<<mincost<<endl;
- }
- done[i] = 0;//本次遞歸調(diào)用完畢,讓下次遞歸調(diào)用
- }
- }
- return mincost;
- }
-
- int main()
- {
- n=4;
- int cc[4][4]={{0,4,1,3},{4,0,2,1},{1,2,0,5},{3,1,5,0}};
- for(int i=0; i<n; i++)
- {
- for(int j=0; j<n; j++)
- {
- cost[i][j]=cc[i][j];
- }
- }
- cout << imin(n, start) << endl;
- return 0;
- }
復(fù)制代碼
程序運(yùn)行結(jié)果如下圖所示:
實(shí)驗(yàn)四 皇后與子集和數(shù)問題算法實(shí)現(xiàn)(用回溯法)
一、實(shí)驗(yàn)?zāi)康?/div>
1.掌握能用回溯法求解的問題應(yīng)滿足的條件;
2.加深對(duì)回溯法算法設(shè)計(jì)方法的理解與應(yīng)用;
3.鍛煉學(xué)生對(duì)程序跟蹤調(diào)試能力;
4.通過(guò)本次實(shí)驗(yàn)的練習(xí)培養(yǎng)學(xué)生應(yīng)用所學(xué)知識(shí)解決實(shí)際問題的能力。
二.實(shí)驗(yàn)內(nèi)容
(1)n皇后問題
由N2個(gè)方塊排成N行N列的正方形,稱為N元棋盤,在N元棋盤上放置N個(gè)皇后,如果某兩個(gè)皇后位于N元棋盤的同一行或同一列或同一斜線(斜率為±1)上,則稱它們?cè)诨ハ喙,試設(shè)計(jì)算法找出使N個(gè)皇后互不攻擊的所有布局。
(2)子集和數(shù)問題
子集和數(shù)問題是假定有n個(gè)不同的正數(shù)(通常稱為權(quán)),要求找出這些數(shù)中所有事的某和數(shù)為M的組合。
(1)我們假設(shè)一個(gè)前提條件:這些正數(shù)是按照非降次序排列的。
(2)引入一個(gè)記號(hào):B(X(1),...,X(k))表示是否可以把第K個(gè)正數(shù)加入進(jìn)來(lái),所以它的取指為true或者false。
那么當(dāng)我們考慮是否要把第K個(gè)正數(shù)加入到解向量中的時(shí)候,我們就能找到兩個(gè)條件組成這個(gè)限界函數(shù)了:
(1)
這個(gè)公式的含義是:當(dāng)你考慮是否要把第K個(gè)正數(shù)加入到解向量的時(shí)候,不管你要加進(jìn)來(lái)或者是不打算把它加進(jìn)來(lái),前K個(gè)解向量的和(包括第K個(gè),當(dāng)然X(k)可能是0或者1),加上后面所有的數(shù)的和一定要大于等于M,否則你把剩下的數(shù)都加了進(jìn)來(lái)還比M小,這次的決策X(k)=0或者1肯定得不到滿足條件的解向量。所以也就沒有必要擴(kuò)展這個(gè)結(jié)點(diǎn)的左兒子或者右兒子了。
(2)
這個(gè)公式的含義是,當(dāng)你考慮是否要把第K個(gè)正數(shù)加入到解向量的時(shí)候,不管你要加進(jìn)來(lái)或者是不打算把它加進(jìn)來(lái),提前往后看一步,判斷如果把第K+1個(gè)正數(shù)算進(jìn)來(lái)后的值大于M,就不把第K個(gè)正數(shù)加進(jìn)來(lái)。也就是說(shuō)不生成第K-1個(gè)節(jié)點(diǎn)的兒子。
三.實(shí)驗(yàn)要求
(1)用回溯法算法設(shè)計(jì)方法求解N元皇后問題;
(2)找出N皇后問題的互不攻擊的所有解;
(3)皇后數(shù)N由鍵盤動(dòng)態(tài)輸入;
(4)上機(jī)實(shí)現(xiàn)所設(shè)計(jì)的算法;
(5)分析所設(shè)計(jì)的算法的時(shí)間/空間復(fù)雜性。
四.實(shí)驗(yàn)過(guò)程設(shè)計(jì)(算法設(shè)計(jì)過(guò)程)
(1)分析N皇后問題的約束條件,并將其顯示化,選擇存儲(chǔ)結(jié)構(gòu)建立相應(yīng)的數(shù)學(xué)模型;
(2)根據(jù)所建立的數(shù)學(xué)模型,設(shè)計(jì)求解該問題的粗略算法;
(3)對(duì)所設(shè)計(jì)的粗略算法求精,得具體算法;
(4)在C/C++下實(shí)現(xiàn)所設(shè)計(jì)的算法;
(5)分析運(yùn)行結(jié)果的正確性;
(6)進(jìn)行算法效率分析;
(7)課后寫出實(shí)驗(yàn)報(bào)告。
五.源代碼
(1) n皇后問題
- #include <iostream>
- using namespace std;
- const int N = 20;
-
- // bool數(shù)組用來(lái)判斷搜索的下一個(gè)位置是否可行
- // col列,dg對(duì)角線,udg反對(duì)角線
- // g[N][N]用來(lái)存路徑
- //count表示第幾個(gè)解
-
- int n;
- char g[N][N];
- bool col[N], dg[N], udg[N];
- int count = 0;
- void dfs(int u)
- {
- // u == n 表示已經(jīng)搜了n行,故輸出這條路徑
- if (u == n)
- {
- count++;
- cout<<"第"<<count<<"個(gè)解:"<<endl;
- for (int i = 0; i < n; i ++ ) cout << g[i] << endl;
- cout<<endl;
- return;
- }
-
- //對(duì)n個(gè)位置按行搜索
- for (int i = 0; i < n; i ++ )
- // 剪枝(對(duì)于不滿足要求的點(diǎn),不再繼續(xù)往下搜索)
- //udg[n - u + i],+n是為了保證大于0
- if (!col[i] && !dg[u + i] && !udg[n - u + i])
- {
- g[u][i] = 'Q';
- col[i] = dg[u + i] = udg[n - u + i] = true;
- dfs(u + 1);
- // 恢復(fù)現(xiàn)場(chǎng) 這步很關(guān)鍵
- col[i] = dg[u + i] = udg[n - u + i] = false;
- g[u][i] = '.';
-
- }
- }
-
- int main()
- {
- cout<<"請(qǐng)問這是幾皇后問題:"<<endl;
- cin >> n;
- cout<<endl;
- for (int i = 0; i < n; i ++ )
- for (int j = 0; j < n; j ++ )
- g[i][j] = '.';
-
- dfs(0);
- return 0;
- }
復(fù)制代碼
運(yùn)行結(jié)果:
(2) 子集和數(shù)問題
- #include <iostream>
- using namespace std;
-
- //求解函數(shù)
- void sumOfSub(float s, int k, float r, int *x, float m, float *w);
- void sumOfSub(int *x, int n, float m, float *w);
-
- int main()
- {
- int n; //n表示集合元素個(gè)數(shù)
- float m; //m表示目標(biāo)和
- cout << "請(qǐng)輸入集合元素個(gè)數(shù):";
- cin >> n;
- cout << "請(qǐng)輸入目標(biāo)和:";
- cin >> m;
- float *arr = new float [n + 1];
- int *x = new int [n + 1]; //求解狀態(tài)
- cout << "請(qǐng)輸入" << n <<"個(gè)集合元素(正值):";
- for(int i=0; i < n; i++)
- {
- cin >> arr[i];
- }
- cout << "可行解:\n";
- sumOfSub(x, n, m, arr);
-
- return 0;
- }
-
- void sumOfSub(float s, int k, float r, int *x, float m, float *w)
- {
- x[k] = 1;
- if(s + w[k] == m)//一個(gè)可行解
- {
- for(int j = 0;j <= k; j++)//兩種輸出方式任選
- {
- if(x[j] == 1) //完成輸出,輸出選取的元素
- cout << w[j] << " ";
- }
- cout << endl;
- }
- else if(s + w[k] + w[k+1] <= m)
- {
- sumOfSub(s+w[k], k+1, r-w[k], x, m, w);//搜索左子樹
- }
- if((s + r - w[k] >= m)&&(s + w[k]+1 <= m))
- {
- x[k] = 0;
- sumOfSub(s, k+1, r - w[k], x, m, w); //搜索右子樹
- }
- }
- void sumOfSub(int *x, int n, float m, float *w)
- {
- float r = 0;
- for(int i = 0; i < n; i++) r += w[i];//計(jì)算總值,判斷是否有解
- if(r >= m && w[0] <= m) sumOfSub(0, 0, r, x, m, w);
- }
復(fù)制代碼
運(yùn)行結(jié)果:
完整的Word格式文檔51黑下載地址:
歡迎光臨 (http://www.torrancerestoration.com/bbs/) |
Powered by Discuz! X3.1 |