آشنایی با مفهوم آرایه دوبعدی (ماتریس) و نحوه تعریف و مقداردهی آن
درک شرطهای قطر اصلی (i==j) و قطر فرعی (i+j==n-1) برای ماتریس
پیمایش ماتریس با حلقه تودرتو و چاپ الگوهای مثلثی
پیادهسازی الگوریتمهای جمع قطر، یافتن ماکزیمم هر سطر و ماتریس مثلثی
آرایه دوبعدی چیست؟
آرایه دوبعدی یا ماتریس، آرایهای از آرایهها است که برای نمایش دادههای جدولی استفاده میشود. هر عنصر با دو اندیس مشخص میشود: اندیس سطر (row) و اندیس ستون (column).
مثلاً int a[3][4] یک ماتریس با ۳ سطر و ۴ ستون است که در مجموع ۱۲ خانه دارد. در حافظه، این خانهها بهصورت سطری پشتسرهم ذخیره میشوند.
قطر فرعی یا ضد قطر (Anti/Secondary Diagonal) از گوشهی بالا-راست به پایین-چپ میرود. شرط آن: i + j == n - 1 (وقتی اندیس از ۰ شروع میشود).
main.cppcpp
نکته: اگر اندیسها از ۱ شروع شوند، شرط قطر فرعی i + j == n + 1 میشود. (این همان فرمولی است که در درس حلقههای تودرتو هم دیدیم.)
جمع عناصر قطر اصلی
برای جمع عناصر قطر اصلی، یک حلقه تکی کافی است: خانههای a[0][0], a[1][1], ..., a[n-1][n-1] را پیمایش میکنیم.
مجموع خانههایی که i == jcpp
$خروجی برنامه
Trace (sum of main diagonal) = 15
در حال باز کردن ویرایشگر…
جمع عناصر قطر فرعی
برای قطر فرعی، خانههای a[0][n-1], a[1][n-2], ..., a[n-1][0] را پیمایش میکنیم. شرط کلی: i + j == n - 1.
مجموع خانههایی که i + j == n - 1cpp
$خروجی برنامه
Secondary diagonal sum = 15
در حال باز کردن ویرایشگر…
چاپ ماتریس با قطر اصلی برجسته
این الگو دید بصری از قطر اصلی ماتریس میدهد.
روی قطر اصلی * و بقیه . چاپ میکنیمcpp
$خروجی برنامه
*....
.*...
..*..
...*.
....*
در حال باز کردن ویرایشگر…
ماتریس مثلثی بالایی
یک ماتریس مثلثی بالایی (Upper Triangular) فقط خانههایی را پر میکند که i <= j (یعنی روی قطر اصلی یا بالاتر از آن).
فقط خانههای بالا-قطر اصلی پر میشوندcpp
$خروجی برنامه
****
***
**
*
در حال باز کردن ویرایشگر…
مثلث خیام–پاسکال
کلاسیکترین مثال «پر کردن ماتریس با یک قاعده» مثلث خیام–پاسکال است (صفحه ۴۱ جزوه): اولین خانهی هر سطر ۱ است و هر خانهی داخلی برابر مجموع دو خانهی بالایی خودش است — a[i][j] = a[i-1][j-1] + a[i-1][j]. چون بقیهی خانهها صفر میمانند، این جمع هیچوقت از محدوده خارج نمیشود.
هر خانه = مجموع دو خانهی بالایی آن (صفحه ۴۱ جزوه)cpp
$خروجی برنامه
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
در حال باز کردن ویرایشگر…
فلوچارت پیمایش ماتریس
گامهای پیمایش و چاپ یک ماتریس n×m:
flow.svg
یافتن ماکزیمم هر سطر
برای یافتن ماکزیمم هر سطر، در هر تکرار حلقه بیرونی، یک متغیر maxInRow را با a[i][0] مقداردهی میکنیم و در حلقه داخلی بهروزرسانی میکنیم.
ماکزیمم محلی در هر سطرcpp
$خروجی برنامه
Max of row 0 = 7
Max of row 1 = 9
Max of row 2 = 11
در حال باز کردن ویرایشگر…
اشتباه جزوه: VLA دوبعدی
مانند آرایه یکبعدی، در C++ استاندارد تعریف ماتریس با اندازهی متغیر (int a[n][m] پس از cin >> n >> m) مجاز نیست. اگر اندازهی حداکثر از قبل معلوم است از const int استفاده کنید؛ در غیر این صورت از vector<vector<int>>.
نسخه اشتباه جزوه
کد دارای باگcpp
نسخه اصلاحشده و استاندارد
کد صحیحcpp
ترتیب ذخیرهسازی سطری (Row-Major)
در C++ آرایههای دوبعدی بهصورت Row-Major Order در حافظه ذخیره میشوند، یعنی ابتدا تمام عناصر سطر اول، سپس سطر دوم، و به همین ترتیب. به همین دلیل، برای پیمایش کارآمد حافظه (کش شدن بهتر CPU)، حلقه بیرونی باید روی سطر و حلقه داخلی روی ستون باشد: for i ... for j .... اگر برعکس بنویسید (for j ... for i ...)، به ازای هر دسترسی، کش misses اتفاق میافتد و برنامه کندتر میشود.
هنگام ارسال آرایه دوبعدی به تابع
در C++، وقتی آرایه دوبعدی را به تابع میفرستید، بعد دوم باید ثابت و مشخص باشد. مثلاً تابعی که int a[3][4] میگیرد باید امضایش void f(int a[][4], int rows) باشد. دلیل: کامپایلر برای محاسبهی a[i][j] باید اندازهی ستون را بداند. اگر ابعاد متغیر هستند، از vector<vector<int>> استفاده کنید که این محدودیت را ندارد.
خلاصه و کاربردها
آرایههای دوبعدی پایهی بسیاری از الگوریتمها هستند: ماتریس همانی، ماتریس مثلثی، ضرب ماتریس، مسیریابی در نقشه، شبیهسازیهای فیزیکی، بازیهای جدولی (شطرنج، مینروب، تیکتکتو) و پردازش تصویر. در درسهای بعد با مرتبسازیها و ماتریسهای پیشرفتهتر آشنا میشویم.
برنامهای بنویسید که یک ماتریس n×n را بخواند و حاصلجمع هر دو قطر اصلی و فرعی را در صورت فرد بودن n چاپ کند (اگر n فرد باشد، خانهی وسط در هر دو قطر مشترک است و نباید دو بار شمرده شود).
نمایش راهنما
ابتدا قطر اصلی را جمع کنید، سپس قطر فرعی را به آن اضافه کنید ولی اگر `i == n-1-i` است، آن خانه را دو بار نفرمایید.
02
تمرین•حل کامل دارد
برنامهای بنویسید که یک ماتریس ۳×۳ را بخواند و تعیین کند آیا ماتریس **همانی** است یا خیر (همانی: قطر اصلی همه ۱ و بقیه ۰).
نمایش راهنما
بررسی کنید `a[i][j]` در صورت `i==j` باید ۱ و در غیر این صورت ۰ باشد.
03
تمرین•حل کامل دارد
برنامهای بنویسید که یک ماتریس n×n را بخواند و **ترانهاده** (Transpose) آن را چاپ کند (یعنی سطرها و ستونها را جابجا کند).
نمایش راهنما
در ترانهاده، عنصر `(i,j)` ماتریس اصلی برابر `(j,i)` ترانهاده است. یعنی در چاپ، حلقه داخلی روی i و بیرونی روی j را عوض کنید.
04
تمرین•حل کامل دارد
برنامهای بنویسید که یک ماتریس n×n را بخواند و یک **ماتریس مثلثی پایینی** بسازد: فقط خانههای `i >= j` را نگه دارد و بقیه را صفر کند. سپس ماتریس را چاپ کند.
نمایش راهنما
در حلقه، اگر `i < j` بود `a[i][j] = 0` کنید.
// نکات-کلیدی۶
01آرایه دوبعدی (ماتریس) با دو اندیس سطر و ستون کار میکند و در حافظه بهصورت سطری (Row-Major) ذخیره میشود.
02قطر اصلی ماتریس مربعی n×n با شرط `i == j` مشخص میشود.
03قطر فرعی با شرط `i + j == n - 1` (وقتی اندیس از ۰ شروع شود) یا `i + j == n + 1` (وقتی از ۱ شروع شود).
04برای پیمایش کارآمد ماتریس، حلقه بیرونی روی سطر و حلقه داخلی روی ستون باشد.
05تعریف ماتریس با اندازهی متغیر (VLA) غیراستاندارد است؛ از `const int` یا `vector<vector<int>>` استفاده کنید.
06هنگام ارسال ماتریس به تابع، بعد دوم باید ثابت و مشخص باشد.
گام بعدی پیشنهادی
مرتبسازی: Bubble Sort و Selection Sort
دو الگوریتم کلاسیک مرتبسازی آرایهها به همراه پیادهسازی صحیح و تحلیل پیچیدگی
1#include<iostream>2usingnamespace std;34intmain(){5// روش ۱: مقداردهی کامل6int a[2][3]={{1,2,3},{4,5,6}};78// روش ۲: مقداردهی جزئی (بقیه صفر)9int b[3][3]={{1,2},{3}};// چند خانه صفر میشود1011// روش ۳: همهی خانهها صفر12int c[3][3]={0};1314// چاپ ماتریس a15for(int i =0; i <2; i++){16for(int j =0; j <3; j++)17 cout << a[i][j]<<' ';18 cout << endl;19}20return0;21}
1#include<iostream>2usingnamespace std;34intmain(){5constint ROW =3, COL =3;6int a[ROW][COL];78 cout <<"Enter 9 numbers: "<< endl;9for(int i =0; i < ROW; i++)10for(int j =0; j < COL; j++)11 cin >> a[i][j];1213 cout <<"Matrix:"<< endl;14for(int i =0; i < ROW; i++){15for(int j =0; j < COL; j++)16 cout << a[i][j]<<'\t';17 cout << endl;18}19return0;20}
در ماتریس 5x5 (اندیس از 0):X ..... X ..... X ..... X ..... X
در ماتریس 5x5 (اندیس از 0):.... X
... X ... X ... X ...X ....
1#include<iostream>2usingnamespace std;34intmain(){5constint N =3;6int a[N][N]={{1,2,3},{4,5,6},{7,8,9}};78int sum =0;9for(int i =0; i < N; i++)10 sum += a[i][i];// قطر اصلی1112 cout <<"Trace (sum of main diagonal) = "<< sum << endl;13// 1 + 5 + 9 = 1514return0;15}
1#include<iostream>2usingnamespace std;34intmain(){5constint N =3;6int a[N][N]={{1,2,3},{4,5,6},{7,8,9}};78int sum =0;9for(int i =0; i < N; i++)10 sum += a[i][N -1- i];// قطر فرعی1112 cout <<"Secondary diagonal sum = "<< sum << endl;13// 3 + 5 + 7 = 1514return0;15}
1#include<iostream>2usingnamespace std;34intmain(){5constint N =5;6for(int i =0; i < N; i++){7for(int j =0; j < N; j++){8if(i == j)9 cout <<'*';10else11 cout <<'.';12}13 cout << endl;14}15return0;16}
1#include<iostream>2usingnamespace std;34intmain(){5constint N =4;6for(int i =0; i < N; i++){7for(int j =0; j < N; j++){8if(i <= j)9 cout <<"*";10else11 cout <<" ";12}13 cout << endl;14}15return0;16}
1#include<iostream>2usingnamespace std;34intmain(){5constint N =5;6int a[N][N]={0};// همهی خانهها صفر7for(int i =0; i < N; i++){8 a[i][0]=1;// شروع هر سطر9for(int j =1; j <= i; j++)10 a[i][j]= a[i-1][j-1]+ a[i-1][j];// قاعدهی خیام11}12for(int i =0; i < N; i++){13for(int j =0; j <= i; j++)14 cout << a[i][j]<<' ';15 cout << endl;16}17return0;18}
flow.svg100%
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.
شروع/پایانپردازشتصمیمورودی/خروجی
1#include<iostream>2usingnamespace std;34intmain(){5constint N =3, M =4;6int a[N][M]={7{3,7,1,4},8{9,2,5,8},9{6,0,11,7}10};1112for(int i =0; i < N; i++){13int maxInRow = a[i][0];14for(int j =1; j < M; j++)15if(a[i][j]> maxInRow) maxInRow = a[i][j];16 cout <<"Max of row "<< i <<" = "<< maxInRow << endl;17}18return0;19}
1#include<iostream>2usingnamespace std;3intmain(){4int n, m;5 cin >> n >> m;6int a[n][m];// ✗ VLA دوبعدی غیراستاندارد7for(int i =0; i < n; i++)8for(int j =0; j < m; j++)9 cin >> a[i][j];10return0;11}
1#include<iostream>2#include<vector>3usingnamespace std;4intmain(){5int n, m;6 cin >> n >> m;7 vector<vector<int>>a(n,vector<int>(m));// ✓ استاندارد8for(int i =0; i < n; i++)9for(int j =0; j < m; j++)10 cin >> a[i][j];11return0;12}