1. 程式人生 > >貪心演算法的五個經典問題分析和實現

貪心演算法的五個經典問題分析和實現

問題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

輸入輸出檔案或程式執行結果截圖
在這裡插入圖片描述
 時間與空間複雜度分析
 程式使用說明
 總結與完善