C++ 練氣期之二維數組與矩陣運算

1. 前言

C++中的一維數組可以存儲線性結構的數據,二維數組可以存儲平面結構的數據。如班上所有學生的各科目成績就有二個維度,學生姓名維度和科目成績維度。

這樣的表格數據可以使用二維數組進行存儲。

1.png

當需要存儲更多維度的數據時,可以使用多維數組

二維數組和矩陣的關係:

有些教材上,把二維數組和矩陣當成一回事,其實,兩者還是有區別的。

矩陣:

  • 矩陣(Matrix)是線性數學中的概念,是一個按照長方陣列排列的複數實數集合,最早用來描述方程組的係數常數資訊。
  • 因為矩陣是數學上的一個概念,要求矩陣必須是數字類型的數據。
  • 使用矩陣時,會把它當成一個整體看待。

數組:

  • 數組(Array)是電腦中的一個概念。二維數組是數組中的一種結構形式。
  • 數組除了可以存儲數字型數據,也能存儲非數字型數據。
  • 數組中的數據總是被當成個體來對待。

當使用電腦解決數學中與矩陣有關的問題時,可以藉助二維數組。所以說,二維數組是矩陣在電腦中的數字模型

下面將了解怎麼創建二維數組以及如何使用二維數組解決與矩陣有關的問題。

2. 創建二維數組

二維數組一維數組創建方式是一樣的,會有 2 種創建方案:

有關數組創建的細節,可以查閱與之相關的博文。

  • 靜態創建:如下創建了一個 33 列的二維數組
int nums[3][3];
  • 動態創建:動態創建的數組本質是指向指針的指針。如下語句,說明數組中保存的是指針(指向一個一維數組的地址)。
int **nums=new int*[3];

無論是靜態創建還是動態創建,都可以使用下標指針兩種訪問方式。

訪問二維數組中的數據之前,先要了解二維數組的記憶體模型結構。二維數組可以認為是一維數組一維數組,第一個一維數組中的每一個存儲單元格中都保存著一個一維數組的地址。

Tip:靜態和動態創建的數組,兩者在記憶體的存儲位置不一樣,但是模型結構是一樣。

15.png
使用下標訪問靜態數組中的數據,可以先在行上移動,然後再在列上移動。

#include <iostream>
using namespace std;
int main(int argc, char** argv) {
    //靜態創建數組
	int nums[5][5];
    //初始化第一個位置,也就是 0 行 0 列
    nums[0][0]=20;
	//訪問第一個位置
	cout<<"第一行第一列數據:"<<nums[0][0]<<endl;
	//遍歷整個數組
	cout<<"遍歷所有數據:"<<endl;
	for(int i=0; i<5; i++) {
		for(int j=0; j<5; j++) {
             //先賦值(值僅用來測試)
			nums[i][j]=i*j;
            //輸出
			cout<<nums[i][j]<<"\t";
		}
		cout<<endl;
	}
	return 0;
}

輸出結果:

第一行第一列數據:20
遍歷所有數據:
0       0       0       0       0
0       1       2       3       4
0       2       4       6       8
0       3       6       9       12
0       4       8       12      16

使用指針訪問靜態二維數組時。

#include <iostream>
using namespace std;
int main(int argc, char** argv) {
	int nums[5][5];
	//初始化第一行,第一列 
	**nums=20;
	//訪問某一個指定位置的數據
	cout<<"第一行第一列數據:"<<**nums<<endl;
	//遍歷整個數組
	cout<<"遍歷所有數據:"<<endl;
	for(int i=0; i<5; i++) {
		for(int j=0; j<5; j++) {
            // nums+i 讓行指針先移 ,確定行後, 再移動列指針。最終確定位置
			*(*(nums+i)+j)=i*j;
			cout<<*(*(nums+i)+j)<<"\t";
		}
		cout<<endl;
	}
	return 0;
}

動態數組創建後,系統只為第一個一維數組分配了存儲空間,需要編碼為一維數組的每一個存儲單元格創建一個一維數組。其它的無論是下標或指針訪問方式和靜態數組一樣。

