计算单纯形表和可逆矩阵

这是一个半自动的计算程序,这要求您必须稍懂一点Java编程的知识!不过不用担心,只需会调用其中的【方法/函数】即可,而且我已经在README.md文档中写好了使用示例。您几乎只用将文件路径写入使用示例中的参数即可

最后,这个项目被我部署在Gitee仓库里了,您可免费从中拿取完整代码以及使用实例

 

 Element

public class Element implements Comparable<Element>
{
	final boolean minus;    //    这种设计支持分数
	final int son;            //    ConstPool中有常见的Element缓存(类似Integer缓存了从-128到127之间的整数)
	final int mom;            //    这是不可变的设计思想,每次计算都会生成新对象
    public Element(int son, int mom)
	{//    这一块是Element的构造函数
	}

	public Element(int son, int mom, boolean minus)
	{//    这一块是Element的构造函数
	}

	public Element(int son)
	{//    这一块是Element的构造函数
	}

	public Element(String fraction)
	{//    这一块是将字符串解析为Element的构造函数(支持分数,不支持小数)
	}

	public Element add(Element e)
	{
        //    这一块是Element之间的加法运算,此处不再赘述,Gitee仓库内有完整代码
		return check(s, m, e.minus && minus);
	}

	public Element sub(Element e)
	{
        //    这一块是Element之间的减法运算,此处不再赘述,Gitee仓库内有完整代码
		return check(s, m, minus);
	}

	public Element mul(Element e)
	{
		int s = toIntExact((long) son * e.son);
		int m = toIntExact((long) mom * e.mom);
		return check(s, m, e.minus ^ minus);
	}

	public Element div(Element e)
	{
		int s = toIntExact((long) son * e.mom);
		int m = toIntExact((long) mom * e.son);
		return check(s, m, e.minus ^ minus);
	}

	@Override     
	public int compareTo(Element e)
	{
		if (e.minus ^ minus)
		{
			if (e.minus)
				return 1;
			else
				return -1;
		}
		int s0 = son * e.mom;
		int s1 = e.son * mom;
		return s0 - s1;
	}

	@Override
	public boolean equals(Object o)
	{
		if (this == o)
			return true;
		if (!(o instanceof Element e))
			return false;
		return minus == e.minus && son == e.son && mom == e.mom;
	}

	@Override
	public String toString()
	{
		if (ema.equals(this))
			return "+INF";
		if (emi.equals(this))
			return "-INF";
		String result = minus ? "-" : "+";
		result += mom == 1 ? son + "" : son + "/" + mom;
		return result;
	}

	private Element check(int son, int mom, boolean minus)
	{
		if (son == 0)
			return e0;        //    ConstantPool的静态常量
		if (mom == 0)
			throw new IllegalArgumentException("分母不可为零");
		if ((son | mom) < 0)
		{
			minus = true;
			son = abs(son);
			mom = abs(mom);
		}
		int gcd = gcd(son, mom);            //    尝试将分子分母之间约分
		son /= gcd;
		mom /= gcd;
		return new Element(son, mom, minus);
	}
}

InteractUtils

public final class InteractUtils    //  这是一个关于交互的代码工具类
{
//    根据name加载一个存放了单纯形表的文件
//    首先去resources文件夹下面找,如果没有找到,则根据name去找
//        即name可以仅为文件名,也可为包含全路径的文件名
	public static Table loadTable(String name)
	{
		InputStream stream = Main.class.getClassLoader().getResourceAsStream(name);
		if (stream!=null)
			return loadTable(stream);
		try
		{
			return loadTable(new FileInputStream(name));
		}
		catch (IOException e)
		{
			throw new IllegalArgumentException("未找到该文件:"+name);
		}
	}

