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

一枚大果殼發表於2022-07-05

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. 總結

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

相關文章