#include <iostream>
using namespace std;
int main(int argc, char** argv) {
    //動態創建二維數組,說明一維數組中存儲的是地址。
	int **nums = new int *[5];
    //為一維數組的每一個存儲位置再創建一個一維數組(一維數組的一維數組)
	for (int i = 0; i < 5; i++) {
        //動態創建
		nums[i] = new int[5];
	}
	//下標、指針訪問都和靜態數組一樣
	nums[0][0] = 5;
	**nums=20;
    //使用動態方案創建的數組需要顯示刪除
	for (int i = 0 ; i < 5; ++i) {
		//此處的[]不可省略
		delete [] nums[i];
	}
	return 0;
}

3. 矩陣的基本運算

二維數組可以模擬矩陣,電腦中可以使用二維數組解決與矩陣相關的運算。

用於矩陣運算操作時,把二維數組當成一個整體,所以,運算的結果也會是一個二維數組。

3.1 加法運算

現假設有 AB 2 個矩陣。矩陣加法運算遵循下面的運算規則:

  • AB矩陣對應位置的數據進行相加。
  • 結果是一個新的矩陣 C

2.png

矩陣之間進行加法運算時,需滿足以下幾個要求:

  • AB 2 個矩陣的維度數據類型必須是相同的。
  • AB 2 個矩陣相加後的結果是矩陣C。此 3 個矩陣滿足: A+B=B+A(A+B)+C=A+(B+C)

編碼實現:

  • 初始化矩陣
#include <iostream>
using namespace std;
//矩陣 A
int **num_a=new int*[3];
//矩陣 B
int **num_b=new int*[3];
//矩陣 C
int **num_c=new int*[3];
//初始化數組
void initArrays() {
	//構建二維數組
	for(int i=0; i<3; i++) {
        // A,B,C 都是 3 行 3 列數組
		num_a[i]=new int[3];
		num_b[i]=new int[3];
		num_c[i]=new int[3];
	}
	//初始化二維數組
	for(int i=0; i<3; i++) {
		for(int j=0; j<3; j++) {
            //數據僅用測試
			*(*(num_a+i)+j)=i*j+4;
			*(*(num_b+i)+j)=i*j+2;
		}
	}
}
//輸出 A,B 中的數據
void outArrays() {
	//輸出數據
	cout<<"數組 A:"<<endl;
	for(int i=0; i<3; i++) {
		for(int j=0; j<3; j++) {
			cout<<*(*(num_a+i)+j)<<"\t";
		}
		cout<<endl;
	}
	//輸出數據
	cout<<"數組B:"<<endl;
	for(int i=0; i<3; i++) {
		for(int j=0; j<3; j++) {
			cout<<*(*(num_b+i)+j)<<"\t";
		}
		cout<<endl;
	}
}
  • 矩陣相加
//矩陣相加
void matrixAdd() {
	cout<<"矩陣相加:"<<endl;
	for(int i=0; i<3; i++) {
		for(int j=0; j<3; j++) {
            //A和B 相同位置的數據相加,保存在C中
			*(*(num_c+i)+j)=*(*(num_a+i)+j)+*(*(num_b+i)+j);
			cout<<*(*(num_c+i)+j)<<"\t";
		}
		cout<<endl;
	}
}
  • 驗證矩陣的 A+B=B+A(A+B)+C=A+(B+C)特性。
//驗證 A+B 是否等於 B+A。根據加法的特性,這個必然成立
void validate() {
	cout<<"A+B=:"<<endl;
	for(int i=0; i<3; i++) {
		for(int j=0; j<3; j++) {
            // A + B
			*(*(num_c+i)+j)=*(*(num_a+i)+j)+*(*(num_b+i)+j);
			cout<<*(*(num_c+i)+j)<<"\t";
		}
		cout<<endl;
	}
	cout<<"B+A=:"<<endl;
	for(int i=0; i<3; i++) {
		for(int j=0; j<3; j++) {
            //B+A
			*(*(num_c+i)+j)=*(*(num_b+i)+j)+*(*(num_a+i)+j);
			cout<<*(*(num_c+i)+j)<<"\t";
		}
		cout<<endl;
	}
}
//驗證 (A+B)+C 是否等於 A+(B+C)
void validate_() {
	//(A+B)+C
	cout<<"(A+B)+C=:"<<endl;
	for(int i=0; i<3; i++) {
		for(int j=0; j<3; j++) {
			//因為:A + B=C 所以,(A+B)+C=C+C 
			cout<<num_c[i][j]+num_c[i][j]<<"\t";
		}
		cout<<endl;
	}
	//計算 B+C,且把結果保存在臨時數組中
	int **tmp=new int*[3];
	for(int i=0; i<3; i++) {
		tmp[i]=new int[3];
		for(int j=0; j<3; j++) {
			//B+C
			tmp[i][j]=num_b[i][j]+num_c[i][j];
		}
		cout<<endl;
	}
	//再計算:A+(B+C)
	cout<<"A+(B+C)=:"<<endl;
	for(int i=0; i<3; i++) {
		for(int j=0; j<3; j++) {
			//B+C
			cout<<num_a[i][j]+tmp[i][j]<<"\t";
		}
		cout<<endl;
	}
}
  • 測試:
int main(int argc, char** argv) {
	initArrays();
	outArrays();
	//矩陣相加
	matrixAdd();
	//A+B
	validate();
	//(A+B)+C=A+(B+C)
	validate_();
	return 0;
}

輸出結果:

數組A:
4       4       4
4       5       6
4       6       8
數組B:
2       2       2
2       3       4
2       4       6
A+B=:
6       6       6
6       8       10
6       10      14
A+B=:
6       6       6
6       8       10
6       10      14
B+A=:
6       6       6
6       8       10
6       10      14
(A+B)+C=:
12      12      12
12      16      20
12      20      28
A+(B+C)=:
12      12      12
12      16      20
12      20      28

從上述結果中,可以看出 (A+B)+C 是等於 A+(B+C)

3.2 減法運算

矩陣相減矩陣相加一樣,把A、B 2 個矩陣對應位置的數字相減,最終生成一個新矩陣C。且維度和數據類型需要保持相同

三者滿足數學上的減法規律:

  • A-B=C

  • A=B+C

  • A-C=B

    如下所示:

3.png

編碼實現:

  • 矩陣相減函數
//矩陣相減 
void matrixJian() {
	cout<<"A-B=:"<<endl;
	for(int i=0; i<3; i++) {
		for(int j=0; j<3; j++) {
            //A B 相同位置數據相減,結果保存在 C中
			*(*(num_c+i)+j)=*(*(num_a+i)+j)-*(*(num_b+i)+j);
            //輸出C
			cout<<*(*(num_c+i)+j)<<"\t";
		}
		cout<<endl;
	}
}
  • 驗證A=C+B
//驗證 A=B+C 
void validate01() {
	cout<<"B+C=:"<<endl;
	for(int i=0; i<3; i++) {
		for(int j=0; j<3; j++) {
			int tmp=*(*(num_b+i)+j)+*(*(num_c+i)+j);
			cout<<tmp<<"\t";
		}
		cout<<endl;
	}
}
  • 測試程式碼。
int main(int argc, char** argv) {
	//初始化數組
    initArrays();
    //輸出數組
	outArrays();
    //矩陣相減
	matrixJian();
    //驗證A=B+C
	validate01();
    return 0;
}

輸出結果:

數組A:
4       4       4
4       5       6
4       6       8
數組B:
2       2       2
2       3       4
2       4       6
A-B=:
2       2       2
2       2       2
2       2       2
B+C=:
4       4       4
4       5       6
4       6       8

3.3 數乘運算

數乘指讓矩陣乘以一個數字。

數乘規則:讓此數字和矩陣的每一個數字相乘,最終生成一個新的矩陣。如下圖所示:

4.png

矩陣的數乘遵循如下的數學上的乘法運算規律。

  • a(bA)=b(aA)
  • a(bA)=(ab)A
  • (a+b)A=aA+bA
  • a(A+B)=aA+aB

小寫 ab 代表 2 個乘數。大寫 A、B代表 2 個矩陣。

編碼實現數乘:

//矩陣相乘
void matrixmultiply(){
	cout<<"2XA=:"<<endl;
	for(int i=0; i<3; i++) {
		for(int j=0; j<3; j++) {
			*(*(num_c+i)+j)=*(*(num_a+i)+j)*2;
			cout<<*(*(num_c+i)+j)<<"\t";
		}
		cout<<endl;
	}
} 