	public static Table loadTable(InputStream stream)
	{
		Scanner input = new Scanner(stream);
		String ax_in = input.next();
		if (!"max".equalsIgnoreCase(ax_in) && !"min".equalsIgnoreCase(ax_in))
			throw new IllegalArgumentException("单纯形表格式异常");
		boolean isMax = "max".equalsIgnoreCase(ax_in);
		final int row = input.nextInt(), col = input.nextInt();
		Table table = new Table(isMax, row, col);
		input.nextLine();
		String[] C = input.nextLine().split(" ");
		for (int i = 0; i < C.length; i++)
			table.setC(i, new Element(C[i]));
		for (int i = 0; i < row; i++)
		{
			for (int j = 0; j < col + 2; j++)
			{
				if (j == col)
					table.setS(i, Symbol.valueOf(input.next().toUpperCase()));
				else
				{
					Element e = new Element(input.next());
					if (j == col + 1)
						table.setB(i, e);
					else
						table.setM(i, j, e);
				}
			}
		}
		for (int i = 0; i < col; i++)
			table.setR(i, Symbol.valueOf(input.next().toUpperCase()));
		return table;

	}
}
Type
//单纯形表的计算结果可能有四种形式
public enum Type
{   //  唯一解,无穷多最优解,无界解,无可行解
	NON, UNI, INF, UNB, UNS; //   尚未计算,unique,infinity,unbounded,unsolvable

	@Override
	public String toString()
	{
		String result = null;
		switch (this)
		{
			case NON -> result = "尚未计算";
			case UNI -> result = "唯一最优解";
			case INF -> result = "无穷多最优解";
			case UNB -> result = "无界解";
			case UNS -> result = "不可解";
			default -> throw new IllegalStateException("Unexpected value: " + this);
		}
		return result;
	}
}

Table(最为繁琐,最为复杂的类!!!)

/**
 * @ClassName: Table
 * @author: Mr.Peng
 * @create: 2023-04-12 16:27
 **/
public class Table implements Iterator<Table>
{
	public Table(boolean isMax, int row, int col)
	{//代码过长,此处不再赘述,完整版在Gitee仓库里
	}

	public Table(boolean isMax, int row, int col, CyclicStrategy strategy)
	{
		this(isMax, row, col);
		this.strategy = strategy.clone();
	}

	private Table(Table table)      //  深克隆
	{//代码过长,此处不再赘述,完整版在Gitee仓库里
	}

	private Table(boolean isMax, Matrix m)    //用于对偶单纯形表
	{//代码过长,此处不再赘述,完整版在Gitee仓库里
	}

	@Override
	public boolean hasNext()
	{
		if (!isInit)
			throw new OperateException("迭代前须先完成初始化");
		if (!strategy.hasNext())    //    若迭代次数过多,则返回false
			return false;
		computeCheckNum();
		for (CheckNum e : this.n)
		{
			if (CalculateUtils.compare(e, n0).equals(GT))
				return true;
		}
		for (int i = 0; i < row; i++)
		{
			int idx = x.get(i);
			if (c[idx].equals(emi) || c[idx].equals(ema))
			{
				type = Type.UNS;
				throw new ResultException(Type.UNS);          //即该问题不可解
			}
		}
		for (int i = 0; i < col; i++)
		{
			if (x.contains(i))              //  跳过基变量
				continue;
			if (n[i].equals(n0))
			{
				type = Type.INF;            //  判断是否为无穷多最优解
				break;
			}
		}
		if (!type.equals(Type.INF))
			type = Type.UNI;
		return false;
	}

	@Override
	public Table next()
	{
		if (!isInit)
			throw new OperateException("迭代前须先完成初始化");
		Table table = new Table(this);
		table.strategy.next();
		int k = table.findIn();         //  k:入基变量
		int l = table.findOut(k);       //  l:出基变量
		table.handleBaseVar(k, l);
		table.transformation(l, k);
		return table;
	}

	private void transformation(int out, int in)       //  入基变量替换出基变量时的初等变换
	{//代码过长,此处不再赘述,完整版在Gitee仓库里
	}


	@Override
	public String toString()
	{
		StringBuilder builder = new StringBuilder((isMax ? "max" : "min") + "=");
		String temp = " ".repeat(builder.length());
		for (Element e : c)
			format(e, builder);
		builder.append(";\n");
		Element[][] es = m.es;
		for (int i = 0; i < es.length; i++)
		{
			builder.append(temp);
			for (int j = 0; j < es[i].length; j++)
				format(es[i][j], builder);
			builder.append("\t").append(s[i]).append("\t").append(b[i]).append(";\n");
		}
		builder.append(temp);
		for (int i = 0; i < col; i++)
			builder.append(" ".repeat(FORMAT_LENGTH - 2)).append(r[i]);
		builder.append("\n").append(temp);
		for (int i = 0; i < col; i++)
			format(n[i], builder);
		return builder.toString();
	}

	private Table getTable()
	{
		Table table = null;
		if (!isInit)
			table = standard();
		else
			table = new Table(this);
		return table;
	}

