判断数域上s个方程的n元线性方程组有无解,有解时解集的结构

以向量之间的关系判断是否有解,以及解的情况

具体说明都放在类和方法的注释上。

2. 列向量组

import java.util.*;

/**
 * 利用向量之间的关系判断s个方程的n元线性方程组有没有解,若有无穷多解,给出解集的结构
 *
 * @author cqh
 * @date 2023/12/15 12:30
 */
/*
取出 a1,...,an 它的线性无关组 a1,...,ar,这个无关组要能够表出向量组的所有向量
a1,...,ar 肯定能被线性表出,那除此之外的向量呢?
r < j <= n,取出向量 aj,则 aj 可由 a1,...,ar 线性表出的充分必要条件是 a1,...,ar,aj 线性相关

从向量组中取出一个部分的线性无关组,若从向量组的其余向量中任取一个向量都与部分组线性相关,称部分组为极大线性无关组
取得向量组 a1,a2,...,an 的极大线性无关组 maxLine,定义 rank{a1,a2,...,an} = maxLine.size
当 maxLine.size == n 时,a1,a2,...,an 的极大线性无关组就是自身,则 a1,a2,...,an 线性无关
即 a1*x1+...+an*xn = 0 只有零解,以 a1,...,an 为列向量的系数矩阵的行列式不等于0,a1*x1+...+an*xn = b 若有解,为唯一解

    (1)maxLine.size == n 时
        若 maxLine 与 B 线性无关,无解
        若 maxLine 与 B 线性相关,唯一解
    (2)maxLine.size < n 时
        若 maxLine 与 B 线性无关,无解
        若 maxLine 与 B 线性相关,无穷多解
 */
public class VectorDisX {
    public static void main(String[] args) {
        /*
            1a + 6b + 3c + 2d = 2
            2a + 2b + 1c + 4d = 1
            3a + 1b + 2c + 1d = 4
         */
        // a1,a2,...an 是列向量组,s维,a[i].length 为方程个数s,A.length 为未知数个数n,不限制 s 一定要等于 n
        // A 是行列颠倒的,第i列是第i个方程的系数
        Rational[] a1 = {new Rational(1), new Rational(2), new Rational(3)};
        Rational[] a2 = {new Rational(6), new Rational(2), new Rational(1)};
        Rational[] a3 = {new Rational(3), new Rational(1), new Rational(2)};
        Rational[] a4 = {new Rational(2), new Rational(4), new Rational(1)};
        Rational[] B = {new Rational(2), new Rational(1), new Rational(4)};
        Rational[][] A = {a1, a2, a3, a4};
        List list = disX(A, B);

        print(list);

        // 获取一个解集
        Rational[] X = getX(list, 20);
        if (X != null) {
            System.out.println("要验证的解集 X = " + Arrays.toString(X));
            verify(A, B, X);
        }
    }

    /**
     * 验证 X 是否是方程组的解
     * @param A 系数矩阵
     * @param B 常数项
     * @param X 待验证的解
     */
    public static void verify(Rational[][] A, Rational[] B, Rational[] X) {
        int n = A.length;
        int s = A[0].length;

        for (int i = 0; i < s; i++) {
            Rational r = Rational.zero;
            for (int j = 0; j < n; j++) {
                // j 从 0 到 n,求方程左边 n 个乘积项之和:Σ x[j] * a[j][i]
                r = r.add(X[j].mul(A[j][i]));
            }
            // 方程左边等于方程右边
            System.out.println(r + " = " + B[i]);
            if (!r.equals(B[i])) {
                System.out.println("第 " + (i + 1) + " 个方程结果错误!");
            }
        }
    }

    /**
     * 获取方程组的某个解集
     * @param result 包含解集信息的集合
     * @param max 无穷多解时,k[i] ∈ [0, max)
     * @return 方程组的解
     */
    public static Rational[] getX(List result, int max) {
        Solution solution = (Solution) result.get(SOLUTION);
        switch (solution) {
            case UNIQUE :
                // 方程有唯一解时
                return (Rational[]) result.get(PARTICULAR);
            case INFINITELY_MANY:
                // 方程有无穷多解时
                Rational[] gam = (Rational[]) result.get(PARTICULAR);
                Rational[][] eta = (Rational[][]) result.get(FUNDAMENTAL);

                // K = {k1,k2,...},k[i]为随机整数
                Rational[] K = new Rational[eta.length];
                Rational[] X = new Rational[gam.length];

                int index = 0;
                random(K, max);
                System.out.println("令 K = " + Arrays.toString(K));

                for (int j = 0; j < gam.length; j++) {
                    Rational xj = gam[j];
                    for (int ei = 0; ei < eta.length; ei++) {
                        // x[j] = gam[j] + eta[0][j] * k[0] + eta[1][j] * k[1] +...
                        xj = xj.add(eta[ei][j].mul(K[ei]));
                    }
                    X[index++] = xj;
                }
                return X;
            default:
                return null;
        }
    }

    // 随机数生成
    private static final Random random = new Random();
    private static void random(Rational[] arr, int max) {
        for (int i = 0; i < arr.length; i++) {
            arr[i] = new Rational(random.nextInt(max));
        }
    }

    /**
     * 用于查找不为0的元素
     * 指明查找方向:向右,向下
     */
    public enum Orient {
        RIGHT, DOWN
    }

    /**
     * 解集情况:无解,唯一解,无穷多解
     */
    public enum Solution {
        NO, UNIQUE, INFINITELY_MANY
    }
    /**
     * 解集情况
     */
    public static final int SOLUTION = 0;
    /**
     * 特解
     */
    public static final int PARTICULAR = 1;
    /**
     * 基础解系
     */
    public static final int FUNDAMENTAL  = 2;

    /**
     * 打印解集情况
     * @param solution 包含解集信息的集合
     */
    public static void print(List solution) {
        Rational[] gam;
        Rational[][] eta;
        switch ((Solution)solution.get(SOLUTION)) {
            case UNIQUE:
                System.out.print("唯一解:");
                gam = (Rational[]) solution.get(PARTICULAR);
                System.out.println(Arrays.toString(gam));
                break;
            case INFINITELY_MANY:
                System.out.println("无穷多解");
                gam = (Rational[]) solution.get(PARTICULAR);
                eta = (Rational[][]) solution.get(FUNDAMENTAL);
                StringBuilder builder = new StringBuilder();
                int s = gam.length;
                int n = eta.length;
                for (int i = 0; i < s; i++) {
                    builder.append("x" + (i+1) + " = ");
                    builder.append(gam[i]);
                    builder.append(" + ");
                    for (int j = 0; j < n; j++) {
                        builder.append(eta[j][i]);
                        builder.append(" * k" + (j+1));
                        if (j != n-1) {
                            builder.append(" + ");
                        }
                    }
                    builder.append("\n");
                }
                builder.append("k1,k2,...,kn ∈ Q");
                System.out.println(builder);
                break;
            default:
                System.out.println("无解");
        }
    }

    // 确保 A 是 s*n 矩阵,B 的长度为 s
    private static void checkLength(Rational[][] A, Rational[] B) {
        int n = A.length;
        int s = A[0].length;
        for (int i = 1; i < n; i++) {
            if (s != A[i].length) {
                throw new ArrayIndexOutOfBoundsException("向量长度应为" + s + ", 而a" + (i+1) + "长度为" + A[i].length);
            }
        }
        if (B.length != s) {
            throw new ArrayIndexOutOfBoundsException("B的长度应为" + s);
        }
    }

    /**
     * 获取矩阵A的列向量组的极大线性无关组
     * @param A 系数矩阵
     * @return 矩阵的列向量组的极大线性无关组
     */
    public static List<Rational[]> getMaxLine(Rational[][] A) {
        int n = A.length;
        List<Rational[]> maxLine = new ArrayList<>();

        for (int i = 0; i < n; i++) {
            // 如果向量 A[i] 与向量组 maxLine 线性无关,则添加进 maxLine
            if (isIndependent(maxLine, A[i])) {
                maxLine.add(A[i]);
            }
        }
        return maxLine;
    }

    // 获取矩阵A的列向量组的极大线性无关组
    @Deprecated
    private static List<Rational[]> getMaxLine2(Rational[][] A) {
        int n = A.length;
        int i = 0;
        List<Rational[]> maxLine = new ArrayList<>();

        // 将非零向量添加进 maxLine,之后慢慢扩充至极大线性无关组
        while (i < n) {
            if (!isZero(A[i])) {
                maxLine.add(A[i]);
                break;
            }
            i++;
        }

        // 全是零向量
        if (maxLine.size() == 0) {
            return null;
        }

        // 若 ai 与向量组 maxLine 线性无关,则添加进 maxLine
        // 最终 maxLine 为向量组 a1,...,an 的极大线性无关组
        for (i = i + 1; i < n; i++) {
            if (isIndependent(maxLine, A[i])) {
                maxLine.add(A[i]);
            }
        }
        return maxLine;
    }

