貪心演算法的五個經典問題分析和實現
阿新 • • 發佈:2018-12-16
問題1:活動安排問題
問題描述:
現在給你一個會場,有許社團需要在這個會場上活動,
已知各個社團在這個會場上活動的時間(起始時間和終止時間)
要求出來怎麼安排
能夠使得這個教室z在這一天之內接待儘可能多的社團
-
解題思路與演算法思想
已經知道我們有n個活動需要安排 不妨考慮我們需要首先安排哪個活動 如果這n個活動的開始時間分別為a1-an 結束時間分別為b1-bn 那麼對於我們要安排的第一個活動而言(假設這個活動是第i個) 無論他的開始時間是多少 **他佔用的時間都是bi** 這是由於雖然她只佔用了ai-bi的時間 但是其實第一個活動,那麼從0 - ai時間段裡註定沒有活動 在選取好了第一個活動之後 我們需要選取第二個活動 那麼通過將和第一次活動重合的全部活動去掉的方式
我們可以將這個問題轉化成選取第一個活動的問題
-
程式模型的建
-
那麼可以通過各個社團的結束時間對各個社團進行排序
-
使得結束時間最早的以一些社團位於最前面
-利用貪心的思想每一都選取結束時間最靠前的哪一個 同時去除其他與這個被選擇社團時間衝突的社團 再進行下一次比較
-
資料結構的選用
-
利用陣列去儲存各個社團的開始和終止時間
-
程式設計流程
-
輸入資料
-
進行排序
-
得到結果
-
程式設計偽碼演算法
get things stored in a sort a while(i<a,size()) { if(現在第第一位的開始時間等於大於上一個被選中社團的終止時間) { 計數器+1 ; } i++ ; }
源程式編碼清單
#include<iostream> #include<utility> #include<vector> #include<algorithm> using namespace std ; bool comp(pair<int ,int >a ,pair<int ,int>b) ; int main(void) { vector< pair<int, int> > b ; int n ; scanf("%d",&n) ; b.resize(n) ; for(int i = 0 ;i<n ;i++) { int tem ; scanf("%d",&tem) ; b[i].first = tem ; scanf("%d",&tem) ; b[i].second = tem ; } sort(b.begin(),b.end(),comp) ; int end ; end =b[0].second; int cnt = 1 ; for(int i = 0 ;i<b.size() ;i++) { if(b[i].first >= end) { end = b[i].second ; cnt++ ; } } printf("%d ",cnt) ; return 0 ; } bool comp(pair<int ,int >a ,pair<int ,int>b) { if(a.second<b.second) { return true ; } else { return false ; } }
-
程式輸入、輸出
輸入: 5 1 2 3 4 5 6 7 8 9 10 輸出:5
輸入輸出檔案或程式執行結果截圖
-
時間與空間複雜度分析
時間複雜度: nlogn+n
程式使用說明
總結與完善
“0-1 揹包問題
- 問題描述
-
有一容積有限的揹包(容積為V)
-
現在有n個物品,每個物品都有自己的價值和提及
-
如何知道一個較優的策略,使得能夠放進揹包裡價值之和最大的的物品
解題的思路
先把各個物品的價值密度求出來 價值/體積
之後通過貪新的思路優先那密度最高的,直到裝不下為止
程式模型的建立
- 利用貪新的思路
- 每次選擇密度大的哪一個
資料結構的選用
- 利用陣列進行儲存
- 之後在陣列中i進行排序
程式設計流程
- 存入資料
- 進行密度計算
- 進行排序
- 進行貪心
程式設計偽碼演算法
get a sorted
while(容量小於選擇所有物品的體積值之和)
選擇價值密度最大的哪一個
源程式編碼清單
#include<iostream>
#include<vector>
#include<utility>
#include<algorithm>
using namespace std ;
bool comp(pair<int ,double> a , pair<int ,double> b ) ;
int main(void)
{
int n ;
scanf("%d",&n) ;
int content ;
scanf("%d",&content) ;
vector< pair<int , double> > a ;
a.resize(n) ;
int tem ;
for(vector< pair<int ,double> >::iterator it = a.begin() ; it !=a.end() ;it++ )
{
scanf("%d",&tem) ;
(*it).first = tem ;// capibility it cost ;
scanf("%d",&tem) ;
(*it).second = tem ;//srcond if value ;
(*it).second = (*it).second/(*it).first ;//the ρof things ;
}
sort(a.begin(),a.end(),comp) ;
int cnt = 0 ;
for(vector< pair<int ,double> > :: iterator it = a.begin() ;it != a.end() ;it++)
{
if(cnt+ (*it).first <= content)
{
printf("%d %f\n", (*it).first ,(*it).second) ;
}
cnt+=(*it).first ;
}
}
bool comp(pair<int ,double> a , pair<int ,double> b )
{
if(a.second > b.second)
{
return true ;
}
else
{
return false ;
}
}
程式輸入、輸出
輸入
7 80
10 7
20 8
30 9
40 12
4 7
14 6
5 7
輸出
4
5
10
14
20
輸入輸出檔案或程式執行結果截圖
時間與空間複雜度分析
- 時間複雜度:n
程式使用說明
總結與完善
“多機排程問題
問題描述
給了你n臺機器,m個物品需要加工
每一個物件加工花費的時間是bi ;
我們需要尋找到一個方案,使得這個加工的總時間較短
解題思路與演算法思想
- 很原始的貪心模型
- 從最長的時間開始貪心
由於耗時最長的物件需要連續加工很長時間
所以最基本的想法就是先讓機器加工最長耗時的物件
為了防止所有其他機器都在等待一個機器的情況
程式模型的建立
- 通過對物品的加工好是的排序
- 之後讓已分配加工時間最短的機器加工當時耗時最長的物品
資料結構的選用
- 選陣列儲存時間
程式設計流程
- 讀入資料
- 資料排序
- 物件分配
程式設計偽碼演算法
sort(所有需要的時間)
while(所有物品沒有別分配完)
{
find(現在被分配任務最少的機器)
把這個機器分配上未加工物件中需要耗時最長的
}
源程式編碼清單
#include<iostream>
#include<stdio.h>
#include<vector>
#include<utility>
#include<algorithm>
using namespace std ;
bool comp(pair<int ,int>a ,pair<int ,int> b) ;
int main(void)
{
int n ;
scanf("%d",&n ) ;
int m ;
scanf("%d",&m) ;
if(n>=m)
{
vector<int>b ;
int tem_int ;
for(int i = 0 ;i<m ;i++)
{
scanf("%d",&tem_int) ;
b.push_back(tem_int) ;
}
sort(b.begin(),b.end()) ;
printf("%d",b.back()) ;//can be used like this
return 0 ;
}
else
{
vector<pair<int ,int > > b ;
int tem_int ;
pair<int ,int >tem_pair ;
for(int i = 0 ;i<m ;i++)
{
scanf("%d",&tem_int) ;
tem_pair = make_pair(i,tem_int) ;
b.push_back(tem_pair) ;
}
sort(b.begin(),b.end(),comp) ;
vector<int> time ;
for(int i = 0 ; i<n ;i++)
{
time.push_back(0) ;
}
//已經排好序了???????????????????????????????
//怎麼貪心???????????????????
int cnt = 0 ;
for(int i = 0 ;i<m ;i++)
{
vector<int>::iterator it = min_element(time.begin(),time.end()) ;
(*it) = (*it)+b[i].second ;
}
printf("%d",*max_element(time.begin(),time.end())) ;
}
}
bool comp(pair<int ,int>a ,pair<int ,int> b)
{
if(a.second>b.second)
{
return true ;
}
else
{
return false ;
}
}
程式輸入、輸出
輸入:
3 10
1 2 3 4 5 6 7 8 9 10
輸出
19
輸入輸出檔案或程式執行結果截圖
時間與空間複雜度分析
- 時間複雜度:nlogn+n
程式使用說明
總結與完善
“01”子序列問題
問題描述
給定 n 個二進位制字串,要求調整字串順序並連線所有的字元
串,使最後得到的字串中有儘可能多的“01”子序列。輸出“01”子序
列的最大個數。 (1 ≤ n ≤ 100000)。
解題思路與演算法思想
如果用貪心的思路來解這道題,那麼就涉及到一個貪心策略問題
這裡我們不妨把一個子串的含1量作為其唯一的評判標準
那麼其評判標準的計算公式為 這個子串中1的數量/這個子串符號總數
程式模型的建立
- 利用貪心模型
- 每次都選取數量最大的放到最前面
資料結構的選用
- 利用陣列中儲存
程式設計流程
- 輸入
- 計算密度
- 排序
- 貪心
程式設計偽碼演算法
for(all the string)
{
a,push_back(1的密度) ;
}
sort(a) ;
while(還有字串沒有被選擇)
{
把當面位置的這個字串放到最後面
}
源程式編碼清單
#include<iostream>
#include<stdio.h>
#include<utility>
#include<vector>
#include<algorithm>
using namespace std ;
bool comp(pair<int ,double> a ,pair<int ,double> b) ;
int main(void)
{
vector<string>a ;
int n ;
scanf("%d",&n) ;
string tem ;
for(int i = 0 ;i<n ;i++)
{
cin>>tem ;
a.push_back(tem) ;
}
vector<pair<int ,double> > b ;
for(int i = 0 ;i<n ;i++)
{
double sum = 0 ;
for(int j = 0 ;j<a[i].size() ;j++)
{
sum += (a[i][j]-48) ;
}
sum = sum/(double)a[i].size() ;
pair<int ,double > tempair = make_pair(i,sum) ;
b.push_back(tempair) ;
}
sort(b.begin(),b.end(),comp) ;
for(int i = 0 ;i<b.size() ;i++)
{
cout<<a[b[i].first] ;
}
}
bool comp(pair<int ,double> a ,pair<int ,double> b)
{
if(a.second>b.second)
{
return true ;
}
else
{
return false ;
}
}
程式輸入、輸出輸入
輸入:
5
1111
1101
0011
1000
輸出:
1111110101100111000
輸入輸出檔案或程式執行結果截圖
時間與空間複雜度分析
- 時間複雜度(n*M) ;
程式使用說明
總結與完善
均分紙牌問題
問題描述
有 N 堆紙牌,編號分別為 1,2,…,N。每堆上有若干張,
但紙牌總數必為 N 的倍數。可以在任一堆上取若干張紙牌,
然後移動。
移牌規則為:在編號為 1 的堆上取的紙牌,
只能移到編號為 2 的堆上;
在編號為 N 的堆上取的紙牌
只能移到編號為 N-1 的堆上;
其他堆上取的紙牌,
可以移到相鄰左邊或右邊的堆上。
現在要求找出一種移動方法,
用最少的移動次數使每堆上紙牌數都一樣多。
解題思路與演算法思想
- 我們遇到的首要問題就是如何解決重複交換問題
- 解決方案是我們以從左往右或者從右往左的順序,每一次都將這個排隊移動到最終結果一應該成為的狀態
不妨設某一個堆的數量為a,而均分之後的數量為b
並且我們從左向右遍歷
如果a=b ,不做操作,次數+0
如果a>b,那麼將a-b張紙牌移動到右邊的相鄰堆上,次數+a-b
如果a<b ,那麼把b-a個東西從右邊相鄰的堆上移動過來 次數+b-a
程式模型的建立
圖上一條所描述
要注意的一點為第三種情況下
如果右邊相鄰的地方數量<b-a
那麼我們就把右邊的個數記為負數就可以了
資料結構的選用
- 利用陣列儲存
程式設計流程
- 輸入
- 遍歷
- 遍歷的同時移動+計數
程式設計偽碼演算法
for(所有的排隊)
{
如果a=b ,不做操作,次數+0
如果a>b,那麼將a-b張紙牌移動到右邊的相鄰堆上,次數+a-b
如果a<b ,那麼把b-a個東西從右邊相鄰的堆上移動過來 次數+b-a
}
源程式編碼清單
#include<iostream>
#include<stdio.h>
#include<vector>
using namespace std ;
int main(void)
{
int n ;
scanf("%d",&n) ;
vector<int> a ;
int tem ;
int sum = 0 ;
for(int i = 0 ;i<n ; i++)
{
scanf("%d",&tem) ;
a.push_back(tem) ;
sum += tem ;
}
sum = sum/n ;
for(int i = 0 ;i<a.size() ;i++)
{
a[i]-=sum ;
}
int action = 0 ;
for(int i = 0 ;i<a.size() ;i++)
{
if(a[i]<0)
{
action++ ;
a[i+1] += a[i] ;
}
if(a[i]>0)
{
action ++;
a[i+1] += a[i] ;
}
}
printf("%d",action) ;
}
程式輸入、輸出
輸入:
5
1 2 3 4 10
輸出
20
輸入輸出檔案或程式執行結果截圖
時間與空間複雜度分析
程式使用說明
總結與完善