測試:

int main(int argc, char** argv) {
	//初始化數組
	initArrays();
    //輸出數組
	outArrays();
    //矩陣相乘
	matrixmultiply();
}

輸出結果:

數組A:
4       4       4
4       5       6
4       6       8
數組B:
2       2       2
2       3       4
2       4       6
2XA=:
8       8       8
8       10      12
8       12      16

矩陣的加減法和矩陣的數乘合稱為矩陣的線性運算。

3.3 轉置運算

把矩陣A的行和列互相交換所產生的矩陣稱為A轉置矩陣,這一過程稱為矩陣的轉置。轉置用大寫字母T表示。如下圖所示:

6.png

矩陣的轉置遵循以下的運算規律:

  • 轉置後再轉置,相當於沒有轉置。
  • 數乘後轉置和數字乘以轉置後的矩陣結果一樣。
  • 矩陣相乘後轉置和轉置後再相乘的結果一樣。

7.png

編碼實現:

設有一矩陣為 m×n 階(即 m 行 n 列),第 ij 列的元素是 a(i,j),需要將該矩陣轉置為 n×m階的矩陣,使其中元素滿足 b(j,i)=a(i,j)

#include <iostream>
using namespace std;
//數組A為 3 行 2 列
int **num_a=new int*[3];
//數組A轉置後的結果
int **num_b=new int*[2];
//輸出
void outArrays() {
	//輸出數據
	cout<<"數組A:"<<endl;
	for(int i=0; i<3; i++) {
		for(int j=0; j<2; j++) {
			cout<<*(*(num_a+i)+j)<<"\t";
		}
		cout<<endl;
	}
	//輸出數據
	cout<<"數組B:"<<endl;
	for(int i=0; i<2; i++) {
		for(int j=0; j<3; j++) {
			cout<<*(*(num_b+i)+j)<<"\t";
		}
		cout<<endl;
	}
}

//初始化數組
void initArrays() {
	//構建  3 行 2 列的二維數組A
	for(int i=0; i<3; i++) {
		num_a[i]=new int[2];
	}
	//構建 2 行 3 列的二維數組B
	for(int i=0; i<2; i++) {
		num_b[i]=new int[3];
	}
	//初始化二維數組A
	for(int i=0; i<3; i++) {
		for(int j=0; j<2; j++) {
			*(*(num_a+i)+j)=i*(j+1)+4;
		}
	}
}
//轉置數組A,轉置後的數據保存在 B 中
void  matrixTranspose() {
	for(int i=0; i<3; i++) {
		for(int j=0; j<2; j++) {
			num_b[j][i]=num_a[i][j];
		}
	}
}
int main(int argc, char** argv) {
	//初始化數組
	initArrays();
	//轉置A
	matrixTranspose();
	//輸出
	outArrays(); 
	return 0;
}

輸出結果:

數組A:
4       4
5       6
6       8
數組B(B是的轉置矩陣):
4       5       6
4       6       8

如果矩陣A和其轉置矩陣B相等,則稱A為對稱矩陣。

16.png

3.4 共軛運算

矩陣的共軛定義為:一個2×2複數矩陣的共軛(實部不變,虛部取負)如下所示:

8.png

C++內置有complex頭文件,提供有計算複數的共軛函數:

#include <iostream>
#include <complex>
using namespace std;
int main() {
	complex<double> cs[2][2]= {{complex<double> (3,1),complex<double> (5,0)},
		{complex<double> (2,-2),complex<double> (0,1)}
	};
	complex<double> cs_[2][2] ;
	//原矩陣
	cout<<"原矩陣"<<endl;
	for(int i=0; i<2; i++) {
		for(int j=0; j<2; j++) {
			cout<<cs[i][j]<<"\t";
		}
		cout<<endl;
	}
	for(int i=0; i<2; i++) {
		for(int j=0; j<2; j++) {
			//對原矩陣中的複數進行共軛運算 
			cs_[i][j]=	conj(cs[i][j]);
		}
	}
    //輸出原矩陣的共軛矩陣
	cout<<"原矩陣的共軛矩陣:"<<endl; 
	for(int i=0; i<2; i++) {
		for(int j=0; j<2; j++) {
			cout<<cs_[i][j]<<"\t";
		}
		cout<<endl;
	}
}