    /**
     * 获取解集情况
     * @param A 系数矩阵
     * @param B 常数项
     * @return 包含解集情况信息的集合
     */
    public static List disX(Rational[][] A, Rational[] B) {
        checkLength(A, B);

        int n = A.length;
        List X = new ArrayList(3);
        List<Rational[]> maxLine = getMaxLine(A);
        int r = maxLine.size();

        // 如果线性无关,则无解
        boolean noExplain = isIndependent(maxLine, B);
        if (noExplain) {
            X.add(SOLUTION, Solution.NO);
        } else {
            // 如果线性相关,B 可由线性组 a1,a2,...,an 线性表出,有唯一解或无穷多解
            // 若 r = n,则是唯一解;否则是无穷多解
            Solution solution = (r == n) ? Solution.UNIQUE : Solution.INFINITELY_MANY;
            X.add(SOLUTION, solution);
            disaggregation(A, B, X, r);
        }
        return X;
    }

    // 获取解集信息
    @Deprecated
    private static List disX2(Rational[][] A, Rational[] B) {
        checkLength(A, B);

        int n = A.length;
        List X = new ArrayList(3);
        List<Rational[]> maxLine = getMaxLine2(A);

        // a1、a2、...、an 都是零向量
        if (maxLine == null || maxLine.size() == 0) {
            // 如果 b 不为零向量,则无解
            if (!isZero(B)) {
                X.add(SOLUTION, Solution.NO);
            } else {
                // 如果 b 为零向量,则无穷多解
                X.add(SOLUTION, Solution.INFINITELY_MANY);
                // 特解为 (0,0,...0)
                Rational[] gam = new Rational[n];
                for (int gi = 0; gi < n; gi++) {
                    gam[gi] = Rational.zero;
                }
                // 基础解系为:(1,0,...,0)、(0,1,0,...0)...、(0,...0,1)
                Rational[][] eta = new Rational[n][n];
                for (int ei = 0; ei < n; ei++) {
                    for (int ej = 0; ej < n; ej++) {
                        if (ei == ej) {
                            eta[ei][ej] = Rational.one;
                        } else {
                            eta[ei][ej] = Rational.zero;
                        }
                    }
                }
                X.add(PARTICULAR, gam);
                X.add(FUNDAMENTAL, eta);
            }
            return X;
        }

        // 若极大线性无关组中的向量数量等于未知量个数,即 rank{a1,...,an} = n
        // a1,...,an 自身为极大线性无关组
        if (maxLine.size() == n) {
            // B,a1,a2...,an 线性无关,则 B 不能由 a1,...,an 线性表出,无解
            if (isIndependent(maxLine, B)) {
                X.add(SOLUTION, Solution.NO);
            } else {
                // B,a1,...,an 线性相关,B 可以由 a1,...,an 线性表出,且表出方式唯一,唯一解
                X.add(SOLUTION, Solution.UNIQUE);
                disaggregation(A, B, X, n);
            }
            return X;
        }

        // 若 rank{a1,...,an} < n,极大线性无关组 list 不是自身,a1,...,an 线性相关
        // a1,...,an 可由向量组 list 线性表出
        // B 由 a1,...,an 线性表出等价于 B 由 list 线性表出
        // B,list 线性无关, B 不能由 list 表出,无解
        if (isIndependent(maxLine, B)) {
            X.add(SOLUTION, Solution.NO);
        } else {
            // B,list 线性相关, B 可以由 list 表出,无穷多解
            X.add(SOLUTION, Solution.INFINITELY_MANY);
            disaggregation(A, B, X, maxLine.size());
        }
        return X;
    }

    // 拷贝时行列互换
    private static void copy(Rational[][] oldArr, Rational[][] newArr) {
        for (int i = 0; i < oldArr.length; i++) {
            for (int j = 0; j < oldArr[i].length; j++) {
                newArr[j][i] = oldArr[i][j];
            }
        }
    }
    // 拷贝 old1 到 newArr 时行列互换
    private static void copy(List<Rational[]> old1, Rational[] old2, Rational[][] newArr) {
        int s = old1.get(0).length;
        int n = old1.size() + 1;
        for (int j = 0; j < n - 1; j++) {
            for (int i = 0; i < s; i++) {
                newArr[i][j] = old1.get(j)[i];
            }
        }
        for (int i = 0; i < s; i++) {
            newArr[i][n-1] = old2[i];
        }
    }
    private static void copy(Rational[] oldArr, Rational[] newArr) {
        for (int i = 0; i < oldArr.length; i++) {
            newArr[i] = oldArr[i];
        }
    }

    // 交换矩阵 A、B 的第 index 和第 i 行,j 是交换的两行的起始元素下标
    private static void swap(Rational[][] A, Rational[] B, int index, int i, int j) {
        Rational temp = B[i];
        B[i] = B[index];
        B[index] = temp;
        swap(A, index, i, j);
    }
    private static void swap(Rational[][] A, int index, int i, int j) {
        int n = A[0].length;
        for (int l = j; l < n; l++) {
            Rational temp = A[i][l];
            A[i][l] = A[index][l];
            A[index][l] = temp;
        }
    }

    // 从第 j 个元素开始,第 index 行加上第 i 行的固定倍数,使其起始位置元素值为0
    private static void addRow(Rational[][] A, Rational[] B, int index, int i, int j) {
        Rational d = A[index][j].div(A[i][j]);
        B[index] = B[index].sub(B[i].mul(d));
        // 相加后 A[index][j] 的值会变,所以倍数的计算要放在首行
        addRow(A, index, i, j);
    }
    private static void addRow(Rational[][] A, int index, int i, int j) {
        int n = A[0].length;
        Rational d = A[index][j].div(A[i][j]);
        for (int l = j; l < n; l++) {
            A[index][l] = A[index][l].sub(A[i][l].mul(d));
        }
    }

