Chuong 4 ~ DANH SACH TUYEN TINH Trong chương này chỳng ta sẽ nghiờn cứu danh sỏch tuyến tớnh, một trong cỏc mụ hỡnh đữ liệu quan trọng nhất, được sử dụng thường xuyờn trong việc cài đặt cỏc bài toỏn ứng dụng Cỏc phương phỏp cài
đặt danh sỏch khỏc nhau sẽ được xem xột Hai kiểu dữ liệu trừu tượng đặc biệt quan trọng là ngăn xếp (Stack) và hàng đợi (Queue) sẽ được
nghiờn cứu Chương này cũng sẽ trỡnh bày một số ứng dụng phổ biến
của danh sỏch
I KHÁI NIỆM DANH SÁCH TUYẾN TÍNH
t.1, Khỏi niệm danh sỏch
VỀ mặt toỏn học, danh sỏch là một dóy hữu hạn cỏc phần tử thuộc cựng một lớp đối tượng nào đú Chăng hạn danh sỏch sinh viờn
của một lớp, danh sỏch cỏc số nguyờn, danh sỏch cỏc bỏo xuất bản
hàng ngày ở thỳ đụ, v.v
Giỏ sử L là một danh sỏch cú n phõn tử (n >= 0)
L =(al, a2, , an)
Ta gọi n là độ dài của đanh sỏch Nộu n >= | thi al được gọi là phần tử đầu tiờn, an được gọi là phần tử cuối cựng của danh sỏch L Nếu n = 0 thỡ danh sỏch L được gọi là danh sỏch rỗng
Trang 284 Cấu trỳc đữ liệu và giải thuật
1.2 Cỏc phộp toỏn trờn danh sỏch
Khi mụ tả một mụ hỡnh dữ liệu, chung ta cần xỏc định cỏc phộp toỏn cú thể thực hiện trờn mụ hỡnh toỏn học được dựng làm cở sở cho mụ hỡnh đữ liệu Cú rất nhiều phộp toỏn trờn danh sỏch Trong cỏc ứng dụng, thụng thường chỳng ta chỉ sử dụng một nhúm cỏc phộp toỏn nào
đú Sau đõy là một số phộp toỏn cơ bản trờn danh sỏch tuyến tớnh
Giả sử L là một đanh sỏch, cỏc phần tử của nú cú kiờu #m, k là
vị trớ của một phõn tử trong đanh sỏch Cỏc phộp toỏn sẽ được mụ tả bởi cỏc hàm sau đõy:
1 Khởi tạo danh sỏch rỗng
void Initialize(List *L);
2 Xỏc định độ đài của danh sỏch
int Length(List *L);
3 Loại phõn tử ở vị trớ thứ k của danh sỏch
void Delete(int k, List *L):
4, Xen phan tie X vao danh sach sau vi tri thir k void Insert_After(Itemt X, int k, List *L); 5 Xen phan tir X vao danh sach tritộc vi tri thie k
void Insert_Before(Item X int k List *1.);
6 Tỡm phõn tử X trong danh danh sỏch
int Search(UItem X Lisf *L);
Hàm Search trả về ] nếu X cú trong L ngược lại trả về 0
7 Kiểm tra xem danh sỏch cú rụng khụng?
int Empty(List *L); //Ham Empty tra về è nếu L rỗng ngược
Trang 3(hương 4: Danh sỏch tuyộn tinh 85
8 Kiểm tra xem danh sỏch cú đõy khụng?
int Full(List *L); //Ham Full tra về I nộu L day, ngược lại trả về 0
9 Duyệt danh sỏch
Trong nhiều ứng dụng chỳng ta phải đi qua danh sỏch, từ đầu
đến cuối danh sỏch và thực hiện một nhúm cỏc thao tỏc nào đú đối với mỗi phần tử của đanh sỏch
void Traverse(List *1.); 10, Cac phộp toan khac
Cũn cú thể kể ra nhiều phộp toỏn khỏc Chăng hạn truy nhập đến phõn tử thứ Ă của danh sỏch (để tham khảo hoặc thay thể), kết hợp hai
danh sỏch thành một danh sỏch, tỏch một danh sỏch thành nhiều danh sỏch v.V,
Vớ dụ: Giả sử cú danh sỏch L = (3, 2, 1, 5) Khi đú, thực hiện
Delete(3, L) ta được danh sỏch (3, 2, $5) Kết quả của Insert_Before(1,
6, L) ta được danh sỏch (6, 3, 2, 1, 5)
Sau đõy ta sẽ xột một số loại danh sỏch và ứng dụng của chỳng
2 LƯU TRỮ KẺ TIẾP CỦA DANH SÁCH TUYẾN TÍNH
Ta biết rằng đanh sỏch tuyến tớnh là một đanh sỏch hoặc rỗng hoặc cú dạng L = (al, a2 , an) Trong danh sỏch tuyến tớnh luụn tụn tại một phần tử đầu là a! và một phần tử cuối là an (n > 1)
Dờ lưu trữ danh sỏch tuyến tỉnh trong bộ nhớ mỏy tớnh, một phương phỏp rất tự nhiờn là sử dụng mảng một chiều, trong đú mỗi
thành phần của mỏng lưu trữ một phần tử tương ứng của danh sỏch, cỏc
nhõn tử kế nhau của danh sỏch được lưu trữ trong cỏc thành phần kế
°hau của mảng Lưu trữ danh sỏch theo cỏch này gọi là lưu trữ kế tiếp
Tuy nhiờn, việc sử đụng mảng một chiều cũng cú những ưu
Trang 486 Cấu trỳc dữ liệu và giải thuật
+ Vỡ mảng được lưu trữ kế tiếp nờn việc truy nhập vào một thành phần nào đú được thực hiện trực tiếp dựa vào địa chớ tớnh được
(chỉ số), nờn tốc độ nhanh và đồng đều đối với mọi phần tử
+ Khi khai bỏo một mảng ta phải xỏc định số lượng phần tử của mảng, điều này sẽ tuỳ thuộc vào số lượng phản tử của danh sỏch mà mảng sẽ lưu trữ, nhưng điều này rất khú thực hiện vi số lượng phản tư
của đanh sỏch luụn luụn biến động Do đú, cú thể dẫn đến lăng phớ bộ
nhớ (cú những phần tử mảng khụng được sử dụng) hoặc thiếu bộ nhớ
(do tất cả cỏc phần tử mỏng đó được sử dụng trong khi ta cần thờm vào danh sỏch một số phần tử nào đú)
Sau đõy ta trỡnh bày cỏch cài đặt danh sỏch tuyờn tớnh bởi mảng một chiều:
Giỏ sử độ dài tối đa của danh sỏch là một số nguyờn đương N
nảo đú, cỏc phần tử trong danh sỏch cú kiểu dữ liệu là em em cú thể là cỏc kiờu đữ liệu đơn (số nguyờn, số thực, ký tự), hoặc cỏc kiểu dữ liệu cú cấu trỳc (chuỗi, cấu trỳc) Danh sỏch được biểu diễn bởi
một cầu trỳc gồm hai thành phần đữ liệu
+ Thành phan thứ nhất là mang cỏc Iứer, phần tử thứ 1 của danh
sỏch được lưu trữ bởi phần tử thứ Ă của mảng
+ Thành phần thứ hai ghi chỉ số của phõn tử mảng lưu trữ phần tử cuối cũng của danh sỏch
Trang 5Chương 4: Danh sỏch tuyến tớnh Đ7 1 phần tử thứ nhất | 2 phần tứ thứ hai Danh sỏch count phần tử cưối cựng | Chưa cú Maxlength |
Hỡnh 4.1: Mỏng biểu diễn danh sỏch
Trong cỏch cài đặt danh sỏch bởi mảng, cỏc phộp toỏn trờn danh sỏch được thực hiện rất dễ dang Đề khởi tạo danh sỏch rỗng chỉ cần một lệnh gỏn: L.count = 0; Độ đài thực của đanh sỏch là L.count, danh sỏch đầy nếu L.count=Maxlength Vi du: Khai bao danh sỏch lưu trữ thụng tin về sinh viờn #define Maxiength 100 struct student item ở đõy là student {
char std_no[10]; Ma sinh viộn
char std_name[30]; /Họ tờn
int age; HTudi
Trang 688 Cấu trỳc đữ liệu và giải thuật
Dưới đõy ta cài đặt hai phộp toỏn trờn danh sỏch: phộp toỏn bổ sung một phần tử mới vào danh sỏch và phộp toỏn loại bỏ một phần tử khúi danh sỏch
l Loại bo một phan tir o vi tri k trong danh sỏch
int DefeteL(int k, struct List *L) { inti: if (k>=1 && k <= L->count) { i=k;: while (i < L->count) { L->E[i] = L->Efi+1] ; i=it+1; } L->count = L->count - 1; return 1; } else retum 0: }
Hàm DeleteL thực hiện phộp loại một phần tử ở vị trớ k trong
danh sỏch Phộp toỏn được thực hiện khi danh sỏch khụng rỗng và k
chỉ vào một phần tử trong danh sỏch Giỏ trị trả về của hàm cho biết phộp toỏn cú được thực hiện thành cụng hay khụng (trả về 1 nếu thành cụng, trả về 0 nếu khụng thành cụng) Khi loại bỏ, ta phải dồn cỏc phần tử ở cỏc vị tri k+1, k+2, , L.count lờn trờn một vị trớ và giảm số lượng phần tử của danh sỏch đi một đơn vị (L.count = L.count — 1)
2 Bồ sung một phản từ vào trước phõn tử ở vị trớ k trong danh sỏch (dữ liệu của phõn tử này được lưu trong biến Xỡ)
int InsertL(int k, Item X, struct List *L)
{
Trang 7Chương 4: Danh sỏch tuyến tinh 89 if (L->count < Maxlength && k <= L->caunt && k>=1) { i= L->count + 1; while (i > k) { L->E€[i] = L->E[i-1] : i=t- 14: } L->count = L->count + 1; L->E[k] = X: return 1; } else return 0; }
Hàm Insertl thực hiện phộp bỏ sung một phần tử vào trước phần
tử ở vị trớ k trong danh sỏch Phộp toỏn được thực hiện khi danh sỏch chưa đõy và k chỉ vào một phần tử trong danh sỏch Giỏ trị trả về của
hảm cho biết phộp toỏn cú được thực hiện thành cụng hay khụng (trả về 1; thành cụng, trả vẻ 0: khụng thành cụng) Khi bụ sung, ta phải dón cỏc phần tử ở cỏc vị trớ L.count, k+1, k xuống đưới một vị trớ và
tăng số lượng phần tử của danh sỏch lờn một đơn vị (L.count = L.count + ])
* Nhận với ve phương phỏp cài đặt danh sỏch bởi mảng: Việc cài đặt danh sỏch bởi mảng cú một số ưu điểm và nhược điểm sau:
Ưu điểm: Do tớnh chất của mảng, nờn việc cài đặt đanh sỏch bởi mảng cho phộp ta truy nhập trực tiếp vào bất kỳ phần tử nào trong danh sỏch nờn tốc độ truy nhập nhanh và đồng đều đổi với mọi phan tử Cỏc phộp toỏn cũng đờu được thực hiện một cỏch dễ dàng
Nhược điểm: Khi thực hiện cỏc phộp toỏn bố sung một phần tử
Trang 890 Cau tric dit liộu va giai thuat
nao đú, ta phải đầy tất cả cỏc phần từ sau k xuống dưới hoặc lờn trờn một vị trớ, nờn tốn nhiều thời gian Tuy nhiờn, nhược điểm chủ yếu của phương phỏp cài đặt này là khụng gian nhớ cụ định dành để lưu trữ cỏc phần tử của danh sỏch Khụng gian nhớ này bị quy định bởi kớch thước của mảng (kớch thước của mỏng được xỏc định khi khai bỏo và
nú khụng thờ thay đổi trong khi thực hiện chương trỡnh) Do đú cú thể
dẫn đến trường hợp lóng phớ bộ nhớ (do khai bỏo kớch thước mảng quả lớn so với số lượng cỏc phần tử của danh sỏch) hoặc thiếu bộ nhớ (mảng đó đầy trong khi ta muốn bụ sung thờm một số phần tử nảo đú vào danh sỏch)
Đề khắc phục cỏc nhược điểm trờn đõy người ta sử đụng một
phương phỏp khỏc để cài đặt danh sỏch tuyến tớnh đú là danh sỏch
múc nồi
3 DANH SÁCH MểC NểI
Như đó nờu ở phần trờn, lưu trữ kế tiếp đối với danh sỏch tuyến
tớnh đó bộc lộ rừ nhược điểm trong trường hợp thực hiện thường xuyờn cỏc phộp bụ sung hoặc loại bỏ phõn tử trường hợp xử lý đồng thời nhiều danh sỏch, v.v
Việc sử dụng con trỏ hoặc mỗi nối để tổ chức danh sỏch tuyến
tớnh, mà ta gọi là danh sỏch múc nối (hay cũn gọi là danh sỏch liờn kết),
chớnh là một giải phỏp nhằm khắc phục nhược điểm trờn Tuy nhiờn,
trước khi tỡm hiểu về danh sỏch múc nổi ta nhắc lại một số khỏi niệm
về con trỏ, phương tiện được sử dụng để cài đặt danh sỏch múc nối
3.1 Kiểu con trồ và cỏc khỏi niệm liờn quan
Tất cả cỏc biến cú kiờu đữ liệu mà ta đó nghiờn cứu như số ký
tự, mảng, cấu trỳc được goi la biộn tinh vi chỳng được xỏc định một cỏch rừ ràng khi khai bỏo, sau đú chỳng được dựng thụng qua tờn
Thời gian tồn tại của biến tĩnh cũng là thời gian tồn tại của khối chương trỡnh cú chứa khai bỏo biến này Chăng hạn, cỏc biến tĩnh
Trang 9Chương 4: Danh sỏch tuyến tớnh 9]
chương trỡnh được thực hiện cho đến khi kết thỳc chương trỡnh, cũn
cỏc biến tĩnh được khai bỏo trong một hàm (biến cục bộ) sẽ tổn tại từ khi hàm được triệu gọi cho đến khi kết thỳc
Ngoài cỏc biến tĩnh được xỏc định trước, người ta cũn cú thờ tạo
ra cỏc biến trong lỳc chạy chương trỡnh, tuỷ theo nhu cầu Việc tạo ra
cỏc biến theo kiểu nảy được gọi là cấp phỏt bộ nhớ động cỏc biến
được tạo ra được gọi là biến động
Cỏc biến động khũng cú tờn Trong C/C++, dộ tao ra biển động người ta sử dụng một kiểu biến đặc biệt; gọi là con trũ và cỏc hàm/toỏn tử cấp phỏt bộ nhớ động (malloc(), ealloc(, reallocÚ trong thư viện malloc.h, toỏn tứ new) thụng qua con trỏ Khi khụng sử dụng biến động nữa, người ta cú thể xoỏ nú khỏi bộ nhớ, việc này gọi là thu
hồi bộ nhớ động Để thu hồi bộ nhớ dành cho biến động, người ta
dựng hàm ỉ#ee(J/toỏn từ đelete và thụng qua con trỏ đó sử dụng để tạo
ra biến động
So với biến tĩnh việc sử dụng biến động cú ưu điểm là tiết kiệm được bộ nhớ Bởi vỡ khi cần dựng biến động thỡ người ta sẽ tạo ra nú
và khi khụng cần nữa người ta lại cú thể xoỏ nú khụi bộ nhớ Cũn đối với cỏc biến tĩnh, chỳng được xỏc định và cấp phỏt bộ nhớ khi biẻn dịch, chỳng sẽ chiếm giữ bộ nhớ trong suốt thời gian chương trỡnh làm
việc Chằng hạn, nếu cần sử dụng một mảng ta phải khai bỏo ngay ở
phần đầu chương trỡnh, ngay lỳc này ta đó phải xỏc định kớch thước của mảng và thường khai bỏo dụi ra, gõy lóng phớ bộ nhớ
3.1.1, Con trỏ
Con trỏ là một biến dựng để chứa địa chỗ nhớ chỉ của một biến
khỏc
Cỏch khai bỏo con trỏ
<Kiộu dit ligu> <*tộn con trộ>; Vidu:
Trang 1092 Cầu trỳc dữ liệu và giải thuật
3.1.2 Cac phộp toỏn con trỏ
Gia su cộ khai bao inf *p, *q, x;
Khi đú ta cú thể thực hiện cỏc phộp toỏn
+ Gan dia chi cho con tro
Vi du: p = &x; //Gỏn địa chỉ của biến x cho p, hay p trỏ vào x
+ Phộp gan hai con trộ cing kiộu Vi du: q = p: //q va p cing tro Vào X
+ Phộp so sỏnh hai con trỏ cựng kiểu gồm: so sỏnh == (băng
nhau) và phộp sỏnh != (khỏc nhau)
Phộp toỏn một ngụi * được sử dụng với con trỏ đề trả về giỏ trị của chỗ nhớ đo con trỏ trỏ đến hoặc làm thay dỗi gia tr] cua chỗ nhớ đú Cỏch việt <*ftờn_con_ trỏ> Vĩ dụ: Pp X inf*p, X, y; Ứ73——— 100 x= 100;
p= &x; Ip tra vao x
y="“p; //khi đú ta cú giỏ trị của y = 100
*p = 500; //Khi đú ta cũng cú x = 500 3.1.3 Giỏ ri NULL
NULL là một giỏ trị con trỏ đặc biệt đảnh cho cỏc biến con trỏ, nú được dựng để bỏo rằng con trỏ khụng lưu địa chỉ của biến nào Giỏ
trị NULL cú thờ được đem gỏn cho bất kỳ biển con tro nao Duong nhiờn khi đú việc thõm nhập vào biến động thụng qua con trộ cộ gia tri NULL la vộ nghia
3.1.4 Con tro cau tric
Con trỏ chứa địa chỉ của một biển cấu trỳc được gọi là con trỏ
cầu trỳc, khi đú ta cú thộ thao tỏc với cầu trỳc thụng qua con trỏ Việc
truy nhập vào cỏc thành phần của cấu trỳc bằng con trỏ được viết theo cỏch sau:
Trang 11Chương 4: Danh sỏch tuyến tớnh 93 Vớ dụ: struct Hoc_sinh { char Hio ten[25]; int tuoi; float diem; k struct Hoc_sinh “p, h: p = &h;
Khi đú việc truy xuất vào cỏc thành phần của cấu trỳc h thụng
qua con trú p được viết như sau:
strcpy(p->Ho_ten, “Nguyen Trong Huan’); p->diem = 7.4
cin>>p->tuoi;
3.1.6 Cap phat va thu hội bộ nhớ động
Trong ngụn ngữ lập trỡnh C cú thờ sử dụng cỏc hàm cấp phỏt bộ
nhớ động gồm: malloe(), calloc() cỏc ham này được định nghĩa trong, thư viện malloc.h
+ Hàm malloc() cấp phỏt cho con trú một vựng nhớ liờn tiếp
kớch thước vựng nhớ được chớ ra bởi tham số size Cỳ phỏp: void *malloc(size_t size)
Trong đú size là kớch thước vựng nhớ được cấp phỏt cho con trỏ được tớnh băng byte
Vĩ dụ:
int *p;
p=(int*) malloc (sizeof(inh),
Cõu lệnh trờn cấp phỏt cho con trỏ p một chỗ nhớ cú kớch thước băng một dữ liệu kiờu fart
Trang 1294 Cấu trỳc dữ liệu và giải thuột + Ham calloc() cấp phỏt một vựng nhớ gồm n chỗ nhớ Cui phap: void *calloc(int n, size_t size) Vĩ dụ: int *p; p=(int*)calloc(1, sizeof(int); Đưới đõy ta xột một chương trỡnh được cài đặt với con trỏ cõu trỳc: #include<stdio.h> #include<conio.h> #include<malloc.h> struct Hoc sinh { char ht{25); int tuoi; char qa{[40]; } void Nhap_du_lieu(struct Hoc_sinh “p) {
cout<< “\tHo ten: ” ; fflush(stdin); gets(p->ht): cout<< “\tTuoi: *; cin>>p->tuai:
cout<< “\tQue quan: ”; fflush(stdin) ; gets(p->qq);
}
void Hien_thi(struct Hoc_sinh p)
{ cout<< “\tHo ten: ”<<p->ht<<endl;
cout<< "fTuoi: "<<p->tuoi<<endl;
Trang 13Chuong 4: Danh sach tuyộn tinh 95 cout<< “Nhap thong tin ve hoc sinh’<<endi; Nhap_ du_lieu(p); cout<< “Thong tin hoc sinh vua nhap"<<endi; Hien_thi(*p); free(p); getch() }
Qua vi du trộn ta thay răng để đưa dữ liệu vào bộ nhớ thụng qua biến con trỏ ta cần phải cấp phỏt bộ nhớ cho nú và sau khi khụng sử
dụng nữa ta cú thờ xoỏ nú khỏi bộ nhớ Tuy nhiờn nếu chỉ lưu trữ đữ
liệu đơn giản như vậy thỡ ta khụng cần đến biến con trỏ, nú được sử dụng trong một ứng dụng quan trọng hơn đú là việc cài đặt danh sỏch
liờn kết Sau đõy ta xột tới một số đạng danh sỏch múc ni 3.2 Danh sỏch múc nụi đơn
3.2.1 Nguyờn tặc
Trong cỏch cải đặt này, danh sỏch múc nỗi được tạo nờn từ cỏc phần tử nhỏ mả ta gọi là nỳt (Node) Cỏc nỳt này cú thể năm bat ky
đõu trong bộ nhớ mỏy tớnh Mỗi nỳt là một cấu trỳc gồm hai thành
phõn, #yor chứa thụng tin của phần tử trong danh sỏch, mex/ là một
con trỏ, nú trỏ vào nỳt đứng sau Qui cỏch của mỗi nỳt cú thể hỡnh
dung như sau:
| infor | next |
Riờng nỳt cuối cựng thỡ khụng cú nỳt đứng sau nú nờn thành
phan next cua nỳt này cú giỏ trị NULL đề bỏo kờt thỳc danh sỏch
Đờ cú thờ truy nhập vào mọi nỳt trong danh sỏch, ta phải truy nhập
từ nỳt đầu tiờn, nghĩa là cần cú một con trỏ L, trỏ tới nỳt đầu tiờn này Nếu dựng mũi tờn đẻ chỉ mỗi nỗi ta sẽ cú hỡnh ảnh của một danh
Trang 1496 Cấu trỳc dữ liệu và giai thuật ty a1 a2 a3 a4 e â
Hỡnh 4.2: Biờu diễn danh sỏch múc nổi đơn
Dấu E4 chỉ giỏ tri trudng next cua phan tt nay bang NULL
Giả sử cỏc phần tử trong danh sỏch cú kiểu dữ liệu là em Dưới đõy là khai bỏo cấu trỳc đữ liệu biờu diễn danh sỏch múc nối đơn
IIđịnh nghĩa kiểu dữ liệu Item (nếu cần) struct Node { {tem infor, Struct Node *next }
Struect Node *L; //Khai bỏo con trỏ L trỏ vào đầu danh sỏch L=NULL nếu danh sỏch rồng
Vi du: Khai bao danh sỏch lưu trữ thụng tin về sinh viờn:
Struct student l'Item ở đõy là student
{
char std_no[10}; /#Ma sinh viờn char std _name[30], /Họ tờn
int age; /Tuỗi
float avg_point: //Điểm trung bỡnh
}
struct Node
{
struct student infor struct Node *next: };
Trang 15Chương 4: Danh sỏch tuyến tỉnh 97
3.2.2 Cỏc phộp toỏn trờn danh sỏch múc nỗi đơn
Bõy giờ chỳng ta sẽ xem xột một số phộp toỏn tỏc động trờn danh sỏch nồi đơn
Điều kiện để danh sỏch múc núi đơn rỗng là L = NULL, Do đú, đờ khởi tạo danh sỏch rỗng ta chỉ cần lệnh gỏn: L = NUI.L;
Danh sỏch múc nỗi chỉ đầy khi khụng cũn khụng gian nhớ dộ
cấp phỏt cho cỏc phần tử mới của danh sỏch Chỳng ta giả thiết điều
này khụng xảy ra, nghĩa là danh sỏch múc nối khụng bao giờ đầy Do
đú, phộp toỏn bố sung một phần tử vào danh sỏch luụn luụn được
thực hiện
a Bồ sung mội nỳt mới vào danh sỏch múc nồi ẩơn
Giả sử Q là một con trỏ, trừ vào một nỳt trong danh sỏch, ta cần
bổ sung một phần tử mới với thụng tin lưu trong biến X vào sau nỳt
được trỏ bởi Q Phộp toỏn này được thực hiện bởi thủ tục sau:
void InsertAfter(struct Node **L, struct Node *Q , Item X) { struct Node “p; //1 Tạo một nỳt mới p=(struct Node *)malloc(sizeof(struct Node)); p->infor = X;
/!2 Thực hiện bổ sung, nộu danh sach rộng thi bộ sung nỳt mới vào
Trang 1698 Cấu trỳc dữ liệu và giải thuật ‘La Q al @ ` a2 | đơ " a3 | *đ a4 ——— a5 +
Hỡnh 4.3: M6 ta phộp bộ sung một phan tir vao sau nut trỏ bởi Q trong danh sach
Giả sử bõy giờ ta cần bỗ sung nỳt mới vào trước nỳt được trỏ bởi Q Phộp toỏn này phức tạp hơn Khú khăn là ở chỗ, nếu Q khụng phải là nỳt đầu tiờn của đanh sỏch (QzL) thỡ ta khụng thể xỏc định được nỳt
đứng trước Q để kết nối nú với nỳt mới Cú thể giải quyết khú khăn
bằng cỏch, đầu tiờn ta vẫn bổ sung nỳt mới vào sau Q, sau đú trao đụi giỏ trị chứa trong phan infor giữa nỳt mới và nỳt được trỏ bởi Q Thủ
tục thực hiện phộp toỏn này xin đành cho bạn đọc
b Loại bỏ một nỳt ra khỏi danh sỏch múc nối đơn
Cho đanh sỏch múc nỗi đơn được trỏ bởi L Q là một con trỏ, trỏ
vào một nỳt trong danh sỏch Giả sử ta cần loại bỏ nỳt được trỏ bởi Q
Ở đõy ta cũng gặp khú khăn là nếu Q khụng phải là nỳt đầu tiờn thỡ
khụng xỏc định được nỳt đứng trước Q Trong trường hợp này (a phải tỡm đến nỳt đứng trước Q và cho con trỏ R trỏ vào nỳt đú, tức là Q =R->next Sau đú ta mới thực hiện loại bỏ nỳt Q Ta cú thủ tục sau:
Trang 17Chương 4: Danh sỏch tuyến tớnh 99 I2 Trường hợp nỳt trỏ bởi Q là nỳt đầu tiờn if (Q == *L) { *L = Q->next; free(Q); return 1; } //⁄3 Tỡm đờn nỳt đứng trước nỳt trỏ bởi Q R=”L; while (R->next != Q) R = R->next; 1/4 Loại bỏ nỳt trỏ bởi Q R->next = Q->next; free(Q); } Phộp toỏn loại bỏ được mụ tả bởi hỡnh 4.4 L R i acy at >| a2 | @4 >| a3 | #+ -" a4 —_ _ ] Hỡnh 4.4: Mụ tả phộp loại bỏ một phõn tử ra khỏi danh sỏch Â
c Ghộp hai danh sỏch múc nồi don
Giả sử cú hai danh sỏch múc nối đơn lần lượt được.trỏ bởi L1! và L2 Thủ tục sau thực hiện việc ghộp hai danh sỏch đú thành một danh sỏch mới được trỗ bởi LI
Trang 18100 Cấu trỳc đữ liệu và giải thuật *L1 = L2; return: } 43, Tim đến nỳt cuối danh sỏch trỏ bởi P =*L1, while (R->next != NULL) R = R->next; 14 Ghộp R->next = L2; }
Nhận xột: Rừ ràng với cỏc danh sỏch tuyến tớnh mà kớch thước luụn biến động trong quỏ trỡnh xử lý hay thường xuyờn cú cỏc phộp bổ
sung và loại bỏ tỏc động, thỡ việc lưu trữ bằng danh sỏch múc nối như
trờn tỏ ra thớch hợp Tuy nhiờn, cỏch cài đặt này cũng cú những nhược
điểm nhất định:
Chỉ cú phần tử đầu tiờn trong danh sỏch được truy nhập trực
tiếp, cỏc phần tử khỏc chỉ được truy nhập sau khi đó di qua cỏc phần
tử đứng trước nú
Ở mỗi nỳt trong danh sỏch phải cú thờm trường ằex để lưu trữ
địa chỉ của nỳt tiếp theo, do đú với cựng một danh sỏch thỡ việc cài đặt bởi danh sỏch múc nối sẽ tốn bộ nhớ hơn so với cài đặt bằng mảng 3.3 Danh sỏch nối vũng
Một cải tiến của danh sỏch múc nối đơn là kiểu danh sỏch múc
nỗi vũng Nú khỏc với danh sỏch múc nối đơn ở chỗ: trường ứex/ của nỳt cuối cựng trong danh sỏch khụng phải bằng NULL,, mà nú trỏ đến
Trang 19Chương 4: Danh sỏch tuyến tớnh 101 Cai tiễn này làm cho việc truy nhập vào cỏc nỳt trong danh sỏch
được lớnh hoạt hơn Ta cú thể truy nhập vào mọi nỳt trong danh sỏch
bất đầu từ nỳt nào cũng được, khụng nhất thiết phải từ nỳt đầu tiờn Điều đú cú nghĩa là nỳt nào cũng cú thể coi là nỳt đầu tiờn và con trỏ
L trỏ tới nỳt nào cũng được Như vậy, đối với danh sỏch múc nối vũng chỉ cõn cho biết con trỏ trỏ tới nỳt muốn loại bỏ ta sẽ thực hiện được
vỡ luụn tỡm được đến nỳt đứng trước đú Với phộp ghộp, phộp tỏch
cũng cú những thuận lợi nhất định
Tuy nhiờn, đanh sỏch nỗi vũng cú một nhược điểm rất rừ là trong
khi xử lý, nếu khụng cần thận sẽ dẫn tới một chu trỡnh khụng kết thỳc, bởi vỡ khụng biết được vị trớ kết thỳc danh sỏch
Đề khắc phục nhược điểm này, người ta đưa thờm vào danh sỏch
một nủt đặc biệt gọi là “nỳt đầu danh sỏch” Trường infor của nỳt này khụng chứa dữ liệu của phần tử nào và con trụ L bõy giờ trỏ tới nỳt đầu danh sỏch này Việc dựng thờm nỳt đầu danh sỏch đó khiến cho danh sỏch về mặt hỡnh thức khụng bao giờ rỗng Hỡnh ảnh của nú
minh họa như hỡnh 4.6 L 77) * ” a1 | đ a2 |*đ a3 | 4 Hỡnh 4.6: Mụ tả danh sỏch múc nồi vũng cú nỳt đầu
Sau đõy là đoạn giải thuật bổ sung một nỳt vào thành nỳt đầu
tiờn trong danh sỏch cú “nỳt đầu danh sỏch” trỏ bởi L P=(struct Node*) malloc(sizeof(struct Node)); P->infor = X;
P->next = L->next:
L->next = P;
3.4 Danh sỏch múc nụi hai chiờu
Khi làm việc với danh sỏch, cú những xử lý trờn mỗi nỳt của
Trang 20102 Cộu tric dit liộu va giải thuật
những trường hợp như thế, để thuận tiện, người ta đưa vào mỗi nỳt
của danh sỏch hai con trỏ: Next Left trỏ đến nỳt đứng trước và
Next_Righ trỏ đến nỳt đứng sau nú Để truy nhập vào danh sỏch ta dựng hai con trỏ: con trỏ Z2/? trỏ vào nỳt đõu tiờn và con tro Right trd vào nỳt cuối cựng của đanh sỏch Hỡnh ảnh của danh sỏch múc nối hai chiều được minh họa trờn hỡnh 4.7 Left IM Right Ls
Hinh 4.7: M6 ta danh sỏch múc nồi đụi
Ta cú thờ khai bỏo cấu trỳc đữ liệu danh sỏch múc nỗi hai chiều như sau: lIKhai bỏo kiễu dữ liệu Item (nếu cõn) struct Node { {tem infor; struct Node *Next_Left, *Next_Right: )
Struct Node “Left, “Right;
Việc cài đặt danh sỏch múc nối hai chiều sẽ tiờu tốn nhiều bộ
nhớ hơn so với danh sỏch múc nối đơn Song bự lại, danh sỏch múc nổi đụi cú những ưu điểm mà danh sỏch múc nối đơn khụng thể cú
được, chăng hạn: khi xem xột danh sỏch múc nối đụi ta cú thờ lựi lại sau, hoặc tiến lờn trước
Cỏc phộp toỏn trờn danh sỏch múc nối hai chiều được thực hiện
dộ dang hơn Chẳng hạn, khi thực hiện phộp toỏn loại bỏ, với danh sỏch múc nỗi đơn, #a khụng thể thực hiện được nếu khụng biết nỳt
đứng trước nỳt cần loại bỏ Trong khi đú, ta cú thể tiến hành dễ dàng
trờn danh sỏch múc nối hai chiờu
Trang 21Chương 4: Danh sỏch tuyến tinh 103
3.4.1 Phộp bỗ sung một nỳt mới
Cho hai con trỏ Lef và Right lõn lượt trỏ tới nỳt đầu và nỳt cuối của một danh sỏch múc nối hai chiờu, M là con trỏ trỏ tới một nỳt
trong danh sỏch này Giải thuật này thực hiện bộ sung mot nut mdi,
mà đữ liệu chứa ở biến X, vào trước nỳt trỏ bởi con trỏ M
Trang 22104 Cấu trỳc đữ liệu và giai thuật
3.4.2 Logi bộ mot nỳt trờn danh sỏch
Cho hai con tro Left va Right lan lvot trộ tới nỳt đầu và nỳt cuối
của một danh sỏch múc nối hai chiều, M là con trỏ trỏ tới một nỳt
trong danh sỏch này Giải thuật này thực hiện loại bỏ nỳt trỏ bởi M ra khỏi danh sỏch int Loai_bo(struct Node **Left, struct Node **Right, struct Node *M) { if (“Left = = NULL) return 0; else if (*Left = = *Right) { *Left = NULL; *Right = NULL; } else if (M = = *Left) { “Left = (*Left)->Next_Right; (*Left)->Next_Left = NULL; } else if (M == Right) { Right = Right*.Next_Left: Right*.Next_Right = NULL; } else { M->Next_Left->Next_Right = M->Next_Right: M->Next_Right->Next_Left = M->Next_Left; } }
Chu y: Trong cac tmg dung, ngudi ta cũng thudng sir dung cdc
danh sỏch múc nối hai chiều vũng trũn, cú nỳt đầu danh sỏch Với loại
danh sỏch này, ta cú tất cả cỏc ưu điểm của danh sỏch múc nối hai
Trang 23Chương 4: Danh sỏch tuyến tớnh 105
3.5 Ung dụng đanh sỏch múc nối: cỏc phộp tớnh số học trờn đa thức Trong mục này ta sẽ xột cỏc phộp tớnh số học cơ bản (cộng, trừ nhõn, chia) đối với đa thức một õn cú dạng:
A(X) = aX" + anX”” +, ẩ aIX + ao (1) Mỗi hạng thức của đa thức được đặc trưng bởi hệ số và số mũ
của x Giỏ sử cỏc bạng thức trong đa thức được sắp xếp theo thứ tự
giảm dần của số mũ, như trong đa thức (1) Ta thấy đa thức như một danh sỏch tuyến tớnh với cỏc phần tử của đanh sỏch là cỏc hạng thức của đa thức Khi ta thực hiện cỏc phộp toỏn trờn đa thức ta sẽ nhận
dược đa thức cú bậc khụng thể đoỏn trước được Ngay cả những da
thức cú bậc xỏc định thỡ số cỏc hạng thức của nú cũng biến đổi rất nhiều từ một đa thức này đến một đa thức khỏc Do đú phương phỏp tốt nhất là biờu điễn đa thức đưới đạng một danh sỏch múc nối Mỗi
nỳt của danh sỏch là một bản ghi gồm ba trường: coef chỉ hệ số, exp chỉ số mũ của x và con trỏ Next để trỏ tới nỳt tiếp theo Cấu trỳc đữ
liệu mụ tả một hạng thức (một nỳt) như sau: struct Node { float coef int exp; struct Node “Next; }
Vỡ những ưu điểm của danh sỏch vũng trũn cú nỳt đầu danh sỏch (Khụng cõn kiểm tra danh sỏch rỗng, mọi thành phần đều cú thành phần đi sau), ta chọn danh sỏch múc nối vũng trũn để biờu diễn đa
thức Với cỏch chọn này việc thực hiện cỏc phộp toỏn đa thức sẽ rất
ứọn Nỳt đầu danh sỏch là nỳt đặc biệt, cỏ exp = -1
Nhu vay voi da thitc: A(x) = 4x° - 2x? + 5x? + 6 sộ duoc biộu
Trang 24106 Cấu trỳc đữ liệu và giải thuật ‘Ll 0 4 -2 5 6 > ô ô + a -1 5 3 2 0
Hỡnh 4.8: Danh sỏch múc nồi đụi biểu điờn đa thức
Sau đõy chỳng ta sẽ xột phộp cộng hai đa thức A(x) và B(x) Con trỏ A trỏ tới đầu danh sỏch biểu diễn đa thức A(x), con trỏ B trỏ tới đầu danh sỏch biểu diễn đa thức B(x) Sau khi thực hiện phộp cộng hai đa thức trờn ta được đa thức C(x) và con trỏ C trỏ tới đầu danh sỏch
biểu diộn C(x)
* Giải thuật
Trước hết cõn phải thấy răng để thực hiện phộp cộng đa thức
A(x) với đa thức B(x) ta phải tỡm đến từng hạng thức của cỏc đa thức
đú, nghĩa là phải dựng hai biến con trỏ P và Q để duyệt qua hai danh
sỏch tương ứng với hai đa thức A(x) và B(x) trong quỏ trỡnh tỡm này Ta thấy cú những trưởng hợp sau:
1, EXP(P) = EXP(Q), ta sẽ phải thực hiện cộng giả trị coef ở hai nỳt đỏ, nếu giỏ trị tổng khỏc khụng thỡ phải tạo ra nỳt mới thể hiện hạng thức tổng đú và găn vào danh sỏch ứng với C(x)
2 EXP(P) > EXP(Q) (hoặc ngược lại cũng tương tự): phải sao
chộp nỳt P và gắn vào danh sỏch của C(x)
3 Nếu một danh sỏch kết thỳc trước: phần cũn lại của danh sỏch
kia sẽ được sao chộp.và gắn vào danh sỏch của C(x)
Mỗi lần một nỳt mới được tạo ra đều phải gắn vào cuối danh sỏch
Trang 25Chương 4: Danh sỏch tuyễn tinh 107
trường exp giỏ trị m (số mũ) và găn nỳt mới đú vào sau nỳt trỏ bởi con trỏ R void Attack(flaat h, int m, struct Node *R) { } struct Node *N; N=(struct Node*)malloc(sizeof(struct Node)); N->coef = h; N->exp = m; R->Next = N; R=N;
Sau day la thu tục cộng hai đa thức:
Trang 26108 Cầu trỳc dữ liệu và giải thuật { Attack(P->coef, P->exp, R); P = P->Next: } while (Q->exp !=-1) /Danh sỏch ứng với A(x) đó hết { Attack(Q->coef, Q->exp, R): Q = Q->Next; } R->Next = *C; } 4 STACK VA QUEUE 4.1 Stack (Ngin xộp) 4.1.1 Khỏi niệm
Ngăn xếp (Staek) là một danh sỏch tuyến tớnh, trong đú phộp bổ sung một phần tử vào ngăn xếp và phộp loại bỏ một phần tử khỏi ngăn Xếp luụn luụn được thực hiện ở một đõu gọi là đỉnh (top)
Cú thờ hỡnh dung Ngăn xếp như cơ cầu của một hộp tiếp đạn
Việc đưa đạn vào hộp đạn hay lấy đạn ra khỏi hộp chỉ được thực hiện
Trang 27Chương 4: Danh sỏch tuyến tớnh 109
4.1.2 Cài đặt ngăn xếp bởi mảng
Giả sử danh sỏch được biểu diễn là một ngăn xộp, cú độ dài tối đa là một số nguyờn dương N nào đú cỏc phần tử của ngăn xếp cú kiểu dữ liệu là em Hem cú thể là cỏc kiểu dữ liệu đơn, hoặc cỏc kiểu dữ liệu cú cõu trỳc Chỳng ta biểu điễn ngăn xếp bởi một bản, ghỉ gồm
2 trường Trường thứ nhất là mảng cỏc ƒem, trường thứ 2 ghi chỉ số
của thành phần mảng lưu trữ phần tử ở đỉnh của ngăn xếp Cấu trỳc dt
liệu biộu điễn ngăn xếp được khai bỏo theo mẫu sau: #define Max N Khai bao kiộu dộ liộu Item (nộu can) struct Stack { ftem E[Max]: unsigned int top; k
struct Stack S; //Khai bao ngan xộp S
Trang 28110 Cấu trỳc dữ liệu và giải thuật { char ho_ten[25]; Ínf tuoi; } struct Stack {
struct Hoc_sinh E(max];
unsigned int top;
}
struct Stack S;
Khai bỏo ngăn xếp S cú thể chứa tối đa 100 phần từ, mỗi phần tử
(em) là một cõu trỳc Hoc_ sinh gồm 2 thành phõn ho _ten và tuoi Cỏc phỏp toản trờn ngăn xếp
Giả sử S là ngăn xếp, cỏc phần tử của nú cú kiờu ệfem và X là
một phõn tử cú cựng kiờu với cỏc phõn tử của ngăn xếp Ta cú cỏc phộp toỏn sau với ngăn xếp S
a Khởi tạo ngăn xếp rụng (ngần xếp khụng chứa phan tir nao)
void initialize (struct Stack *S)
{
S->top = 0;
}
b Kiểm tra ngăn xếp rỗng
int Empty (struct Stack S)
{
return (S.top = = 0):
}
Ham Empty nhan gid tri true nộu S rỗng va false nếu S khụng rồng c Kiộm tra ngan xộp day
int Full (struct Stack S) {
Trang 29Chương 4: Danh sỏch tuyến tinh 111
Ham Full() nhan gia tri true nếu Đ day va false nộu khong
d Thờm một phõn tử mới vào đỉnh ngăn xếp
Đề bố sung phần tử X vào đỉnh của ngăn xếp S, trước hết kiểm
tra xem S cú đầy khụng Nếu Đ đõy thỡ bỗ sung khụng thực hiện được,
ngược lại X được bồ sung vào đỉnh của S Hàm PUSH trả về 1 nếu bổ
sung thành cụng, ngược lại trả về 0
int PUSH (struct Stack *S, [tem X) { if (Full(S)) return 0; else { S->top = S->top + 1; S->E[S->top] = X; return 1; } }
e Loại bỏ phõn tử ở định của ngăn xếp
Việc loại bỏ được thực hiện nếu S khụng rỗng, giỏ trị của phõn tử bị loại bỏ được gắn cho biờn X Hàm PểP(Q) trả về l nờu loại bỏ
thành cụng, ngược lại trả về 0
int POP (struct Stack *S; item *X) { if (Empty(*S)) return 0: else { *X = S->E[S->top]; S->top = S->top — 1; return 1; } }
4.1.3 Cài đặt ngăn xếp bởi danh sỏch múc nỗi đơn
Để cải đặt ngăn xếp bởi đanh sỏch múc nối đơn, ta sử dụng con
Trang 30112 Cau trỳc dữ liệu và giải thuật 1 an õn-1 Poe ằ a4 NI Đỉnh Day
Hỡnh 4.11: Danh xỏch múc nối đơn biếu diễn ngăn xếp Cõu trỳc đữ liệu của ngăn xếp được khai bỏo như sau: struct Node { Item Infor: Node “Next, k Struct Node *S;
Trong cỏch cài đặt này, ngăn xếp rỗng khi S = NULL Ta giả sử việc cấp phỏt bộ nhớ động cho cỏc phần tử mới luụn thực hiện Do
đỏ, ngăn xếp khụng bao giờ đầy và phộp toỏn PUSH luụn thực hiện
thành cụng
*) Cac ham va thủ tục thực hiện cỏc phộp toỏn trờn ngăn xộp: a Khởi tạo ngăn xếp rỗng:
void Create(struct Node **S)
{ *S = NULL; }
b Kiểm tra ngăn xếp rỗng:
int Empty(struct Node *S) { return (S == NULL);
}
c Bồ sung một phản tử vào định ngăn xờp:
void PUSH(struct Node “*S, Item X)
{
Trang 31Chuong 4: Danh sach tuyộn tinh 113 P = new Node: P->Infor = X; P->Next = NULL; if (S== NULL) *S =P; else { P->Next = *S; *S = P; } } 3 ane1 | * a, |e an | ep e+ ai i
d Lấy ra một phõn tử ở đỉnh ngăn xếp:
int POP(struct Node **S, Item *X)
Trang 32114 Cấu trỳc dữ liệu và giải thuật To 8n An-1 n-2 oe a b P 4.1.4 Xử lý với nhiều ngăn xếp
Hỡnh 4.13: Minh hoạ thao tỏc POP
Cú những trường hợp cựng một lỳc ta phải xử lý nhiều ngăn xếp
trờn cựng một khụng gian nhớ Như vậy, cú thể xảy ra tỡnh trạng một
ngăn xếp này đó bị tràn trong khi khụng gian dự trữ cho ngăn xếp
khỏc vẫn cũn chỗ trống (tràn cục bộ) Làm thế nào để khắc phục được
tỡnh trạng này?
Nếu là hai ngăn xếp thỡ cú thể giải quyết đễ dàng Ta khụng qui
định kớch thước tối đa cho từng ngăn xếp nữa mà khụng gian nhớ dành ra sẽ được dựng chung Ta đặt hai ngăn xếp ở hai đầu sao cho hướng phỏt triển của chỳng ngược nhau, như hỡnh 4.14
77 mm
Đỏy 1 Đỉnh 1 Đỉnh 2 Đỏy 2
Hỡnh 4.14: Hai ngăn xếp trờn một khụng gian nhớ
Như vậy, cú thể một ngăn xếp này dựng lần sang quỏ nửa khụng
gian dự trữ nếu như ngăn xếp kia chưa dựng đến Do đú hiện tượng tràn chỉ xảy ra khi toàn bộ khụng gian nhớ dành cho chỳng đó được
dựng hết
Nhưng nếu số lượng ngăn xếp từ 3 trở lờn thỡ khụng thể làm theo
kiểu như vậy được, mà phải cú giải phỏp linh hoạt hơn nữa Chẳng
hạn cú 3 ngăn xếp, lỳc đầu khụng gian nhớ cú thể chia đều cho cả 3,
Trang 33Chương 4: Danh sỏch tuyến tớnh 115
Lda
LÍ rot of
Day 1 Đỉnh 1 Day 2 Đỉnh 2 Đỏy 3 Đỉnh 3
Hỡnh 4.15: Ba ngăn xếp trờn một khụng gian nhớ
Nhưng nếu cú một ngăn xếp nào phỏt triển nhanh bị tràn trước
mà ngăn xếp khỏc vẫn cũn chỗ thỡ phải dồn chỗ cho nú bằng cỏch
hoặc đầy ngăn xếp đứng sau nú sang bờn phải hoặc lựi chớnh ngăn xếp đú sang trỏi trong trường hợp cú thể Như vậy thỡ đỏy của cỏc ngăn
xếp phải được phộp di động và dĩ nhiờn cỏc giải thuật bổ sung hoặc
loại bỏ phần tử đối với cỏc ngăn xếp hoạt động theo kiểu này cũng
phải thay đổi
4.1.5 Một số ứng dụng của ngăn xếp a Ứng dụng đổi cơ số
Ta biết rằng dữ liệu lưu trữ trong bộ nhớ của mỏy tớnh đều được
biểu diện dưới dạng mó nhị phõn Như vậy cỏc số xuất hiện trong chương trỡnh đều phải chuyển đổi từ hệ thập phõn sang hệ nhị phõn
trước khi thực hiện cỏc phộp xử lý
Khi đổi một số nguyờn từ hệ thập phõn sang hệ nhị phõn người
ta dựng phộp chia liờn tiếp cho 2 và lấy cỏc số dư (là cỏc chữ số nhị phõn) theo chiều ngược lại
Trang 34116 Cấu trỳc dữ liệu và giải thuật
——> 11010111
Ta thấy trong cỏch biến đổi này cỏc số được tạo ra sau lại được
hiển thị trước Cơ chế sắp xếp này chớnh là cơ chế hoạt động của ngăn xếp Để thực hiện biến đổi ta sẽ dựng một ngăn xếp để lưu trữ cỏc số
Trang 35Chương 4: Danh sỏch tuyến tinh 117 0 POP —> 1 7 0 0 0 1 1 0 1 0
Hỡnh 4 1ú.(b): Mụ tả hoạt động lấy dữ liệu từ ngăn xếp
Ta khai bỏo cầu trỳc dữ liệu cho bài toỏn này như sau:
#define Max 16 !/thực hiện đỗi số nguyờn cú kớch thước 2 byte typedef int Item; //Item là chữ số nhị phõn struct Stack { Item E[Max]; int top; } Sfruct Stack S; /“S là ngăn chứa cỏc số dư qua cỏc phộp chỡa trong quỏ trỡnh chuyờn đụi"!
Giải thuật sử dụng ngăn xếp thực hiện chuyờn đổi số nguyờn
Trang 36118 Cấu trỳc đữ liệu và giải thuật
call POP(S, R); cout<<R;
}
b Ứng dụng định giỏ biểu thức số học theo ký phỏp nghịch đảo
Nhiệm vụ của bộ dịch là tạo ra cỏc chỉ thị mỏy cần thiết để thực hiện cỏc lệnh của chương trỡnh nguồn Một phần trong nhiệm vụ này
là tạo ra cỏc chỉ thị định giỏ cỏc biểu thức số học Chắng hạn cõu lệnh
gỏn X= A*B + C
Bộ dịch phải tạo ra cỏc chỉ thị mỏy tương ứng như sau:
1 - LOA A: Tim giỏ trị của A lưu trữ trong bộ nhớ và tải nú vào thanh ghi 2 - MUL B: Tỡm giỏ trị của B và nhõn nú với giỏ trị đang ở thanh ghi 3 - ADD C: Tim giỏ trị của C và cộng nú với giỏ trị trong thanh ghi 4 - STO X: Đưa giả trị trong thanh ghi vào lưu trữ ở vị trớ tương ứng của X, trong bộ nhớ
Trong cỏc ngụn ngữ lập trỡnh, biờu thức số học được viết như dạng thụng thường của toỏn học nghĩa là theo kớ phỏp trung tộ (infix notation) mỗi kớ hiệu của phộp toỏn hai ngụi được đặt giữa hai toản
hạng, cú thờ thờm dấu ngoặc
Chănghạn 5*(7+3)
Dấu ngoặc là cần thiết vỡ nếu viết 5*7 + 3 thỡ theo qui ước về thứ
tự ưu tiờn của phộp toỏn (mà cỏc ngụn ngữ lập trỡnh đều chấp nhận)
thỡ biờu thức trờn nghĩa là lấy 5 nhõn 7 được kết quỏ cộng với 3
Nhà logie học người Ba Lan Lukasiewicz đó đưa ra dạng biểu thức số học theo ký phỏp hậu tố (postủx notation) và tiờn tố (prefix
Trang 37Chương 4: Danh sỏch tuyến tớnh 119 Ở dạng hậu tố cỏc toỏn tử đi sau cỏc toỏn hạng Như biểu thức 5*(7+3) sẽ cú dạng: Š 7 3 - * Cũn ở dạng tiờn tụ thỡ cỏc toỏn tử sẽ đi trước cỏc toỏn hạng Khi đú biểu thức 5*(7+3) cú dạng: * 5 - 7 3
ễng cũng khăng định rằng đối với cỏc dạng ký phỏp này dấu
ngoặc là khụng cần thiết
Nhiều bộ dịch khi định giỏ biểu thức số học thường thực hiện:
trước hết chuyển cỏc biểu thức dạng trung tố cú dấu ngoặc sang dạng hậu tố, sau đú mới tạo cỏc chỉ thị mỏy để định giỏ biểu thức ở dạng
hậu tố Việc biến đổi từ đạng trung tố sang dạng dạng hậu tế khụng
khú khăn gỡ, cũn việc định giỏ theo dạng hậu tổ thỡ để dàng hơn, “mỏy
múc” hơn so với dạng trung tụ
Đề minh hoa ta xột định giỏ của biểu thức sau: 15+841 *
tương ứng với biểu thức thụng thường: (1 + 5) * (8 - (4 - 1))
Biểu thức này được đọc tử trải sang phải cho tới khi tỡm ra một toỏn tử Hai toỏn hạng được đọc cuụi cựng, trước toỏn tử này, sẽ được kết hợp với nú Trong vớ dụ của chỳng ta thỡ toỏn tử đõu tiờn được đọc
là + và hai toỏn hạng tương ứng với nú là l và 5, sau khi kờt hợp biờu thực con này cú giả trị là ú, thay vào ta cú biờu thức rỳt gọn:
6841 *
Lại đọc từ trỏi sang phải, toỏn tử tiếp theo là - và ta xỏc định
được 2 toỏn hạng của nú là 4 và I Thực hiện phộp toỏn ta cú dạng rỳt
gon:
683-* Lại tiếp tục ta đi tới:
6S#
và cuối cựng thực hiện phộp toỏn * ta cú kết quỏ là 30
Phương phỏp định giỏ biểu thức hậu tế như trờn đũi hỏi phải lưu
Trang 38120 Cấu trỳc dữ liệu và giải thuật
hai toỏn hạng cuối cựng phải được tỡm ra và kết hợp với toỏn tử này Như vậy ở đõy đó xuất hiện cơ chế hoạt động “vào sau ra trước” nghĩa là ta sẽ phải sử dụng tới ngăn xếp dộ lưu trữ cỏc toỏn hạng Cứ mỗi lần
đọc được một toỏn tử thỡ hai giỏ trị sẽ được lấy ra tỪ ngăn xếp để ỏp đặt toỏn tử đú lờn chỳng và kết quả lại được đõy vào ngăn xếp
Trang 39Chương 4: Danh sỏch tuyến tớnh 12] Biểu thức Ngăn xếp 151+841 * 1 |<—T 15+841 " 5 |e—T t 1 +841 * B8 |<=T 8 |<—T 841 * 6 4 |=T 41 * 8 T 6 1 |<—T 1 * 4 8 6 3 |<—T ~~? 8 1 6 -* 5 |<—T T 6 * 30 |<—=T Chỳ thớch
Đẩy 1 vào ngăn xếp
Đẩy 5 vào ngăn xếp
Lấy 5 và 1 từ ngăn xếp cộng lại rồi đẩy
kết quả vào ngăn xếp
Đẩy 8 vào ngăn xếp
ĐẦy 4 vào ngăn xếp
Đẩy 1 vào ngăn xếp Lấy 1 và 4 từngăn xếp _ Thực hiện (4 - 1) rồi đẩy kết qua vào ngăn xếp Lẩy 3 và 8 từ ngăn xếp Thực hiện (8 - 3) rồi đẩy kết quả vào ngăn xếp
Lấy 5 và 6, thực hiện (5*6) rồi day
kết quả vào ngăn xếp
Trang 40122 Cấu trỳc dữ liệu và giải thuật
4.2 Queue (Hàng đợi) 4.2.1 Khai niộm
Một kiểu dữ liệu trừu tượng quan trọng khỏc được xõy dựng trờn cơ sở mụ hỡnh đữ liệu danh sỏch tuyến tớnh là hàng đợi Hàng đợi là kiểu danh sỏch tuyến tớnh trong đú, phộp bổ sung một phần tử vào hàng đợi được thực hiện ở một đầu, gọi là lỗi sau (rear) và phộp loại bỏ một phần tử được thực hiện ở đầu kia, gọi là lối trước (#ont)
Như vậy, cơ cầu của hàng đợi là vào ở một đầu, ra ở đầu khỏc, phần tử vào trước thỡ ra trước, phõn tử vào sau thỡ ra sau Do đú, hàng đợi cũn được gọi là danh sỏch kiộu FIFO (First In First Out) Trong
thực tế ta cũng thấy cú những hinh ảnh giống hang doi, chang han,
hàng người chờ mua vộ tàu, học sinh xếp hàng đi vào lớp, v.v 4.2.2 Cài đặt hàng đợi bởi mảng
Ta cú thể biểu diễn hàng đợi bởi mảng với việc sử dụng hai chỉ sộ front dộ chỉ vi trớ đầu hàng đợi (lỗi trước) và rear dộ chi vi tri cudi
hàng đợt (lối sau) Cấu trỳc đữ liệu hàng đợi được biểu điễn như sau: #define maxN iiKhai bao kiểu dữ liệu ltem nếu cần struct Queue { unsigned int front, rear; ftem E[max]; k struct Queue Q;
!IKhai bỏo hàng đợi Q lưu trữ cỏc phõn tử của danh sỏch
Trong cỏch cài đặt như trờn, hàng đợi Q là rỗng nếu Q.rear = 0