儲油問題
一輛重型卡車的油耗是1l/km,載油能力為500l,今欲穿過1000km的沙漠。由於卡車一次過不了沙漠,因此司機必須在沿途設幾個儲油點。問:如何建立這些儲油點,每乙個儲油點儲存多少油才能使卡車以最小油耗通過沙漠?
例項解析:
本例採用倒推法來解題。所謂倒推法,就是在不知初始值的情況下,通過某種遞推關係,由最終值推算出初始值的方法。儲油問題和猴子吃桃子問題等都是典型的倒退問題。
顯然,卡車要通過沙漠,必須在離起點500km處儲存500l油,如圖17-1所示。
圖17-1 儲油點及儲油量示意圖
而要在500km處儲油500l,需要卡車從某處(設離起點x1公里處)向500km處運送n趟(最後一趟不需要返回),卡車往返總耗油是:(2n-1)*(500-x1)*1,因此,x1處儲油量y1應是:y1 = (2n -1)*(500 -x1) +500,而這個儲油量也是卡車n趟的總載油量,即:y1=500n。
可以證明,卡車往返的總耗油 (2n-1)*(500-x1) =500時,最為省油。故:y1=500+500 =1000,此時n = 2,即x1處要儲油1000l,其中的500l送往中點500km處,另外500公里用來跑路(3個單程)。可以算出,x1的值為333km
同樣的道理,要在333km處儲油1000l,需要在前面某點x2處儲油500l*3=1500l,其中500l用來跑路(耗油500l最省油),另外1000l運送到333km處,即:y2 = (2*3 -1)*(x1-x2)+500*2 = 1500。
......
可以得出乙個通用公式:
某儲油點的儲油量應為:
yk = (2*(k+1) -1)*(xk-1-xk) + 500*k = 500*(k+1)
初始值:x0 = 500, k = 1。
我們可以定義乙個陣列distance[n]用來儲存各儲油點離起點的距離,其中distance[0] = 500,定義乙個陣列oil[n]儲存儲油量,oil[0] = 500。其他儲油點的座標和儲油量資料由上面的通用公式求得,即:
xk = x k-1-500/(2k+1)
yk = 500*(k+1)
注意一點:推導的過程中,xk的值越來越小,當某次計算出的座標xk<=0時,意味著已經計算到起點了。若xk = 0,意味著計算出的最經濟的儲油點正好位於起點,儲油量按照上面的通用公式計算即可,即yk=500*(k+1)。但若xk<0,意味著計算出的儲油點是不實際的,因此實際儲油量不需要公式計算的那麼多,所以要根據實際距離重新計算。
下面是程式**:
#include#define n 10int main()
, oil[n] = ;
int k;
for(k = 1; distance[k-1] > 0; k++)
k--;
if(distance[k] < 0)
printf("\ndistance oil\n\n");
for( ; k >= 0; k--)
printf("%5d,%8d\n", distance[k], oil[k]);
getch();
return 0;
}
揹包問題
給定n種物品和乙個揹包,其中物品i的重量為wi,對應的價值為vi,揹包可裝物品的最大重量為c,問題:怎樣選擇物品,才能使裝入揹包的物品的價值最大?
例項解析:
在選擇物品時,每個物品的選擇都只有兩種:選或者不選,因此這個問題叫做0-1揹包問題。
我們採用動態規劃的方法來解決這個問題。其基本思想是:
將待解決的問題分成若干個子問題,先求子問題的解,然後從子問題的解中得出整個問題的解。
適用於動態規劃的問題經分解後形成的子問題往往不是相互獨立的,在求解過程中如果能儲存已解決的子問題的答案,以便在需要時加以利用,就可以避免大量重複計算。為了達到這個目的,可以用乙個表來記錄所有已解決的子問題的答案(不管有用無用,都儲存),這就是動態規劃的主要思想。
本例中,我們定義三個陣列來描述揹包問題:
int value[n]; //用來儲存各物品的價值int weight[n]; //用來儲存各物品的重量
int maxvalue[n][content]; //儲存最優解
陣列maxvalue用來儲存動態規劃過程中的最優解。例如:maxvalue[i][j]表示揹包剩餘容量為j,還有第i,i+1,i+2,......n-1物品可選擇時的最優解。
程式**如下:
#define content 5#define n 10
#include void knapsack(int v[n],int w[n], int c, int m[n][content+1])}}
void traceback(int flag[n], int w[n], int m[n][content+1])
if(m[n][c]>0) //判斷最後一種物品選擇與否
flag[n] = 1;
}void printresult(int flag, int w, int v, int m[content+1])
int main()
;int weight[n] = ;
int c = content;
int maxvalue[n][content+1];
int flag[n] = ;
clrscr();
knapsack(value, weight, c, maxvalue);
traceback(flag, weight, maxvalue);
printresult(flag, weight, value, maxvalue);
getch();
return 0;
}
鍊錶的逆置
程式設計實現鍊錶(無頭結點)的逆置。
例項解析:
所謂鍊錶逆置,就是將所有結點逆序排列。本例可用兩種方法來做。
方法一:從第乙個結點開始直到鏈尾,依次處理每乙個結點。
(1)對於第乙個結點,其指標域next賦值為null,使之成為鏈尾。
(2)對於之後的結點,使之指向前一結點。
(3)最後乙個結點的指標,賦給head,使之成為第乙個結點。
下面是逆置部分的程式**:
struct student* reverse(struct student *head)head = p2; //最後乙個結點成為第乙個結點
return head;
}
方法二:
從鍊錶第二個結點開始,依次將每個結點插入到鍊錶的最前面。
相應的函式**是:
struct student* reverse(struct student *head)h->next = null; //最早的第乙個結點成為鏈尾
}return head;
}
約瑟夫環
n個小孩圍成一圈,從第乙個人開始報數,報到k的人退出圈子,下面的人繼續從1開始報數.....若最後圈子裡只剩下m個人,他們分別是多少號? n、k、m都從鍵盤輸入。
例項解析:
這是乙個典型的單迴圈鍊錶問題。先建立鍊錶,然後從第乙個結點開始計數,將第k個結點刪除,然後再從下一結點開始計數,第k個結點刪除......,直到鍊錶中只剩m個結點。
下面是程式**:
#include "stdio.h"#include "stdlib.h"
struct boy ;
void create(struct boy**, int);
struct boy* proceed(struct boy*, int, int, int);
void print(struct boy*);
void del(struct boy*);
int main()
void create(struct boy **p,int n)
p1->n = i;
if(i == 1)
*p = p1;
else
p2->next = p1;
p2 = p1;
p1->next = *p;}}
void print(struct boy *head)
}struct boy* proceed(struct boy *head,int n,int k,int m)
p2->next = p1->next;
p2 = p1; //p2指向要刪結點
p1 = p1->next;
free(p2);
}head = p1;
//以下**使head指向序號最小的結點,以便按從小到大順序輸出結果
min = p1->n;
for(i = 1; i <= m; i++)
}return head;
}//刪除整個鍊錶
void del(struct boy *head)
}
本文出自 「成鵬致遠」 部落格,請務必保留此出處
kosaraju演算法應用(一)
poj 2186 解題思路 kosaraju演算法,本以為要縮點,但是題目只要求找到拓撲排序的乙個唯一的頭,可以水過 通過計算強連通分量的出度。include include include include include using namespace std int n,m,v 10005 nu...
遞迴演算法的應用
提起漢諾塔,大家都會想起遞迴程式,大家都知道遞迴程式的實現是用棧來實現的,但是,有些程式是需要用到棧,但是我們還要編寫一棧的資料結構,挺麻煩的,所以,用遞迴程式實現起來是很簡單的!1.學習資料結構時,講到迷宮演算法,是用棧實現的,如果用遞迴演算法實現會更簡單的.掃雷程式也是實行遞迴搜尋的.對於迷宮程...
排序演算法的應用
假設序列中有n個元素,取其前k個組成乙個最大堆。由於最大堆的堆頂為序列中最大元素,所以組成的最大堆的堆頂是前k個元素中最大的元素。依次用第k 1到第n個元素與堆頂進行比較,如果比堆頂元素大,那麼該元素肯定不會是前k個最小的元素 如果比堆頂小,那麼堆頂的元素肯定不是前k個最小的元素,此時更新堆頂元素,...