    /**
     * 将解集信息存入集合中
     * @param A_ 系数矩阵
     * @param B_ 常数项
     * @param X 包含解集信息的集合
     * @param r 系数矩阵的列向量组的秩
     */
    public static void disaggregation(Rational[][] A_, Rational[] B_, List X, int r) {
        int i = 0;
        int j = 0;
        int index = -1;
        int s = A_[0].length;
        int n = A_.length;

        Rational[][] A = new Rational[s][n];
        Rational[] B = new Rational[s];
        copy(A_, A);
        copy(B_, B);

        // 将 A 化为阶梯形矩阵
        while (i < s && j < n) {
            // 找到第 j 列中不为 0 的元素,其元素行标赋给 index
            while (j < n && (index = find(A, i, j, Orient.DOWN)) == -1) {j++;}
            // 直至最后一列都是零元素
            if (j == n) {break;}
            // 若找到了非零元素,则将第 index 与第 i 行对换,保证 A[i][j] 不为 0
            if (index != i) {
                swap(A, B, index, i, j);
            }
            // 将第 (i, s) 行加上第 i 行的倍数,将这些行的第 j 列元素置为 0
            for (int k = i+1; k < s; k++) {
                addRow(A, B, k, i, j);
            }
            i++;
            j++;
        }
        // 将主元所在列其余元素置为0,注意主元不一定为1,不是简化行阶梯矩阵
        i = r - 1;
        index = r;
        // 记录每个主元的列标
        int[] primary = new int[r];
        while (i >= 0) {
            // 找到第 i 行中不为 0 的元素,其元素行标赋给 j,则 A[i][j] 是主元
            while (i >= 0 && (j = find(A, i, i, Orient.RIGHT)) == -1) {i--;}
            if (i < 0) {break;}
            primary[--index] = j;
            // 将第 [0, i) 行加上第 i 行的倍数,将这些行的第 j 列元素置为 0
            for (int k = 0; k < i; k++) {
                addRow(A, B, k, i, j);
            }
            i--;
        }
        index = 0;
        // 记录自由未知量的下标
        int[] free = new int[n-r];
        for (j = 0; j < n; j++) {
            if (!contains(primary, j)) {
                free[index++] = j;
            }
        }
        // 讨论解集的结构
        /*
            非零行个数 r = 2,未知量个数 n = 4,自由量是 x[2] 和 x[3]
            1  0  a0  a1    b0
            0  1  a2  a3    b1

            x[0] = -a0*x[2] - a1*x[3] + b0
            x[1] = -a2*x[2] - a3*x[3] + b1

            取方程的一个任意解:{c0,c1,c2,c3} 代入此方程
            c0 = -a0*c2 - a1*c3 + b0
            c1 = -a2*c2 - a3*c3 + b1
            c2 =   1*c2 +  0*c3 + 0
            c3 =   0*c2 +  1*c3 + 0

            X = (c0,c1,c2,c3) = (-a0,-a2,1,0)*c2 + (-a1,-a3,0,1)*c3 + (b0,b1,0,0)
            令 eta[0] = (-a0,-a2,1,0),eta[1] = (-a1,-a3,0,1),gam = (b0,b1,0,0)
            eta[i] 和 gam 的长度都为 n,eta 的长度为 n-r

            X = eta[0]*k1 + eta[1]*k2 + gam,k1,k2 可取任意值(域内)
            则任意一个解集 X 都可以由 eta[0]、eta[1]、gam 线性表出
         */
        /*
            eta 的求法
            令自由量分别取 (1,0,...,0), (0,1,0,...,0),...,(0,...,0,1) 这 n-r 组值
            并分别代入对应的齐次线性方程组求得主变量,并将 n-r 组 n 个未知量存入 eta 中

            gam 的求法
            令自由量为 0,代入方程得到主变量,并将 n 个未知量存入 gam 中
         */
        // 讨论 eta 中自由量的取值
        /*
            令自由量分别取线性无关的 n-r 组值
            如 (d1,0,...,0), (0,d2,0,...,0),...,(0,...,0,dn),di ≠ 0
            代入到齐次线性方程组得:
            (x[0],x[1],x[2],x[3]) = (-a0*d1,-a2*d1,d1,0) = (-a0,-a2,1,0)*d1 = eta[0]*d1
            (x[0],x[1],x[2],x[3]) = (-a1*d2,-a3*d2,0,d2) = (-a1,-a3,0,1)*d2 = eta[1]*d2
            X = (eta[0]*d1)*u1 + (eta[1]*d2)*u2 + gam
            令 u1 = k1/d1,u2 = k2/d2
            X = (eta[0]*d1)*(k1/d1) + (eta[1]*d2)*(k2/d2) + gam
              = (eta[0]*d1)*k1 + (eta[1]*d2)*k2 + gam
              = eta[0]*k1 + eta[1]*k2 + gam
            则任意一个解集 X 都可以由 eta[0]*d1、eta[1]*d2、gam 线性表出

            令自由量取 (d1,d2)、(d3,d4)
            (x[0],x[1],x[2],x[3]) = (-a0*d1-a1*d2,-a2*d1-a3*d2,d1,d2) = (-a0,-a2,1,0)*d1 + (-a1,-a3,0,1)*d2 = eta[0]*d1 + eta[1]*d2
            (x[0],x[1],x[2],x[3]) = (-a0*d3-a1*d4,-a2*d3-a3*d4,d3,d4) = (-a0,-a2,1,0)*d3 + (-a1,-a3,0,1)*d4 = eta[0]*d3 + eta[1]*d4

            X = eta[0]*k1 + eta[1]*k2 + gam ①
            X = (eta[0]*d1 + eta[1]*d2) * u1 + (eta[0]*d3 + eta[1]*d4) * u2 + gam ②
              = eta[0]*(d1*u1+d3*u2) + eta[1]*(d2*u1+d4*u2) + gam

            无论 k1、k2 任取何值时,下列方程总有解,u1=?,u2=?
            d1*u1 + d3*u2 = k1
            d2*u1 + d4*u2 = k2
            只有当系数行列式 |A| = d1*d4 - d3*d2 ≠ 0 时,此方程才有唯一解
            对应的齐次线性方程组:
            d1*u1 + d3*u2 = 0
            d2*u1 + d4*u2 = 0
            写成:(d1,d2)*u1 + (d3,d4)*u2 = 0,|A|≠0时,只有零解,则 (d1,d2)、(d3,d4) 线性无关

            存在一组值(c1,c2),使得 (k1,k2) = (d1,d2)*c1 + (d3,d4)*c2,则 c1、c2 就是方程的解
            即 (k1,k2) 能由 (d1,d2)、(d3,d4) 线性表出

            任一向量 K = (k1,k2) 都可以由 (d1,d2)、(d3,d4) 线性表出
            则向量组 (1,0)、(0,1) 也可以由 (d1,d2)、(d3,d4) 线性表出
            2 = rank{(1,0)、(0,1)} <= rank{(d1,d2)、(d3,d4)}
            rank{(d1,d2)、(d3,d4)} = 2,从而 (d1,d2)、(d3,d4) 线性无关

            当(d1,d2)、(d3,d4) 线性无关时,任意一个解 X 都能由 eta[0]*d1+eta[1]*d2, eta[0]*d3+eta[1]*d4, gam 线性表出
            所以求 eta 时可以取线性无关的 n-r 组值,不一定非要是 1,0 组成的
         */
        // 讨论 gam 中自由量的取值
        /*
            令自由量取 (d1,d2),代入到方程组得:
            (x[0],x[1],x[2],x[3]) = (-a0*d1-a1*d2+b0, -a2*d1-a3*d2+b1, d1, d2)
            = (-a0,-a2,1,0)*d1 + (-a1,-a3,0,1)*d2 + (b0,b1,0,0)
            = eta[0]*d1 + eta[1]*d2 + gam

            X = eta[0]*u1 + eta[1]*u2 + (eta[0]*d1 + eta[1]*d2 + gam)
              = eta[0]*(u1+d1) + eta[1]*(u2+d2) + gam
            令 u1 = k1-d1,u2 = k2-d2
            X = eta[0]*k1 + eta[1]*k2 + gam

            无论令自由量取何值,X 都能由 eta[0], eta[1], (eta[0]*d1+eta[1]*d2+gam) 线性表出
            所以求 gam 时,不限制一定要自由量为 0
         */
        // 讨论主变量 x[j] 的取值,j = primary[i] 是第 i 行主元的列标
        /*
             3 2 1 4     3
             3x[0] + 2x[1] + 1x[2] + 4x[3] = 3
             x[0] = 3/3 - (2/3)x[1] - (1/3)x[2] - (4/3)x[3]
             ?[primary[i]] = x[primary[i]] = (b[i] - Σ A[i][free[l]] * ?[free[l]]) / A[i][primary[i]],i ∈ [0,r)
             Σ 是对 l 从 0 到 free.length-1 求和,? 是存所有未知量值的数组

             (1) 求 gam,令自由未知量等于(0,...,0),存入 ? 数组中,求和中共有 free.length 个乘积项,
             每个乘积项中的因式 ?[free[l]] 为 0,则乘积为 0,求和项为 0
             则主变量的值 x[primary[i]] = b[i] / A[i][primary[i]]

             (2) 求 eta,令自由量取值 (1,0,...,0),只有当 l = 0 时的第一个乘积项的 ?[free[0]] = 1,其余乘积项都为零
             对应的齐次线性方程组的主变量值为
             x[primary[i]] = (-A[i][free[0]] * ?[free[0]]) / A[i][primary[i]]
                           = (-A[i][free[0]]) / A[i][primary[i]]

             令自由量取值 (0,1,0,...,0),则是只有第二个乘积项的值不为0,?[free[0]]、?[free[2]]、... 都为 0
             x[primary[i]] = (-A[i][free[1]]) / A[i][primary[i]]

             令 eta[0] 保存 (1,0,...,0),第1个自由量为1,其余都为0,?[free[0]] = eta[0][free[0]] = 1
             令 eta[1] 保存 (0,1,0,...,0),第2个自由量为1,其余都为0,?[free[1]] = eta[1][free[1]] = 1
             ...
             则 eta[ei] 中,第 ei+1 个自由量值为 1,其余都为 0
             eta[ei][free[ei]] = 1,eta[ei][free[除ei外的下标]] = 0

             for(int i = 0; i < r; i++) {
                eta[ei][primary[i]] = -A[i][free[ei]] / A[i][primary[i]]
             }
         */
        // 下面为了简化运算,自由量只取 0 或 1
        // 求某个特定解,将它们存入数组 gam
        Rational[] gam = new Rational[n];
        for (int fi = 0; fi < free.length; fi++) {
            // 先存入值为 0 的自由量
            gam[free[fi]] = Rational.zero;
        }
        /*
            阶梯形矩阵如下:
                3 0 0 0     3
                0 0 4 0     5
            可得:x[0] = 3/3,x[2] = 5/4,primary记录每行主元列标:[0, 2]
            x[2] = x[primary[1]] = B[1] / A[1][primary[1]]

            j = primary[i] 是第 i 行主元的列标,也是以此主元为系数的未知量的下标 x[j]
            A[i][j] 是第 i 行主元的值,x[j] 的值为 B[i]/A[i][j]
        */
        for (int pi = 0; pi < primary.length; pi++) {
            // 再存入主变量
            gam[primary[pi]] = B[pi].div(A[pi][primary[pi]]);
        }

        // 令 n-r 个自由量分别取 (1,0,...,0)、(0,1,...,0)、...、(0,...,0,1) 这 n-r 组数,求得对应齐次线性方程组的解
        Rational[][] eta = new Rational[n-r][n];
        for (int ei = 0; ei < eta.length; ei++) {
            for (int fi = 0; fi < free.length; fi++) {
                if (ei == fi) {
                    eta[ei][free[fi]] = Rational.one;
                } else {
                    eta[ei][free[fi]] = Rational.zero;
                }
            }
            /*
                0 3 0 2     0
                可得:3 * x[1] + 2 * x[3] = 0,primary:[1],free:[0, 2, 3]
                两边同时除以 3 得:x[1] = -(2/3) * x[3]

                x[0]、x[2]、x[3] 取 (1, 0, 0) 时,x[1] = x[primary[0]] = 0,值为第 0 行的 x[0] 的系数除以第 0 行的主元的相反数
                x[0]、x[2]、x[3] 取 (0, 1, 0) 时,x[1] = 0,值为第 0 行的 x[2] 的系数除以第 0 行的主元的相反数
                x[0]、x[2]、x[3] 取 (0, 0, 1) 时,x[1] = -2/3,值为第 0 行的 x[3] 的系数除以第 0 行的主元的相反数

                ei 为 (0,...,0,1,0,...,0) 中 1 的下标
                x[j] = -A[i][free[ei]] / A[i][j]
            */
            for (int pi = 0; pi < primary.length; pi++) {
                eta[ei][primary[pi]] = A[pi][free[ei]].sign().div(A[pi][primary[pi]]);
            }
        }
        X.add(PARTICULAR, gam);
        X.add(FUNDAMENTAL, eta);
    }

