II. KIỂU MẢNG, KIỂU CHUỖ
1. Dữ liệu kiểu mảng (Array-Type Data)
Một mảng dữ liệu là một tập hợp số hữu hạn phần tử có giống như các biến, có cùng kiểu, gọi là kiểu cơ bản.
Mảng được được tổ chức theo một trật tự xác định. Số phần tử của mảng được khai báo ngay từ khi định nghĩa ra mảng.
a. Mảng một chiều (One-Dimensional Array)
Mảng một chiều có thể được hiểu như một danh sách các phần tử (theo cột), có cùng kiểu. Mỗi phần tử của mảng được xác định được truy nhập trực tiếp thông qua tên mảng cùng với chỉ dẫn truy nhập được để giữa hai ngoặc vuông [ ].
Ví dụ 8.7:
List là một mảng 1 chiều có n phần tử. Các phần tử của List có thể mang các tên List[1], List[2], List[3], ..., List[n], và có thể minh họa như hình sau:
Hình 8.1: Minh họa mảng một chiều
+ Khai báo gián tiếp:
TYPE
<Kiểu mảng> = ARRAY [Kiểu chỉ số ] OF <Kiểu phần tử > ; VAR
<Danh sách biến> : Kiểu mảng ;
+ Khai báo trực tiếp :
VAR
< Danh sách biến > : ARRAY [ Kiểu chỉ số] OF < Kiểu phần tử > ; * Chú ý: Kiểu chỉ số phải là kiểu rời rạc (đếm được).
Ví dụ 8.8:
TYPE
KM1 = ARRAY [1.. 100] OF INTEGER ; KM2 = ARRAY [1 .. 20 ] OF CHAR ; DAY = (Sun, Mon, Tue, Wed, Thu, Fri, Sat) ; VAR
TUOI : KM1 ; TEN : KM2 ;
NGAY : ARRAY [DAY] OF BOOLEAN ; Ý nghĩa:
- KM1 là kiểu mảng gồm 100 phần tử được đánh số từ 1 đến 100 thông qua kiểu chỉ dẫn là một miền con các số nguyên từ 1 .. 100. TUOI là biến có kiểu là KM1.
- KM2 là kiểu mảng gồm 20 phần tử đánh số từ 1 .. 20 có kiểu là các ký tự. Biến TEN có kiểu là KM2.
- NGAY là một biến mảng gồm 7 phần tử kiểu Boolean được đánh dấu qua kiểu chỉ dẫn là tên của 7 ngày trong tuần.
Chú ý:
Khi khai báo mảng, kiểu chỉ dẫn chỉ có thể là:
- Kiểu miển con của các loại dữ liệu vô hướng đếm được như ký tự, số nguyên - Kiểu liệt kê do người viết định nghĩa (như NGAY trong tuần)
- Kiểu Boolean
Kiểu chỉ dẫn không thể là kiểu không đếm được như REAL Viết như sau là SAI : X1 : ARRAY [Real] OF Integer ;
Ta cũng không thể khai báo như: X2 : ARRAY [Integer] OF Integer ;
Mặc dầu Integer là kiểu vô hướng đếm được do giới hạn của vùng nhớ dành cho dữ liệu, số lượng phần tử của 1 mảng cũng bị hạn chế tùy theo kích thước của kiểu dữ liệu của các phần tử, ta nên dùng kiểu miền con để khai báo số phần tử của mảng.
+ Truy xuất các phần tử của mảng:
Mỗi phần tử của mảng được truy xuất thông qua Tên Biến Mảng cùng với chỉ số của mảng trong dấu ngoặc vuông [ ]. Ví dụ tên biến mảng là A, khi viết A[7], ta hiểu nó là phần tử thứ 7 của mảng A. Ví dụ 8.9: Lập trình giải một bài toán tính trung bình một dãy số x[i] :
x[1], x[2], x[3], ... , x[n]
sau đó tiếp tục tính độ lệch (deviation) của từng phần tử so với trị trung bình, theo công thức: độ_lệch = x[i] - trung_bình
Giả sử dãy số của chúng ta có giới hạn n = 100 phần tử trở lại, n là một biến số để khai báo số phần tử muốn tính . Sau đó ta lần lượt nhập tính giá trị của phần tử kiểu số thực (real) từ phần tử thứ 1 đến phần tử thứ n. Trong chương trình sẽ tạo ra một mảng 1 chiều x với n các phần tử. Tính trung bình của n phần tử và độ lệch. In kết quả ra màn hình.
PROGRAM Average_deviations ;
{ Nhập n số phần tử kiểu số thực, tính trị trung bình của chúng, sau đó tính tiếp độ lệch của từng phần tử số so với trị trung bình }
VAR
n, count : integer ;
sum, average, deviation : real ; x : ARRAY [1 .. 100] OF real ;
BEGIN
(* Nhập số phần tử và tính trung bình*)
Write (' Nhập bao nhiêu số n để tính trung bình ? ') ; Readln (n) ; Writeln ; sum := 0 ; FOR count := 1 TO n DO BEGIN Write ( ‘ i = ‘, count : 3, ‘ x = ‘ ) ; Readln (x [count] ) ;
sum := sum + x[count]; END ;
average := sum/n ;
Writeln (' Trung bình của dãy số là = , average ') ; Writeln ;
(* Tính độ lệch so với trị trung bình *) FOR count := 1 TO n DO
BEGIN
deviation := x[count] - average ;
Write ( ‘ i = ‘, count : 3, ‘ x = ‘, x[count] ) ; Writeln (' Ðộ lệch d = , deviation ');
END ; Readln; END.
Giả sử, ta nhập vào 5 số hạng (các số có gạch dưới là phần của người nhập): x[1] = 3.0 x[2] = -2.0 x[3] = 12.0 x[4] = 4.4 x[5] = 3.5 Khi chạy chương trình (nhấn Ctrl + F9), trên màn hình ta sẽ thấy : Nhập bao nhiêu số n để tính trung bình ? 5
i = 1 x = 3.0
i = 2 x = -2.0
i = 3 x = 12.0
i = 4 x = 4.4
i = 5 x = 3.5
Trung bình của dãy số là = 4. 1800000E+00
i = 1 x = 3. 0000000E+00 Ðộ lệch d = - 1. 1800000E+00 i = 2 x = -2. 0000000E+00 Ðộ lệch d = - 6. 1800000E+00 i = 3 x = 1. 2000000E+00 Ðộ lệch d = 7. 8200000E+00 i = 4 x = 4. 4000000E+00 Ðộ lệch d = 2. 2000000E - 01 i = 5 x = 3. 5000000E+00 Ðộ lệch d = - 6. 8000000E - 01 Ta có thể định khoảng chừa kết quả và phần lẻ thập phân, dùng lệnh : m : n Ví dụ 8.10: Sắp xếp một dãy số theo thứ tự từ nhỏ đến lớn
Tiến trình của bài toán:
- Giả sử chuỗi số của ta có n phần tử . Lần lượt cho chương trình đọc giá trị của các phần tử nhập được.
- Một thủ tục (Procedure) sẽ làm công việc sắp xếp như sau : đầu tiên đưa phần tử thứ nhất so sánh với các phần tử tiếp theo, nếu nó lớn hơn phần tử so sánh thì đem đổi chổ giá trị của hai phần tử với nhau. Sau đó tiếp tục đem phần tử thứ 2 so sánh các phần tử tiếp theo theo trình tự như vậy, ... và cứ như thế cho đến phần tử thứ n - 1.
- In kết quả ra màn hình Chương trình Pascal như sau: PROGRAM Reorder ;
(* Sắp xếp một mảng các phần tử số thực từ nhỏ đến lớn*) VAR n, i, loc: 1 .. 100 ;
x : ARRAY [1 .. 100] OF real ; temp : real ;
PROCEDURE interchange ;
(* Ðổi chỗ các phần tử mảng từ nhỏ đến lớn*) BEGIN
FOR loc := 1 TO n-1 DO
FOR i := loc + 1 TO n DO IF x[i] < x [loc] THEN
BEGIN temp := x[loc] ; x[loc] := x[i] ; x[i] := temp ; END ; END ; BEGIN
Write (' Có bao nhiêu phần tử số ? ') ;Readln (n) ; FOR i := 1 TO n DO BEGIN Write ( ‘ x[ ‘, i : 3, ‘] = ? ‘ ) ; Readln( x[i] ) ; END ; interchange ; Writeln ; Writeln (' Số liệu đã sắp xếp : ') ; Writeln ; FOR i := 1 TO n DO Writeln ( ‘x[ ‘, i : 3, ‘ ] = ‘, x[i] : 4 : 1 ) ; Readln; END.
Khi chạy chương trình, giả sử ta có 5 số liệu như phần nhập : (các số có gạch dưới là phần nhập từ bàn phím) Có bao nhiêu phần tử số ? 5 x[ 1] = ? 4. 7 x[ 2] = ? - 2. 3 x[ 3] = ? 12. 9 x[ 4] = ? 8. 8 x[ 5] = ? 6. 0 Kết quả là : Số liệu đã sắp xếp : x[ 1] = ? - 2. 3 x[ 2] = ? 4. 7 x[ 3] = ? 6. 0 x[ 4] = ? 8. 8 x[ 5] = ? 12. 9
b. Mảng nhiều chiều (Multi-Dimensional Array)
Trong một số bài toán thực tế, người ta sử dụng các mảng nhiều hơn 1 chiều, gọi là mảng nhiều chiều.
Ví dụ 8.11: Phòng Ðào tạo quản lý điểm của sinh viên. Trong khoá 22 chẳng hạn, người ta tạo ra một mảng 2 chiều: ví dụ một chiều là số thứ tự của sinh viên, chiều còn lại là các môn học (dạng kiểu vô hướng liệt kê), ta có thể hình dung dạng của mảng ghi điểm (tên mảng là ghi_diem) như sau:
Lưu ý: Thực tế, danh sách tên sinh viên lưu lại trong máy tính thường được ghi bằng cách gán mã số sinh viên (coding) cho mỗi sinh viên ngay từ năm đầu vào học.
Với ví dụ trên, muốn nhập điểm một sinh viên nào đó ta phải khai báo 2 tham số là số thứ tự sinh viên và môn học.
Tương tự, cũng với các khoá kế tiếp theo học những môn như vậy, ta sẽ tạo ra mảng nhiều chiều như hình vẽ minh họa sau:
Trong trường hợp này, muốn biết điểm một sinh viên nào đó ta phải khai báo 3 tham số: Khoá học, số thứ tự sinh viên và môn học, chẳng hạn:
ghi_diem[K22,0001,AV] nhập điểm 10,...
Khai báo cũng có 2 cách như đối với mảng 1 chiều:
+ Khai báo gián tiếp:
TYPE
<Kiểu mảng> = ARRAY [Kiểu_chỉ_số_1, ..., Kiểu_chỉ_số_n] OF <Kiểu phần tử>; VAR
<Danh sách biến>:<Kiểu mảng>; Ví dụ 8.12:
TYPE matrix = ARRAY [1 .. 20, 1 .. 30] OF integer ; VAR A:matrix;
Lệnh trên khai báo một kiểu tên matrix. Ðây là một mảng 2 chiều, chiều thứ nhất có các chỉ số từ 1 đến 20, chiều thứ hai có các chỉ số từ 1 đến 30, tổng cộng ta có (20 x 30) phần tử số nguyên. Và ta có một biến A là biến có kiểu matrix.
Ví dụ trên cũng có thể được khai báo tương đương với:
TYPE matrix = ARRAY [1 .. 20] OF ARRAY [1 .. 30] OF integer ; VAR A:matrix;
+ Khai báo gián tiếp:
VAR
<Danh sách biến>: ARRAY [Kiểu_chỉ_số_1, ..., Kiểu_chỉ_số_n] OF <Kiểu phần tử>; Khai báo một biến A có 5 dòng và 10 cột kiểu phần tử là Integer như sau:
VAR A : ARRAY [1 .. 5, 1 .. 10] OF integer ;
Tương tự như cách truy xuất phần tử của mảng 1 chiều, mảngg nhiều chiều cũng được truy xuất thông qua tên biến mảng kết hợp với các chỉ số của nó được đặt trong cặp dấu ngoặc vuông.
Mảng 2 chiều là một ma trận, như ví dụ trên ta có một ma trận 5 dòng và 10 cột. Các phần tử của ma trận A được ký hiệu là a[i,j] với i là vị trí cột và j là dòng. Khi viết a[2, 7] thì hiểu đây là phần tử ở dòng 2 và cột 7.
Trong Pascal, ta có thể viết a[i,j] thành a[i] [j] với ý nghĩa hoàn toàn như nhau.
Chú ý: Trên nguyên tắc, ta có thể khai báo một mảng có đến 255 chiều. Tuy vậy, một điều cần lưu ý là kích thước bộ nhớ của máy tính có hạn nên thường chỉ khai báo mảng từ 1 đến 3 chiều. Khai biến quá nhiều thì phải cần máy lớn hơn.
Chẳng hạn khi báo 1 mảng [1.. 10] các phần tử số nguyên đã lấy 10 bytes bộ nhớ - Mảng 2 chiều 10 x 10 = 100 bytes bộ nhớ. - Mảng 3 chiều 10 x 10 x 10 = 1 000 bytes bộ nhớ - Mảng 4 chiều 10 x 10 x 10 x 10 = 10 000 bytes bộ nhớ - Mảng 5 chiều 10 x 10 x 10 x 10 x 10 = 100 000 bytes bộ nhớ - v.v... Ví dụ 8.13:
Viết một chương trình Pascal để đọc một bảng các số thực được nhập vào máy tính dưới dạng một mảng 2 chiều. Tính tổng các giá trị số theo hàng và theo cột. Kết quả được in ra màn hình theo vị trí hàng và cột tương ứng.
Trước tiên, ta bắt đầu bằng định nghĩa các biến:
table = mảng 2 chiều chứa số thực dưới dạng bảng gồm các số nhập và kết quả nrows = một biến số nguyên chỉ số hàng
ncols = một biến số nguyên chỉ số cột row = một số đếm nguyên chỉ số hàng col = một số đếm nguyên chỉ số cột
Ðể đơn giản, chúng ta giả sử rằng kích thước số liệu nhập vào bảng tính không vượt quá 10 hàng và 10 cột. Ta sẽ thêm vào 1 hàng cộng phía dưới và 1 cột cộng bên phải vào bảng để ghi kết quả tính cộng các phần tử hàng và cột tương ứng. Như vậy, mảng 2 chiều của chúng ta sẽ trở thành mảng sẽ được in ra có số hàng là (nrows + 1) và số cột là (ncols +1). Do vậy, ta phải khai báo biến table là 1 mảng 2 chiều số nguyên có tối đa 11 cột và 11 hàng.
Ðể dễ theo dõi chương trình, ta thực hiện cấu trúc module khi viết chương trình bằng cách tiến hành làm các thủ tục procedure cho đọc số liệu, tính tổng các phần tử theo hàng, tính tổng các phần tử theo cột và in ra màn hình bảng kết quả. Các thủ tục này sẽ có tên tương ứng là readinput, rowsums, columsums và writeoutput.
Thuật toán logic yêu cầu cho mỗi thủ tục là cách khai báo thẳng trước (straightforward), chú ý rằng trong mỗi thủ tục ta có một vòng lặp đôi (double loop). Ví dụ, để đọc số liệu ở bảng gốc, ta sẽ phải làm một vòng lặp đôi sau:
FOR row := 1 TO nrows DO BEGIN
FOR col := 1 TO ncols DO readln( table[row, col] ) ; Writeln;
END ;
Câu lệnh Writeln để báo chương trình nhảy tới dòng kế.
Tương tự, vòng lặp sau được viết để tính tổng các phần tử theo hàng: FOR row := 1 TO nrows DO
BEGIN
table [row, ncols + 1] := 0 ; FOR col := 1 TO ncols DO
table [row, ncols + 1] := table [row, ncols + 1] + table [row, col]; END ;
Tương tự, cấu trúc vòng lặp đôi cũng được dùng để tính tổng các phần tử cột và in ra bảng kết quả cuối cùng.
Sau đây là chương trình Pascal của bài toán trên: PROGRAM Tongbang ;
VAR
row, col : 1 .. 11 ; nrows, ncols : 1 .. 10 ;
table : ARRAY [1 .. 11, 1 .. 11] OF real ;
PROCEDURE Rowsums ; {cộng các phần tử theo cột bên trong mỗi hàng } BEGIN
FOR row := 1 TO nrows DO BEGIN
table [row,ncols+1] := 0 ; FOR col := 1 TO ncols DO
table[row, ncols+1] := table[row, ncols+1] + table[row,col]; END ;
END ;
PROCEDURE Columnsums ; {cộng các phần tử theo hàng bên trong từng cột } BEGIN
FOR col := 1 TO ncols DO BEGIN
table [nrows+1, col] := 0 ; FOR row := 1 TO nrows DO
table[nrows+1,col] := table[nrows+1,col] + table[row,col]; END ;
END ;
PROCEDURE Readinput ; {đọc các phần tử của bảng } BEGIN
Write(' Nhập số hàng (1 .. 10) ? ') ;Readln(nrows) ; Write(' Nhập số cột (1 .. 10) ? ') ;Readln(ncols) ; FOR row := 1 TO nrows DO
BEGIN
Writeln (' Nhập số liệu hàng số , row :2') ;
FOR col := 1 TO ncols DO readln(table [row, col] ) ; END ;
END ;
PROCEDURE Writeoutput ; { In ra bảng số liệu và kết quả tính tổng } BEGIN
Writeln('Bảng số liệu và kết quả tính tổng các phần tử theo hàng và cột '); Writeln(‘============================================= ‘); Writeln;
FOR row := 1 TO nrows + 1 DO BEGIN
FOR col := 1 TO ncols+1 DO Write (table [row,col] : 6 : 1) ; Writeln;
END ; END ;
BEGIN { Thân chương trình chính } Readinput ;
Rowsums ; Columnsums ; Writeoutput;
END. { Chấm dứt chương trình } Giả sử, ta có bảng số liệu sau :
2.5 -6.3 14.7 4.0 10.8 12.4 -8.2 5.5 10.8 12.4 -8.2 5.5
-7.2 3.1 17.7 -9.1
Khi chạy chương trình, ta có (số có gạch dưới là số của người thử chương trình): Nhập số hàng (1 .. 10 ) ? 3
Nhập số cột (1 .. 10) ? 4 Nhập số liệu hàng số 1 2.5 -6.3 14.7 4.0 Nhập số liệu hàng số 2 10.8 12.4 -8.2 5.5 Nhập số liệu hàng số 3 -7.2 3.1 17.7 -9.1
Chương trình sẽ tính tổng các giá trị ở hàng và cột, xong in ra màn hình kết quả: Bảng số liệu và kết quả tính tổng các phần tử theo hàng và cột
2.5 -6.3 14.7 4.0 14.9 10.8 12.4 -8.2 5.5 20.5 10.8 12.4 -8.2 5.5 20.5 -7.2 3.1 17.7 -9.1 4.5 6.1 9.2 24.2 0.4 0.0
Ta có thể kiểm tra kết quả ở các hàng và cột.