最近,参加了一个提交答案类的编程比赛,有一道题可用线性规划解决。搜索发现,GLPK (GNU Linear Programming Kit) 是一个免费的线性规划计算库,可以方便地被 C/C++ 代码调用。现将基本使用方法整理如下:
准备工作
安装 GLPK
多数的发行版都应该提供了 GLPK 的包。在 Fedora 下,只需运行
sudo dnf install glpk glpk-devel
编译 GLPK
Fedora 将 glpk.h
存在了 /usr/include/glpk.h
,因此,不需添加指令即可找到头文件。如果你是手动编译安装的 GLPK ,那可能需要使用 -I
指定头文件目录。链接的指令为 -lglpk
。如果使用的是 C++ 的话,在调用头文件时,记得声明 extern
。你可以试着使用 g++ -lglpk test.cpp
编译如下代码
#include <iostream>
extern "C" {
#include "glpk.h"
}
int main ()
{
std :: cout << glp_version () << std :: endl ;
return 0 ;
}
在我撰写本文时(2018年2月初),GLPK 的版本应当为 4.61。
实战演练
GLPK 官方手册上的例子有点让人混淆。在这里,我将采用更精简的例子。
Maximize
\[z=10x_1+6x_2\]
Subject to
\[x_1+x_2\leqslant200\]
\[x_1+2x_2\geqslant10\]
\[3x_1+x_2\leqslant275.5\]
where all variables are non-negative
\[x_1\geqslant 0, x_2\geqslant 0\]
对于三个约束条件,我们可以创建三个辅助变量(auxiliary variables),将问题转化为如下形式:
Maximize
\[z=10x_1+6x_2\]
Subject to
\[p=x_1+x_2\]
\[q=x_1+2x_2\]
\[r=3x_1+x_2\]
where all variables are non-negative
\[x_1\geqslant 0, x_2\geqslant 0\]
\[0\leqslant p\leqslant200,q\geqslant10,0\leqslant r\leqslant275.5\]
现在,可以将我们的问题输入程序了。GLPK 将各辅助变量看作行(row),将原有的变量看作列(column),用一个矩阵表示辅助变量和原有变量的关系。
#include <cstdio>
extern "C" {
#include "glpk.h"
}
int main () {
initialize:
glp_prob * lp ;
lp = glp_create_prob ();
glp_set_obj_dir ( lp , GLP_MAX );
auxiliary_variables_rows:
glp_add_rows ( lp , 3 );
glp_set_row_name ( lp , 1 , "p" );
glp_set_row_bnds ( lp , 1 , GLP_DB , 0 . 0 , 200 . 0 );
glp_set_row_name ( lp , 2 , "q" );
glp_set_row_bnds ( lp , 2 , GLP_LO , 10 . 0 , 0 . 0 );
glp_set_row_name ( lp , 3 , "r" );
glp_set_row_bnds ( lp , 3 , GLP_DB , 0 . 0 , 275 . 5 );
variables_columns:
glp_add_cols ( lp , 2 );
glp_set_col_name ( lp , 1 , "x1" );
glp_set_col_bnds ( lp , 1 , GLP_LO , 0 . 0 , 0 . 0 );
glp_set_col_name ( lp , 2 , "x2" );
glp_set_col_bnds ( lp , 2 , GLP_LO , 0 . 0 , 0 . 0 );
to_maximize:
glp_set_obj_coef ( lp , 1 , 10 . 0 );
glp_set_obj_coef ( lp , 2 , 6 . 0 );
constrant_matrix:
int ia [ 7 ], ja [ 7 ];
double ar [ 7 ];
ia [ 1 ] = 1 , ja [ 1 ] = 1 , ar [ 1 ] = 1 ;
ia [ 2 ] = 1 , ja [ 2 ] = 2 , ar [ 2 ] = 1 ; // p = x1 + x2
ia [ 3 ] = 2 , ja [ 3 ] = 1 , ar [ 3 ] = 1 ;
ia [ 4 ] = 2 , ja [ 4 ] = 2 , ar [ 4 ] = 2 ; // q = x1 + 2x2
ia [ 5 ] = 3 , ja [ 5 ] = 1 , ar [ 5 ] = 3 ;
ia [ 6 ] = 3 , ja [ 6 ] = 2 , ar [ 6 ] = 1 ; // r = 3x1 + x2
glp_load_matrix ( lp , 6 , ia , ja , ar );
calculate:
glp_simplex ( lp , NULL );
output:
double z , x1 , x2 ;
z = glp_get_obj_val ( lp );
x1 = glp_get_col_prim ( lp , 1 );
x2 = glp_get_col_prim ( lp , 2 );
printf ( "z = %lf, x1 = %lf, x2 = %lf \n " , z , x1 , x2 );
cleanup:
glp_delete_prob ( lp );
return 0 ;
}
/*
GLPK Simplex Optimizer, v4.61
3 rows, 2 columns, 6 non-zeros
0: obj = -0.000000000e+00 inf = 1.000e+01 (1)
1: obj = 3.000000000e+01 inf = 0.000e+00 (0)
* 4: obj = 1.351000000e+03 inf = 0.000e+00 (0)
OPTIMAL LP SOLUTION FOUND
z = 1351.000000, x1 = 37.750000, x2 = 162.250000
*/
第一次看到示例代码,对于 constrant_matrix
部分可能会比较费解。在这里,ia
表示行号(第几个辅助变量),ja
表示列号(第几个变量),而 ar
的类型是 double
,表示 constrant matrix 中的系数。将这些条件成功导入后,使用 glp_simplex
就可以求解了。
一个常见的需求是,需要求出对应的整数解 。使用 glpk,这一问题也很好解决。
set_variables_to_integer:
glp_set_col_kind ( lp , 1 , GLP_IV );
glp_set_col_kind ( lp , 2 , GLP_IV );
calculate :
glp_simplex ( lp , NULL );
glp_intopt ( lp , NULL );
output :
double z , x1 , x2 ;
z = glp_mip_obj_val ( lp );
x1 = glp_mip_col_val ( lp , 1 );
x2 = glp_mip_col_val ( lp , 2 );
printf ( "z = %lf, x1 = %lf, x2 = %lf \n " , z , x1 , x2 );
/*
GLPK Simplex Optimizer, v4.61
3 rows, 2 columns, 6 non-zeros
0: obj = -0.000000000e+00 inf = 1.000e+01 (1)
1: obj = 3.000000000e+01 inf = 0.000e+00 (0)
* 4: obj = 1.351000000e+03 inf = 0.000e+00 (0)
OPTIMAL LP SOLUTION FOUND
GLPK Integer Optimizer, v4.61
3 rows, 2 columns, 6 non-zeros
2 integer variables, none of which are binary
Integer optimization begins...
+ 4: mip = not found yet <= +inf (1; 0)
Solution found by heuristic: 1346
+ 6: >>>>> 1.348000000e+03 <= 1.348000000e+03 0.0% (1; 1)
+ 6: mip = 1.348000000e+03 <= tree is empty 0.0% (0; 3)
INTEGER OPTIMAL SOLUTION FOUND
z = 1348.000000, x1 = 37.000000, x2 = 163.000000
*/
在运行过glp_simplex
后,再运行glp_intopt
即可得到整数解。但要注意的是,提取整数解的命令是 glp_mip_obj_val
/
glp_mip_col_val
。
PS 写这篇 blog 时,我第一次使用了 mathjax 。只能说,写作体验超乎想象地好。
讲一个逸闻吧。之前我曾把线性规划和高斯消元的时间复杂度弄混了,以至于我成功将大量NP问题转化为了线性规划问题后得到了“多项式”的解。