    // 若 arr 包含 num,则返回 true
    private static boolean contains(int[] arr, int num) {
        for (int i = 0; i < arr.length; i++) {
            if (num == arr[i]) {return true;}
        }
        return false;
    }

    /**
     * 根据模式查找,返回非零元素的下标
     * @param A 待查的矩阵
     * @param i 起始位置的行标
     * @param j 起始位置的列标
     * @param o 查找模式。down是向下查找,行标变;right是向右查找,列标变
     * @return 返回非零元素的下标。向下查找时,返回非零元素的行标;向右查找时,返回非零元素的列标。
     *         若没有查找到,返回-1
     */
    public static int find(Rational[][] A, int i, int j, Orient o) {
        int s = A.length;
        int n = A[0].length;
        // 向下查找不为 0 的元素的行标,或向右查找不为 0 的元素的列标
        switch (o) {
            case DOWN:
                while (i < s) {
                    if (!Rational.zero.equals(A[i][j])) {
                        break;
                    }
                    i++;
                }
                if (i == s) {return -1;}
                return i;
            case RIGHT:
                while (j < n) {
                    if (!Rational.zero.equals(A[i][j])) {
                        break;
                    }
                    j++;
                }
                if (j == n) {return -1;}
                return j;
            default:
                return -1;
        }
    }

    /**
     * 若 a 与向量组 maxLine 线性无关,则返回 true
     * @param maxLine 线性无关组
     * @param a 某个向量
     * @return 若向量组 a,maxLine 线性无关,则返回 true,否则返回 false
     */
    public static boolean isIndependent(List<Rational[]> maxLine, Rational[] a) {
        boolean isZero = isZero(a);
        // 有零向量,必定线性相关
        if (isZero) {return false;}
        boolean isEmpty = maxLine == null || maxLine.isEmpty();
        // 若集合为空,且 a 不等于零向量,则线性无关
        if (isEmpty)  {return true;}

        int s = maxLine.get(0).length;
        int n = maxLine.size() + 1;

        // s < n 必有非零解,则线性相关
        // 超过 s 个 s 维向量必线性相关
        if (s < n) {
            return false;
        }

        // 将 maxLine 与 a 合并
        Rational[][] A = new Rational[s][n];
        copy(maxLine, a, A);

        /*
            若 maxLine 与 a 线性无关,则方程组 x1*maxLine + x2*a = 0 只有零解
            即以 maxLine、a 为列(行)向量组的矩阵的行列式不等于0
            利用两行互换、一行的倍数到另一行上,行列式的绝对值不改变的性质
            将行列式转为上三角形行列式,将主对角线上的元素相乘,得到行列式的绝对值

            若绝对值不为0,则线性无关,返回true,否则就返回false
         */
        int i = 0;
        int j = 0;
        while (i < s && j < n) {
            // 如果 A[i][j] 为 0
            // 找到第 j 列中不为 0 的元素的行标 k,并将第 i 行与第 k 行对换
            if (Rational.zero.equals(A[i][j])) {
                for (int k = i + 1; k < s; k++) {
                    if (!Rational.zero.equals(A[k][j])) {
                        swap(A, k, i, j);
                        break;
                    }
                }
            }
            // 如果此列元素全为 0,行列式值为 0,线性相关
            if (Rational.zero.equals(A[i][j])) {
                return false;
            }
            // 将第 (i, s) 行加上第 i 行的倍数,将这些行的第 j 列元素置为 0
            for (int k = i+1; k < s; k++) {
                addRow(A, k, i, j);
            }
            i++;
            j++;
        }
        // 经过上述过程后,前 n 行已转为上三角行列式,后 s-n 行全为零行
        // 将主对角线上的元素相乘,得到的 l 为行列式的值
        // 若 l = 0,则线性相关,否则线性无关
        Rational l = Rational.one;
        for (i = 0; i < n; i++) {
            l = l.mul(A[i][i]);
        }
        if (Rational.zero.equals(l)) {
            return false;
        }
        return true;
    }

    /**
     * 如果向量 a 的每个分量都为 0,则返回 true
     * 规定 {} 为零向量
     * @param a 向量
     * @return 若向量的每个元素都为0,或向量长度为0,则返回 true;否则返回 false
     */
    public static boolean isZero(Rational[] a) {
        for (int i = 0; i < a.length; i++) {
            if (!Rational.zero.equals(a[i])) {
                return false;
            }
        }
        return true;
    }

    // 将解集信息存入集合中
    @Deprecated
    private static void disaggregation2(Rational[][] A_, Rational[] B_, List X, int r) {
        int i = 0;
        int j = 0;
        int index = -1;
        int s = A_[0].length;
        int n = A_.length;

        Rational[][] A = new Rational[s][n];
        Rational[] B = new Rational[s];
        copy(A_, A);
        copy(B_, B);

        while (i < s && j < n) {
            while (j < n && (index = find(A, i, j, Orient.DOWN)) == -1) {j++;}
            if (j == n) {break;}
            if (index != i) {
                swap(A, B, index, i, j);
            }
            for (int k = i+1; k < s; k++) {
                addRow(A, B, k, i, j);
            }
            i++;
            j++;
        }
        i = r - 1;
        index = r;
        int[] primary = new int[r];
        while (i >= 0) {
            while (i >= 0 && (j = find(A, i, i, Orient.RIGHT)) == -1) {i--;}
            if (i < 0) {break;}
            primary[--index] = j;
            for (int k = 0; k < i; k++) {
                addRow(A, B, k, i, j);
            }
            i--;
        }
        index = 0;
        int[] free = new int[n-r];
        for (j = 0; j < n; j++) {
            if (!contains(primary, j)) {
                free[index++] = j;
            }
        }

        Rational[] gam = new Rational[n];
        // 这里的自由量不限制取值,这里都取1
        for (int fi = 0; fi < free.length; fi++) {
            gam[free[fi]] = Rational.one;
        }
        // x[primary[i]] = (b[i] - Σ A[i][free[l]] * ?[free[l]]) / A[i][primary[i]],i ∈ [0,r)
        for (int pi = 0; pi < primary.length; pi++) {
            Rational bi = B[pi];
            for (int l = 0; l < free.length; l++) {
                bi = bi.sub(A[pi][free[l]].mul(gam[free[l]]));
            }
            gam[primary[pi]] = bi.div(A[pi][primary[pi]]);
        }
        Rational[][] eta = new Rational[n-r][n];
        // 这里取 (随机数,0,...,0)、(0,随机数,0,...,0)...,(0,...,0,随机数)
        for (int ei = 0; ei < eta.length; ei++) {
            for (int fi = 0; fi < free.length; fi++) {
                if (ei == fi) {
                    eta[ei][free[fi]] = new Rational(random.nextInt(50));
                } else {
                    eta[ei][free[fi]] = Rational.zero;
                }
            }

            for (int pi = 0; pi < primary.length; pi++) {
                // x[primary[i]] = (-Σ A[i][free[l]] * ?[free[l]]) / A[i][primary[i]]
                /*
                Rational rs = Rational.zero;
                for (int l = 0; l < free.length; l++) {
                    rs = rs.add(A[pi][free[l]].mul(eta[ei][free[l]]));
                }
                eta[ei][primary[pi]] = rs.sign().div(A[pi][primary[pi]]);
                */
                // 只有第 l+1 个自由量不为 0 时,上述等式可以简化为:
                // x[primary[i]] = (-A[i][free[l]] * ?[free[l]]) / A[i][primary[i]]
                // 若它的值为 1,还可以省略 ?[free[l]
                eta[ei][primary[pi]] = A[pi][free[ei]].mul(eta[ei][free[ei]]).sign().div(A[pi][primary[pi]]);
            }
        }
        X.add(PARTICULAR, gam);
        X.add(FUNDAMENTAL, eta);
    }
}

