1、正互反矩陣
首先說一下什么是正互反矩陣,見下圖,一看圖其實就知道什么是正互反矩陣。

1.1 使用場景
當我們現在有一堆參數,分了好幾個層次,每個層次里面又有好多參數,那么每個層次的每個參數權重如何設定,這時候,會用到這種類型的矩陣。為方便理解,可以將矩陣A看成下面的表格

- 對角線元素:自己和自己相比,同等重要,所以都為1
- A[0][1]:參數2比參數1重要,所以是2(或其他的數字,這是有標准的)
- A[0][2]:參數3和參數1相比,參數3重要,同時也比參數2重要,所以參數3的重要程度取值至少要比A[0][1]的值大
至於比較兩個參數之間的重要程度的取值,自行尋找標准。下面是標准之一:1-9標度法
| 相對重要性 | 定義 | 說明 |
|---|---|---|
| 1 | 同等重要 | 兩個目標同樣重要 |
| 3 | 略微重要 | 經驗或判斷,認為一個目標比另一個略微重要 |
| 5 | 相當重要 | 經驗或判斷,認為一個目標比另一個重要 |
| 7 | 明顯重要 | 深感一個目標比另一個重要,並且這種重要性已經有實踐證明 |
| 9 | 絕對重要 | 強烈的感到一個目標比另一個重要的多 |
| 2,4,6,8 | 兩個相鄰判斷的中間值 | 折中時采用 |
1.2 矩陣計算
https://wenku.baidu.com/view/d70e0c746cdb6f1aff00bed5b9f3f90f76c64df3.html
可以使用平常手算時的那種計算方式,通過|λE-A| = 0得到特征值,再代入(λE-A)中求解該特征值的特征向量。其中,我們只用到最大特征值、及其特向。
這種算法很精確,但是很麻煩,(如果你要是用Matlab當我沒說。。。),由於參數之間的重要程度關系,都是由經驗判斷,主觀性很強,所以對精度要求其實不高,大概即可,故有三種快速計算最大特征值及其特向的方法:根法、和法、冪法
2 計算
三個方法公共的代碼
/**
* 計算方陣的最大特征根及其特征向量
*
* @author silverbeats
* @date 2021/8/13 013 20:26
*/
public class MatrixUtil {
// 保留小數的位數
private final int BIT = 6;
/**
* 矩陣乘法
*
* @param a 矩陣a
* @param b 矩陣b
*/
public double[][] matrixMult(double[][] a, double[][] b) {
int arows = a.length,
acols = a[0].length,
brows = b.length,
bcols = b[0].length;
// 判斷兩個矩陣是否能夠進行相乘
if (acols != brows) {
throw new RuntimeException("矩陣a的列數與矩陣b的行數不等,無法進行矩陣相乘");
}
// 保存矩陣相乘的結果
double[][] res = new double[arows][bcols];
for (int i = 0; i < bcols; ++i) {
// j,k 定位行乘列
for (int j = 0; j < arows; ++j) {
double sum = 0.0;
for (int k = 0; k < acols; ++k) {
sum += a[j][k] * b[k][i];
}
res[j][i] = sum;
}
}
return res;
}
/**
* 矩陣的列進行歸一化,某元素在其所在列的比例
*
* @param matrix
*/
public void normalizedColumn(double[][] matrix) {
int rows = matrix.length,
cols = matrix[0].length,
row, col;
// 求出矩陣每一列之和
double[] temp = new double[cols];
for (col = 0; col < cols; ++col) {
for (row = 0; row < rows; ++row) {
temp[col] += matrix[row][col];
}
}
// 對matrix進行處理,得出每個元素在其所在列的占比
for (row = 0; row < rows; ++row) {
for (col = 0; col < cols; ++col) {
matrix[row][col] /= temp[col];
}
}
}
/**
* 合並矩陣的列,將m x n的矩陣變為m x 1的矩陣
*
* @param matrix
*/
public double[][] mergeColumn(double[][] matrix) {
int rows = matrix.length, cols = matrix[0].length;
double[][] res = new double[rows][1];
for (int row = 0; row < rows; ++row) {
double sum = 0.0;
for (int col = 0; col < cols; ++col) {
sum += matrix[row][col];
}
res[row][0] = sum;
}
return res;
}
/**
* 合並矩陣的行,將m x n矩陣變為1 x n矩陣
*
* @param matrix
*/
public double[][] mergeRow(double[][] matrix) {
int rows = matrix.length, cols = matrix[0].length;
double[][] res = new double[1][cols];
for (int col = 0; col < cols; ++col) {
for (int row = 0; row < rows; ++row) {
res[0][col] += matrix[row][col];
}
}
return res;
}
/**
* 保留指定個數的小數
*
* @param number 數字
*/
public double toFixed(double number) {
double temp = Math.pow(10, BIT);
return Math.round(number * temp) / temp;
}
/**
* arr是一維的最大特征向量,
* 由於最大特征向量是一個n x 1的二維矩陣,會轉換為一維數組
* 易知特向的每一個元素以及最大特征根是double類型,會對其保留BIT位的小數
* 存在四舍五入后,最大特征根的特向元素之和不為1的情況,可能會多或少Math.pow(10, -BIT)
* 這里做的處理:將這個誤差給到權重最小的參數身上,保證所有參數的權重和為1
*
* @param arr
*/
public void correction(double[] arr) {
int length = arr.length;
double bei = Math.pow(10, BIT);
double sum = 0.0;
for (double v : arr) {
sum = sum + v * bei;
}
// 四舍五入后沒問題
if (sum == bei) return;
// 誤差
double err = sum - bei;
// 定位到權重最小值的參數位置
int minRow = 0;
for (int i = 1; i < length; ++i) {
if (arr[i] < arr[minRow]) {
minRow = i;
}
}
arr[minRow] -= err / bei;
}
/**
* 二維數組轉一維數組
*
* @param matrix
*/
public double[] convertData(double[][] matrix) {
int rows = matrix.length, cols = matrix[0].length;
int arrLen = rows * cols;
double[] res = new double[arrLen];
int index = 0;
for (int row = 0; row < rows; ++row) {
for (int col = 0; col < cols; ++col) {
res[index++] = matrix[row][col];
}
}
return res;
}
}
2.1 根法

