java_稀疏矩阵快速转置算法(+调试心得)

  • Post author:
  • Post category:java




主类

/*
* @Description:
* @Version: 2.0
* @Author: xuchaoxin
* @Date: 2021-03-09 19:27:00
 * @LastEditors: xuchaoxin
 * @LastEditTime: 2021-03-10 20:47:09
*/

import java.util.ArrayList;
import java.util.TreeMap;

/**
 * @Author xuchaoxin
 * @Date 2021/3/9 21:00
 * @Version 1.0 the detail and improved version(if I update it ) to see: the
 * github repository (search the
 * article):https://github.com/xuchaoxin1375/LearnJava
 */
public class main {
    /**
     * 主函数,测试这个系统的功能*/
public static void main(String[] args) {
    /*创建可以读取矩阵的对象*/
    InputMatrix inputMatrix = new InputMatrix();
    /*读取矩阵*/
    SparseMatrix sparseMatrix1 = inputMatrix.getSparseMatrix();
    /*执行转置,并打印矩阵*/
    translate(sparseMatrix1).print();

}
/**调度各个功能方法,得到以行优先顺序的稀疏矩阵的元素(三元组)所构成的列表所构成的对象:SparseMatrix*/
static SparseMatrix translate(SparseMatrix sparseMatrix1) {
    /**
     *
     * 快速转置算法充分利用了Triple结构中的坐标成员 实质是对三元组列表中的元素进行调序(某种规则下的重排序),以得到一个等长度的新列表
     *
     * 目的是求得转置后的三元组列表(即计算该列表中的(非零)元素是怎么排列的),而不是直接区计算得到二维分布(届时写一个解压函数来将位置分布呈现出来)
     */
    SparseMatrix sparseMatrix2 = new SparseMatrix();
    setProfile(sparseMatrix1, sparseMatrix2);
    /*定义计数器(注意,有必要指明该计数器中保存的值表示什么:保存的是转置矩阵各行所具有的非零元素个数
    * 那么,第一行是从0计还是从1计数?
    * 这里就规定,从0行开始计数,那么统计过程中各个元素的坐标也要从0开始计数*/
    int[] elementNumOfEachRow2 = new int[sparseMatrix1.ncol];
    /*初始换计数器*/
    for (int i = 0; i < sparseMatrix1.ncol; i++) {
        elementNumOfEachRow2[i] = 0;
    }
    /* 统计转置后的矩阵的各个行中具有的非零元素的个数 */
    for (int i = 0; i < sparseMatrix1.num; i++) {
        /*调试需要:*/

        elementNumOfEachRow2[sparseMatrix1.getTriple(i).col - 1]++;
        System.out.println(sparseMatrix1.getTriple(i));
    }
    /*  */
    int[] startInsertArray = divideZone(sparseMatrix1, elementNumOfEachRow2);
    getResortTripleList(startInsertArray, sparseMatrix1, sparseMatrix2);

    return sparseMatrix2;

}
/**设置转置矩阵总体SparseMatrix对象包含的信息参数*/
static void setProfile(SparseMatrix sparseMatrix1, SparseMatrix sparseMatrix2) {
    sparseMatrix2.ncol = sparseMatrix1.nrow;
    sparseMatrix2.nrow = sparseMatrix1.ncol;
    sparseMatrix2.num = sparseMatrix1.num;
}

/** 将转置矩阵的三元组数组以各个行中具有的非零元素数目为标准,做相应分区划段,来存储各行中的元素;需要划分的段数为转置前矩阵的列数 */
static int[] divideZone(SparseMatrix sparseMatrix, int[] elementNumOfEachRow2) {

    int[] startInsertArray = new int[sparseMatrix.ncol];
    startInsertArray[0] = 0;// 初始化第一个值,手动初始化指明(从[0]个位置开始插入),为地推做准备
    for (int i = 1; /* i表示为转置矩阵的每一行计算其区间段的宽度(以存放对应行的非零元素) */
            i < sparseMatrix.ncol; i++) {
        startInsertArray[i] = startInsertArray[i - 1] + elementNumOfEachRow2[i - 1];
    }

    return startInsertArray;
}
/** 调序插入到转置矩阵中的三元组列表中(核心步骤) */
static void getResortTripleList(int[] startInsertArray, SparseMatrix sparseMatrix1, SparseMatrix sparseMatrix2) {
    /*提取常用表达式的计算结果,简化代码*/
    int position;
    Triple elementPoint, srcElementPoint;
    /*批量生成Triple对象*/
    for (int j = 0; j < sparseMatrix2.num; j++) {
        sparseMatrix2.tripleList.add(new Triple());
    }
    for (int i = 0; i < sparseMatrix1.num; i++) {
        /* 先计算当前非零元素在转置三元组列表中会落在那个区间(左边界) */
        position = getPosition(i, startInsertArray, sparseMatrix1);
        /*检查position的值:  */
        System.out.println(position);

        elementPoint = sparseMatrix2.getTriple(position);

        srcElementPoint = sparseMatrix1.getTriple(i);
        /* 以下该值操作不影响原矩阵中的三元组 */
        elementPoint.setRow(srcElementPoint.getCol());
        elementPoint.setCol(srcElementPoint.getRow());
        elementPoint.setData(srcElementPoint.getData());
        /* 修改该元素所在行的下一个元素要插入的位置(虽然不一定还会有) */
        updateStartInsertArray(i, startInsertArray, sparseMatrix1);
    }

}

/** 用于计算原始矩阵(三元组列表)中各个元素转置到新矩阵中时会在第几行,并依此计算在该元素所在的行在转置矩阵对象中的三元组列表的起始插入位置索引
* 应当配和updateStartInsertArray()方法,来确保当同一行的非零元素有多个时的情况能够正确计算索引并插入
* 当然这两个方法不是必须的,可以都嵌在translate方法中,这里只是为例划分逻辑*/
static int getPosition(int index, int[] startInsertArray, SparseMatrix sparseMatrix1) {
    return startInsertArray[sparseMatrix1.getTriple(index).getCol()-1];

}
/**更新插入到转置矩阵三元组列表中索引值(针对当同一行非零元素不知1个时所作出的索引引导修正)*/
static void updateStartInsertArray(int index, int[] startInsertArray, SparseMatrix sparseMatrix1) {
    startInsertArray[sparseMatrix1.tripleList.get(index).col-1]++;
}

}
/**三元组中各个非零元素的类*/
class Triple {
/**
 * 稀疏矩阵中的单个元素包含的信息
 */
int row; /* 横坐标 */
int col; /* 纵坐标 */
int data;/* 值 */

@Override
/**用于调试打印元素信息使用:*/
public String toString() {
    return "Triple{" +
            "row=" + row +
            ", col=" + col +
            ", data=" + data +
            '}';//+"注意这里坐标是从1计数";

}

/**javaBean 基本操作系列*/
public int getRow() {
    return this.row;
}

public void setRow(int row) {
    this.row = row;
}

public int getCol() {
    return this.col;
}

public void setCol(int col) {
    this.col = col;
}

public int getData() {
    return this.data;
}

public void setData(int data) {
    this.data = data;
}

}