3. 行向量组

/**
 * 由于增广行向量是系数行向量的延伸,所以系数行向量组{a1,a2,...,as}的部分组线性无关,则增广行向量组{b1,b2,...,bs}的对应组也线性无关
 * rank{a1,a2,...,as} <= rank{b1,b2,...,bs}
 * 设系数行向量组的极大线性无关组为 {a1,a2,...,ar},则 {b1,b2,...,br} 也线性无关
 *
 * 从向量组中的其余向量中取出一个向量 aj,使得它的延伸向量 bj 与 b1,...,br 线性无关
 * 则 rank{a1,a2,...,as} < rank{b1,b2,...,bs}
 * aj,a1,...,ar 线性相关,aj 可被 a1,...,ar 线性表出,有 aj = k1*a1 +...+ kr*ar
 * 系数矩阵的第 j 行加上第 1 行的 -k1 倍,...,再加上第 r 行的 -kr 倍,可以转化为零行
 * 而此时 bj 由于不能被 b1,...,br 线性表出,经过同样的操作,增广矩阵第 j 行的常数项不为 0
 * 出现 "0=非零数",无解
 *
 * 若没有这样的向量 bj,则 {b1,...,br} 是增广行向量组的极大线性无关组,rank{a1,a2,...,as} = rank{b1,b2,...,bs}
 * bj 可被 b1,...,br 线性表出,有 bj = l1*b1 +...+ lr*br,也有 aj = l1*a1 +...+ lr*ar
 * 增广矩阵的第 j 行加上第 1 行的 -l1 倍,...,再加上第 r 行的 -lr 倍,可以转化为零行,这行的系数和常数项都为 0
 * 不会出现 "0=非零数" 的情况,必有解
 * 
 * 系数矩阵、增广矩阵的行向量组的极大线性无关组中向量的个数不一致,无解;否则有解
 * 
 * @author cqh
 * @date 2023/12/19 21:44
 */
public class RowDis {
    public static void main(String[] args) {
        /*
            1*x1 + 2*x2 + 3*x3 + 5*x4 + 3*x5 = 2
            6*x1 + 2*x2 + 1*x3 + 4*x4 + 1*x5  = 1
            3*x1 + 1*x2 + 2*x3 + 3*x4 + 4*x5  = 4
            2*x1 + 4*x2 + 1*x3 + 2*x4 + 8*x5  = 5
         */
        // 行向量是 n 维
        Rational[] a1 = {new Rational(1), new Rational(2), new Rational(3), new Rational(5), new Rational(3)};
        Rational[] a2 = {new Rational(6), new Rational(2), new Rational(1), new Rational(4), new Rational(1)};
        Rational[] a3 = {new Rational(3), new Rational(1), new Rational(2), new Rational(3), new Rational(4)};
        Rational[] a4 = {new Rational(2), new Rational(4), new Rational(1), new Rational(2), new Rational(8)};
        Rational[] B = {new Rational(2), new Rational(1), new Rational(4), new Rational(5)};
        Rational[][] A = {a1, a2, a3, a4};
        List list = disX(A, B);

        print(list);

        Rational[] X = getX(list, 10);
        if (X != null) {
            System.out.println("要验证的解集 X = " + Arrays.toString(X));
            verify(A, B, X);
        }
    }

    /**
     * 验证 X 是否是方程组的解
     * @param A 系数矩阵
     * @param B 常数项
     * @param X 待验证的解
     */
    public static void verify(Rational[][] A, Rational[] B, Rational[] X) {
        int s = A.length;
        int n = A[0].length;

        for (int i = 0; i < s; i++) {
            Rational r = Rational.zero;
            for (int j = 0; j < n; j++) {
                r = r.add(X[j].mul(A[i][j]));
            }
            System.out.println(r + " = " + B[i]);
            if (!r.equals(B[i])) {
                System.out.println("第 " + (i + 1) + " 个方程结果错误!");
            }
        }
    }

    /**
     * 获取方程组的某个解集
     * @param result 包含解集信息的集合
     * @param max 无穷多解时,k[i] ∈ [0, max)
     * @return 方程组的解
     */
    public static Rational[] getX(List result, int max) {
        Solution solution = (Solution) result.get(SOLUTION);
        switch (solution) {
            case UNIQUE :
                return (Rational[]) result.get(PARTICULAR);
            case INFINITELY_MANY:
                Rational[] gam = (Rational[]) result.get(PARTICULAR);
                Rational[][] eta = (Rational[][]) result.get(FUNDAMENTAL);
                Rational[] K = new Rational[eta.length];
                Rational[] X = new Rational[gam.length];

                int index = 0;
                random(K, max);
                System.out.println("令 K = " + Arrays.toString(K));

                for (int j = 0; j < gam.length; j++) {
                    Rational xj = gam[j];
                    for (int ei = 0; ei < eta.length; ei++) {
                        xj = xj.add(eta[ei][j].mul(K[ei]));
                    }
                    X[index++] = xj;
                }
                return X;
            default:
                return null;
        }
    }

    private static final Random random = new Random();
    private static void random(Rational[] arr, int max) {
        for (int i = 0; i < arr.length; i++) {
            arr[i] = new Rational(random.nextInt(max));
        }
    }

    /**
     * 用于查找不为0的元素
     * 指明查找方向:向右,向下
     */
    public enum Orient {
        RIGHT, DOWN
    }

    /**
     * 解集情况:无解,唯一解,无穷多解
     */
    public enum Solution {
        NO, UNIQUE, INFINITELY_MANY
    }
    /**
     * 解集情况
     */
    public static final int SOLUTION = 0;
    /**
     * 特解
     */
    public static final int PARTICULAR = 1;
    /**
     * 基础解系
     */
    public static final int FUNDAMENTAL  = 2;

    /**
     * 打印解集情况
     * @param solution 包含解集信息的集合
     */
    public static void print(List solution) {
        Rational[] gam;
        Rational[][] eta;
        switch ((Solution)solution.get(SOLUTION)) {
            case UNIQUE:
                System.out.print("唯一解:");
                gam = (Rational[]) solution.get(PARTICULAR);
                System.out.println(Arrays.toString(gam));
                break;
            case INFINITELY_MANY:
                System.out.println("无穷多解");
                gam = (Rational[]) solution.get(PARTICULAR);
                eta = (Rational[][]) solution.get(FUNDAMENTAL);
                StringBuilder builder = new StringBuilder();
                int s = gam.length;
                int n = eta.length;
                for (int i = 0; i < s; i++) {
                    builder.append("x" + (i+1) + " = ");
                    builder.append(gam[i]);
                    builder.append(" + ");
                    for (int j = 0; j < n; j++) {
                        builder.append(eta[j][i]);
                        builder.append(" * k" + (j+1));
                        if (j != n-1) {
                            builder.append(" + ");
                        }
                    }
                    builder.append("\n");
                }
                builder.append("k1,k2,...,kn ∈ Q");
                System.out.println(builder);
                break;
            default:
                System.out.println("无解");
        }
    }

