Ngăn xếp (stack) là một cấu trúc dữ liệu đơn giản nhưng rất hữu ích trong lập trình. Ngăn xếp được sử dụng để lưu trữ các phần tử theo cơ chế “Last In First Out” (LIFO) – phần tử được thêm vào cuối cùng sẽ được lấy ra đầu tiên.
DANH SÁCH BÀI VIẾT Tìm hiểu Cấu trúc dữ liệu Hash table trong lập trình C/C++ Cây đa phân trong lập trình C/C++ – Cấu trúc dữ liệu cây đa phân Hiểu về cấu trúc dữ liệu cây trong lập trình C/C++ Chinh phục cấu trúc dữ liệu hàng đợi lập trình C/C++ trong 5 phút Cấu trúc dữ liệu ngăn xếp trong lập trình C/C++ Cấu trúc dữ liệu Danh sách liên kết (Linked list) trong lập trình C/C++ Cấu trúc dữ liệu cây nhị phân trong lập trình C/C++ Cây nhị phân tìm kiếm – Tìm hiểu cây nhị phân tìm kiếm trong C/C++ Duyệt tiền tố, trung tố, hậu tố trong cây nhị phân – Lập trình C/C++
Cấu trúc dữ liệu ngăn xếp
Để hình dung ra ngăn xếp, bạn có thể hình dung tới trồng sách. Khi thêm trồng sách quyển sách sẽ luôn được đặt lên bên trên cùng, và khi lấy ra cũng lấy từ bên trên cùng.
Trong C/C++, ngăn xếp thường được triển khai bằng cách sử dụng mảng hoặc danh sách liên kết. Trong bài viết này, chúng ta sẽ tìm hiểu cách triển khai ngăn xếp bằng mảng và cả danh sách liên kết.
Cấu trúc dữ liệu ngăn xếp bằng mảng
Trong ngăn xếp bằng mảng, các phần tử được lưu trữ trong một mảng và chỉ có thể truy cập đến phần tử cuối cùng được thêm vào. Vị trí cuối cùng được thêm vào được gọi là “đỉnh” của ngăn xếp.
Để triển khai ngăn xếp bằng mảng trong C/C++, chúng ta cần định nghĩa một cấu trúc dữ liệu bao gồm các thành phần sau:
#define MAX 100 // định nghĩa kích thước tối đa của ngăn xếp
struct Stack {
int top; // vị trí của đỉnh trong mảng
int arr[MAX]; // mảng để lưu trữ các phần tử
};
Trong đó, top
là biến đếm vị trí của đỉnh trong mảng và arr
là mảng để lưu trữ các phần tử của ngăn xếp. Chúng ta cũng định nghĩa hàm để thực hiện các hoạt động trên ngăn xếp.
- Khóa học lập trình C/C++ từ A-Z cho người mới – Giảm giá 40% hôm nay
- Khóa học C# từ cơ bản tới nâng cao – Đăng ký hôm nay giảm 40%
- Khóa học Java cơ bản dành cho người mới bắt đầu- Giảm 40% hôm nay
- Khóa học lập trình Android từ cơ bản đến thành thạo – Giảm ngay 40%
1. Hàm khởi tạo
Hàm khởi tạo được sử dụng để khởi tạo ngăn xếp trước khi sử dụng. Trong hàm này, ta thiết lập giá trị ban đầu cho biến đếm vị trí top
.
void initStack(Stack* s) {
s->top = -1; // đặt top bằng -1 để biểu thị ngăn xếp rỗng
}
2. Hàm kiểm tra ngăn xếp rỗng
Hàm isEmpty()
kiểm tra xem ngăn xếp có rỗng hay không. Nếu top
bằng -1 thì ngăn xếp rỗng.
bool isEmpty(Stack* s) {
return s->top == -1;
}
3. Hàm kiểm tra ngăn xếp đầy
Hàm isFull()
kiểm tra xem ngăn xếp có đầy hay không. Nếu top
bằng MAX-1
thì ngăn xếp đã đầy.
bool isFull(Stack* s) {
return s->top == MAX - 1;
}
XEM THÊM: Khóa học lập trình C/C++ từ A-Z cho người mới – Giảm giá 40% hôm nay
4. Hàm thêm phần tử vào ngăn xếp
Hàm push()
thêm một phần tử mới vào ngăn xếp. Trước khi thêm, ta kiểm tra xem ngăn xếp đã đầy hay chưa. Nếu chưa đầy, ta tăng giá trị của top
lên 1 và gán giá trị của phần tử vào vị trí top
của mảng.
void push(Stack* s, int value) {
if (isFull(s)) {
cout << "Stack is full" << endl;
} else {
s->top++;
s->arr[s->top] = value;
}
}
5. Hàm lấy phần tử ra khỏi ngăn xếp
Hàm pop()
lấy phần tử cuối cùng ra khỏi ngăn xếp. Trước khi lấy, ta kiểm tra xem ngăn xếp có rỗng hay không. Nếu không rỗng, ta trả về giá trị của phần tử tại vị trí top
của mảng và giảm giá trị của top
đi 1.
int pop(Stack* s) {
if (isEmpty(s)) {
cout << "Stack is empty" << endl;
return -1;
} else {
int value = s->arr[s->top];
s->top--;
return value;
}
}
6. Hàm lấy giá trị của phần tử ở đỉnh ngăn xếp
Hàm peek()
trả về giá trị của phần tử ở đỉnh của ngăn xếp mà không lấy nó ra khỏi ngăn xếp. Trước khi trả về giá trị, ta kiểm tra xem ngăn xếp có rỗng hay không. Nếu không rỗng, ta trả về giá trị của phần tử tại vị trí top
của mảng.
int peek(Stack* s) {
if (isEmpty(s)) {
cout << "Stack is empty" << endl;
return -1;
} else {
return s->arr[s->top];
}
}
XEM THÊM: Khóa học C# từ cơ bản tới nâng cao – Đăng ký hôm nay giảm 40%
7. Chương trình ví dụ
Dưới đây là một chương trình ví dụ minh họa sử dụng ngăn xếp bằng mảng.
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100 // định nghĩa kích thước tối đa của ngăn xếp
struct Stack {
int items[MAX_SIZE]; // mảng để lưu trữ phần tử trong ngăn xếp
int top; // biến đếm số phần tử hiện có trong ngăn xếp
};
void createStack(struct Stack *s) { // hàm khởi tạo ngăn xếp
s->top = -1; // thiết lập top ban đầu là -1 để đánh dấu ngăn xếp rỗng
}
int isEmpty(struct Stack *s) { // hàm kiểm tra ngăn xếp rỗng
return s->top == -1;
}
int isFull(struct Stack *s) { // hàm kiểm tra ngăn xếp đầy
return s->top == MAX_SIZE - 1;
}
void push(struct Stack *s, int value) { // hàm đưa phần tử vào ngăn xếp
if (isFull(s)) { // nếu ngăn xếp đã đầy thì hiển thị thông báo lỗi
printf("Stack overflow\n");
} else {
s->top++; // tăng biến top để đánh dấu thêm một phần tử mới
s->items[s->top] = value; // gán giá trị của phần tử mới vào mảng
}
}
int pop(struct Stack *s) { // hàm lấy phần tử khỏi ngăn xếp
if (isEmpty(s)) { // nếu ngăn xếp rỗng thì hiển thị thông báo lỗi và trả về giá trị -1
printf("Stack underflow\n");
return -1;
} else {
int value = s->items[s->top]; // lưu giá trị của phần tử cuối cùng vào biến value
s->top--; // giảm biến top để đánh dấu bỏ đi một phần tử
return value; // trả về giá trị của phần tử cuối cùng
}
}
int peek(struct Stack *s) { // hàm lấy giá trị của phần tử ở đỉnh ngăn xếp
if (isEmpty(s)) { // nếu ngăn xếp rỗng thì hiển thị thông báo lỗi và trả về giá trị -1
printf("Stack is empty\n");
return -1;
} else {
return s->items[s->top]; // trả về giá trị của phần tử ở đỉnh ngăn xếp
}
}
int main() {
struct Stack s;
// Tạo mới stack
createStack(&s);
// Thêm các phần tử vào stack
push(&s, 5);
push(&s, 10);
push(&s, 15);
// Hiển thị phần tử trên cùng của stack
printf("Top element: %d\n", peek(&s));
// Xóa phần tử trên cùng của stack
pop(&s);
// Hiển thị phần tử trên cùng của stack sau khi xóa phần tử trên cùng
printf("Top element: %d\n", peek(&s));
// Thêm các phần tử khác vào stack
push(&s, 20);
push(&s, 25);
// Kiểm tra xem stack có đầy không
if (isFull(&s)) {
printf("Stack is full\n");
} else {
printf("Stack is not full\n");
}
// Lấy các phần tử từ stack cho đến khi stack trống
while (!isEmpty(&s)) {
int value = pop(&s);
printf("%d ", value);
}
printf("\n");
return 0;
}
Kết quả chạy chương trình trên
Top element: 15
Top element: 10
Stack is not full
25 20 10 5
Cấu trúc dữ liệu ngăn xếp bằng danh sách liên kết(LinkedList)
Để triển khai được hàng đợi bằng danh sách liên kết, trước tiên bạn cần nắm rõ danh sách liên kết là gì, có thể thăm khảo ngay bài viết này.
Ngăn xếp bằng danh sách liên kết (Stack implemented using Linked List), trong đó mỗi phần tử được lưu trữ trong một node của danh sách liên kết, mỗi node sẽ chứa dữ liệu và con trỏ trỏ tới node tiếp theo.
Xây dựng cấu trúc cho 1 node như sau:
struct Node {
int data;
struct Node* next;
};
Để triển khai một ngăn xếp bằng cấu trúc dữ liệu linkedlist, chúng ta cần xây dựng các hàm thành phần sau:
1. Hàm tạo một node mới
Hàm newNode sử dụng để tạo một nút mới để nối nút vào linkedlist.
Hàm tạo 1 nút mới
struct Node* newNode(int data) {
struct Node* node = (struct Node*)malloc(sizeof(struct Node)); //Khai báo và cấp phát bộ nhớ cho nút mới
node->data = data; //Gắn data cho nút
node->next = NULL; //Cho nút này trỏ tới 1 node tiếp theo là NULL
return node;
}
2. Hàm kiểm tra ngăn xếp rỗng:
Hàm isEmpty sử dụng để kiểm tra ngăn xếp rỗng hay không, nếu nút root thì ngăn xếp rỗng, ngược lại ngăn xếp không rỗng.
int isEmpty(struct Node* root) {
return !root;
}
3. Hàm thêm một phần tử vào đầu ngăn xếp:
Hàm push sử dụng để thêm 1 nút vào ngăn xếp.
void push(struct Node** root, int data) {
struct Node* node = newNode(data);
node->next = *root;
*root = node;
}
4. Hàm lấy phần tử đầu ngăn xếp:
Hàm pop sử dụng lấy ra 1 nút ngăn xếp.
int pop(struct Node** root) {
if (isEmpty(*root)) {
printf("Stack Underflow\n");
return INT_MIN;
}
struct Node* temp = *root;
*root = (*root)->next;
int popped = temp->data;
free(temp);
return popped;
}
5. Hàm lấy giá trị phần tử đầu ngăn xếp
Hàm peek sử dụng để lấy giá trị phần tử của đầu ngăn xếp, khác với hàm pop lấy một nút khỏi danh sách, Tức là bạn lấy quyển sách khỏi trồng sách. Hàm peek ta chỉ đọc giá trị của nút đó.
int peek(struct Node* root) {
if (isEmpty(root)) {
printf("Stack is empty\n");
return INT_MIN;
}
return root->data;
}
XEM THÊM: Khóa học lập trình Android từ cơ bản đến thành thạo – Giảm ngay 40%
6. Hàm hiển thị toàn bộ các phần tử trong ngăn xếp
Hàm outputStack sử dụng để hiển thị toàn bộ phần tử trong ngăn xếp.
int outputStack(struct Node* root) {
if (isEmpty(root)) {
printf("Stack is empty\n");
return INT_MIN;
}
return root->data;
}
7. Hàm hủy toàn bộ các phần tử trong ngăn xếp:
Hàm destroyStack sử dụng để hủy bỏ các node trong ngăn xếp.
void destroyStack(struct Node** root) {
struct Node* current = *root;
struct Node* next;
while (current != NULL) {
next = current->next;
free(current);
current = next;
}
*root = NULL;
}
8. Chương trình ví dụ
Dưới đây là một chương trình ví dụ minh họa sử dụng ngăn xếp bằng Danh sách liên kết(LinkedList).
#include <stdio.h>
#include <stdlib.h>
// Định nghĩa cấu trúc cho một nút trong linked list
struct Node {
int data; // Dữ liệu của nút
struct Node* next; // Con trỏ đến nút tiếp theo trong danh sách
};
// Hàm để tạo một nút mới
struct Node* newNode(int data) {
struct Node* node = (struct Node*)malloc(sizeof(struct Node));
node->data = data;
node->next = NULL;
return node;
}
// Hàm để thêm một phần tử vào đầu ngăn xếp
void push(struct Node** top, int data) {
struct Node* node = newNode(data);
node->next = *top;
*top = node;
printf("%d pushed to stack\n", data);
}
// Hàm để lấy phần tử trên đầu ngăn xếp
int pop(struct Node** top) {
if (*top == NULL) {
printf("Stack is empty\n");
return -1;
}
struct Node* node = *top;
*top = node->next;
int data = node->data;
free(node);
return data;
}
// Hàm để hiển thị tất cả các phần tử trong ngăn xếp
void display(struct Node* node) {
if (node == NULL) {
printf("Stack is empty\n");
return;
}
while (node != NULL) {
printf("%d ", node->data);
node = node->next;
}
printf("\n");
}
int main() {
struct Node* top = NULL;
// Thêm các phần tử vào ngăn xếp
push(&top, 1);
push(&top, 2);
push(&top, 3);
// Hiển thị tất cả các phần tử trong ngăn xếp
printf("Stack: ");
display(top);
// Lấy phần tử trên đầu ngăn xếp và hiển thị
int data = pop(&top);
printf("%d popped from stack\n", data);
// Hiển thị tất cả các phần tử trong ngăn xếp
printf("Stack: ");
display(top);
return 0;
}
Kết quả chạy chương trình
1 pushed to stack
2 pushed to stack
3 pushed to stack
Stack: 3 2 1
3 popped from stack
Stack: 2 1
Cảm ơn bạn đã đọc hết bài viết, hy vọng qua bài viết này đã giúp bạn nắm rõ và áp dụng được cấu trúc dữ liệu danh sách liên kết.
[Xem tất cả bài viết chủ đề C/C++ tại đây]
XÊM THÊM Cấu trúc dữ liệu cây nhị phân trong lập trình C/C++ Tìm hiểu các toán tử thao tác bit trong lập trình C/C++ Cấu trúc dữ liệu cây nhị phân trong lập trình C/C++ Thuật toán đếm số lượng chữ số của số nguyên dương n Thuật toán tính dãy số Fibonacci Bài toán chuẩn hóa xâu ký tự Kiểm tra số chẵn lẻ trong lập trình C/C++