輸出結果:

原矩陣
(3,1)   (5,0)
(2,-2)  (0,1)
原矩陣的共軛矩陣:
(3,-1)  (5,-0)
(2,2)   (0,-1)

3.5 共軛轉置

共軛轉置顧名思義,共軛後再轉置。

矩陣的共軛轉置定義為:9.png,也可以寫為:10.png。或者寫為11.png

一個2×2複數矩陣的共軛轉置如下所示:

12.png

3.6 乘法運算

兩個矩陣的乘法僅當第一個矩陣A的列數和另一個矩陣B的行數相等時才能運算。

如果m×n矩陣An×p的矩陣B相乘,它們的乘積C是一個m×p矩陣,它的一個元素:

13.png

並將此乘積記為:C=AB

14.png

矩陣的乘法滿足以下運算規律:

  • 結合律:(AB)C=A(BC)

  • 左分配律:(A+B)C=AC+BC

  • 右分配律:C(A+B)=CA+CB

矩陣乘法不滿足交換律。

編碼實現:

#include <iostream>
using namespace std;
//數組A 為 3 行 2 列
int **num_a=new int*[3];
//數組 B為 2行3列 數組B的行數和A數組的列數相同
int **num_b=new int*[2];
//C 保存 A 乘以 B 後的結果, 3 行 3 列 
int **num_c=new int*[3];
//輸出
void outArrays() {
	//輸出數據 3 行 2 列
	cout<<"數組A:"<<endl;
	for(int i=0; i<3; i++) {
		for(int j=0; j<2; j++) {
			cout<<*(*(num_a+i)+j)<<"\t";
		}
		cout<<endl;
	}
	//輸出數據
	cout<<"數組B:"<<endl;
	for(int i=0; i<2; i++) {
		for(int j=0; j<3; j++) {
			cout<<*(*(num_b+i)+j)<<"\t";
		}
		cout<<endl;
	}
}

//初始化數組
void initArrays() {
	for(int i=0; i<3; i++) {
        //構建  3 行 2 列的二維數組A
		num_a[i]=new int[2];
        //構建 3 行 3 列的二維數組C
		num_c[i]=new int[3];
	}
	//構建 2 行 3 列的二維數組B
	for(int i=0; i<2; i++) {
		num_b[i]=new int[3];
	}
	//初始化二維數組A
	for(int i=0; i<3; i++) {
		for(int j=0; j<2; j++) {
            //測試數據
			*(*(num_a+i)+j)=i*(j+1)+4;
		}
	}
    //初始化二維數組 B
	for(int i=0; i<2; i++) {
		for(int j=0; j<3; j++) {
            //測試數據
			*(*(num_b+i)+j)=i*(j+2)+3;
		}
	}
    //初始化二維數組 C
	for(int i=0; i<3; i++) {
		for(int j=0; j<3; j++) {
			num_c[i][j]=0;
		}
	}
}
//矩陣相乘 A X B
void matrixCheng() {
    // i 表示A的行號
	for(int i=0; i<3; i++) {
	   //C表示 C  的列號
        int c=0;
        // k 表示 B  的列號,有多少列,乘多少次
		for(int k=0; k<3; k++) {
            // A 的列數和 B 的行數(兩者是相等的)
			for(int j=0; j<2; j++) {
                //A 第一行的數據乘以 B 每一列的數據
				num_c[i][c]+= num_a[i][j]*num_b[j][k];
			}
			c++;
		}
	}
	cout<<"AXB="<<endl;
    //輸出 C 
	for(int i=0; i<3; i++) {
		for(int j=0; j<3; j++) {
			cout<<num_c[i][j]<<"\t";
		}
		cout<<endl;
	}
}

int main(int argc, char** argv) {
	//初始化數組
	initArrays();
	//輸出
	outArrays();
	matrixCheng();
	return 0;
}

輸出結果:

數組A:
4       4
5       6
6       8
數組B:
3       3       3
5       6       7
AXB=
32      36      40
45      51      57
58      66      74

4. 總結

站在數學角度,矩陣有很多特性,本文通過二維數組初窺矩陣相關問題。讓大家對二維數組和矩陣有一個大致的理解。