    private static void checkLength(Rational[][] A, Rational[] B) {
        int s = A.length;
        int n = A[0].length;
        for (int i = 0; i < s; i++) {
            if (n != A[i].length) {
                throw new ArrayIndexOutOfBoundsException("向量长度应为" + n + ", 而a" + (i+1) + "长度为" + A[i].length);
            }
        }
        if (B.length != s) {
            throw new ArrayIndexOutOfBoundsException("B的长度应为" + s);
        }
    }

    /**
     * 获取矩阵A的行向量组的极大线性无关组
     * @param A 系数矩阵
     * @return 矩阵的行向量组的极大线性无关组
     */
    public static List<Rational[]> getMaxLine(Rational[][] A) {
        int s = A.length;
        List<Rational[]> maxLine = new ArrayList<>();

        for (int i = 0; i < s; i++) {
            if (isIndependent(maxLine, A[i])) {
                maxLine.add(A[i]);
            }
        }
        return maxLine;
    }

    /**
     * 获取解集情况
     * @param A 系数矩阵
     * @param B 常数项
     * @return 包含解集情况信息的集合
     */
    public static List disX(Rational[][] A, Rational[] B) {
        checkLength(A, B);

        int n = A[0].length;
        List X = new ArrayList(3);
        int r1 = CRank(A);
        int r2 = ARank(A, B);

        if (r1 != r2) {
            X.add(SOLUTION, Solution.NO);
        } else {
            Solution solution = (r1 == n) ? Solution.UNIQUE : Solution.INFINITELY_MANY;
            X.add(SOLUTION, solution);
            disaggregation(A, B, X, r1);
        }
        return X;
    }

    /**
     * 返回系数矩阵的秩
     */
    private static int CRank(Rational[][] A) {
        return getMaxLine(A).size();
    }

    /**
     * 返回增广矩阵的秩
     */
    private static int ARank(Rational[][] A, Rational[] B) {
        Rational[][] enlarge = copy(A, B);
        return getMaxLine(enlarge).size();
    }

    private static void copy(Rational[][] oldArr, Rational[][] newArr) {
        for (int i = 0; i < oldArr.length; i++) {
            for (int j = 0; j < oldArr[i].length; j++) {
                newArr[i][j] = oldArr[i][j];
            }
        }
    }

    /**
     * 行数不变,old2 被拷贝到新数组多出的一列
     * @param old1 系数矩阵
     * @param old2 常数项
     * @return 返回增广矩阵
     */
    private static Rational[][] copy(Rational[][] old1, Rational[] old2) {
        int s = old1.length;
        int n = old1[0].length + 1;

        Rational[][] newArr = new Rational[s][n];
        for (int i = 0; i < s; i++) {
            for (int j = 0; j < n-1; j++) {
                newArr[i][j] = old1[i][j];
            }
        }
        for (int i = 0; i < s; i++) {
            newArr[i][n-1] = old2[i];
        }
        return newArr;
    }

    /**
     * 列数不变,行数加一,合并后的矩阵行列互换
     * @param old1 一个行向量
     * @param old2 另一个行向量
     * @return 合并之后再转置的矩阵
     */
    private static Rational[][] copy(List<Rational[]> old1, Rational[] old2) {
        int s = old1.size() + 1;
        int n = old1.get(0).length;

        Rational[][] newArr = new Rational[n][s];
        for (int i = 0; i < s - 1; i++) {
            for (int j = 0; j < n; j++) {
                newArr[j][i] = old1.get(i)[j];
            }
        }
        for (int j = 0; j < n; j++) {
            newArr[j][s-1] = old2[j];
        }
        return newArr;
    }
    private static void copy(Rational[] oldArr, Rational[] newArr) {
        for (int i = 0; i < oldArr.length; i++) {
            newArr[i] = oldArr[i];
        }
    }

    private static void swap(Rational[][] A, Rational[] B, int index, int i, int j) {
        Rational temp = B[i];
        B[i] = B[index];
        B[index] = temp;
        swap(A, index, i, j);
    }
    private static void swap(Rational[][] A, int index, int i, int j) {
        int n = A[0].length;
        for (int l = j; l < n; l++) {
            Rational temp = A[i][l];
            A[i][l] = A[index][l];
            A[index][l] = temp;
        }
    }

    private static void addRow(Rational[][] A, Rational[] B, int index, int i, int j) {
        Rational d = A[index][j].div(A[i][j]);
        B[index] = B[index].sub(B[i].mul(d));
        addRow(A, index, i, j);
    }

    private static void addRow(Rational[][] A, int index, int i, int j) {
        int n = A[0].length;
        Rational d = A[index][j].div(A[i][j]);
        for (int l = j; l < n; l++) {
            A[index][l] = A[index][l].sub(A[i][l].mul(d));
        }
    }

    /**
     * 将解集信息存入集合中
     * @param A_ 系数矩阵
     * @param B_ 常数项
     * @param X 包含解集信息的集合
     * @param r 系数矩阵的行向量组的秩
     */
    public static void disaggregation(Rational[][] A_, Rational[] B_, List X, int r) {
        int i = 0;
        int j = 0;
        int index = -1;
        int s = A_.length;
        int n = A_[0].length;

        Rational[][] A = new Rational[s][n];
        Rational[] B = new Rational[s];
        copy(A_, A);
        copy(B_, B);

        while (i < s && j < n) {
            while (j < n && (index = find(A, i, j, Orient.DOWN)) == -1) {j++;}
            if (j == n) {break;}
            if (index != i) {
                swap(A, B, index, i, j);
            }
            for (int k = i+1; k < s; k++) {
                addRow(A, B, k, i, j);
            }
            i++;
            j++;
        }
        i = r - 1;
        index = r;
        int[] primary = new int[r];
        while (i >= 0) {
            while (i >= 0 && (j = find(A, i, i, Orient.RIGHT)) == -1) {i--;}
            if (i < 0) {break;}
            primary[--index] = j;
            for (int k = 0; k < i; k++) {
                addRow(A, B, k, i, j);
            }
            i--;
        }

        index = 0;
        int[] free = new int[n-r];
        for (j = 0; j < n; j++) {
            if (!contains(primary, j)) {
                free[index++] = j;
            }
        }

        Rational[] gam = new Rational[n];
        for (int fi = 0; fi < free.length; fi++) {
            gam[free[fi]] = Rational.zero;
        }
        for (int pi = 0; pi < primary.length; pi++) {
            gam[primary[pi]] = B[pi].div(A[pi][primary[pi]]);
        }

        Rational[][] eta = new Rational[n-r][n];
        for (int ei = 0; ei < eta.length; ei++) {
            for (int fi = 0; fi < free.length; fi++) {
                if (ei == fi) {
                    eta[ei][free[fi]] = Rational.one;
                } else {
                    eta[ei][free[fi]] = Rational.zero;
                }
            }
            for (int pi = 0; pi < primary.length; pi++) {
                eta[ei][primary[pi]] = A[pi][free[ei]].sign().div(A[pi][primary[pi]]);
            }
        }
        X.add(PARTICULAR, gam);
        X.add(FUNDAMENTAL, eta);
    }

    private static boolean contains(int[] arr, int num) {
        for (int i = 0; i < arr.length; i++) {
            if (num == arr[i]) {return true;}
        }
        return false;
    }

    /**
     * 根据模式查找,返回非零元素的下标
     * @param A 待查的矩阵
     * @param i 起始位置的行标
     * @param j 起始位置的列标
     * @param o 查找模式。down是向下查找,行标变;right是向右查找,列标变
     * @return 返回非零元素的下标。向下查找时,返回非零元素的行标;向右查找时,返回非零元素的列标。
     *         若没有查找到,返回-1
     */
    public static int find(Rational[][] A, int i, int j, Orient o) {
        int s = A.length;
        int n = A[0].length;
        switch (o) {
            case DOWN:
                while (i < s) {
                    if (!Rational.zero.equals(A[i][j])) {
                        break;
                    }
                    i++;
                }
                if (i == s) {return -1;}
                return i;
            case RIGHT:
                while (j < n) {
                    if (!Rational.zero.equals(A[i][j])) {
                        break;
                    }
                    j++;
                }
                if (j == n) {return -1;}
                return j;
            default:
                return -1;
        }
    }