/**稀疏矩阵整体的数据结构(保存的是压缩成三元组数组/列表的形式) */
class SparseMatrix {
public void setTripleList(ArrayList<Triple> tripleList) {
    this.tripleList = tripleList;
}

public void setNrow(int nrow) {
    this.nrow = nrow;
}

public void setNcol(int ncol) {
    this.ncol = ncol;
}

public void setNum(int num) {
    this.num = num;
}

ArrayList<Triple> tripleList = new ArrayList<>();
int nrow, /* 矩阵的行数 */
        ncol, /* 矩阵的列数 */
        num;

Triple getTriple(int index) {
    return tripleList.get(index);
}
/**解压打印稀疏矩阵(中的三元组列表),非静态方法不需要设置参数*/
void print(/*SparseMatrix sparseMatrix*/) {
    int[][] matrix = new int[nrow][ncol];

    /*填充数组*/
    for (int i = 0; i < num; i++) {
        Triple triple = getTriple(i);
        if (triple.getData() != 0) {
            matrix[triple.getRow()-1][triple.getCol()-1] = triple.getData();
        }
    }
    /*打印矩阵(二维数组)*/
    for (int i = 0; i < nrow; i++) {
        for (int j = 0; j < ncol; j++) {
            System.out.print(matrix[i][j]+" ");
        }
        System.out.println();
    }
}
}



辅助功能类(读入任意规格的矩阵)

