用各種方法解01揹包
01揹包:
說明:
由於不同演算法解01揹包問題的耗時差異甚遠, 一些耗時較高的演算法即使結果正確也無法在OJ上通過測試, 會爆TLE
而手動輸入資料具有差異性, 所以本部分的測試使用拍題程式進行:
隨機生成至少50組資料, 將其他演算法版本的01揹包程式與使用動態規劃的01揹包程式進行答案對拍
拍題程式:
#include <iostream>
#include <ctime>
#include <cstdlib>
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define MOD 100000007
typedef long long ll;
using namespace std;
int myRand() {
return rand()%100+1;
}
const int MAX=1005;
int dp[MAX][MAX]={0};
//這個是標準的正確的動態規劃01揹包演算法
void pro_AC() {
int t=0, m=0;
scanf("%d%d",&t,&m);
int time[m]={0};
int value[m]={0};
for(int i=0;i<m;++i){
scanf("%d%d",&time[i],&value[i]);
}
for(int i=0;i<=t;++i){
if(i>=time[0]){
dp[0][i]=value[0];
}
}
// for(int i=0;i<=t;++i){
// printf("%d ",dp[0][i]);
// }
// putchar('\n');
// int maxPrice=dp[0][t];
for(int i=1;i<m;++i){
for(int j=1;j<=t;++j){
if(j<time[i]){
dp[i][j]=dp[i-1][j];
}else{
dp[i][j]=max(value[i]+dp[i-1][j-time[i]],dp[i-1][j]);
}
// printf("%d ",dp[i][j]);
}
// putchar('\n');
}
printf("%d\n",dp[m-1][t]);
return ;
}
//----------------------------------------
//各種演算法嘗試解01揹包
void pro_1() {
return ;
}
//----------------------------------------
int main() {
//重置隨機數
srand(time(0));
//將隨機資料輸入到檔案中共享
auto pf=fopen("medic.txt","w+");
//根據程式輸入調整隨機數生成:
// cin>>m>>n;
int m=rand()%30+1;
fprintf(pf,"%d %d\n", rand()%1000+1,m);
for(int i=0; i<m; ++i) {
fprintf (pf,"%d %d\n",myRand (),myRand ());
}
// for(int i=0;i<2;++i){
// for(int j=0;j<n-1;++j){
// fprintf(pf,"%c",'a'+myRand());
// }
// fprintf(pf,"\n");
// }
fclose(pf);
//輸入重定向到檔案
//根據需求選擇cin或stdin
freopen("medic.txt","r",stdin);
freopen("outputP1.txt","w",stdout);
int startTime=clock ();
pro_1 (); //這裡將題寫成函式の形式
// printf("\n耗時: %ld\n",clock ()-startTime);
fclose(stdin);
fclose(stdout);
putchar('\n');
printf("------------------\n");
//同樣的, 這裡是拍題程式
freopen("medic.txt","r",stdin);
freopen("outputAC.txt","w",stdout);
memset (dp,0,sizeof(dp));
startTime=clock ();
pro_AC ();
// printf("\n耗時: %ld\n",clock ()-startTime);
fclose(stdin);
fclose(stdout);
putchar('\n');
printf("------------------\n");
// freopen("medic.txt","r",stdin);
// freopen("outputP2.txt","w",stdout);
// pro_2 ();
// fclose(stdin);
// fclose(stdout);
//注意這裡已經破壞了stdin, 無法在正常從控制檯讀入, 所以無法使用迴圈
return 0;
}
.bat程式:
不斷執行編譯好的拍題程式, 對兩個輸出結果進行比對, 直到答案不一樣才退出
@echo off
:loop
debug\JudgeKit.exe
fc outputAC.txt outputP1.txt
if not errorlevel 1 goto loop
pause
蠻力法:
蠻力法使用DFS暴力搜尋所有的物品組合來實現
搜尋到最後一層葉子節點後返回
原始碼:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<bits/stdc++.h>
#include <queue>
#include <vector>
#include <set>
#include <map>
using namespace std;
#define INF 0x3f3f3f3f
#define ll long long
const int MAX=1005;
int dp[MAX][MAX]={0};
bool isUsed[MAX]={0};
int weight[MAX]={0};
int value[MAX]={0};
long long nowValue=0, maxValue=0, nowWeight=0;
int t=0, m=0;
void dfs(int goods){
//如果最後一層已經統計完畢, 則返回
if(goods>=m){
maxValue=max(maxValue,nowValue);
return ;
}
//是否拿當前goodss
if(nowWeight+weight[goods]<=t){
nowWeight+=weight[goods];
nowValue+=value[goods];
dfs(goods+1);
nowWeight-=weight[goods];
nowValue-=value[goods];
}
dfs(goods+1);
return ;
}
//各種演算法嘗試解01揹包
void pro_1() {
scanf("%d%d",&t,&m);
for(int i=0;i<m;++i){
scanf("%d%d",&weight[i],&value[i]);
}
dfs(0);
printf("%lld\n",maxValue);
return ;
}
int main() {
pro_1 ();
return 0;
}
對拍:
本部分與使用動態規劃的標準01揹包解法進行結果對拍
隨機生成多組資料, 輸入兩個程式中進行比較, 結果均無差異
(由於蠻力法耗時較長, 這裡為了比較快速, 物品數m<=30)
//重置隨機數
srand(time(0));
//將隨機資料輸入到檔案中共享
auto pf=fopen("medic.txt","w+");
//根據程式輸入調整隨機數生成:
//隨機資料生成:
int m=rand()%30+1;
fprintf(pf,"%d %d\n", rand()%1000+1,m);
for(int i=0; i<m; ++i) {
fprintf (pf,"%d %d\n",myRand (),myRand ());
}
fclose(pf);
結果:
分治法:
分治法與蠻力法的決策樹相似, 但蠻力法是自頂向下, 分治法是自下而上
定義子問題:
從第i個物品開始, 若只考慮第i個物品, 有兩種可能
- 拿:
問題轉化為前i-1個物品在容量為j-weight[i]的揹包中的最大價值 - 不拿:
問題轉化為前i-1個物品在容量為j的揹包中的最大值
而判斷是否拿有兩種情況:
- 如果揹包剩餘容量不足以容納下, 則不拿
- 如果容納的下, 則比較拿或不拿所得到的揹包中物品價值的大小, 選較大的一個
所以可以很容易的確定轉化函式:
int vTotal(int i, int j){
if(i<0){
return 0;
}
if(j<weight[i]){
return vTotal (i-1,j);
}else{
return max(vTotal (i-1,j), vTotal (i-1, j-weight[i])+value[i]);
}
}
原始碼:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<bits/stdc++.h>
#include <queue>
#include <vector>
#include <set>
#include <map>
using namespace std;
#define INF 0x3f3f3f3f
#define ll long long
const int MAX=1005;
int weight[MAX]={0};
int value[MAX]={0};
int t=0, m=0;
int vTotal(int i, int j){
if(i<0){
return 0;
}
if(j<weight[i]){
return vTotal (i-1,j);
}else{
return max(vTotal (i-1,j), vTotal (i-1, j-weight[i])+value[i]);
}
}
//各種演算法嘗試解01揹包
void pro_1() {
scanf("%d%d",&t,&m);
for(int i=0;i<m;++i){
scanf("%d%d",&weight[i],&value[i]);
}
cout<<vTotal (m-1, t)<<endl;
return ;
}
int main() {
pro_1 ();
return 0;
}
對拍:
這裡由於分治法效能較高, 所以隨機測試資料中最多生成100個物品:
//重置隨機數
srand(time(0));
//將隨機資料輸入到檔案中共享
auto pf=fopen("medic.txt","w+");
//根據程式輸入調整隨機數生成:
// cin>>m>>n;
int m=rand()%100+1;
fprintf(pf,"%d %d\n", rand()%1000+1,m);
for(int i=0; i<m; ++i) {
fprintf (pf,"%d %d\n",myRand (),myRand ());
}
fclose(pf);
結果:
動態規劃:
動態規劃是最標準的解01揹包的演算法了, 這裡直接貼上程式碼:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<bits/stdc++.h>
#include <queue>
#include <vector>
#include <set>
#include <map>
using namespace std;
#define INF 0x3f3f3f3f
#define ll long long
const int MAX=1005;
int dp[MAX][MAX]={0};
void pro_1() {
int t=0, m=0;
scanf("%d%d",&t,&m);
int time[m]={0};
int value[m]={0};
for(int i=0;i<m;++i){
scanf("%d%d",&time[i],&value[i]);
}
for(int i=0;i<=t;++i){
if(i>=time[0]){
dp[0][i]=value[0];
}
}
for(int i=1;i<m;++i){
for(int j=1;j<=t;++j){
if(j<time[i]){
dp[i][j]=dp[i-1][j];
}else{
dp[i][j]=max(value[i]+dp[i-1][j-time[i]],dp[i-1][j]);
}
}
}
printf("%d\n",dp[m-1][t]);
return ;
}
int main() {
pro_1 ();
return 0;
}
結果:
在洛谷上的測試:
貪心法:
由於01揹包問題中的物體無法分割, 所以用貪心法通常無法得到最優解, 只有可分割的揹包問題才能用貪心法
貪心法就是每次選擇單位價值最大的物品, 以最大量裝入揹包, 策略較為簡單
這裡以CUGBOJ的題為例:
http://acm.cugb.edu.cn/problem_show.php?pid=1262
#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <map>
#include <vector>
#include <queue>
#define INF 0x3f3f3f3f
using namespace std;
//---------------------------------
//貪心法:
//貪心法求解揹包問題
/*
7 15
2 10
3 5
5 15
7 7
1 6
4 18
1 3
*/
struct thing{
int id=0;
int w=0;
int p=0;
double wp=0;
int useWeight=0;
};
bool cmp(thing &t1, thing &t2){
return t1.wp>t2.wp;
}
bool cmp_id(thing &t1, thing &t2){
return t1.id<t2.id;
}
void pro_1(){
int n=0, size=0;
scanf("%d%d",&n, &size);
vector<thing> things(n);
for(int i=0;i<n;++i){
things.at (i).id=i;
scanf("%d%d",&things.at (i).w,&things.at (i).p);
things.at (i).wp=things.at (i).p*1.0/things.at (i).w;
}
sort(things.begin (), things.end (), cmp);
// for(auto &temp: things){
// printf("%d %d %lf\n",temp.w, temp.p, temp.wp);
// }
int sizeNow=0;
double price=0;
// vector<int> weight;
for(int i=0;i<n; ++i){
if(sizeNow+things.at (i).w<=size){
sizeNow+=things.at (i).w;
things.at (i).useWeight=things.at (i).w;
price+=things.at (i).p;
}else{
things.at (i).useWeight=size-sizeNow;
price+=(size-sizeNow)*things.at (i).wp;
break;
}
}
sort(things.begin (), things.end (), cmp_id );
printf("%.2lf\n",price);
printf("%d",things.at (0).useWeight);
for(int i=1;i<n;++i){
printf(" %d",things.at (i).useWeight);
}
return ;
}
int main() {
pro_1 ();
return 0;
}
結果:
回溯法:
回溯法與蠻力法類似, 只不過多了個剪枝策略
噹噹前狀態所能達到的最大價值<已知的最大價值時, 表明後頭無論咋樣也沒法獲得更優解, 此時剪枝:
bool checkBound(int g){
double tempWeight=nowWeight, tempValue=nowValue;
int i=g;
for(;i<m;++i){
if(tempWeight+vg[i].w<=t){
tempValue+=vg[i].v;
tempWeight+=vg[i].w;
}else{
break;
}
}
tempValue+=(1.0*vg[i].v/vg[i].w)*(t-tempWeight);
if(tempValue<=(double)maxValue){
return true;
}else{
return false;
}
}
// //剪枝: 如果當前能夠達到的最好情況<maxValue, 直接return
if(checkBound (g)){
return ;
}
原始碼:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<bits/stdc++.h>
#include <queue>
#include <vector>
#include <set>
#include <map>
using namespace std;
#define INF 0x3f3f3f3f
#define ll long long
const int MAX=1005;
int dp[MAX][MAX]={0};
bool isUsed[MAX]={0};
long long nowValue=0, maxValue=0, nowWeight=0;
int t=0, m=0;
struct goods{
int w;
int v;
goods(int ww, int vv):w(ww),v(vv){}
};
vector<goods> vg;
bool cmp(goods &g1, goods &g2){
return (1.0*g1.v/g1.w) > (1.0*g2.v/g2.w);
}
bool checkBound(int g){
double tempWeight=nowWeight, tempValue=nowValue;
int i=g;
for(;i<m;++i){
if(tempWeight+vg[i].w<=t){
tempValue+=vg[i].v;
tempWeight+=vg[i].w;
}else{
break;
}
}
tempValue+=(1.0*vg[i].v/vg[i].w)*(t-tempWeight);
if(tempValue<=(double)maxValue){
return true;
}else{
return false;
}
}
void dfs(int g){
//如果最後一層已經統計完畢, 則返回
if(g>=m){
maxValue=max(maxValue,nowValue);
return ;
}
// //剪枝: 如果當前能夠達到的最好情況<maxValue, 直接return
if(checkBound (g)){
return ;
}
//是否拿當前物品
if(nowWeight+vg[g].w<=t){
nowWeight+=vg[g].w;
nowValue+=vg[g].v;
dfs(g+1);
nowWeight-=vg[g].w;
nowValue-=vg[g].v;
}
dfs(g+1);
return ;
}
//各種演算法嘗試解01揹包
void pro_1() {
scanf("%d%d",&t,&m);
int tw, tv;
for(int i=0;i<m;++i){
scanf("%d%d",&tw,&tv);
vg.push_back (goods(tw, tv));
}
sort(vg.begin (), vg.end (), cmp);
dfs(0);
printf("%lld\n",maxValue);
return ;
}
int main() {
pro_1 ();
return 0;
}
結果:
拍題結果也對:
分支限界法:
分支限界法求揹包問題在最後一次上機課中已經完成過了,
這裡以CUGBOJ中的題為例, 直接貼上程式碼:
http://acm.cugb.edu.cn/problem_show.php?pid=1003
#include <iostream>
#include <queue>
#include <algorithm>
#define N 105 //物品數為N
using namespace std;
struct Node
{
Node()
{
value = 0;
weight = 0;
level = 0;
parent = 0;
bound = 0;
}
int value; //搜尋到該節點時的價值
int weight; //搜尋到該節點時的總重量
float bound; //搜尋以該節點為根的子樹能達到的價值上界
int level; //該節點所處層次,從0開始
struct Node *parent; //指向父節點
};
struct cmp
{
bool operator()(Node *a, Node *b)
{
return a->bound < b->bound;
}
};
struct Item
{
int ItemID; //物品編號
int value; //物品價值
int weight; //物品重量
float ratio; //價值/重量
};
bool ItemCmp(Item item1, Item item2)
{
return item1.ratio > item2.ratio;
}
int maxSize = 0;
int searchCount = 0;
int w[N], v[N];
Item items[N];
int maxVlue;
int c, n;
//限界函式
float maxBound(Node *node, Item items[], int c)
{ //求以該節點為根的子樹能達到的價值上界
float maxValue;
int restCapacity; //擴充套件到該節點時的剩餘容量
int i;
maxValue = node->value;
restCapacity = c - node->weight;
i = node->level;
while (i < n && restCapacity > items[i].weight)
{
maxValue += items[i].value;
restCapacity -= items[i].weight;
i++;
}
if (restCapacity != 0)
{
maxValue += restCapacity * items[i].ratio;
}
return maxValue;
}
//bfs
int branchAndBound(Item items[], int c)
{
int x[n + 1] = {0};
int maxValue; //儲存當前搜尋到的最大價值
Node *maxNode = new Node(); //儲存當前最大價值節點(葉節點)
priority_queue<Node *, vector<Node *>, cmp> maxQueue; //最大價值優先佇列
Node *firstNode, *curNode;
searchCount = 1;
firstNode = new Node();
firstNode->bound = maxBound(firstNode, items, c);
firstNode->parent = NULL;
maxQueue.push(firstNode); //入隊第一個結點
maxValue = 0;
maxNode = firstNode;
while (!maxQueue.empty())
{
curNode = maxQueue.top();
maxQueue.pop();
//擴充套件左孩子結點
if (curNode->weight + items[curNode->level].weight <= c)
{ //最大重量限界
Node *leftNode = new Node();
leftNode->value = curNode->value + items[curNode->level].value;
leftNode->weight = curNode->weight + items[curNode->level].weight;
leftNode->level = curNode->level + 1;
leftNode->parent = curNode;
leftNode->bound = curNode->bound;
if (leftNode->level < n)
{
maxQueue.push(leftNode);
searchCount++;
}
if (maxValue < leftNode->value)
{
maxValue = leftNode->value;
maxNode = leftNode;
}
}
//擴充套件右孩子結點
if (maxBound(curNode, items, c) > maxValue)
{ //最大價值上界限界
Node *rightNode = new Node();
rightNode->value = curNode->value;
rightNode->weight = curNode->weight;
rightNode->level = curNode->level + 1;
rightNode->parent = curNode;
rightNode->bound = maxBound(rightNode, items, c);
if (rightNode->level < n)
{
maxQueue.push(rightNode);
searchCount++;
}
}
//跟蹤佇列大小
if (maxQueue.size() > maxSize)
maxSize = maxQueue.size();
}
curNode = maxNode;
while (curNode)
{
int tempValue = curNode->value;
curNode = curNode->parent;
if (curNode && curNode->value != tempValue)
x[items[curNode->level].ItemID] = 1;
}
return maxValue;
}
int main()
{
cin >> c >> n;
for (int i = 0; i < n; ++i)
{
cin >> w[i] >> v[i];
}
for (int i = 0; i < n; i++)
{
items[i].ItemID = i;
items[i].value = v[i];
items[i].weight = w[i];
items[i].ratio = (float)v[i] / w[i];
}
sort(items, items + n, ItemCmp); //按價值率排序
cout << branchAndBound(items, c) << endl;
return 0;
}
結果:
對於隨機生成的資料, 拍題也正確:
相關文章
- 01揹包、完全揹包、多重揹包詳解
- 【模板】01揹包、完全揹包
- 關於各種揹包問題
- 01 揹包
- 揹包問題(01揹包與完全揹包)
- 01揹包、有依賴的揹包
- 01揹包問題的解決
- 01揹包問題
- POJ 2184 (01揹包)
- 01 揹包問題
- 01揹包和完全揹包問題解法模板
- 01揹包詳解第一版
- 01 揹包的變形
- javascript演算法基礎之01揹包,完全揹包,多重揹包實現JavaScript演算法
- 2. 01揹包問題
- ZOJ 3956——Course Selection System(01揹包)
- 01揹包面試題系列(一)面試題
- 揹包問題解題方法總結
- 你真的懂01揹包問題嗎?01揹包的這幾問你能答出來嗎?
- 從【零錢兌換】問題看01揹包和完全揹包問題
- 動態規劃 01揹包問題動態規劃
- 01揹包優先佇列優化佇列優化
- 【動態規劃】01揹包問題動態規劃
- 動態規劃--01揹包問題動態規劃
- 動態規劃-01揹包問題動態規劃
- 詳解動態規劃01揹包問題--JavaScript實現動態規劃JavaScript
- 詳解動態規劃01揹包問題–JavaScript實現動態規劃JavaScript
- 【動態規劃】01揹包問題【續】動態規劃
- 揹包DP——完全揹包
- 揹包DP——混合揹包
- 【ACPC2013】馬里奧賽車(01揹包)
- 01揹包動態規劃空間優化動態規劃優化
- OpenJ_Bailian - 2773 採藥(01揹包板題)AI
- 分組揹包、完全揹包
- 每日演算法第103期:演算法實驗5(三種方法解決01揹包問題:動態規劃,回溯,分支限界)演算法動態規劃
- 關於01揹包個人的一些理解
- 動態規劃系列之六01揹包問題動態規劃
- 揹包