    /**
     * 若 a 与向量组 maxLine 线性无关,则返回 true
     * @param maxLine 线性无关组
     * @param a 向量
     * @return 若向量组 a,maxLine 线性无关,则返回 true,否则返回 false
     */
    public static boolean isIndependent(List<Rational[]> maxLine, Rational[] a) {
        boolean isZero = isZero(a);
        if (isZero) {return false;}
        boolean isEmpty = maxLine == null || maxLine.isEmpty();
        if (isEmpty)  {return true;}

        int s = maxLine.size() + 1;
        int n = maxLine.get(0).length;

        // 超过 n 个的 n 维向量必线性相关
        if (s > n) {
            return false;
        }

        /*
            到达这里时,s 必然 <= n
            A 是 n 行 s 列,若不想转置,可以将下面的计算改为互换两列,一列加另一列的倍数
            作用是将多出的 n-s 列化为零行,然后取前 s 列,计算行列式
         */
        Rational[][] A = copy(maxLine, a);
        int i = 0;
        int j = 0;
        while (i < n && j < s) {
            if (Rational.zero.equals(A[i][j])) {
                for (int k = i + 1; k < n; k++) {
                    if (!Rational.zero.equals(A[k][j])) {
                        swap(A, k, i, j);
                        break;
                    }
                }
            }
            if (Rational.zero.equals(A[i][j])) {
                return false;
            }
            for (int k = i+1; k < n; k++) {
                addRow(A, k, i, j);
            }
            i++;
            j++;
        }
        Rational l = Rational.one;
        for (i = 0; i < s; i++) {
            l = l.mul(A[i][i]);
        }
        if (Rational.zero.equals(l)) {
            return false;
        }
        return true;
    }

    /**
     * 如果向量 a 的每个分量都为 0,则返回 true
     * 规定 {} 为零向量
     * @param a 向量
     * @return 若向量的每个元素都为0,或向量长度为0,则返回 true;否则返回 false
     */
    public static boolean isZero(Rational[] a) {
        for (int i = 0; i < a.length; i++) {
            if (!Rational.zero.equals(a[i])) {
                return false;
            }
        }
        return true;
    }
}

1. 行列式

import java.util.Arrays;

/**
 * 给出有理数域上n个方程的n元线性方程组的唯一解(若有)
 *
 * @author cqh
 * @date 2023/12/09 14:03
 */
/*
有理数域上n个方程的n元线性方程组有唯一解 <=> 系数矩阵A的行列式不等于零
可给出唯一解:{x1,x2,...xn}, xj = |Bj| / |A|,Bj是将A的第j列换成常数项, j = 1,2,...,n
限制:(1)方程个数必须与未知量个数相等
     (2)只能判断是否有唯一解,当不是唯一解时,无法分辨方程组是无解还是无穷多解

用处之一:用来判断向量组是线性无关还是相关的
无解是阶梯型方程组出现了 "0=d" 这种情况(d为非零数),但行列式并不囊括常数项,无法判断
当常数项都为 0 时,方程必定有解,所以这种特殊情形时,行列式可以判断是唯一解(零解)还是无穷多解(非零解)

x1*a1 +...+ xn*an + l*b = 0,若|A|≠0,则只有零解(全为0),a1,...,an,b线性无关
若|A|=0,则有非零解(不全为0),a1,...,an,b线性相关

x1*a1 +...+ xn*an = b 有解 <=> 存在一组数使得 k1*a1 +...+ kn*an = b
(称作 b 可由 a1,...,an 线性表出)
=> k1*a1 +...+ kn*an - b = 0 => a1,...,an,b线性相关

则它的逆否命题成立,若a1,...,an,b线性无关 => x1*a1 +...+ xn*an = b 无解
但 a1,...,an,b 相关,无法推出方程组有解还是无解

若 a1,...,ar 线性无关,a1,...,ar,b 线性相关 <=> b 可由 a1,...,ar 线性表出
存在不全为0的数 k1,...,kr,l,使得 k1*a1+...+kr*ar+l*b = 0 成立
假设 l=0,则 k1,...,kr 不全为0,同时 k1*a1+...+kr*ar = 0,推出 a1,...,ar 线性相关
矛盾,假设错误,所以 l≠0
b = -(k1/l)*a1-...-(kr/l)*ar
b 可由 a1,...,ar 线性表出,即 x1*a1 +...+ xr*ar = b 有解

所以如果 a1,...,an 线性相关,取出它的线性无关组 a1,...,ar,这个无关组能够表出向量组的所有向量
如果 b 能由 a1,...,an 表出,则一定能由 a1,...,ar 表出

所以问题转化为,方程组有解 <=> a1,...,ar,b 线性相关
方程组无解 <=> a1,...,ar,b 线性无关

所以在另一个类中,主要考虑如何取出能够表出所有向量的线性无关组
 */
public class DisX {
    public static void main(String[] args) {
        /*
            5x1 + 1x2 = 13;
            2x1 + 3x2 = 13;
            求 x1、x2
         */
        Rational[][] A = {
                {new Rational(5), new Rational(1)},
                {new Rational(2), new Rational(3)},
        };
        Rational[] B = {new Rational(13), new Rational(13)};
        // X = (x1, x2);
        Rational[] X = disX(A, B);
        // 修改成错误的解集 (3, -2),使其不满足第2个方程
        // X[0] = new Rational(3);
        // X[1] = new Rational(-2);
        if (X != null) {
            System.out.println("唯一解:" + Arrays.toString(X));
            verify(A, B, X);
        } else {
            System.out.println("本方程组无解或无穷多解");
        }
    }

    /**
     * 当有唯一解时,验证解集是否正确
     * @param A 系数矩阵
     * @param B 常数项
     * @param X 要验证的解集
     */
    public static void verify(Rational[][] A, Rational[] B, Rational[] X) {
        int n = A.length;
        // 将解集(x1,x2,...,x3)代入方程中验证
        for (int i = 0; i < n; i++) {
            Rational r = Rational.zero;
            for (int j = 0; j < n; j++) {
                r = r.add(A[i][j].mul(X[j]));
            }
            System.out.println(r + " = " + B[i]);
            if (!r.equals(B[i])) {
                System.out.println("第 " + (i + 1) + " 个方程结果错误!");
            }
        }
    }

    // 确保 A 是 n 级矩阵,B 的长度为 n,不满足条件抛出ArrayIndexOutOfBoundsException
    private static void checkLength(Rational[][] A, Rational[] B) {
        int n = A.length;
        for (int i = 0; i < n; i++) {
            if (n != A[i].length) {
                throw new ArrayIndexOutOfBoundsException("A的第" + (i+1) + "行长度应为" + n);
            }
        }
        if (n != B.length) {
            throw new ArrayIndexOutOfBoundsException("B的长度应为" + n);
        }
    }