	public Result result()
	{
		Table table = getTable();
		try
		{
			while (table.hasNext())
				table = table.next();
			Element r = e0;
			for (int i = 0; i < table.row; i++)
			{
				int baseVar = table.x.get(i);
				r = r.add(table.b[i].mul(table.c[baseVar]));
			}
			return new Result(r,table.type);
		}
		catch (ResultException re)
		{
			return new Result(null, table.type);
		}
	}

	private void computeCheckNum()        //    计算检验数
	{//代码过长,此处不再赘述,完整版在Gitee仓库里
	}

	public int findIn()     //  普通单纯形表中,先找到入基变量,再找出基变量
	{
		int idx = 0;
		for (int i = 0; i < n.length; i++)
		{
			if (CalculateUtils.compare(n[i], n[idx]) == GT)
				idx = i;
		}
		return idx;
	}

	public int findOut(int k)     //  普通单纯形表中,先找到入基变量,再找出基变量
	{
		int idx = -1;
		Element temp = null;
		for (int r = 0; r < row; r++)
		{
			if (m.es[r][k].minus || !CalculateUtils.compare(m.es[r][k], e0).equals(GT))
				continue;
			Element div = b[r].div(m.es[r][k]);
			if (idx == -1 || CalculateUtils.compare(temp, div) == GT)
			{
				temp = div;
				idx = r;
			}
		}
		if (idx == -1)
		{
			type = Type.UNB;
			throw new ResultException(Type.UNB);          //  即该问题有无界解}
		}
		return idx;
	}

	public void setC(int idx, Element e) {this.c[idx] = e;}
	public void setB(int idx, Element e) {this.b[idx] = e;}
	public void setM(int row, int col, Element e) {this.m.es[row][col] = e;
	public void setS(int idx, Symbol symbol) {this.s[idx] = symbol;}
	public void setR(int idx, Symbol restrain) {this.r[idx] = restrain;}

	private void checkS()
	{
		for (int i = 0; i < this.s.length; i++)
		{
			if (EQ.equals(this.s[i]))
				continue;
			switch (this.s[i])
			{
				case LE -> this.appendC(i, e0, false);
				case GE -> this.appendC(i, e0, true);
				default -> throw new IllegalStateException("Unexpected value: " + s[i]);
			}
		}
	}

	private void checkR()    //  对约束条件的标准化
	{//代码过长,此处不再赘述,完整版在Gitee仓库里
	}

	private void checkC()         //  对目标函数的标准化
	{//代码过长,此处不再赘述,完整版在Gitee仓库里
	}

	private void checkB()        //   标准化
	{
		for (int i = 0; i < row; i++)   //检测`b[i]`是否小于0,若成立则对该行全体成员*(-1):此时可能会导致某些`Xj`全为负
		{
			if (CalculateUtils.compare(e0, this.b[i]) == GT)
			{
				this.b[i] = this.b[i].mul(_e1);
				Element[] eles = this.m.es[i];
				for (int j = 0; j < eles.length; j++)
					eles[j] = eles[j].mul(_e1);
			}
		}
	}

	private void checkM()
	{
//  该方法起初是为解决【存在一列`Xj`全为负】,但后来查书得:若全为负且该列检验数>0,则为无界解
	}

	public Table standard()
	{
		Table table = new Table(this);
		table.checkC();            table.checkS();
		table.checkR();table.checkB();table.checkM();
		table.handleBaseVar(0, 0);                       //  确定基矩阵
		if (table.x.size() != row)                      //  化为标准形之后,基变量个数不满足
			table.customVar();
		table.isInit = true;                        //  表示其已化为基矩阵
		return table;
	}

	private void customVar()                //  人工变量法
	{//代码过长,此处不再赘述,完整版在Gitee仓库里
	}

	private void handleBaseVar(int in, int out)     //  处理入基变量与出基变量
	{//代码过长,此处不再赘述,完整版在Gitee仓库里
	}

	public Table adjust()        //  进行列的交换,以使其基变量聚在一起(更好看)
	{
		Table table = getTable();
		for (int i = 0; i < row; i++)
		{
			int idx = table.x.get(i);
			if (idx != i)
			{
				swap(table.c, i, idx);
				swap(table.m.es, Select.COL, i, idx);
				table.x.update(i, idx);
			}
		}
		return table;
	}
	private void appendC(int idx)        //    用于标准化中的加一列
	{//代码过长,此处不再赘述,完整版在Gitee仓库里
	}
	private void appendC(int row, Element c, boolean minus)  //    用于标准化中的加一列
	{//代码过长,此处不再赘述,完整版在Gitee仓库里
	}

	public Table dual()        //    转为单纯形表
	{//代码过长,此处不再赘述,完整版在Gitee仓库里
	}

	private Type type;
	private boolean isInit;
	private boolean isMax;
	private BaseVar x;                //  基变量
	private int row;
	private int col;
	private Symbol[] s;      //  原式符号(化为标准形时,一律为等于)
	private Matrix m;
	private Element[] c;        //  目标函数
	private Element[] b;
	private CheckNum[] n;       //  检验数:checkNum
	private Symbol[] r;     //  restrains:该变量是否
	private CyclicStrategy strategy;//  这算是一个策略模式,为了解决迭代次数问题(前期开发时,迭代流程不稳定,有时会陷入死循环,所以通过这个方式来解决此问题)
}

如何计算可逆矩阵

所有会用到的数学公式如下

