Khai báo cấu trúc dữ liệu: struct ITEM
{ int key;
struct titem *pDown; }; struct STACK { ITEM *Top; int numOfltem; }; Các thao tác:
a) Hàm tạo một phần tử để chuẩn bị đưa vào ngăn xếp
(Hàm này giống với hàm tạo node trong bài danh sách liên kết đom)
ITEM *makeltem (int x) { ITEM *t = new ITEM;
if (t == NULL) {
cout « "Stack is full!" « endl; return NULL; } t -> key = x; t -> pDown = NULL; return t; } b) Hàm khởi động ngăn xếp:
void initstack(STACK &s) í s.Top = NULL;
s .numOfItem = 0;
}
c) Hàm kiểm tra ngăn xếp đã rồng hay chưa
bool isEmpty(STACK s) {
if (s.Top = NULL && s.numOfltem == 0) return true;
return false;
}
d) Hàm kiểm tra ngăn xếp đã đầy hay chưa: /*
Nếu khơng cịn đủ bộ nhớ để cấp phát thì Ngăn xếp bị đầy */
bool isFull() { ITEM *t = new ITEM; if (t == NULL)
return true; delete t; return false;
}
e) Hàm thêm một phần tử vào đỉnh của ngăn xếp:
(Hàm này tưong đưomg với hàm addHead trong bài danh sách liên kết đon. Hàm addTop để phục vụ cho hàm push)
void addTop(STACK &s, ITEM *item) { if (s .Top = NULL) {
s .Top = item; return;
}
item -> pDown = s.Top; s.Top = item;
>
f) Hàm đưa một phần tử vào ngăn xếp
bool push (STACK &s, int x) { if (isFullO == true)
return false;
ITEM *item = makeltem(x); addTop(s, item);
s .numOfItem++; return true;
}
(Hàm này tương đương với hàm deleteHead trong bài danh sách liên kết đơn. Hàm deleteTop để phục vụ cho hàm pop)
void deleteTop(STACK &s) { if (s.Top = NULL) return; ITEM *item = s.Top;
s .Top = s .Top -> pDown; delete item;
}
h) Hàm lấy một phần tử ra khỏi đỉnh ngăn xếp:
bool pop(STACK &s, int &x) { if (isEmpty(s) == true) return false; X = s.Top->key; deleteTop(s) ; s .numOfItem--; return true; }
i) Hàm kiểm tra phần tử ở đỉnh ngăn xếp có giá trị bằng bao nhiêu:
bool checkTop(STACK s, int &x) { if (isEmpty(s) = NULL)
return false;
X = s.Top -> key; return true;
}
j) Hàm xuất các phần tử có trong ngăn xếp:
ITEM *i;
if (isEmpty(s) = true) {
cout « "Stack is empty!" « e n d u re turn;
}
for (i = s.Top; i != NULL; i = i -> pDown) cout « i->key « " ";
cout « endl « "Number of items is " « s.numOfltem « endl ;
cout « endl;
}
k) M ột ví dụ về hàm main cho bài lập trình ngăn xếp bằng danh sách liên kết đon: void main() { STACK s; int i, x; bool check; initstack(s) ; for ( i = 0; i < 7; i++) { cout « "Input a value = "; cin » x;
check = push(s,x) ; if (check = false) {
cout « "Stack is full!" « endl; break;
}
cout « "After input, content of the stack is:" « endl; outputs tack (s) ;
cout « "Start to pop out...(the first time)" « endl; int n = s .numOfItem;
for (i = 0; i < n - 3; i++) { check = pop(s,x);
if (check = true)
cout « "Poped out X = " « X « endl; if(check == false) {
cout « "Stack is empty!" « endl; break;
}
} //end for
cout « "After (the first time) pop out, content of the stack is:" « endl;
outputstack(s) ;
cout « "Start to pop out...(the second time)" « endl; n = s.numOfltem;
for (i = 0; i < n + 3; i++) { check = pop(s,x);
if (check == true)
cout « "Poped out X = " « X « endl; if (check = false)
cout « "Stack is empty!" « endl;
}
cout « "After (the second) pop out, content of the stack is:" « endl;
outputstack(s) ; cout « endl;
}
5.4. ứ n g dụng của Ngăn xếp
Bài toán đổi sổ thập phân sang hệ cơ sổ bất kỳ:
Ý tường giải quyết bài toán là sau mỗi lần chia chủng ta sẽ push lần lượt các số dư vào ngăn xếp. Sau khi phép chia ngừng chúng ta sẽ xuất ra các phần tử trong ngăn xếp từ đỉnh. Kết quả cuối cùng chúng ta thu được dãy số đã được đổi.
Cài đặt: #include "stdio.h" #include "conio.h" #include "string.h" #define MAX 100 #define TRUE 1 #define FALSE 0
typedef unsigned int Data; typedef struct!
int top;
Data s[MAX]; } Stack;
void push(Stack & st, Data x) ; Data pop (Stack &st) ;
void initstack(Stack &st); int isEmpty(Stack St); int isFull (Stack st) ; Data top(Stack st);
if (isFull (st) )
p r i n t f ("\nStack is full!"); else
st.S[++st.top] = x;
}
Data pop (Stack &st) { if (isEmpty(st))
p r i n t f ("\nStack is empty!"); else
return (st .S [st. top--] ) ;
}
void initStack(Stack &st) { st.top = -1; } int isEmpty(Stack st) { if (st.top == -1) return TRUE; else return FALSE; }
int isFull (Stack st) { if (st.top >= MAX) return TRUE;
else
return FALSE;
Data top(stack st) { Data d;
if (isEmpty(st))
printf("\n Stack is empty! else d = st.S[st.top]; return d; } void main() { char s[MAX];
int coso, so, du; stack st;
clrscr () ;
printf("Nhap co so can doi: "); scanf("%d", Scoso); printf("Nhap so:"); scanf("%d",&so); initStack(st); while (so != 0) { du = so % coso; push(st, du) ; so = so/coso; } printf("\n Co so : "); while (!isEmpty(st)) printf ("%3X" , pop(st));
getch 0 ;
}
Bài tốn tính giá trị biểu thức hậu tố:
Trong những năm đầu 1950 nhà logic học người Ba Lan Jan Lukasiewicz đã chứng minh rằng: biểu thức hậu tố không cần có dấu ngoặc vẫn có thể tính đủng bằng cách đọc lần lượt biểu thức từ trái qua phải và dùng một ngăn xếp để lưu trữ kết quả trung gian.
Các bước thực hiện như sau:
Bước 1: Khời tạo một ngăn xếp = {0}
Bước 2: Đọc lần lượt các phần tử của biểu thức hậu tố từ trái sang phải, với mồi phần tử đó thực hiện kiểm tra:
- Neu phần tử này là tốn hạng thì đẩy nó vào ngăn xếp
- Neu phần tử này là tốn tử thì ta lấy hai toán hạng trong ngăn xếp ra và thực hiện phép tốn với hai tốn hạng đó. Kết quả tính được sẽ được lưu vào trong ngăn xếp.
Bước 3: Sau khi kết thúc bước hai thì tồn bộ biểu thức đã được đọc xong, trong ngăn xếp chỉ còn duy nhất một phần tử, phần tử đó chính là giá trị của biểu thức.
Ví dụ: tính giá trị biểu thức 1 0 2 / 3 + 7 4 - * , có biểu thức trung tố là: (10/2 +
Đọc Xử l ý S t a c k Output
10 Đ ư a v à o s t a c k 10
2 Đ ư a v à o s t a c k 10 2
/
L ấ y hai p h ầ n từ đầu s t a c k là 2 , 10 và th ự c hiện ph ép to án 10/2 = 5. S a u đó lưu kết q u ả 5 v à o sta c k
5
3 Đ ư a 3 v ả o s t a c k 5 3
+
L ấ y hai giá trị 3, 5 ra khỏi sta ck , th ự c hiện phép cộ n g c ủ a hai s ố đó, kểt q u ả lồ 8 đ ư ợ c đ ư a v à o lai sta c k 8 7 Đ ư a 7 v à o sta c k 8 7 4 Đ ư a 4 v ả o sta ck 8 7 4 -
L ấ y hai g iá trị 4, 7 ra khỏi sta ck , th ự c hiện phép tính 7 - 4 = 3, kết q u ả 3 đ ư ợ c đ ư a v à o lại s ta c k 8 3
* L ấ y hai g iá trị 3, 8 ra khỏi sta c k , th ự c hiện p h ép
tính 8 * 3 = 2 4 , lưu kết q u ả v à o sta ck
24
L ấ y kểt q u ả từ sta ck =3 đ â y chỉnh là kết q u à c ủ a
biểu th ứ c. 2 4
về phần cài đặt, sinh viên tự cài đặt sau khi đọc xong thuật toán.
6. Hàng đợi (Queue)
Hình 5-15. Mơ phỏng hàng đọi 6.1. Khái niệm
Hàng đợi là một đối tượng dùng để lưu trữ các phần tử dữ liệu (item) một cách có thứ tự.
Thêm một item thì thêm vào cuối hàng đợi (đầu rear), lấy một item ra thì lấy từ đầu hàng đợi (đầu front).
Hình 5-16. Thao tác chính trên hàng đợi
Tóm lại hàng đợi là một danh sách gồm các phần tử (cùng kiểu), việc thêm vào và lấy ra các phần tử trong hàng đợi buộc phải tuân thủ theo cơ chế FIFO.
Có thể dừng mảng một chiều hoặc danh sách liên kết đơn để xây dựng hàng đợi (queue)
Các thao tác cơ bản trên hàng đợi:
- Kiểm tra hàng đợi rồng hay chưa. - Kiểm tra hàng đợi đầy hay chưa.
- Thêm một phần tử vào hàng đợi (enQueue). - Lấy một phần tử ra khỏi hàng đợi (deQueue). - Khởi tạo hàng đợi rỗng.
Chú ý:
- Front: cho biết vị trí trên hàng đợi, nơi mà một phần tử sẽ bị lấy ra khỏi hàng đợi (chỉ phần tử ở đầu hàng đợi)
Rear: cho biết vị trí trên hàng đợi, nơi mà một phần tử mới sẽ được thêm vào tại đó (chỉ phần tử ở cuối hàng đợi)
6.2. Cài đặt Hàng đọi bằng mảng vói phương pháp tịnh tiến:
Sau đây là hình mơ tả hàng đợi trong các tình huống: rồng, có một phần tử, có nhiều phần tử và khi hàng đợi đầy:
n=6 phẩn tử
Rear Front
n=6 phân tử
-1
thêm giá trị 10 vào
n=ó phần tữ n=6 phẩn tú 5 5 4 4 3 3 R ear— ►! 7 Rear — ► 2 7 1 15 Front— ► 1 15 Front— ►o 10 0 -1 -1 thêm 15. rồi thcm 7 lấv 10 ra
n=6 phần tử
Ta muốn thêm 105 vào hàng đợi thì phải làm sao?
Đây là trạng thái đầy giả, ta không thể thêm vào được nữa. Nếu muốn thèm vào thì phải tịnh tiến các giá trị trong hàng đợi xuống phía dưới. Vì vậy ta gọi đây là phưcmg pháp tịnh tiến.
n=6 phân tú
Rear — »
Front
•1
n=6 phẩn tử Rear— ► 5 4 3 2 Front— ► 0 •I
Hàng đợi sau khi thêm 105
n=6 phần từ
Thêm tiếp 37 vào thì hàng đợi đầy thật. Khai báo cẩu trúc dữ liệu cho hàng đợi: struct tqueue
{
int * queue Array; int n;
};
typedef struct tqueue QUEUE; a) Hàm khởi tạo hàng đợi:
void queuelnitialize(QUEUE &q, int spt) { q.queueArray = new int[spt];
q .n = spt; q.front = -1; q.rear = -1;
}
b) Hàm kiểm tra hàng đợi rỗng hay chưa:
bool isEmpty(QUEUE q) {
if (q.front == -1) return true; return false;
}
c) Hàm kiểm tra hàng đợi đầy hay chưa:
Vì hàng đợi có thể bị đầy giả (tức là chưa đầy thật), cho nên nếu chỉ viết: if (q.rear == q.n - 1) là khơng đúng? n -ó phần tử n=6 phần tử Rear — ► 5 100 R ear— ► 5 37 4 30 4 105 3 21 3 100 Front— *■ 2 7 2 30 1 1 21 0 Front— ► 0 7 -1 -1
bool isFull(QUEUE q) {
if (q.rear - q.front == q.n - 1) return true; return false;
}
d) Hàm thêm một phần tử vào hàng đợi (enQueue):
bool addltem (QUEUE &q, int in_item) { if (isFull(q))
return false; if (isEmpty (q) )
q .front++; //q.front=0; if (q.rear == q.n - 1) {
for (int i = q.front; i < q.n; i++) q .queueArray[i - 1] = q .queueArray[i]; q .rear -= 1; q .front += 1; } q.queueArray[++q.rear] = in_item; return true; }
e) Hàm lấy một phần tử ra khỏi hàng đợi (deQueue):
n=6 phần tử
Hàng đợi trước khi lấy ra 50 n=6 phẩn từ 5 -ront— > 4 R.ear — ► 3 2 1 0 -1
Sau khi lấy 50 ra thì hàng đợi rồng. Do đó ta phải cập nhật lại ngay Front=-l và Rear=-1.
bool remov e l t e m (QUEUE &q, int &out_item) { if (isEmpty(q))
return false;
out_item = q.queueArray[q.front++]; if (q.front > q.rear)
return true;
}
void outputToTestQueue(QUEUE q) { int i ;
if (isEmpty(q) ) {
cout « "Queue is empty!";
cout « "\nq.front = " « q.front; cout « " q.rear = " « q.rear; cout « " q.n = " « q.n;
return;
}
for (int i = q.front; i <= q.rear; i++) cout « q.queueArray[i] « " ";
cout « "\nq.front = " « q.front; cout « " q.rear = " « q.rear; cout « " q.n = " « q.n; } g) M ột ví dụ về hàm main: void main() { QUEUE q; int i; int out_item; queueInitialize(q,10);
cout « "Queue after initializing is:\n"; outputToTestQueue(q) ;
for (i = 0; i < 15; i++) i f (ãaddltem(q,i))
cout ô "Queue is full!\n"; outputToTestQueue(q) ;
cout « "\nRemove 5 items from queue:\n"; for (i = 0; i < 5; i++)
if (removeItem(q,out_item))
cout « "Have just removed item: " « out_item « endl;
outputToTestQueue(q);
cout « "\nAdd one item that values 100:\n"; addltem(q,100);
outputToTestQueue(q);
cout « "\nNow, we remove all items from queue:\n"; for (i = 0; i < q.n; i++)
if (removeltem(q,out_item))
cout « "Have just removed item: " « out_item « endl;
else cout « "Queue has no item left." « endl; outputToTestQueue (q) ;
cout « endl;
}
6.3. Cài đặt Hàng đọi bằng danh sách liên kết
Tương tự như danh sách liên kết. Phần tử đầu của danh sách liên kết (pHead) sẽ là phần tử đầu của Hàng đợi (front), phần tử cuối của danh sách liên kết (pTail) sẽ là phàn tử cuối của Hàng đợi (rear).
Khai báo cấu trúc dữ liệu: struct Node { DataType data; Node * pNext; } ; struct Queue {
Node * front, *rear; };
a Khởi tạo Queue rồng:
void init (Queue &q) { q.front = q.rear = NULL;
}
b. Kiểm tra hàng đợi rồng
int isEmpty (Queue &q) { if (q.front — NULL)
return 1; return 0;
}
c. Thêm một phần tử p vào cuối hàng đợi Queue:
int enQueue (Queue &q, DataType x) { Node * new_node = new Node;
if (new_node == NULL) return 0; // không dủ bộ nhớ new_node -> pNext = NULL;
new_node -> data = x;
if (q.front = NULL) // trường hợp Hàng đợi rỗng q.front = q.rear = new_node;
else {
q.rear -> pNext = new_node; q .rear = new_node;
}
return 1;
}
d. Lấy phần tử ra khỏi cuối hàng đợi
DataType deQueue (Queue &q) { if (isEmpty(q) ) {
cout « "Queue is empty"; exit(1);
}
Node *p = q.front;
q.front = q.front -> pNext;
if (q.front = NULL) q.rear = NULL; DataType X = p - > data;
p -> pNext = NULL; delete p;
return x;
}
e. Xem thông tin của phần tử ở đầu Hàng đợi:
DataType viewFront (Queue q) { if (isEmpty(q)) {
cout « "Queue is empty"; exit (1) ;
}
return q.front -> data;
}
6.4. ử n g dụng
Trong tin học, CTDL hàng đợi có nhiều ứng dụng: khử đệ qui, tổ chức lưu vết các quá trình tìm kiếm theo chiều rộng và quay lui, vét cạn, tổ chức quản lý vả phân phối tiến trình trong các hệ điều hành, tổ chức bộ đệm bàn phím, ...
Thơng thường các vấn đề liên quan đến cơ chế “vào trước ra trước” đều có thể dừng cấu trúc dữ liệu Hàng đợi. Ví dụ như cơ chế sản xuất và tiêu thụ, hàng hóa sản xuất trước được đưa vào kho và sẽ được xuất ra trước. Các ứng dụng đặt vé tàu lửa máy bay, hệ thống rút tiền...Ngồi ra hàng đợi cịn được dùng nhiều trong hệ điều hành: bộ đệm ứng dụng, hàng đợi xử lý các sự kiện, hàng đợi xử lý phím nhấn, tiến trình...
• Bài tốn quản lý kho hàng:
Đây là dạng bài toán sản xuất & tiêu dùng, mặt hàng được sản xuất ra sẽ được lưu vào kho, hàng hóa từ kho này sẽ được xuất ra ngoài cho nhà phân phối. Khi đó những mặt hàng nào đưa vào kho trước tiên sẽ ra được xuất kho trước. Đây là dạng FIFO nên chúng ta có thể dùng cấu trúc dừ liệu Hàng đợi để minh họa cho nó.
Cài đặt: Việc cài sinh viên tự cài đặt. Cách cài đặt tương tự như cài đặt Hàng đợi (chứa các số nguyên ở trên), ở đây chỉ khác là kiểu dữ liệu không phải là in mà là kiểu Data được khai báo như sau:
typedef struct {
char maSP[10]; // Ma san pham char tenSP[50]; // Ten san pham } Data;
TỎNG KÉT CHƯƠNG
Sau khi học xong chương này chúng ta cần nhớ:
- Sự khác biệt giữa cấu trúc dữ liệu động và cấu trúc dữ liệu tĩnh (một điểm mấu chốt dễ nhớ nhất là hai chữ “động” và “tĩnh”. Động có nghĩa là có thể thay đổi linh hoạt còn tĩnh thi không thể thay đổi). Mồi loại đều có ưu và nhược điểm nhất định và tùy vào ứng dụng trong thực tế mà người ta sẽ quyết định xem nên sử dụng loại cấu trúc dừ liệu nào để đạt được hiệu quả tối đa kể cả về mặt lưu trữ, thao ..ác và thời gian thực hiện ứng dụng.
- Con trỏ có ứng dụng rất nhiều trong thực tế, vì vậy chứng ta phải nắm rõ bản chất của biến con trỏ và người ta thường sử dụng nó để quản lý vùng nhớ động.
- Phân biệt hai loại danh sách đó là danh sách đặc và danh sách liên kết. Trong chương này chúng ta đã được tìm hiểu sâu về loại danh sách liên kết đơn - làm việc với danh sách động với các thao tác thêm, xóa, sửa.
- Có hai loại danh sách đặc biệt được ứng dụng nhiều trong thực tế đó là ngăn xếp (Stack) và hàng đợi (Queue). Ngăn xếp là loại danh sách hoạt động theo cơ chế LIFO (vào sau ra trước) còn Hàng đợi là loại danh sách hoạt động theo cơ che FIFO (vào trước ra trước). Trên thực tể khi gặp bài toán nào mà bắt buộc phải làm việc theo cơ chế của Ngăn xểp hoặc Hàng đợi thì chúng ta nên áp dụng nó để giải quyết bài tốn.
CÂU HỎI VẢ BẢI TÁP
1. Biến con trỏ là gì? Nêu các thao tác trên biển con trỏ 2. Nêu sự khác nhau giữa biến tĩnh và biến động
3. Cho biết kết quả xuất ra màn hình của đoạn code sau:
int *p = new int; int X = 10;
int *q = &x; *p = 20; X + + ; cout « *q « endl; q = p; (*q)++; cout « *p « endl; P++; cout « p - q « endl;