Từ mơ hình chứng ta sẽ viết ra được thuật tốn như sau: • Neu Danh sách rồng Thì
— pHead = new_ele; // new_ele là phần tử mới — pTail = pHead;
• Ngược lại
— new ele ->next = pHead; — pHead = new_ele ; Hàm thể hiện thao tác thêm:
void addHead(list &1, node* new_ele) { if (1. pHead == NULL) { / / X â u r ỗ n g 1. pHead = new_ele; l.pTail = 1. pHead; } else { new_ele->next = 1. pHead; 1. pHead = new_ele; }
/ / }
node* insertHead(list &1, int x)
{
node* new_ele = getNode(x); if (new_ele = NULL)
return NULL; addHead(l, new_ele); return new_ele;
}
- Thao tác thêm phần tử vào cuối danh sách liên kết đorn:
Hình 5-4. Thao tác thêm phần tử vào cuối danh sách
Từ mơ hình chúng ta sẽ viết ra được thuật tốn như sau: • Nếu Danh sách rỗng Thì
— pHead = new_ele; // new_ele là phần tử mới — pTail = pHead;
• Ngược lại
— pTail = new_ele ; Hàm thể hiện thao tác thêm:
void insertTail(list & 1 , node *new_ele) { if (l.pHead = NULL) {
1. pHead = new_ele; l.pTail = 1. pHead;
}
else {
1. pTail ->next = new_ele; 1. pTail = new_ele ;
} }
/ / .......................................................
void inputTail(List &1,int x) { node * p;
p = getNode(x); insertTail(1, p ) ;
}
- Thao tác thêm phần tử vào giữa danh sách (thêm vào sau một phần tử khác trong danh sách liên kết)
Giả sử chúng ta cần thêm một phần tử có giá trị dữ liệu là X vào trong danh sách List vào ngay sau nút do con trỏ q đang quản lý. Trong thực tế nhiều khi chúng ta phải thực hiện thao tác tìm kiếm để xác định q, ờ đây giả sử chủng ta đã xác định được q và nhiệm vụ của chúng ta là chèn thêm một phần tử mới có dữ liệu là X vào sau q.
pH ead
Hình 5-5. Thao tác thêm phần tử vào giữa danh sách
Thuật toán:
//input: danh sách 1 (pHead, pTail), q, phần tử mới new_ele //output: danh sách 1 (pHead, pTail) với new_ele ở sau q Nếu ( q != NULL) thi
new_ele -> next = q -> next; q -> next = new ele ;
Nếu ( q = p T a il) thì pTail = new_ele; Ngược lại
Không tồn tại phần tử cần thêm Hàm mô tả thao tác thêm:
void insertAfter(list &l,node *q, node* new_ele)
{
if (q != NULL && new_ele != NULL){ new_ele -> next = q -> next; q -> next = new_ele;
else {
cout « "not exsist element that need to be add";
} }
d. Duyệt qua các nút trong danh sách
Duyệt danh sách là thao tác thường được thực hiện khi cỏ nhu cầu xử lý các phần tử của danh sách theo cùng một cách thức hoặc khi cần lấy thông tin tổng họp từ các phần tử của danh sách như:
— Đem các phần tử của danh sách, — Tìm tất cả các phần tử thoả điều kiện,
— Hủy tồn bộ danh sách (và giải phóng bộ nhớ) Thuật toán duyệt danh sách:
Bước 1: p = pHead; //Cho p trỏ đến phần tử đầu danh sách Bước 2: Trong khi (Danh sách chưa hết) thực hiện
— B 21 : Xử lý phần tử p;
— B22 : p = p -> next; // Cho p trỏ tới phần tử kế
void processList (list &1)
{ node * p ; p = l.pHead; while (p!= NULL){ processNode(p); // xử lý cụ thể tùy ứng dụng p = p->next; } 1. pTail = new_ele; }
}
Ví dụ hàm liệt kê giá trị của các phần tử trong danh sách được viết như sau:
void output(list 1) { node* p = l.pHead; while(p != NULL) { cout « p -> data « "\t"; p = p -> next; } cout « endl; }
e. Tim kiếm một phần tử trong dan sách liên kết
Giả sử cầm tìm phần tử có khóa là X trong danh sách liên kết đorn 1. Ý tưởng của thuật toán là luôn luôn đi từ đầu danh sách, tìm ra vị trí thích hợp có khóa là X. Neu X tồn tại trong danh sách liên kết thì hàm trả về con trỏ đang quản lý vùng nhớ có khóa là X, nếu không trả về NULL.
node* search(list 1 , int x)
{
node *p = l.pHead;
while ( p != NULL && p -> data != x) p = p -> next;
return p;
}
f. Loại bỏ bởi một phần tử ra khỏi danh sách
Giả sử chủng ta cần loại bỏ phần tử có giá trị dữ liệu là X trong danh sách liên kết đom. Đe thực hiện điều này trước tiên chủng ta phải thực hiện thao tác tìm kiếm địa chỉ của nút có thành phần dữ liệu là X, sau đo mới thực hiện thao tác loại bỏ
nếu tìm thấy. Ket quả tìm kiếm có thể cho kết quả là phần tử có dữ liệu là X nằm ở đầu, ở cuối hoặc ở giữa danh sách liên kết . Tuy nhiên trong quá trình tìm kiếm, nếu tìm thấy chúng ta phải ghi nhận địa chỉ của nút đứng ngay trước nút tìm thấy (trừ trường hợp phần tử tìm thấy nằm ở đầu danh sách).
- Xóa phần tử ở đầu danh sách
Hình 5-6. Thao tác xóa phần tử ở đầu danh sách
Cài đặt:
void removeHead(list &1) {
if (l.pHead == NULL) return ;//Nấu danh sách rỗng else {
node* p = 1. pHead; 1. pHead = p -> next;
if (1. pHead == NULL) l.pTail = NULL; delete p;
} }
- Xóa phần tử ở cuối danh sách
Hình 5-7. Thao tác xóa phần tử ở cuối danh sách
Điều kiện để thực hiện thuật toán là p != NULL (tức là tồn tại phần tử cần xóa).
Cài đặt:
void removeTail (list &1) { node *p = 1.pTail;
if (p!= NULL && p == l.pHead && p == l.pTail) { delete p;
l.pHead = l.pTail = NULL; return;
}
//find q q = l.pHead;
while (q -> next != p ) q = q -> next; q -> next = p -> next;
l.pTail = q; delete p;
}
- Xóa một phần tử sau phần tử do q quản lý:
Hình 5-8. Thao tác xóa phần tử sau q
q phải khác NULL
Node sau q phải khác NULL Cài đặt:
void removeAfter(list &1, node *q ) { if (q != NULL && q -> next !=NULL) {
node* p = q -> next; q -> next = p->next; if (p == l.pTail) l.pTail = q; delete p; } } g. Hủy danh sách
Thao tác này thực chất là thực hiện nhiều lần thao tác hủy một nút. Cài đặt:
void removeList(list &1) { Node *p;
while (1.pHead!= NULL) { p = 1. pHead; 1. pHead = p->next; delete ); } l.pTail = NULL; } h. Sắp xếp danh sách liên kết
Đe thực hiện các thuật toán sắp xếp đã học để sắp xếp các phần tử trên danh sách liên kết đcm chúng ta có hai cách tiếp cận:
Phương án 1: Hoán vị nội dung các phần tử trong danh sách liên kết tức là thao tác trên vùng dữ liệu.
Phương án 2: Thay đổi các mối liên kết tức là thao tác trên vùng liên kết.
Nhận xét: Với phương án 1 thì dễ dàng thao tác hơn phương án hai, nhưng nó có nhược điểm là khi kích thước dữ liệu của một phần tử rất lớn thì phương án 1 sẽ không hiệu quả bằng phương án 2, vì phương án 2 các thao tác sắp xếp nó khơng ảnh hưởng tới kích thước của một phần tử trong danh sách liên kết.
Ở trong tài liệu này chúng ta cùng tìm hiểu cách sắp xếp sử dụng phương án 1. Sau đây chúng ta tìm hiểu m ột số thuật tốn sắp xen trên danh sách liên kết đơn:
Sắp xếp bằng phương pháp đổi chồ trực tiếp ( Interchange S o r t)
Sau đây là mô phỏng của thuật toán sắp xểp các phần tử trong danh sách liên kết đơn tăng dần:
Hình 5-9. Mơ phỏng sắp xếp danh sách liên kết đon bằng thuật toán đổi chỗ trực tiếp
Sau một vịng thì chứng ta thu được phần tử nhỏ nhất nằm ở đầu danh sách liên kết. Tương tự cho các lần lặp kế tiếp (lưu ỷ với mồi lần lặp thì p luôn trỏ vào đầu danh sách hiện hành cần sắp xếp và chứng ta tìm cách để đưa phần tử nhỏ nhất về đầu danh sách ), kết quả cuối cùng chúng ta thu được một danh sách mà các phần tử đã được sắp xếp tăng dần:
l.pHead
LpTail
.. j : Ì I j M D . 1 1 ] sm
void interChangeSort ( list &1 ) {
for ( node* p = l.pHead ; p != l.pTail ; p = p -> next ) for (node* q = p -> next; q != NULL ;q = q -> next)
if ( p -> data > q -> data )
swap( p -> data , q -> data );
}
Sắp xếp bàng phương pháp chọn trực tiếp ( Selection s o r t)
Sau đây là mô phỏng của thuật toán sắp xếp các phần tử trong danh sách liên kết đơn tăng dần:
1 • p ĩ ĩ c a đ I J M 111111
• 1 ° t r i1 m 8 rH 1 tH. 1 1
l.pHead
LnTail
Hình 5-10. Mơ phỏng sắp xếp danh sách liên kết đơn bằng thuật toán chọn trực tiếp
Sau một vòng lặp chúng ta chọn được phần tử nhở nhất nằm ờ dầu danh sách liên kết. Tương tự cho các lần lặp tiếp theo (lưu ý với mồi lần lặp thì p ln trỏ vào đầu danh sách hiện hành cần sắp xếp và chúng ta tìm phần tử nhỏ nhất để hoán đổi với phần tử ở đầu danh sách hiện hành) chúng ta được một danh sách liên kểt có các phần tử được sắp xếp tăng dần:
Cài đặt:
void listSelectionSort (list &1) {
for (node* p = l.pHead; p != l.pTail; p = p -> next ) { node * min = p;
for (node *q = p -> next; q != NULL; q = q-> next) if ( min -> data > q -> data ) min = q ;
swap(min -> data, p -> data);
}
5. Ngăn xếp (Stack)
Hình 5-11. Hình mơ phỏng một ngăn xếp5.1. Khái niệm 5.1. Khái niệm
s Ngăn xếp là một đối tượng dùng để chứa các phần tử dữ liệu (item) một cách có thứ tự.
s Khi đưa một phần tử vào hoặc lấy một phần tử ra khỏi ngăn xếp thì ta đều thực hiện ở cùng một phía (từ phía đỉnh của ngăn xếp).
•S Phần tử (item) đưa vào sau sẽ được lấy ra trước, phần tử đưa vào trước sẽ
được lấy ra sau. Cách đưa vào và lẩy ra như thế được gọi là LIFO (Last In First Out).
Tóm lại ngăn xếp chẳng qua cũng là một danh sách (gồm các phần tử cùng kiểu) nhưng nó phải hoạt động tuân thủ theo quy tắc LIFO.
Hình 5-12. Mơ phỏng thao tác chính trên ngăn xếpCác thao tác cơ bản trên ngăn xếp: 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;