1. 2: Sản phẩm và dịch vụ
2.1.3 Chuỗi (String) trong C/C++
C++ cung cấp hai kiểu biểu diễn chuỗi như sau:
Chuỗi theo phong cách của ngôn ngữ C (C-style),
Lớp Chuỗi (String) được giới thiệu trong C/C++ chuẩn. Chuỗi theo phong cách C
Dạng chuỗi này bắt nguồn từ ngôn ngữ C và tiếp tục được hỗ trợ trong C/C++. Chuỗi trong ngôn ngữ lập trình C thực chất là mảng một chiều của các ký tự mà kết thúc bởi một ký tự null '\0'.
Phần khai báo và khởi tạo dưới đây tạo ra một chuỗi bao gồm một từ "Hello". Để giữ các giá trị null tại cuối của mảng, cỡ của mảng các ký tự bao gồm một chuỗi phải nhiều hơn số lượng các ký tự trong từ khóa "Hello". char loiChao[6]={'H','e','l','l','o','\0'};
Nếu bạn theo quy tắc khởi tạo các chuỗi, bạn có thể viết lệnh như sau: char loiChao[]="Hello";
Dưới đây là phần biểu diễn ô nhớ cho đoạn chuỗi trên trong ngôn ngữ C/C++:
Thực tế, bạn không đặt ký tự null tại vị trí cuối cùng của biến hằng số. Bộ biên dịch C tự động thêm '\0' tại ví trí cuối cùng của chuỗi khi nó khởi tạo chuỗi. Cùng thử ví dụ in ra chuỗi sau đây:
#include<iostream>
usingnamespace std;
int main () {
char loiChao[6]={'H','e','l','l','o','\0'};
cout <<"Khi gap nhau, chung ta noi: "; cout << loiChao << endl;
return0; }
Khi đoạn code trên được biên dịch và thực hiện, kết quả in ra sẽ như sau:
Khi gap nhau, chung ta noi:Hello
Ngôn ngữ C/C++ hỗ trợ nhiều hàm đa dạng để thao tác các chuỗi kết thúc là null:
STT Hàm & Mục đích
1 strcpy(s1, s2);
Sao chép chuỗi s2 cho chuỗi s1. 2 strcat(s1, s2);
Nối chuỗi s2 vào cuối chuỗi s1.
3 strlen(s1);
Trả về độ dài của chuỗi s1. 4 strcmp(s1, s2);
Trả về 0 nếu s1 và s2 là như nhau; nhỏ hơn 0 nếu s1<s2; lớn hơn 0 nếu s1>s2.
5 strchr(s1, ch);
Trả về con trỏ tới vị trí đầu tiên của ch trong s1. 6 strstr(s1, s2);
Trả về con trỏ tới vị trí đầu tiên của chuỗi s2 trong chuỗi s1.
#include<cstring> usingnamespace std; int main ()
{
char chuoi1[10]="Hello"; char chuoi2[10]="Christmas"; char chuoi3[10];
int len ;
// sao chep chuoi1 vao trong chuoi3 strcpy( chuoi3, chuoi1);
cout <<"strcpy( chuoi3, chuoi1) : "<< chuoi3 << endl; // noi hai chuoi: chuoi1 va chuoi2
strcat( chuoi1, chuoi2);
cout <<"strcat( chuoi1, chuoi2): "<< chuoi1 << endl; // tong do dai cua chuoi1 mot sau khi thuc hien noi chuoi len = strlen(chuoi1);
cout <<"Dung ham strlen(chuoi1) de tinh do dai chuoi1: "<< len << endl;
return0; }
• Kết quả
Chạy chương trình C/C++ trên sẽ cho kết quả như hình sau:
Lớp String trong C/C++
Thư viện chuẩn C/C++ cung cấp một kiểu lớp String mà hỗ trợ tất cả hoạt động liên quan tới chuỗi đã đề cập ở trên, và bổ sung thêm nhiều tính năng nữa. Chúng ta sẽ học lớp này trong Thư viện chuẩn C/C++ (C++ Standard Library), nhưng lúc này, chúng ta xem xét ví dụ sau:
Lúc này, có thể bạn không hiểu ví dụ này, bởi vì chúng ta chưa bàn luận về Lớp và Đối tượngtrong C/C++. Vì thế, bạn quan sát và ghi nhớ chúng tới khi bạn đã hiểu các khái niệm về Hướng đối tượng được trình bày ở chương sau đó. #include<iostream> #include<string> usingnamespace std; int main () {
string chuoi1 ="Hello"; string chuoi2 ="Christmas"; string chuoi3;
// sao chep chuoi1 vao trong chuoi3 chuoi3 = chuoi1;
cout <<"Bay gio chuoi3 la: "<< chuoi3 << endl;
// noi hai chuoi: chuoi1 va chuoi2 chuoi3 = chuoi1 + chuoi2;
cout <<"chuoi1 + chuoi2 co ket qua la: "<< chuoi3 << endl;
// tong do dai cua chuoi3 mot sau khi thuc hien noi chuoi len = chuoi3.size();
cout <<"Tinh do dai voi ham chuoi3.size() : "<< len << endl;
return0; }
Chạy chương trình C/C++ trên sẽ cho kết quả như hình sau:
2.1.3 Ngăn xếp (Stack) trong C
Một ngăn xếp là một cấu trúc dữ liệu trừu tượng (Abstract Data Type – viết tắt là ADT), hầu như được sử dụng trong hầu hết mọi ngôn ngữ lập
trình. Đặt tên là ngăn xếp bởi vì nó hoạt động như một ngăn xếp trong đời sống thực, ví dụ như một cỗ bài hay một chồng đĩa, …
Chương trình minh họa Ngăn xếp (Stack) trong C #include<stdio.h> int MAXSIZE =8; int stack[8]; int top =-1; int isempty(){ if(top ==-1) return1; else return0; } int isfull(){ if(top == MAXSIZE) return1; else return0; } int peek(){ return stack[top]; } int pop(){ int data; if(!isempty()){ data = stack[top];
return data; }else{
printf("Khong the thu thap du lieu, ngan xep (Stack) la trong.\n"); }
}
int push(int data){ if(!isfull()){
top = top +1; stack[top]= data; }else{
printf("Khong the chen du lieu, ngan xep (Stack) da day.\n"); }
}
int main(){
// chen cac phan tu vao ngan xep push(3); push(5); push(9); push(1); push(12); push(15);
printf("Phan tu tai vi tri tren cung cua ngan xep: %d\n",peek()); printf("Cac phan tu: \n");
// in cac phan tu trong ngan xep while(!isempty()){
printf("%d\n",data); }
printf("Ngan xep da day: %s\n", isfull()?"true":"false"); printf("Ngan xep la trong: %s\n", isempty()?"true":"false"); return0;
}
• Kết quả
Biên dịch và chạy chương trình C trên sẽ cho kết quả:
2.1.4 Cấu trúc dữ liệu hàng đợi (Queue)
Cấu trúc dữ liệu hàng đợi (Queue) là gì ?
Hàng đợi (Queue) là một cấu trúc dữ liệu trừu tượng, là một cái gì đó tương tự như hàng đợi trong đời sống hàng ngày (xếp hàng).
Khác với ngăn xếp, hàng đợi là mở ở cả hai đầu. Một đầu luôn luôn được sử dụng để chèn dữ liệu vào (hay còn gọi là sắp vào hàng) và đầu kia được sử dụng để xóa dữ liệu (rời hàng). Cấu trúc dữ liệu hàng đợi tuân theo phương pháp First-In-First-Out, tức là dữ liệu được nhập vào đầu tiên sẽ được truy cập đầu tiên.
Trong đời sống thực chúng ta có rất nhiều ví dụ về hàng đợi, chẳng hạn như hàng xe ô tô trên đường một chiều (đặc biệt là khi tắc xe), trong đó xe nào vào đầu tiên sẽ thoát ra đầu tiên. Một vài ví dụ khác là xếp hàng học sinh, xếp hàng mua vé, …
Biểu diễn cấu trúc dữ liệu hàng đợi (Queue)
Giờ thì có lẽ bạn đã tưởng tượng ra hàng đợi là gì rồi. Chúng ta có thể truy cập cả hai đầu của hàng đợi. Dưới đây là biểu diễn hàng đợi dưới dạng cấu trúc dữ liệu:
Tương tự như cấu trúc dữ liệu ngăn xếp, thì cấu trúc dữ liệu hàng đợi cũng có thể được triển khai bởi sử dụng Mảng (Array), Danh sách liên kết (Linked List), Con trỏ (Pointer) và Cấu trúc (Struct). Để đơn giản, phần tiếp theo chúng ta sẽ tìm hiểu tiếp về hàng đợi được triển khai bởi sử dụng mảng một chiều.
Các hoạt động cơ bản trên cấu trúc dữ liệu hàng đợi
Các hoạt động trên cấu trúc dữ liệu hàng đợi có thể liên quan tới việc khởi tạo hàng đợi, sử dụng dữ liệu trên hàng đợi và sau đó là xóa dữ liệu
khỏi bộ nhớ. Danh sách dưới đây là một số hoạt động cơ bản có thể thực hiện trên cấu trúc dữ liệu hàng đợi:
Hoạt động enqueue(): thêm (hay lưu trữ) một phần tử vào trong hàng
đợi.
Hoạt động dequeue(): xóa một phần tử từ hàng đợi.
Để sử dụng hàng đợi một cách hiệu quả, chúng ta cũng cần kiểm tra trạng thái của hàng đợi. Để phục vụ cho mục đích này, dưới đây là một số tính năng hỗ trợ khác của hàng đợi:
Phương thức peek(): lấy phần tử ở đầu hàng đợi, mà không xóa phần
tử này.
Phương thức isFull(): kiểm tra xem hàng đợi là đầy hay không.
Phương thức isEmpty(): kiểm tra xem hàng đợi là trống hay hay
không.
Trong cấu trúc dữ liệu hàng đợi, chúng ta luôn luôn: (1) dequeue (xóa) dữ liệu được trỏ bởi con trỏ front và (2) enqueue (nhập) dữ liệu vào trong hàng đợi bởi sự giúp đỡ của con trỏ rear.
Trong phần tiếp chúng ta sẽ tìm hiểu về các tính năng hỗ trợ của cấu trúc dữ liệu hàng đợi:
Phương thức peek() của cấu trúc dữ liệu hàng đợi
Giống như trong cấu trúc dữ liệu ngăn xếp, hàm này giúp chúng ta quan sát dữ liệu tại đầu hàng đợi. Giải thuật của hàm peek() là:
bắt đầu hàm peek return queue[front] kết thúc hàm
return queue[front]; }
Phương thức isFull() trong cấu trúc dữ liệu hàng đợi
Nếu khi chúng ta đang sử dụng mảng một chiều để triển khai hàng đợi, chúng ta chỉ cần kiểm tra con trỏ rear có tiến đến giá trị MAXSIZE hay không để xác định hàng đợi là đầy hay không. Trong trường hợp triển khai hàng đợi bởi sử dụng Danh sách liên kết vòng (Circular Linked List), giải thuật cho hàm isFull() sẽ khác.
Phần dưới đây là giải thuật của hàm isFull(): bắt đầu hàm isfull
ifrear equals to MAXSIZE
returntrue else
returnfalse end if kết thúc hàm
Sự triển khai giải thuật của hàm isFull() trong ngôn ngữ C: bool isfull(){
if(rear == MAXSIZE -1) returntrue;
returnfalse; }
Phương thức isEmpty() trong cấu trúc dữ liệu hàng đợi
Giải thuật của hàm isEmpty(): bắt đầu hàm isempty
iffront là nhỏ hơn MIN OR front là lớn hơn rear
returntrue else
returnfalse kết thúc if kết thúc hàm
Nếu giá trị của front là nhỏ hơn MIN hoặc 0 thì tức là hàng đợi vẫn chưa được khởi tạo, vì thế hàng đợi là trống.
Dưới đây là sự triển khai code trong ngôn ngữ C: bool isempty(){
if(front <0|| front > rear) returntrue;
}
Hoạt động enqueue trong cấu trúc dữ liệu hàng đợi
Bởi vì cấu trúc dữ liệu hàng đợi duy trì hai con trỏ dữ liệu: front và rear, do đó các hoạt động của loại cấu trúc dữ liệu này là khá phức tạp khi so sánh với cấu trúc dữ liệu ngăn xếp.
Dưới đây là các bước để enqueue (chèn) dữ liệu vào trong hàng đợi: Bước 1: kiểm tra xem hàng đợi là có đầy không.
Bước 2: nếu hàng đợi là đầy, tiến trình bị lỗi và bị thoát.
Bước 3: nếu hàng đợi không đầy, tăng con trỏ rear để trỏ tới vị trí bộ
nhớ trống tiếp theo.
Bước 4: thêm phần tử dữ liệu vào vị trí con trỏ rear đang trỏ tới trong
hàng đợi.
Bước 5: trả về success.
Hình 2.4. Mô tả hàng đợi trong thuật toán Queue.
Đôi khi chúng ta cũng cần kiểm tra xem hàng đợi đã được khởi tạo hay chưa để xử lý các tình huống không mong đợi.
Giải thuật cho hoạt động enqueue trong cấu trúc dữ liệu hàng đợi bắt đầu enqueue(data) if queue làđầy return overflow endif rear ← rear +1 queue[rear]← data returntrue kết thúc hàm
Sự triển khai giải thuật của hoạt động enqueue() trong ngôn ngữ C: int enqueue(int data)
if(isfull()) return0; rear = rear +1; queue[rear]= data; return1; kết thúc hàm
Để theo dõi sự triển khai code đầy đủ của các hoạt động trên trong ngôn ngữ C, mời bạn click chuột vào chương: Hàng đợi trong C.
Việc truy cập dữ liệu từ hàng đợi là một tiến trình gồm hai tác vụ: truy cập dữ liệu tại nơi con trỏ front đang trỏ tới và xóa dữ liệu sau khi đã truy cập đó. Dưới đây là các bước để thực hiện hoạt động dequeue:
Bước 1: kiểm tra xem hàng đợi là trống hay không.
Bước 2: nếu hàng đợi là trống, tiến trình bị lỗi và bị thoát.
Bước 3: nếu hàng đợi không trống, truy cập dữ liệu tại nơi con
trỏ front đang trỏ.
Bước 4: tăng con trỏ front để trỏ tới vị trí chứa phần tử tiếp theo.
Bước 5: trả về success.
Hình 2.5. Hoạt động của hàng đợi .
Giải thuật cho hoạt động dequeue
bắt đầu hàm dequeue if queue là trống return underflow end if
data = queue[front] front ← front +1
returntrue kết thúc hàm
Sự triển khai hoạt động dequeue() trong ngôn ngữ C: int dequeue(){
if(isempty()) return0;
int data = queue[front]; front = front +1; return data;
}
2.1.5 Cấu trúc dữ liệu cây
-Cấu trúc dữ liệu cây Cấu trúc dữ liệu cây là gì ?
Cấu trúc dữ liệu cây biểu diễn các nút (node) được kết nối bởi các cạnh. Chúng ta sẽ tìm hiểu về Cây nhị phân (Binary Tree) và Cây tìm kiếm nhị phân (Binary Search Tree) trong phần này.
Cây nhị phân là một cấu trúc dữ liệu đặc biệt được sử dụng cho mục đích lưu trữ dữ liệu. Một cây nhị phân có một điều kiện đặc biệt là mỗi nút có thể có tối đa hai nút con. Một cây nhị phân tận dụng lợi thế của hai kiểu cấu trúc dữ liệu: một mảng đã sắp thứ tự và một danh sách liên kết (Linked
tác chèn và xóa cũng sẽ nhanh bằng trong Linked List.
Hình 2.6: Cấu trúc cây dữ liệu.
Các khái niệm cơ bản về cây nhị phân
Dưới đây là một số khái niệm quan trọng liên quan tới cây nhị phân: Đường: là một dãy các nút cùng với các cạnh của một cây.
Nút gốc (Root): nút trên cùng của cây được gọi là nút gốc. Một cây sẽ
chỉ có một nút gốc và một đường xuất phát từ nút gốc tới bất kỳ nút nào khác. Nút gốc là nút duy nhất không có bất kỳ nút cha nào.
Nút cha: bất kỳ nút nào ngoại trừ nút gốc mà có một cạnh hướng lên
một nút khác thì được gọi là nút cha.
Nút con: nút ở dưới một nút đã cho được kết nối bởi cạnh dưới của nó
được gọi là nút con của nút đó.
Nút lá: nút mà không có bất kỳ nút con nào thì được gọi là nút lá.
Truy cập: kiểm tra giá trị của một nút khi điều khiển là đang trên một
nút đó.
Duyệt: duyệt qua các nút theo một thứ tự nào đó.
Bậc: bậc của một nút biểu diễn số con của một nút. Nếu nút gốc có bậc
là 0, thì nút con tiếp theo sẽ có bậc là 1, và nút cháu của nó sẽ có bậc là 2, … Khóa (Key): biểu diễn một giá trị của một nút dựa trên những gì mà
một thao tác tìm kiếm thực hiện trên nút. Biểu diễn cây tìm kiếm nhị phân
Cây tìm kiếm nhị phân biểu diễn một hành vi đặc biệt. Con bên trái của một nút phải có giá trị nhỏ hơn giá trị của nút cha (của nút con này) và con bên phải của nút phải có giá trị lớn hơn giá trị của nút cha (của nút con này). Hình minh họa:
Chúng ta đang triển khai cây bởi sử dụng đối tượng nút và kết nối chúng thông qua các tham chiếu.
Nút (Node) trong cây tìm kiếm nhị phân
Một nút sẽ có cấu trúc như dưới đây. Nút có phần dữ liệu và phần tham chiếu tới các nút con bên trái và nút con bên phải.
struct node { int data;
struct node *leftChild; struct node *rightChild;
Trong một cây, tất cả các nút chia sẻ cùng một cấu trúc.
Hoạt động cơ bản trên cây tìm kiếm nhị phân
Dưới đây liệt kê các hoạt động cơ bản có thể được thực hiện trên cấu trúc dữ liệu cây tìm kiếm nhị phân:
Chèn: chèn một phần tử vào trong một cây/ tạo một cây.
Tìm kiếm: tìm kiếm một phần tử trong một cây.
Duyệt tiền thứ tự: duyệt một cây theo cách thức duyệt tiền thứ tự
(tham khảo chương sau).
Duyệt trung thứ tự: duyệt một cây theo cách thức duyệt trung thứ tự
(tham khảo chương sau).
Duyệt hậu thứ tự: duyệt một cây theo cách thức duyệt hậu thứ tự
(tham khảo chương sau).
Trong chương này, chúng ta sẽ tìm hiểu chi tiết cách tạo (chèn) cấu trúc cây và cách tìm kiếm một phần tử dữ liệu trên một cây. Chương sau chúng ta sẽ tìm hiểu chi tiết về các cách duyệt cây.
Hoạt động chèn trong cây tìm kiếm nhị phân
Bước chèn đầu tiên sẽ tạo thành cây. Tiếp đó là sẽ chèn từng phần tử vào trong cây. Đầu tiên chúng ta cần xác định vị trí chính xác của nó. Bắt đầu tìm kiếm từ nút gốc, sau đó nếu dữ liệu là nhỏ hơn giá trị khóa, thì tìm kiếm vị trí rỗng trong cây con bên trái và chèn dữ liệu. Nếu không nhỏ hơn, tìm vị trí rỗng trong cây con bên phải và chèn dữ liệu. (Nếu bạn chưa hiểu, bạn có thể đọc lại phần Biểu diễn cây tìm kiếm nhị phân ở trên để biết tại sao lại chèn như vậy và xem hình minh họa)