線性規劃網路流 :工廠最大效益——單純形演算法【超詳+題解】
問題:某食品加工廠一共有三個車間,第一車間用 1 個單位的原料 N 可以加工 5 個單位的產品 A 或 2 個單位的產品 B。產品 A 如果直接售出,售價為 10 元,如果在第二車間繼續加工,則需要額外加工費 5 元,加工後售價為 19 元。產品 B 如果直接售出,售價 16 元,如果在第三車間繼續加工,則需要額外加工費 4 元,加工後售價為 24 元。原材料 N 的單位購入價為 5 元,每工時的資是15 元,第一車間加工一個單位的 N,需要 0.05 個工時,第二車間加工一個單位需要 0.1 工時,第三車間加工一個單位需要 0.08工時。每個月最多能得到 12000 單位的原材料 N,工時最多為 1000 工時。如何安排生產,才能使工廠的效益最大呢?
這種題先想想,它就是一道高中數學題,所以先不管程式碼怎麼設計,先把目標函式和約束條件推出來啦~
先來設變數、;
x1:產品 A 的售出量。 x2:產品 A 在第二車間加工後的售出量。 x3:產品 B 的售出量。 x4:產品 B 在第三車間加工後的售出量。 x5:第一車間所用原材料數量。 目標函式和約束條件如下:
maxz=10x1+12.5x2+16x3+18.8x4-5.75x5;
約束條件:
x1+x2-5x5=0;
{ x3+x4-2x5=0;
x5≤12000;
0.05x5+0.1x3+0.08x4≤1000;
xi(i=1, 2, 3, 4, 5)
好了,最重要的都能寫出來後面就是演算法和設計程式碼的問題了。
解決這種線性規劃問題一般都採用單純形演算法,即用一個單純形表來儲存上面推出來的條件。
首先在儲存之前需要將線性規劃轉化為標準型。
把不等式變為等式,如果目標函式是要求最小值,則需要轉化為最大值,即新增一個負號,在最好求出來的最優解新增一個負號。這道題不需要這樣的操作。
約束條件中等式中只出現一個等式的變數為基本變數,其餘為非基本變數,在目標函式中的非基本變數的係數為檢驗數,目標函式若有基本變數,則將基本變數用非基本變數來表示:
maxz=2.5x2+2.8x4+76.5x5;
約束條件:
x1+x2-5x5=0;
x3+x4-2x5=0;
{ x5+x6=12000;
0.05x5+0.1x3+0.08x4+x7=1000;
xi≥0(i=1,2,3,4,5,6,7)
單純形表 :
(1)判斷是否得到最優解:
①:若所有的檢驗數都小於零,則已獲得最優解,演算法結束。
②:若檢驗數中存在正數,而某一正數所對應的列向量的各分量都小於等於零,則線性規劃問題無界,演算法結束。
③:若檢驗數中有正數,且他們所對應的列向量有正數,則繼續下面的計算。
(2)選入基變數
選取所有正檢驗數中最大的一個,記為 Ce,其對應的非基本變數為 Xe稱為入基變數,Xe對應的列向量[a1e,a2e,…,ame]^T.為入基列.
(3) 選離基變數
選離基變數 選取“常數列元素/入基列元素”正比值的最小者,所對應的非基本變數 xk為離基變數。xk對應的行向量[ak1,ak2,…,akn]為離基行。
(4)換基變換
(5)換基變換 在單純形表上將入基變數和離基變數互換位置,即 x3 和 x5 交換位置,換基變換之後如圖 7-5 所示。
(6)計算新的單純形表
按以下方法計算新的單純形表: 4 個特殊位置如下: • 入基列=−原值/交叉位值(不包括交叉位)。 • 離基行=原值/交叉位值(不包括交叉位)。 • 交叉位=原值取倒數。 • c0 位=原值+同行入基列元素*同列離基行元素/交叉位值。
一般位置元素=原值−同行入基列元素*同列離基行元素/交叉位值。
新表:
然後繼續從(1)開始,直到 找到最優解或者判定無界停止。
程式碼:
#include<iostream>
#include<math.h>
#include<iomanip>
#include<stdio.h>
using namespace std;
float kernel[100][100];
char FJL[100] = {};
char JL[100] = {};
int n, m;
void print()
{
cout << endl;
cout << "---------單純形表如下:----------" << endl;
cout << " ";
cout << setw(7) << "b ";
for (int i = 1; i <= m; i++)
cout << setw(7) << "X" << FJL[i];
cout << endl;
cout << "c ";
for (int i = 0; i <= n; i++)
{
if (i >= 1)
{
cout << "X" << JL[i];
}
for (int j = 0; j <= m; j++)
cout << setw(7) << kernel[i][j] << " ";
cout << endl;
}
}
void DCXA()
{
float max1, max2, min;
int e = -1, k = -1;
while (1)
{
max1 = 0; max2 = 0;
min = 99999999.9;
//入基變數
for (int i = 1; i <= m; i++)
{
if (max1 < kernel[0][i])
{
max1 = kernel[0][i];
e = i;
}
}
if (max1 <= 0)
{
cout << endl;
cout << "獲得最優解: " << kernel[0][0] << endl;
break;
}
for (int i = 1; i <= n; i++)
{
if (max2 < kernel[i][e])
{
max2 = kernel[i][e];
}
float temp = kernel[i][0] / kernel[i][e];
if (temp > 0 && temp < min)
{
min = temp;
k = i;
}
}
cout << "入基變數" << "X" << FJL[e] << " ";
cout << "離基變數" << "X" << JL[k] << " ";
if (max2 == 0)
{
cout << "解無屆" << endl;
break;
}
//變基變換
char temp = FJL[e];
FJL[e] = JL[k];
JL[k] = temp;
//更新非入基列和非離基行的所有位置的元素
for (int i = 0; i <= n; i++)
{
if (i != k)
{
for (int j = 0; j <= m; j++)
{
if (j != e)
{
if (i == 0 && j == 0)
kernel[i][j] = kernel[i][j] + kernel[i][e] * kernel[k][j] / kernel[k][e];
else
kernel[i][j] = kernel[i][j] - kernel[i][e] * kernel[k][j] / kernel[k][e];
}
}
}
}
//更新入基列元素(不包括交叉為)
for (int i = 0; i <= n; i++)
{
if (i != k)
{
kernel[i][e] = -kernel[i][e] / kernel[k][e];
}
}
//更新離基行元素(不包括交叉位)
for (int i = 0; i <= m; i++)
{
if (i != e)
{
kernel[k][i] = kernel[k][i] / kernel[k][e];
}
}
kernel[k][e] = 1 / kernel[k][e];
print();
}
}
int main()
{
//非基本變數個數和非基本變數下標
cin >> m;
for (int i = 1; i <= m; i++)
cin >> FJL[i];
//基本變數個數和基本變數下標
cin >> n;
//標準型初始單純形表引數
for (int i = 1; i <= n; i++)
cin >> JL[i];
for (int i = 0; i <= n; i++)
{
for (int j = 0; j <= m; j++)
cin >> kernel[i][j];
}
print();
DCXA();
return 0;
}