/**
* 計算方陣的最大特征根及其特征向量
*
* @author silverbeats
* @date 2021/8/13 013 20:26
*/
public class MatrixUtil {
/**
* 根法求解矩陣matrix最大特征根、及其特征向量
*
* @param matrix
*/
public Map<String, Object> rootMethod(double[][] matrix) {
if (matrix.length != matrix[0].length) {
throw new RuntimeException("必須是方陣");
}
// 保存結果用的
Map<String, Object> resultMap = new HashMap<>();
final int DIM = matrix.length;
// 1、計算權重向量(最大特征根的特向)
// 1.1 矩陣有DIM行,同行的元素進行相乘,之后再開DIM根號,得到DIM x 1矩陣
double[][] w = new double[DIM][1];
for (int row = 0; row < DIM; ++row) {
double mult = 1.0;
for (int col = 0; col < DIM; ++col) {
mult *= matrix[row][col];
}
w[row][0] = Math.pow(mult, 1.0 / DIM);
}
// 1.2 將w矩陣進行歸一化,得到的就是權重向量
normalizedColumn(w);
// 2、計算最大特征根
// 2.1 將matrix按行進行合並相加
double[][] s = mergeRow(matrix);
// 2.2 s x w 的結果即為最大特征值,s是1 x DIM矩陣,w是DIM x 1矩陣,得到1 x 1矩陣
double maxEigenvalue = matrixMult(s,w)[0][0];
// 3、可選:將DIM x 1矩陣轉一維,對數據進行小數保留,以及保留后權重和不為1的處理
double[] maxEigenVector = convertData(w);
for (int i = 0; i < maxEigenVector.length; i++) {
maxEigenVector[i] = toFixed(maxEigenVector[i]);
}
// 4、易知權重和為1,然而在經過小數保留后,可能會導致權重和不為1,進行處理
correction(maxEigenVector);
// --------------------------------------------------
resultMap.put("maxEigenvalue", toFixed(maxEigenvalue));
resultMap.put("maxEigenVector", maxEigenVector);
return resultMap;
}
}
2.2 和法


