7.2 แถว คอลัมน์ และเมทริกซ์
พอเก็บตารางได้แล้ว งานที่เจอบ่อยที่สุดคือรวมตัวเลขตามแนวต่างๆ คะแนนรวมของนักเรียนแต่ละคนคือผลรวมตามแถว คะแนนรวมของแต่ละวิชาคือผลรวมตามคอลัมน์ หน้านี้ฝึกเดินตารางตามแถว ตามคอลัมน์ และตามเส้นทแยง แล้วต่อด้วยงานของ เมทริกซ์ (matrix) ซึ่งคือชื่อในวิชาคณิตศาสตร์ของตารางตัวเลข ได้แก่ สลับแถวเป็นคอลัมน์ บวก และคูณ
ผลรวมแต่ละแถวและแต่ละคอลัมน์
ใช้ตารางคะแนนจากหน้า 7.1 แต่คราวนี้รับจากข้อมูลเข้า บรรทัดแรกคือจำนวนนักเรียนกับจำนวนวิชา
#include <stdio.h>
int score[100][100];
int main()
{
int students, subjects;
scanf("%d %d", &students, &subjects);
for (int r = 0; r < students; r++) {
for (int c = 0; c < subjects; c++) {
scanf("%d", &score[r][c]);
}
}
for (int r = 0; r < students; r++) {
int total = 0; // (1)!
for (int c = 0; c < subjects; c++) {
total += score[r][c];
}
printf("student %d: %d\n", r + 1, total);
}
for (int c = 0; c < subjects; c++) { // (2)!
int total = 0;
for (int r = 0; r < students; r++) {
total += score[r][c];
}
printf("subject %d: %d\n", c + 1, total);
}
return 0;
}
- ตั้ง
totalเป็น 0 ใหม่ทุกครั้งที่เริ่มแถวใหม่ ถ้าตั้งไว้นอกลูปนอก ผลรวมจะทบต่อจากแถวก่อน ดูในส่วนลองทำให้พัง - ผลรวมตามคอลัมน์ สลับให้ลูปนอกเดินคอลัมน์ ลูปในเดินแถว ส่วน
score[r][c]ยังเขียนแบบเดิม แถวมาก่อนคอลัมน์เสมอ
4 3
78 65 90
82 91 70
55 88 73
95 60 84
student 1: 233
student 2: 243
student 3: 216
student 4: 239
subject 1: 310
subject 2: 304
subject 3: 317
สองส่วนนี้ต่างกันแค่ว่าลูปไหนอยู่นอก ลูปนอกคือสิ่งที่เราอยากได้คำตอบทีละตัว อยากได้คำตอบต่อนักเรียนหนึ่งคน ลูปนอกก็คือแถว อยากได้คำตอบต่อวิชา ลูปนอกก็คือคอลัมน์
เส้นทแยง
ตารางที่จำนวนแถวเท่ากับจำนวนคอลัมน์เรียกว่า ตารางจัตุรัส (square matrix) ขนาด N × N มีเส้นทแยงสองเส้น เส้นทแยงหลัก ลากจากมุมบนซ้ายลงไปมุมล่างขวา และ เส้นทแยงรอง ลากจากมุมบนขวาลงไปมุมล่างซ้าย
ดูเลขแถวกับเลขคอลัมน์ของแต่ละช่องแล้วจะเห็นกฎ
- เส้นทแยงหลัก เลขแถวเท่ากับเลขคอลัมน์ ช่องในแถว i คือ
a[i][i] - เส้นทแยงรอง เลขแถวบวกเลขคอลัมน์ได้ N - 1 เสมอ ช่องในแถว i จึงอยู่คอลัมน์ N - 1 - i คือ
a[i][N - 1 - i]
แต่ละแถวมีช่องบนเส้นทแยงแค่ช่องเดียว จึงใช้ลูปชั้นเดียวก็พอ ไม่ต้องไล่ทั้งตารางแล้วเช็กว่า r == c
#include <stdio.h>
int a[100][100];
int main()
{
int n;
scanf("%d", &n);
for (int r = 0; r < n; r++) {
for (int c = 0; c < n; c++) {
scanf("%d", &a[r][c]);
}
}
int main_sum = 0, anti_sum = 0;
for (int i = 0; i < n; i++) {
main_sum += a[i][i];
anti_sum += a[i][n - 1 - i];
}
printf("main diagonal = %d\n", main_sum);
printf("anti diagonal = %d\n", anti_sum);
return 0;
}
4
3 8 1 6
7 2 9 4
5 0 6 1
2 7 3 8
main diagonal = 19
anti diagonal = 17
3 + 2 + 6 + 8 = 19 และ 6 + 9 + 0 + 2 = 17 ถ้า N เป็นเลขคี่ ช่องตรงกลางพอดีจะอยู่บนเส้นทแยงทั้งสองเส้น โจทย์ที่ให้รวมสองเส้นเข้าด้วยกันจึงต้องระวังนับช่องนี้ซ้ำ
สลับแถวเป็นคอลัมน์ (transpose)
การสลับให้แถวที่ 1 กลายเป็นคอลัมน์ที่ 1 แถวที่ 2 กลายเป็นคอลัมน์ที่ 2 ไปเรื่อยๆ เรียกว่า transpose ตาราง R × C จะกลายเป็น C × R เช่น ตารางคะแนนที่แถวเป็นนักเรียน พอ transpose แล้วแถวจะเป็นวิชาแทน
ช่อง a[r][c] ย้ายไปอยู่ที่ t[c][r] แค่สลับเลขสองตัว
#include <stdio.h>
int a[100][100], t[100][100];
int main()
{
int rows, cols;
scanf("%d %d", &rows, &cols);
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
scanf("%d", &a[r][c]);
t[c][r] = a[r][c];
}
}
for (int r = 0; r < cols; r++) {
for (int c = 0; c < rows; c++) {
printf("%d", t[r][c]);
if (c < rows - 1) {
printf(" ");
}
}
printf("\n");
}
return 0;
}
2 3
1 2 3
4 5 6
1 4
2 5
3 6
ตาราง t มี cols แถว และ rows คอลัมน์ ลูปพิมพ์จึงสลับขอบเขตกับลูปอ่าน ถ้าแค่ต้องการพิมพ์ ไม่ได้เอา t ไปใช้ต่อ ไม่ต้องมี t ก็ได้ ให้ลูปนอกเดินคอลัมน์ของ a ลูปในเดินแถว แล้วพิมพ์ a[r][c] แบบเดียวกับผลรวมตามคอลัมน์
บวกเมทริกซ์
ตารางสองตารางขนาดเท่ากัน บวกกันได้โดยบวกช่องที่ตำแหน่งตรงกัน เช่น คะแนนกลางภาคกับคะแนนปลายภาคของนักเรียนคนเดียวกันวิชาเดียวกัน
#include <stdio.h>
int midterm[100][100], final_exam[100][100];
int main()
{
int rows, cols;
scanf("%d %d", &rows, &cols);
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
scanf("%d", &midterm[r][c]);
}
}
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
scanf("%d", &final_exam[r][c]);
}
}
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
printf("%d", midterm[r][c] + final_exam[r][c]);
if (c < cols - 1) {
printf(" ");
}
}
printf("\n");
}
return 0;
}
2 3
30 25 40
35 20 28
45 38 41
50 29 33
75 63 81
85 49 61
ลบเมทริกซ์ก็ทำแบบเดียวกัน แค่เปลี่ยนบวกเป็นลบ
คูณเมทริกซ์
การคูณเมทริกซ์ไม่ได้คูณช่องที่ตรงกันแบบการบวก ดูจากเรื่องนี้ก่อน ลูกค้า 2 คนจะซื้อของ 3 อย่าง ตาราง qty บอกว่าลูกค้าแต่ละคนซื้ออะไรกี่ชิ้น ส่วนตาราง price บอกราคาของแต่ละอย่างที่ร้าน 2 ร้าน อยากรู้ว่าลูกค้าแต่ละคนจะจ่ายเท่าไรถ้าซื้อที่แต่ละร้าน
ลูกค้าคนที่มีเลข 1 ไปร้านที่มีเลข 0 จ่าย 0 × 10 + 4 × 25 + 1 × 8 = 108 คือเอา แถว 1 ของ qty มาคูณกับ คอลัมน์ 0 ของ price ทีละคู่ แล้วบวกกันทั้งหมด เขียนเป็นสูตรได้ว่า
total[i][j] = qty[i][0] * price[0][j] + qty[i][1] * price[1][j] + ... + qty[i][M-1] * price[M-1][j]
คูณได้เมื่อ จำนวนคอลัมน์ของตารางซ้ายเท่ากับจำนวนแถวของตารางขวา ในที่นี้คือจำนวนชนิดของสินค้า M = 3 ตาราง N × M คูณกับ M × P ได้ N × P โปรแกรมต้องมีลูปสามชั้น สองชั้นนอกเลือกช่อง [i][j] ของคำตอบ ชั้นในสุดเดิน k ไปตามแถวของตารางซ้ายพร้อมกับคอลัมน์ของตารางขวา
#include <stdio.h>
int qty[100][100], price[100][100];
long long total[100][100];
int main()
{
int customers, items, shops;
scanf("%d %d %d", &customers, &items, &shops);
for (int i = 0; i < customers; i++) {
for (int k = 0; k < items; k++) {
scanf("%d", &qty[i][k]);
}
}
for (int k = 0; k < items; k++) {
for (int j = 0; j < shops; j++) {
scanf("%d", &price[k][j]);
}
}
for (int i = 0; i < customers; i++) {
for (int j = 0; j < shops; j++) {
total[i][j] = 0;
for (int k = 0; k < items; k++) {
total[i][j] += (long long)qty[i][k] * price[k][j]; // (1)!
}
}
}
for (int i = 0; i < customers; i++) {
for (int j = 0; j < shops; j++) {
printf("%lld", total[i][j]);
if (j < shops - 1) {
printf(" ");
}
}
printf("\n");
}
return 0;
}
- ถ้าโจทย์ให้ค่าแต่ละช่องถึง 104 ผลคูณหนึ่งคู่ถึง 108 พอบวกกัน 100 คู่ได้ถึง 1010 เกิน
intจึงเก็บคำตอบในlong longและ cast ก่อนคูณ ตามกับดักintคูณintในหน้า 1.3
2 3 2
2 1 3
0 4 1
10 12
25 20
8 9
69 71
108 89
ลูกค้าคนแรกควรซื้อร้านแรก จ่าย 69 ส่วนคนที่สองควรซื้อร้านที่สอง จ่าย 89 ตัวนับในโปรแกรมนี้ใช้ i j k แทน r กับ c เพราะมีสามตาราง k เป็นคอลัมน์ของ qty แต่เป็นแถวของ price ตั้งชื่อตามตารางใดตารางหนึ่งจะชวนสับสน
ลูปสามชั้นทำงาน N × M × P รอบ ถ้าทั้งสามค่าเป็น 100 คือหนึ่งล้านรอบ เร็วมาก แต่ถ้าเป็น 1000 จะเป็นพันล้านรอบ ซึ่งเริ่มช้า เรื่องประมาณเวลาจากขอบเขตโจทย์จะเรียนในบทที่ 18
ลองทำให้พัง
แบบที่ 1 ตั้งผลรวมเป็นศูนย์ครั้งเดียว
ย้าย int total = 0; ออกไปไว้ก่อนลูปนอก ตัดส่วนผลรวมคอลัมน์ออกให้สั้นลง
#include <stdio.h>
int score[100][100];
int main()
{
int students, subjects;
scanf("%d %d", &students, &subjects);
for (int r = 0; r < students; r++) {
for (int c = 0; c < subjects; c++) {
scanf("%d", &score[r][c]);
}
}
int total = 0;
for (int r = 0; r < students; r++) {
for (int c = 0; c < subjects; c++) {
total += score[r][c];
}
printf("student %d: %d\n", r + 1, total);
}
return 0;
}
4 3
78 65 90
82 91 70
55 88 73
95 60 84
student 1: 233
student 2: 476
student 3: 692
student 4: 931
คนแรกถูก คนต่อมาผิดหมด เพราะคะแนนของคนก่อนหน้าทบเข้ามาด้วย 476 คือ 233 + 243 อาการ "ตัวแรกถูก ตัวหลังโตขึ้นเรื่อยๆ" แบบนี้ ให้สงสัยไว้ก่อนเลยว่าลืมตั้งค่าเริ่มต้นใหม่ในลูปนอก
แบบที่ 2 transpose ในตารางเดิมด้วยการสลับทุกช่อง
ตารางจัตุรัส transpose ในตารางเดิมได้ ไม่ต้องมีตารางที่สอง โดยสลับค่า a[r][c] กับ a[c][r] หลายคนเขียนลูปเดินทั้งตารางแบบนี้
#include <stdio.h>
int main()
{
int a[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
for (int r = 0; r < 3; r++) {
for (int c = 0; c < 3; c++) {
int temp = a[r][c];
a[r][c] = a[c][r];
a[c][r] = temp;
}
}
for (int r = 0; r < 3; r++) {
printf("%d %d %d\n", a[r][0], a[r][1], a[r][2]);
}
return 0;
}
1 2 3
4 5 6
7 8 9
ได้ตารางเดิมเป๊ะ เพราะทุกคู่ถูกสลับสองครั้ง ตอน r = 0, c = 1 สลับ 2 กับ 4 ไปแล้ว พอถึง r = 1, c = 0 ก็สลับคู่เดิมกลับคืน แก้โดยสลับเฉพาะช่องที่อยู่เหนือเส้นทแยงหลัก ให้ลูปในเริ่มที่ c = r + 1 แต่ละคู่จะถูกสลับครั้งเดียว
for (int r = 0; r < 3; r++) {
for (int c = r + 1; c < 3; c++) {
แก้แล้วจะได้ 1 4 7 2 5 8 3 6 9 วิธีนี้ใช้ได้กับตารางจัตุรัสเท่านั้น ตาราง R × C ที่ R ไม่เท่ากับ C เปลี่ยนรูปร่างหลัง transpose จึงต้องใช้ตารางที่สองแบบในหัวข้อ transpose
สรุป
- ผลรวมตามแถว ลูปนอกเดินแถว ผลรวมตามคอลัมน์ ลูปนอกเดินคอลัมน์ ตั้งผลรวมเป็น 0 ใหม่ทุกรอบของลูปนอก
- เส้นทแยงหลักคือ
a[i][i]เส้นทแยงรองคือa[i][N - 1 - i]ใช้ลูปชั้นเดียว - transpose ย้าย
a[r][c]ไปt[c][r]ตาราง R × C กลายเป็น C × R - บวกเมทริกซ์คือบวกช่องที่ตรงกัน ต้องขนาดเท่ากัน
- คูณเมทริกซ์ N × M กับ M × P ได้ N × P แต่ละช่องคือแถวซ้ายคูณคอลัมน์ขวาทีละคู่แล้วบวก ใช้ลูปสามชั้น และระวังผลรวมล้น
int