 问题拆分

计算过程中,最耗时最繁琐的地方就是计算行列式了。它需要n个数的全排列,这又是一个耗时严重的地方——列举过程的时间复杂度是【O(N * N!)】。所以这里我采用多线程的方式来计算,并通过阻塞队列来进行线程间的通信。

又因为再计算一个可逆矩阵的问题时,要计算多个规格相同的行列式,所以我又将全排列的列举结果缓存起来(这里我借用了hutool.cache.LRU作为我的缓存管理类)

排列数的计算以及多线程的计算

public static Permutation permutation(final int length)
{
	if (!cache.containsKey(length))
	{
		Permutation p = new Permutation(length);
		cache.put(length, p);
		local.put(length, new ArrayBlockingQueue<>(p.accumulation));
//		local.put(length, new LinkedBlockingQueue<>());
		threadPool.execute(() ->            //   通过线程池来控制线程数量
		{
			int[] nums = new int[length];
			for (int i = 0; i < length; i++)
				nums[i] = i;
			backtrack(0, nums, length);
		});
	}
	return cache.get(length);
}

public static int[] nextA(int idx,int length)
{
	Permutation p = cache.get(length);
	if (p.handle[idx])                //    若已拿到相关数据
		return p.arrays[idx];
	else
	{
		p.arrays[idx] = nextA(length);
		p.handle[idx] = true;
	    return p.arrays[idx];
	}
}
private static int[] nextA(int length)  //  从阻塞队列里拿到计算线程的数据(线程间通信)
{
	try
	{
		return local.get(length).take();
	}
	catch (Exception e)
	{
		e.printStackTrace();
		throw new RuntimeException();
    }
}

//计算排列数
private static void backtrack(int idx, int[] nums, final int id)
{
	if (idx == nums.length)
	{
		try
		{
			int[] result = new int[nums.length];
			System.arraycopy(nums, 0, result, 0, nums.length);
			local.get(id).put(result);    //    向这个规格里的阻塞队列里放数据
		}
		catch (InterruptedException e)
		{
			e.printStackTrace();
		}
		return;
	}
	for (int i = idx; i < nums.length; ++i)
	{
		swap(nums, i, idx);            //    交换数组中i与idx的元素
		backtrack(idx + 1, nums, id);
		swap(nums, i, idx);
	}
}

//计算逆序数
public static int inverseNum(int[] nums)
{
	int num = 0;
	for (int i = 0; i < nums.length; i++)
		num += DFS(i, i, nums);
	return num;
}

private static int DFS(int idx, int key, int[] nums)
{
	if (idx == nums.length)
		return 0;
	int temp = nums[idx] < nums[key] ? 1 : 0;
	return DFS(idx + 1, key, nums) + temp;
}

可逆矩阵的计算

public Matrix invert()              //  可逆矩阵
{
	if (es.length != es[0].length)
		throw new OperateException("该矩阵不可计算逆矩阵");
	Element calculate = determinant();
	if (e0.equals(calculate))
		throw new OperateException("方阵不可逆");
	Matrix matrix = new Matrix(es.length, es.length);
	Element[][] es = matrix.es;
	for (int i = 0; i < es.length; i++)
	{
		for (int j = 0; j < this.es[i].length; j++)
			es[i][j] = complementMinor(i, j);
	}
	return matrix.div(calculate).transposition();
}

private Element complementMinor(int ii, int jj)        // 代数余子式
{
	if (es.length != es[0].length)
		throw new OperateException("该矩阵不可计算代数余子式");
	boolean minus = ((ii ^ jj) & 1) == 1;
	Matrix matrix = new Matrix(es.length - 1);
	boolean flagI = false;
	for (int i = 0; i < es.length; i++)
	{
		if (i == ii)
		{
			flagI = true;
			continue;
		}
		boolean flagJ = false;
		for (int j = 0; j < es[i].length; j++)
		{
			if (j == jj)
			{
				flagJ = true;
				continue;
			}
			int I = flagI ? i - 1 : i;
			int J = flagJ ? j - 1 : j;
			matrix.es[I][J] = es[i][j];
		}
	}
	return minus ? _e1.mul(matrix.determinant()) : e_1.mul(matrix.determinant());
}

public Element determinant()                            //  系数行列式
{
	if (es.length != es[0].length)
		throw new OperateException("该矩阵不可计算系数行列式");
	if (es.length>14)           
		throw new IllegalArgumentException("该方阵规模较大,不可计算行列式");
	Permutation permutation = CalculateUtils.permutation(es.length);
	Element result = e0;
	for (int i = 0; i < permutation.accumulation; i++)
	{
		Element r = e_1;
		int[] ints = CalculateUtils.nextA(i,permutation.num);
		for (int j = 0; j < ints.length; j++)
		{
			r = r.mul(es[j][ints[j]]);
			if (e0.equals(r))
				break;
		}
		if (e0.equals(r))
			continue;
		if ((CalculateUtils.inverseNum(ints) & 1) == 1) //判断该排列的逆序数,以决定其正负性
			result = result.sub(r);
		else
			result = result.add(r);
	}
	return result;
}

如何计算单纯形表

由于此过程较为复杂,所以不再贴代码,完整版在Gitee仓库

