prim算法_贪心算法详解(附例题)
貪心算法的特征規(guī)律
貪心算法,“貪心”二字顧名思義,因此其規(guī)律特征就是更加注重當(dāng)前的狀態(tài),貪心法做出的選擇是對于當(dāng)前所處狀態(tài)的最優(yōu)選擇,它的解決問題的視角是微觀的“局部”,而不是從全局宏觀的角度思考和看待問題,根據(jù)這樣的性質(zhì),要求貪心法解決的問題有“無后效性”——當(dāng)前的決策不會影響到后續(xù)的決策,因為如果問題前后勾連緊密的話,會造成求解過程十分混亂。貪心算法常常用于組合優(yōu)化問題,它的求解過程是多步判斷的過程。
如果一個待求解的問題具有以上的特征,很有可能可以使用貪心算法解決。
算法設(shè)計策略
貪心算法設(shè)計的核心是——“貪心選擇的標(biāo)準(zhǔn)”,結(jié)合《算法設(shè)計與分析》書中的“活動安排問題”,該問題有“最早開始時間”“持續(xù)時間最短”“結(jié)束時間最早”三種貪心選擇的標(biāo)準(zhǔn)。
1.如果按照“最早開始時間”
選擇了活動1,就無法選擇活動2和活動3
2.如果按照“持續(xù)時間最短”
選擇了活動2.就無法選擇活動1和活動3
3.綜合上述分析該問題應(yīng)該選擇“結(jié)束時間最早”的貪心選擇標(biāo)準(zhǔn)
通過上面的例子可知,對于貪心算法,核心是通過分析找到合適的“貪心選擇標(biāo)準(zhǔn)”。
此外,還需要對貪心算法的合理性進(jìn)行證明,通常采用反證法證明,如果想要證明某一貪心策略不正確,可以尋找反例(如“活動安排問題”的開始時間最早、持續(xù)時間最長這兩種貪心策略都是通過舉反例的方法排除的)。
當(dāng)選定了“貪心選擇的標(biāo)準(zhǔn)”之后,要按照這個對已知的數(shù)據(jù)信息進(jìn)行預(yù)處理,通常的預(yù)處理是“排序”。本題中就要按照結(jié)束時間從小到大的順序進(jìn)行排序。
將數(shù)據(jù)進(jìn)行預(yù)處理(排序)之后,一次按順序遍歷,并根據(jù)條件進(jìn)行選取,構(gòu)建兩個集合,其中一個集合用于裝符合條件的元素,另一個集合用于裝未進(jìn)行判斷的元素,這是一個分步完成的過程。(有這個特征的典型應(yīng)用就是在《數(shù)據(jù)結(jié)構(gòu)》課程中曾經(jīng)學(xué)習(xí)的求解最小生成樹使用的prim算法、kruskal算法,兩者都是分步的將符合條件的邊收納進(jìn)入一個集合,再從另一個集合中挑選出符合“貪心標(biāo)準(zhǔn)”的邊放入最小生成樹集合)。
下面用“活動安排問題”來描述貪心算法的一般過程:
輸入:活動集合S, si(每個活動開始時間),fi(每個活動的結(jié)束時間)
其中i=1,2,3……,n, f1<f2<f3<……<fn;
輸出:選中活動的結(jié)合A(A是S的子集)
算法主要框架:
貪心算法的局限性
貪心算法不能保證最后求得的解是最優(yōu)的,同時,貪心算法只能求滿足某些約束條件的可行解的范圍。
下面通過TSP問題來說明貪心算法的局限性
題目描述:一個旅行家要旅行經(jīng)過n個城市,要求各個城市經(jīng)歷且僅經(jīng)歷一次然后回到出發(fā)的城市,并要求所走的路線長度最短。
給定一個矩陣,表示一個圖的鄰接矩陣,假設(shè)從頂點1出發(fā),按照貪心策略,每次決策時都找到相鄰的權(quán)值最小的邊,此時得到的路徑是1-4-3-5-2-1,總代價是14,然而最優(yōu)解1-2-5-4-3-1的總代價是13,總代價14雖然不是最優(yōu)解,但是近似接近最優(yōu)解13,但是貪心法有他的不足——無法確定得到的近似最優(yōu)解是以何種程度近似接近最優(yōu)解。
下面是用貪心法求TSP問題的代碼:
int TSP(int arc[n][n], int w)//從頂點w出發(fā)
{
int edgeCount = 0, TSPlength = 0;
int min, u, v;
int flag[n] = { 0 };//此時所有的頂點都沒有被收入到哈密頓回路中
u = w, flag[w] = 1;//flag用于標(biāo)記是否已經(jīng)經(jīng)過該點
while (edgeCount < n - 1)
{
min = 100;
for (int j = 0; j < n; j++)
{
if (flag[j] == 0 && arc[u][j] != 0 && (arc[u][j] < min))
{ //遍歷,如果周圍有鄰接的、未被訪問過的、且符合貪心策略的
v = j; //用臨時變量記錄該點
min = arc[u][j];//更新最小值
}
}
TSPlength += arc[u][v];
flag[v] = 1;//將該點收入到集合
edgeCount++;
u = v;
}
return (TSPlength + arc[u][w]);
}
貪心算法的優(yōu)點
如果一個問題的最優(yōu)解只能用蠻力法窮舉得到,則貪心法不失為尋找問題近似最優(yōu)解的一種較好的方法。
典型題目
1.跳躍問題
題目描述:給定一個非負(fù)整數(shù)數(shù)組,你最初位于數(shù)組的第一個位置。
數(shù)組中的每個元素代表你在該位置可以跳躍的最大長度。
判斷你是否能夠到達(dá)最后一個位置。
貪心算法解題分析:
這道算法題作為引入,主要體現(xiàn)了貪心算法的最直接的特點——“貪婪”,每一步都選擇都選擇從當(dāng)前所在位置出發(fā)可以到達(dá)的最遠(yuǎn)距離,如給出一組數(shù)據(jù)list=[2,3,1,1,4],對于位置0,list[0]=2,即使可以選擇跳0,1,2步,但是我們“貪心的”只關(guān)注最遠(yuǎn)的mostlong的值,遍歷整個數(shù)組,循環(huán)用mostlong=max(mostlong,i+nums[i]);更新mostlong的值,直到找到可以達(dá)到的最右端的位置。
代碼實現(xiàn):
class Solution
{
public:
bool canJump(vector<int>& nums)
{
int mostlong=0;
for(int i=0;i<nums.size()-1;i++)
{
mostlong=max(mostlong,i+nums[i]);
//貪心選擇右端可達(dá)最大值
if(mostlong<i+1)
return false;
}
return true;
}
};
時間、空間復(fù)雜度分析:
時間復(fù)雜度:O(n),其中 n為數(shù)組的大小。整個程序把 nums 數(shù)組遍歷了一遍。
空間復(fù)雜度:O(1),程序運(yùn)行過程中沒有額外的空間開銷。
2.Prim算法
與prim算法相關(guān)的——公路村村通問題:
題目描述:現(xiàn)有村落間道路的統(tǒng)計數(shù)據(jù)表中,列出了有可能建設(shè)成標(biāo)準(zhǔn)公路的若干條道路的成本,求使每個村落都有公路連通所需要的最低成本。
輸入格式:
輸入數(shù)據(jù)包括城鎮(zhèn)數(shù)目正整數(shù)N(≤1000)和候選道路數(shù)目M(≤3N);隨后的M行對應(yīng)M條道路,每行給出3個正整數(shù),分別是該條道路直接連通的兩個城鎮(zhèn)的編號以及該道路改建的預(yù)算成本。為簡單起見,城鎮(zhèn)從1到N編號。
貪心算法解題分析:
Prim算法是非常典型的貪心算法應(yīng)用,幾乎體現(xiàn)了貪心法的全部特點,prim算法的貪心策略是每次以選取距離已經(jīng)生成的部分權(quán)值最小的邊作為“貪心選擇的標(biāo)準(zhǔn)”,bool visited[ ]作為標(biāo)記數(shù)組,當(dāng)圖中的一個頂點 i被選入集合時就將其標(biāo)記為true,即visited[i]=true,表示頂點 i被收入符合“貪心標(biāo)準(zhǔn)”的集合,標(biāo)記為false的頂點表示不滿足“貪心選擇標(biāo)準(zhǔn)”或者仍處于候選集合,這種“標(biāo)記”的方式將集合的邏輯結(jié)構(gòu)轉(zhuǎn)換為計算機(jī)的存儲結(jié)構(gòu),可以讓我們用計算機(jī)實現(xiàn)程序。Prim算法相關(guān)的操作為解決貪心算法其他問題提供了一般性的解題方法和相應(yīng)的數(shù)據(jù)結(jié)構(gòu)。
代碼實現(xiàn):
int Prim()
{
bool visited[Max] = { false };
visited[1] = true;
int cost = 0;
int min;
int nVex1, nVex2;
int minTreeEdgeNum = mGraph.VexNum - 1;
for (int k = 0; k < minTreeEdgeNum; k++)
{
min = INT16_MAX;
for (int i = 1; i <= mGraph.VexNum; i++)
{
if (visited[i]) //判斷是否被訪問過
{
for (int j = 1; j <= mGraph.VexNum; j++)
{
if (!visited[j] && mGraph.Matrix[i][j] != 0 && mGraph.Matrix[i][j] < min)
//沒有被訪問過的、鄰接的且符合貪心策略的
{
nVex1 = i;
nVex2 = j;
min = mGraph.Matrix[i][j];//更新最小值
}
}
}
}
cost = cost + min;
visited[nVex1] = true;
visited[nVex2] = true;
}
return cost;
}
3.排隊問題
題目描述:
假設(shè)有打亂順序的一群人站成一個隊列。 每個人由一個整數(shù)對(h, k)表示,其中h是這個人的身高,k是排在這個人前面且身高大于或等于h的人數(shù)。 編寫一個算法來重建這個隊列。
注意:
總?cè)藬?shù)少于1100人。
示例
輸入:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
輸出:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
貪心算法解題分析:
這道題主要體現(xiàn)了貪心算法中的“預(yù)處理”的思想和作用,在我們選定了“貪心選擇標(biāo)準(zhǔn)”之后,要對原有的給定的數(shù)據(jù)進(jìn)行“預(yù)處理”(通常是對數(shù)據(jù)按照一定的規(guī)則進(jìn)行排序),比如在“活動安排問題”中,按照每個活動的結(jié)束時間的先后順序進(jìn)行排序,再進(jìn)行后續(xù)的操作。本題中應(yīng)該先將個子更高的人先進(jìn)行安排(貪心策略),再安排個子低的人,這樣可以保證后插入的人不會影響到先插入人的相對次序,即先按身高的降序排列,對于相同的身高按照前面人數(shù)k進(jìn)行升序排列。這個過程都體現(xiàn)了貪心的思想以及數(shù)據(jù)預(yù)處理的一般步驟。
代碼實現(xiàn):
class Solution {
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
// 排序
sort(people.begin(), people.end(),
[](const vector<int>& lhs, const vector<int>& rhs)
{return lhs[0] == rhs[0] ? lhs[1] <= rhs[1] : lhs[0] > rhs[0];});
int len = people.size();
list<vector<int>> tmp;
// 循環(huán)插入
for(int i = 0; i < len; ++i){
auto pos = tmp.begin();
advance(pos, people[i][1]);
tmp.insert(pos, people[i]);
}
// 重建vector返回
return vector<vector<int>>(tmp.begin(), tmp.end());
}
};
4.員工分配問題
題目描述
公司計劃面試 2N 人。第 i 人飛往 A 市的費用為 costs[i][0],飛往 B 市的費用為 costs[i][1]。返回將每個人都飛到某座城市的最低費用,要求每個城市都有 N 人抵達(dá)。
示例:
輸入:[[10,20],[30,200],[400,50],[30,20]]
輸出:110
解釋:
第一個人去 A 市,費用為 10。
第二個人去 A 市,費用為 30。
第三個人去 B 市,費用為 50。
第四個人去 B 市,費用為 20。
最低總費用為 10 + 30 + 50 + 20 = 110,每個城市都有一半的人在面試。
貪心算法解題分析:
假設(shè)全部的2N個員工都被派去了A市,一共需要費用是sum,如果中途調(diào)用N個人去了B市,此時這被調(diào)到B市的員工中節(jié)省了去A市的費用,增加了去B市的費用,此時的費用可以用公式表示,要使result值最小,只需要盡可能大,體現(xiàn)了貪心選擇標(biāo)準(zhǔn)——每次都減掉去A市和去B市花費相差最大的值,選定了貪心選擇標(biāo)準(zhǔn)之后,在進(jìn)行數(shù)據(jù)的預(yù)處理(排序)——對(cost[i][o]-cost[i][1])進(jìn)行從大到小的排列,再進(jìn)行后續(xù)操作,本題每次選擇(cost[i][o]-cost[i][1])最大者體現(xiàn)了貪心策略,也體現(xiàn)了預(yù)處理在解題過程中的重要作用。
代碼實現(xiàn):
class Solution {
public:
int twoCitySchedCost(vector<vector<int>>& costs) {
int sum=0;
int*temp=new int[2*N];
for(int i=0;i<2*N;i++)
{
sum=sum+costs[i][0];
temp[i]=costs[i][0]-costs[i][1];
}
sort(temp); //對temp數(shù)組進(jìn)行從大到小的排序
for(int i=0;i<N;i++)
{
sum=sum-temp[i];
}
return sum;
}
};
收獲與體會
通過這次課程報告,我詳細(xì)的、系統(tǒng)性的從貪心算法的特征規(guī)律、算法設(shè)計策略、貪心法的主要適用場景、局限性以及貪心法在“圖問題”、“組合優(yōu)化問題”等角度,枚舉了包括
“活動安排問題”——體現(xiàn)了貪心選擇標(biāo)準(zhǔn)選取的重要性;
“跳躍問題”“分配員工”——體現(xiàn)了貪心法的“貪婪特性”,找最值;
“排隊問題”——體現(xiàn)了預(yù)處理的重要性;
“prim算法”“公路村村通”——分步的將符合條件的邊收納進(jìn)入一個集合,再從另一個集合中挑選出符合“貪心標(biāo)準(zhǔn)”的放入該集合的思想;
“TSP問題”——無法確定得到的近似最優(yōu)解是以何種程度近似接近最優(yōu)解(體現(xiàn)貪心算法局限性)。
這些具體題目,突出貪心算法中“貪心思想”“貪心標(biāo)準(zhǔn)選擇”“預(yù)處理(排序)”“分集合分步判斷”等重要方法的應(yīng)用,從而完整全面的理解了——什么情況下應(yīng)該采用貪心算法以及如何使用貪心算法。
七. 參考文獻(xiàn)
1.《算法設(shè)計與分析》王紅梅,胡明
2. 《數(shù)據(jù)結(jié)構(gòu)(第二版)》高等教育出版社,主編陳越
3.貪心算法——百度百科
https://baike.baidu.com/item/%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95/5411800?fr=aladdin
4.LeetCode官網(wǎng)
https://leetcode-cn.com/problems/queue-reconstruction-by-height/
https://pintia.cn/problem-sets/15/problems/718
總結(jié)
以上是生活随笔為你收集整理的prim算法_贪心算法详解(附例题)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何root安卓手机_你的手机你做主!免
- 下一篇: failed building whee