    /**
     * 返回方程的解集
     * @param A 系数矩阵
     * @param B 常数项
     * @return 若无解或无穷多解,返回 null
     */
    public static Rational[] disX(Rational[][] A, Rational[] B) {
        checkLength(A, B);
        int n = A.length;

        // 求 A 的行列式
        Rational detA = detA(A);
        // 如果 |A| = 0,返回 null
        if (detA.equals(Rational.zero)) {
            return null;
        }
        // |A| ≠ 0,返回解集 {x1,x2,...,xn}
        Rational[] X = new Rational[n];
        for (int index = 0; index < n; index++) {
            // 将 A 的第 index 列换成常数项得到矩阵 Bi
            Rational[][] Bi = new Rational[n][n];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if (j == index) {
                        Bi[i][j] = B[i];
                    } else {
                        Bi[i][j] = A[i][j];
                    }
                }
            }
            X[index] = detA(Bi).div(detA);
        }
        return X;
    }

    /**
     * 拷贝数组中的元素到一个新数组中
     * @param old 要拷贝的数组
     * @return 新数组
     */
    public static Rational[][] copy(Rational[][] old) {
        Rational[][] newArr = new Rational[old.length][];
        for (int i = 0; i < old.length; i++) {
            newArr[i] = new Rational[old[i].length];
            for (int j = 0; j < old[i].length; j++) {
                newArr[i][j] = old[i][j];
            }
        }
        return newArr;
    }

    /**
     * 将矩阵的第 k 行与第 i 行对换
     * @param A 系数矩阵
     * @param k 被交换的一行的下标
     * @param i 被交换的另一行的下标
     */
    public static void swap(Rational[][] A, int k, int i) {
        Rational[] temp = A[k];
        A[k] = A[i];
        A[i] = temp;
    }
    // 将 A 的第 k 行与第 i 行对换,起始位置从第 j 个元素开始
    @Deprecated
    private static void swap2(Rational[][] A, int k, int i, int j) {
        for (int l = j; l < A.length; l++) {
            Rational temp = A[k][l];
            A[k][l] = A[i][l];
            A[i][l] = temp;
        }
    }

    /**
     * 求矩阵的行列式
     * @param A_ 系数矩阵
     * @return 系数矩阵的行列式
     */
    public static Rational detA(Rational[][] A_) {
        int i = 0;
        int j = 0;
        Rational l = Rational.one;
        int n = A_.length;

        // 创建新的数组 A,以免换行时影响原数组 A_
        Rational[][] A = copy(A_);

        while (i < n && j < n) {
            // 如果第 i 行第 j 列为 0,则在第 2、3、...n 行中寻找这列不为 0 的元素,并记录行标 k
            // 若找到,则将第 k 行和第 i 行替换
            if (A[i][j].equals(Rational.zero)) {
                for (int k = i + 1; k < n; k++) {
                    if (!A[k][j].equals(Rational.zero)) {
                        swap(A, k, i);
                        // 两行互换后,行列式变号
                        l = l.sign();
                        break;
                    }
                }
            }
            // 说明这一列元素全为0,行列式值为0
            if (A[i][j].equals(Rational.zero)) {
                return Rational.zero;
            }
            // 使 i 之后的所有行加上第 i 行的 mul 倍,使第 k 行第 j 列的元素为 0
            for (int k = i + 1; k < n; k++) {
                Rational mul = A[k][j].div(A[i][j]).sign();
                addRow(A, k, i, j, mul);
            }
            i++;
            j++;
        }
        // 上述操作后,A已转为上三角形行列式,将主对角线上的n个元素相乘
        for (i = 0; i < n; i++) {
            l = l.mul(A[i][i]);
        }
        return l;
    }

    /**
     * 将矩阵的第 k 行元素加上第 i 行的 mul 倍,从第 j 个元素开始
     * @param A 系数矩阵
     * @param k 被加的一行
     * @param i 这行的倍数要加到另一行上
     * @param j 起始元素下标
     * @param multiple 指定倍数
     */
    public static void addRow(Rational[][] A, int k, int i, int j, Rational multiple) {
        // 第 k 行加上第 i 行的 multiple 倍
        for (int l = j; l < A.length; l++) {
            A[k][l] = A[k][l].add(A[i][l].mul(multiple));
        }
    }
}

4. 有理数

/**
 * 有理数
 *
 * @author cqh
 * @date 2023/12/10 9:35
 */
// 以两个整数之比表示有理数
public class Rational {
    /**
     * 有理数0
     */
    public final static Rational zero = new Rational(0);
    /**
     * 有理数1
     */
    public final static Rational one = new Rational(1);
    /**
     * 有理数-1
     */
    public final static Rational negativeOne = new Rational(-1);

    // 分子
    private int numerator;
    // 分母
    private int denominator;

    /**
     * 构建值为 numerator 的有理数
     * @param numerator 分子
     */
    public Rational(int numerator) {
        this.numerator = numerator;
        this.denominator = 1;
    }

    /**
     * 构建值为 numerator/denominator 的有理数
     * @param numerator 分子
     * @param denominator 分母
     */
    public Rational(int numerator, int denominator) {
        if (denominator == 0) {
            throw new ArithmeticException("分母不能为0!");
        }
        if (numerator == Integer.MIN_VALUE || denominator == Integer.MIN_VALUE) {
            throw new ArithmeticException("整数取值超过范围!");
        }
        // 使分母为正数
        if (denominator < 0) {
            numerator = -numerator;
            denominator = -denominator;
        }
        int gcd = gcd(numerator, denominator);
        this.numerator = numerator/gcd;
        this.denominator = denominator/gcd;
    }

    /**
     * 返回值为 this+r2 的有理数
     * @param r2 加数
     * @return 返回值为 this+r2 的有理数
     */
    public Rational add(Rational r2) {
        int d1 = this.denominator;
        int d2 = r2.denominator;
        int d3 = lcm(d1, d2);
        int n3 = (d3 / d1) * this.numerator + (d3 / d2) * r2.numerator;
        Rational r3 = new Rational(n3, d3);
        return r3;
    }

    /**
     * 返回值为 this-r2 的有理数
     * @param r2 减数
     * @return 返回值为 this-r2 的有理数
     */
    public Rational sub(Rational r2) {
        int d1 = this.denominator;
        int d2 = r2.denominator;
        int d3 = lcm(d1, d2);
        int n3 = (d3 / d1) * this.numerator - (d3 / d2) * r2.numerator;
        Rational r3 = new Rational(n3, d3);
        return r3;
    }

    /**
     * 返回值为 this*r2 的有理数
     * @param r2 乘数
     * @return 返回值为 this*r2 的有理数
     */
    public Rational mul(Rational r2) {
        int n1 = this.numerator;
        int d1 = this.denominator;

        int n2 = r2.numerator;
        int d2 = r2.denominator;

        // 分式1的分子与分式2的分母进行约分
        int g1 = gcd(n1, d2);
        int n31 = n1 / g1;
        int d32 = d2 / g1;

        // 分式1的分母与分式2的分子进行约分
        int g2 = gcd(n2, d1);
        int n32 = n2 / g2;
        int d31 = d1 / g2;

        // 分子与分子相乘得到另一个分式的分子
        long n3 = (long)n31 * (long)n32;
        long d3 = (long)d31 * (long)d32;
        if (n3 > Integer.MAX_VALUE || d3 > Integer.MAX_VALUE) {
            throw new ArithmeticException("两数相乘溢出!");
        }
        return new Rational((int)n3, (int)d3);
    }
    /**
     * 返回值为 this/r2 的有理数
     * @param r2 除数
     * @return 返回值为 this/r2 的有理数
     */
    public Rational div(Rational r2) {
        if (r2.numerator == 0) {
            throw new ArithmeticException("分母不能为0!");
        }
        // 除以 r2 等于乘以 r2 的倒数
        return this.mul(new Rational(r2.denominator, r2.numerator));
    }
    // 返回值为 this/r2 的有理数
    @Deprecated
    private Rational div2(Rational r2) {
        if (r2.numerator == 0) {
            throw new ArithmeticException("分母不能为0!");
        }
        int n1 = this.numerator;
        int d1 = this.denominator;

        int n2 = r2.numerator;
        int d2 = r2.denominator;

        int g1 = gcd(n1, n2);
        int n31 = n1 / g1;
        int d32 = n2 / g1;

        int g2 = gcd(d1, d2);
        int n32 = d2 / g2;
        int d31 = d1 / g2;

        long n3 = (long)n31 * (long)n32;
        long d3 = (long)d31 * (long)d32;
        if (n3 > Integer.MAX_VALUE || d3 > Integer.MAX_VALUE) {
            throw new ArithmeticException("两数相乘溢出!");
        }
        return new Rational((int)n3, (int)d3);
    }

    /**
     * 返回 this 的相反数
     * @return 不改变 this 的符号,而是返回它的相反数
     */
    public Rational sign() {
        if (Rational.zero.equals(this)) {return Rational.zero;}
        if (Rational.one.equals(this)) {return Rational.negativeOne;}
        if (Rational.negativeOne.equals(this)) {return Rational.one;}
        return new Rational(-this.numerator, this.denominator);
    }

    @Override
    public String toString() {
        if (this.numerator == 0) {return "0";}
        if (this.denominator == 1) {return "" + numerator;}
        return numerator + "/" + denominator;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Rational rational = (Rational) o;
        return numerator == rational.numerator && denominator == rational.denominator;
    }

    // 返回 a 与 b 的最小公倍数
    private static int lcm(int a, int b) {
        if (a == 0 || a == b) {
            return Math.abs(a);
        } else if (b == 0) {
            return 0;
        }
        int n = gcd(a, b);
        long result = Math.abs((long)a * (long)b);
        result = result / n;
        if (result > Integer.MAX_VALUE) {
            throw new ArithmeticException("两数相乘溢出!");
        }
        return (int)result;
    }

    // 返回 a 与 b 的最大公因数
    private static int gcd(int a, int b) {
        if (a == 0 || a == b) {
            return Math.abs(b);
        } else if (b == 0) {
            return Math.abs(a);
        }
        int m = Math.abs(a);
        int n = Math.abs(b);

        if (m < n) {
            int temp = m;
            m = n;
            n = temp;
        }
        for (int r = m % n; r != 0; r = m % n) {
            m = n;
            n = r;
        }
        return n;
    }
}
  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值