public class MatrixUtil {
/**
* 和法求解矩陣matrix的最大特征根、及其特征向量
*
* @param matrix
*/
public Map<String, Object> sumMethod(double[][] matrix) {
if (matrix.length != matrix[0].length) {
throw new RuntimeException("必須是方陣");
}
Map<String, Object> resultMap = new HashMap<>();
final int DIM = matrix.length;
// 判斷矩陣w,拷貝一份matrix,目前與matrix一致,后面會對w進行修改
double[][] w = new double[DIM][DIM];
for (int row = 0; row < DIM; ++row) {
for (int col = 0; col < DIM; ++col) {
w[row][col] = matrix[row][col];
}
}
// 1、權重向量(最大特征根的特向)
// 1.1 將矩陣的每一列進行歸一化處理,得到判斷矩陣w
normalizedColumn(w);
// 1.2 判斷矩陣w的所有列進行相加,變成n x 1的矩陣后進行歸一化處理
// 此時得到的n x 1矩陣就是matrix最大特征根的特征向量
double[][] t = mergeColumn(w);
normalizedColumn(t);
// 2、計算最大特征根
// 2.1 將原矩陣matrix(m x m)與最大特向t(m x 1)進行矩陣乘法(m x 1)
double[][] mx = matrixMult(matrix, t);
// 2.2 將mx和maxEigenVector這兩個m x 1的列矩陣對應位置進行相除
for (int row = 0; row < DIM; ++row) {
mx[row][0] /= t[row][0];
}
// 2.3 將mx這一列矩陣的所有元素求和,最后取個均值就是最大特征根
double maxEigenValue = 0.0;
for (int row = 0; row < DIM; ++row) {
maxEigenValue += mx[row][0];
}
maxEigenValue /= DIM;
// 3、可選:對數據進行小數保留,以及保留后權重和不為1的處理
double[] maxEigenVector = convertData(t);
for (int i = 0; i < maxEigenVector.length; i++) {
maxEigenVector[i] = toFixed(maxEigenVector[i]);
}
correction(maxEigenVector);
// --------------------------------------------------
resultMap.put("maxEigenvalue", toFixed(maxEigenValue));
resultMap.put("maxEigenVector", maxEigenVector);
return resultMap;
}
}
2.3 冪法

public class MatrixUtil {
/**
* 冪法
*
* @param matrix 矩陣
* @param times 最大迭代次數
* @param accept 可接受的誤差
*/
public Map<String, Object> powMethod(double[][] matrix, int times, double accept) {
final int DIM = matrix.length;
// 1、初始化
// 1.1 初始正列向量Xk,用來迭代(k代表是第幾次迭代)
// 1.2 初始化mk(mk是Xk這個列向量中最大的值)和yk(Xk/mk)
double mk = 1.0;
double[][] Xk = new double[DIM][1];
double[][] yk = new double[DIM][1];
for (int i = 0; i < DIM; ++i) {
yk[i][0] = Xk[i][0] = 1.0;
}
// 2、迭代計算
int cnt = 0;
while(true) {
double oldMk = mk;
// 2.1 迭代Xk
Xk = matrixMult(matrix, yk);
// 2.2 更新mk
mk = Xk[0][0];
for (int i = 1; i < DIM; ++i) {
if(Xk[i][0] > mk)
mk = Xk[i][0];
}
// 2.3 迭代yk
for (int i = 1; i < DIM; ++i) {
yk[i][0] = Xk[i][0] / mk;
}
++cnt;
// 2.4 精度檢查,accept是可接受的精度誤差
// 2.5 迭代次數檢查,times是最大迭代次數
if(Math.abs(mk - oldMk) < accept || cnt >= times)
break;
}
// 3、求最大特征值,以及特向
// 3.1 最大特征值: 即mk
// 3.2 特向:對yk進行歸一化后即為所求
normalizedColumn(yk);
// 4、可選:對mk和yk進行小數取舍
double[] maxEigenVector = convertData(yk);
for(int i = 0; i < DIM; ++i) {
maxEigenVector[i] = toFixed(maxEigenVector[i]);
}
correction(maxEigenVector);
// ---------------------------------------------
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("maxEigenvalue", toFixed(mk));
resultMap.put("maxEigenVector", maxEigenVector);
return resultMap;
}
}
2.4 測試
public static void main(String[] args) {
double[][] arr = {
{1, 2, 6},
{0.5, 1, 4},
{1 / 6.0, 0.25, 1}
};
int maxTimes = 10000;
double accept = 1e-7;
MatrixUtil matrixUtil = new MatrixUtil();
Map<String, Object> rootMethodResultMap = matrixUtil.rootMethod(arr);
Map<String, Object> sumMethodResultMap = matrixUtil.sumMethod(arr);
Map<String, Object> powMethodResultMap = matrixUtil.powMethod(arr, maxTimes, accept);
System.out.println("----根法----");
rootMethodResultMap.forEach((k, v) -> {
System.out.println(k);
if (v instanceof double[])
System.out.println(Arrays.toString((double[]) v));
else
System.out.println(v);
});
System.out.println("----和法----");
sumMethodResultMap.forEach((k, v) -> {
System.out.println(k);
if (v instanceof double[])
System.out.println(Arrays.toString((double[]) v));
else
System.out.println(v);
});
System.out.println("----冪法----");
powMethodResultMap.forEach((k, v) -> {
System.out.println(k);
if (v instanceof double[])
System.out.println(Arrays.toString((double[]) v));
else
System.out.println(v);
});
}