/*
 * @Description:
 * @Version: 2.0
 * @Author: xuchaoxin
 * @Date: 2021-03-10 16:24:37
 * @LastEditors: xuchaoxin
 * @LastEditTime: 2021-03-10 17:35:31
 */

import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;

public class InputMatrix {
    /**
     * 读入矩阵(任意长度和宽度) 以两个连续的enter键来结束矩阵的输入
     */
    public static int[][] ReadMatrix() {
        /* 代开输入流(标准输入流) */
        Scanner sc = new Scanner(System.in);
        /* 建立二重列表 */
        List<List<String>> lists = new ArrayList<>();
        /* 定义一个字符串行 */
        String Line = null;
        /* 循环读入并处理每一行数据 */
        while ((Line = sc.nextLine()).length() != 0) {
            /**
             * 当读入的内容不为空(可以用字符串长度来判断),则读入并添加到list中
             */
            lists.add(Arrays.asList(Line.trim().split(" ")));
            /**
             * 这里String对象的 split()方法返回String[]数组
             * 将数组Line.trim().split()作为Arrays.asList()方法作为参数,返回一个List public static <T>
             * List<T> asList(T... a) { return new ArrayList<>(a); }
             */
        }
        /* 读取完毕,关闭输入流 */
        sc.close();
        /* 转为二维数组 */
        int m = lists.size();// 相当于行数
        int n = lists.get(0).size();// 相当于列数:lists.get(0)为第一行输入
        int[][] matrix = new int[m][n];
        // 将字符串变为int类型
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                matrix[i][j] = Integer.parseInt(lists.get(i).get(j));
            }
        }
        return matrix;
    }

    /**矩阵压缩*/
    public SparseMatrix getSparseMatrix(/*int[][] matrix*/) {
        int[][] matrix = ReadMatrix();
        int nrow = matrix.length;
        int ncol = matrix[0].length;
        SparseMatrix sparseMatrix = new SparseMatrix();
        int count = 0;
        /*注意,这里索引变量从0开始*/
        for (int i = 0; i < nrow; i++) {
            for (int j = 0; j < ncol; j++) {
                if (matrix[i][j] != 0) {
                    count++;
                    Triple triple = new Triple();
                    /*转化为从1开始计数的坐标(根据需要)*/
                    triple.setRow(i+1);
                    triple.setCol(j+1);
                    triple.setData(matrix[i][j]);
                    sparseMatrix.tripleList.add(triple);
                }
            }
        }
        sparseMatrix.setNum(count);
        sparseMatrix.setNrow(nrow);
        sparseMatrix.setNcol(ncol);
        return sparseMatrix;
    }

    // 打印验证
    public static void main(String[] args) {
//        int[][] matrix = ReadMatrix();
        System.out.println("test:...");
/*        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[0].length; j++) {
                System.out.print(matrix[i][j] + " ");
            }
            System.out.println("\n");
        }*/
        InputMatrix inputMatrix=new InputMatrix();
        inputMatrix.getSparseMatrix().print();

    }
}



编写和调试心得

我们应该明确的是,尽管调试很重要,但是不应当轻易,总是依赖于调试,容易造成编码的马虎,毕竟调试(这是很花时间的)

我们编码时一定要尽可能地准确,对于自己定义的任意变量或对象,乃至每一个函数和方法,都必须要有十分清晰的目的,容不得半点含糊,文档注释一定要跟上,这对于算法思路的检查乃至调试时都提供了遍历

这其中尤为重要的是同一度量体系(如果无法全局同一,也一定按模块同一,而且附加必要的计量系统/参考系说明)

比如二位数组/矩阵相关的计算问题中,单个元素的坐标从1计数,而放到二位数组中,下表从0开始计数,两个地方不要混淆.

应当养成描述清楚需求,再编写对应的函数的习惯

编写方法/函数

如果方法的用途比较复杂,不宜完全体现在方法名上

可以考虑简写,不过要记得把函数的用法文档写清楚

如果只是写小程序,则不要过多拘泥于将功能外包

可以通过代码功能分块(功能任务分配通过文档注释的方式来体现逻辑性

同样的,在编写一个大的方法也可以这么做

通过创建变量保存比较长的表达式,来减少重复计算,也可以是代码更加简洁直观,高效



版权声明:本文为xuchaoxin1375原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。