Các thao tác cơ bản trên ngăn xếp:
s initStack: khởi tạo một ngăn xếp rồng
■S isEmpty: kiểm tra ngăn xếp có rồng hay chưa.
•S isFull: kiểm tra ngăn xếp đầy hay chưa.
■S push: đưa một phần tử (item) dữ liệu vào ngăn xếp, việc đưa vào này có thể làm ngăn xếp bị đầy.
■S pop: lấy một phần tử (item) dữ liệu ra khỏi ngăn xếp, việc này có thể làm ngăn xếp bị rỗng.
Khi viết code hiện thực một ngăn xếp, ta có thể dùng: • Mảng một chiều.
• Danh sách liên kết đơn.
5.2. Cài đặt ngăn xếp bằng mảng
ũ t ? ĩ 4 5 6 7
s 1 10 5 8 9
Hình 5-13. Mơ hình cài đặt ngăn xếp bằng mảng
Sau đây chúng ta sẽ minh họa cài đặt ngăn xếp chứa các sổ nguyên: Khai báo cấu trúc dữ liệu:
struct STACK {
int *a; //mảng một chiều chưa các item của ngăn xếp int n; //cho biết kích thước của ngăn xếp
int top; //quản lý đỉnh của ngăn xếp };
Các thao tác:
a) Hàm khởi tạo ngăn xếp:
void initstack (STACK &s, int spt) { s.a = new int[spt];
if (s.a == NULL) { cout « "Khơng dủ bộ nhớ để cấp phát!"; exit(0); } s .n = spt; s .top = -1; }
b) Hàm kiểm tra ngăn xếp rồng hay chưa:
bool isEmpty(STACK s) { if (s.top = -1)
return true; return false;
c) Hàm kiểm tra ngăn xếp bị đầy hay chưa:
bool isFull (STACK s) { if (s .top = s .n-1) return true;
return false;
}
d) Hàm đưa một phần tử vào ngăn xếp:
bool push(STACK &s, int x) { if (isFull(s) == true)
return false; //khơng đua vào được vì ngăn xếp dã dầy s .top++;
s.a[s.top] = X; //hoặc viết gọn lại là s.a[++s.top] = X return true; // đưa vào thành công
}
e) Hàm lấy một phần tử ra khỏi ngăn xếp:
bool pop(STACK &s, int &x) { if (isEmpty(s) = true)
return false; // lấy ra khơng thành cơng vì ngăn xếp đã rỗng
X = s .a [s.top--];
return true; //lấy ra thành công }
f) 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) = true)
X = s .a [s . top] ; return true;
}
g) Hàm xuất các phần tử có trong ngăn xếp:
void outputstack(STACK s) { int i ;
if (isEmpty(s) = true) return;
for (i = s.top; X >= 0; i— ) cout « s.a[i] « " ";
cout « "Đỉnh của ngăn xếp đang ở tại vị tri:" « s.top;
}
h) Một ví dụ về hàm main cho bài lập trình ngăn xếp bằng mảng một chiều:
void main() { bool over, empty; STACK s;
int n, X , i;
cout « "Hãy cho biết kich thước của ngăn x ế p :"; ein » n; //số phần tử của ngăn xếp
initstack(s,n);
cout « "Thi nghiệm hàm Push:";
//cố tình push vượt kich thước ngăn xếp for (i = 0; i < s.n + 1; i++) {
cout « "Nhập giá trị dể dưa vào ngăn xếp, X = "; ein » x;
if (over == false)
cout « "Ngăn xếp bị tràn!";
}
cout « "Thi nghiệm hàm Pop:";
//cố tình pop ra vượt kích thước ngăn xếp for (i = 0; i < s.n + 3; i++) { empty = pop(s,x); cout « "Giá trị X = " « x; if (empty = false) cout « "Ngăn xếp dã rỗng!"; } }
5.3. Cài đặt Ngăn xếp bằng danh sách liên kết đơn
✓ Sử dụng một danh sách liên kết đơn để hiện thực ngăn xếp với đỉnh ngăn xếp (biến top) chỉ vào phần tử đầu tiên của danh sách.
■S push một phần tử (item) vào ngăn xếp thì tương đương với hàm addHead trong bài danh sách liên kết đơn.
•S pop một phần tử (item) ra khỏi ngăn xếp thỉ tương đương với hàm removeHead trong bài danh sách liên kết đơn.
rtem-i NULL S .T o p - STACK s ' + 11 Ĩ 21 Ĩ €0 37 zap NULL
Hình 5-14. Mơ hình cài đặt ngăn xếp bằng danh sách liên kết đơn
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 tố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) ;