 虽然计算单纯形表的程序开发起来较为繁琐,但其实只要牢记基本规则/流程,然后不断拆分就好。任何再复杂的流程都可通过此方式来解决

  1.  化为标准型
  2. 确定初始基和可行解
  3. 计算检验数,并判断是否要迭代
    1. 找到检验数最大的那一列——确定入基变量
    2. 从该列找到 b/a 最大的一行——确定出基变量
    3. 用入基变量替代出基变量
  4. 找到最优解,并计算最终结果

写在最后

我在大二下学期时就学了运筹学这门课程,第一眼看到单纯形表时就感觉可以用程序来解,但一直拖着没做(眼高手低,又懒又傲)。当时觉得只要把流程看看就好,没什么复杂的,所以直到上考场,也没亲手解决一个单纯形表的问题,所以我没有通过这门课程。

我很早就看过刘慈欣写的《三体》,里面有一句话很触动我——“弱小和无知都不是人类生存的障碍,傲慢才是”。但真正面对问题时,我每一次都会选择逃避,每一次都会选择傲慢,每一次都会选择没必要……然后吃到苦头时,才会感觉那句话写的真好(后人哀之而不鉴之,亦使后人而复哀后人也)。后来补考时,我又傲慢的认为补考很简单,我肯定能过……所以很多时候并不是没有机会,而是给我机会,我傲慢的没有伸手去接。

在写这篇文章时,我已经大三了。从学编程到现在,学了很多底层原理,学了很多程序框架,但一直没去做我想做的东西。做的项目也都是去咀嚼别人吃过的东西(阿里面试官直言我简历上的项目不行),然后用“先看后做”的借口来逃避。关于开发,我疯狂的学了一大堆,但几乎没有实战的校验……

就在前几天,我开始尝试找实习工作,但没有一个中大厂给我面试机会,所以我只好不甘心地来到一个小公司实习,做着不喜欢的工作……有一说一,这完全不符合我的预期,完全不符合我在高考结束后的那个暑假所作的梦。这几天的每一个夜晚,我都会在想:到底是哪一步做错了,我到底应不应该把我如今的现状归结为【市场不好(我学的是Java后端)+经济下行】?

辗转反侧后,我觉得不能再这样了,我应该主动面对。逃避+傲慢之后让这一切变得更糟,积极准备才能走向未来。酌贪泉而觉爽,处涸辙以犹欢,享受无法回避的痛苦才是人生的常态(之前总因社会上的一些负面新闻而觉得上升渠道关闭,底层人就应躺平)。牢记:傲慢才是人类生存的障碍!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值