Tải bản đầy đủ (.docx) (99 trang)

Cấu trúc dữ liệu là gì cấu trúc dữ liệu khác cấu trúc lưu trữ ở những điểm nào

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (324.08 KB, 99 trang )

<span class="text_page_counter">Trang 1</span><div class="page_container" data-page="1">

<b>ĐẠI HỌC DUY TÂN</b>

<b>TRƯỜNG KHOA HỌC MÁY TÍNH</b>

<b>KHOA KỸ THUẬT MẠNG MÁY TÍNH & TRUYỀN THƠNG</b>

<b>BÁO CÁO BÀI TẬP VỀ NHÀ </b>

<b>MÔN: CS316 – CẤU TRÚC DỮ LIỆU & GIẢI THUẬT </b>

</div><span class="text_page_counter">Trang 2</span><div class="page_container" data-page="2">

<b>TRẢ LỊI NGẮN</b>

1: Cấu trúc dữ liệu là gì? Cấu trúc dữ liệu khác cấu trúc lưu trữ ở những điểm nào?

2: Nêu khái niệm giải thuật là gì? Mối quan hệ giữa giải thuật và cấu trúc dữ liệu là gì? Nêu các tính chất của giải thuật.

3: Một ngơn ngữ lập trình có nên cho phép người sử dụng tự định nghĩa thêm các kiểu dữ liệu có cấu trúc? Giải thích và cho ví dụ.

<b>BÀI TẬP</b>

1: Hãy viết mỗi yêu cầu sau bằng giải thuật đệ qui: a. Tính tổng S = 1 – 1/2 + 1/3 -1/4 + ...+(-1)<small>n+1</small>1/n b. Tính S= 1 + 3 + 5 + ... + (2k+1) với (2k+1) ≤ n c. Đổi số n hệ 10 sang hệ nhị phân (chỉ sử dụng đệ qui) d. Tìm số đảo ngược 1 số nguyên dương.

e. Tìm ước số chung lớn nhất và bội số chung nhỏ nhất của 2 số nguyên A và B f. Tính tổng S= 1/2 +2/3+ 3/4 + ...+ n/ n+1

g. Tính tổng S= 1/2+ 1/2.3 +1/3.4 + ... + 1/n(n+1) h. Tìm chữ số lớn nhất của số nguyên dương n. 2: Tính tổng các ước số của số nguyên dương n.

3: Viết giải thuật đệ quy cho bài tốn tìm số Fibonaci F(n)=F(n-1)+ F(n-2)

4: Viết giải thuật đệ quy cho Bài toán tháp Hà Nội chuyển n đĩa từ cột A sang cột C với

</div><span class="text_page_counter">Trang 3</span><div class="page_container" data-page="3">

Cấu trúc dữ liệu là cách lưu trữ, tổ chức dữ liệu có thứ tự, có hệ thống để dữ liệu có thể được sử dụng một cách hiệu quả.

Cấu trúc dữ liệu và cấu trúc lưu trữ đều liên quan đến việc tổ chức và quản lý dữ liệu, nhưng chúng khác nhau ở một số điểm quan trọng:

<b>1. Mục đích: Cấu trúc dữ liệu tập trung vào việc tổ chức và quản lý dữ liệu </b>

trong bộ nhớ máy tính để tối ưu hóa việc truy xuất và sử dụng dữ liệu. Trong khi đó, cấu trúc lưu trữ liên quan đến việc tổ chức và lưu trữ dữ liệu trên các thiết bị lưu trữ vật lý.

<b>2. Vị trí lưu trữ: Cấu trúc dữ liệu được lưu trong bộ nhớ máy tính (RAM), </b>

trong khi cấu trúc lưu trữ được lưu trên các thiết bị lưu trữ vật lý như ổ đĩa cứng, SSD, hoặc các hệ thống lưu trữ đám mây.

<b>3. Tối ưu hóa: Cấu trúc dữ liệu tập trung vào việc tối ưu hóa hiệu suất của các</b>

thuật tốn và ứng dụng. Trong khi đó, cấu trúc lưu trữ tập trung vào việc tối ưu hóa khơng gian lưu trữ và thời gian truy cập.

<b>4. Công nghệ: Cấu trúc dữ liệu sử dụng các ngôn ngữ lập trình để tổ chức và </b>

quản lý dữ liệu. Trong khi đó, cấu trúc lưu trữ sử dụng các cơng nghệ phần cứng và phần mềm để lưu trữ và quản lý dữ liệu.

2: Nêu khái niệm giải thuật là gì? Mối quan hệ giữa giải thuật và cấu trúc dữ liệu là gì? Nêu các tính chất của giải thuật.

Giaỉ thuật là một tập hợp hữuGiaỉ hạn các chỉ thị để được thực thi theo một thứ tự nào đó để thu được kết quả mong muốn. Nói chung thì giải thuật là độc lập với các ngơn ngữ lập trình, tức là một giải thuật có thể được triển khai trong nhiều ngơn ngữ lập trình khác nhau.

Một chương trình nào cũng cần có dữ liệu để tính tốn, xử lý. Nhiệm vụ tính tốn, xử lý sẽ được giao cho thuật tốn. Để chương trình hoạt động tốt, ổn định thì thuật tốn phải xử lý tốt và chính xác trên dữ liệu. Do đó, những dữ liệu này cần được lưu trữ, tổ chức một cách hợp lý với thuật tốn.

Các tính chất của giải thuật

1. Tính rõ ràng: Giải thuật được thể hiện bằng các thao tác tường minh được sắp xếp theo thứ tự xác định.

2. Tính xác định: Mỗi thao tác của giải thuật có mục đích cụ thể và rõ ràng².

</div><span class="text_page_counter">Trang 4</span><div class="page_container" data-page="4">

3. Tính chính xác: Kết quả tính tốn của từng thao tác hay từng giai đoạn của giải thuật ln chính xác và đơn nhất.

4. Tính dừng (tính kết thúc): Sau một số hữu hạn các thao tác, giải thuật phải kết thúc và cho kết quả mong muốn².

5. Tính hiệu quả: Giải thuật có khả năng cho kết quả mong muốn trong điều kiện thời gian và tài nguyên cho phép.

6. Tính khách quan: Giải thuật được thiết kế sao cho bất kì ai thực hiện cũng đều cho kết quả như nhau.

7. Tính phổ dụng: Giải thuật khơng chỉ áp dụng cho một bài tốn nhất định mà có thể áp dụng cho một lớp các bài tốn có đầu vào tương tự nhau. 8. Dữ liệu đầu vào và kết quả đầu ra xác định: Dữ liệu đầu vào và kết quả đầu ra sau khi hoàn thành thực hiện giải thuật luôn là các yếu tố được xác định ngay trong quá trình thiết kế giải thuật.

3: Một ngơn ngữ lập trình có nên cho phép người sử dụng tự định nghĩa thêm các kiểu dữ liệu có cấu trúc? Giải thích và cho ví dụ.

Có . vì một ngơn ngữ lập trình nên cho phép người sử dụng tự định nghĩa thêm các kiểu dữ liệu có cấu trúc. Điều này tạo ra sự linh hoạt và cho phép lập trình viên tạo ra các cấu trúc dữ liệu phức tạp hơn để phù hợp với yêu cầu của họ. Ví dụ, trong ngơn ngữ lập trình C++, bạn có thể tạo ra một kiểu dữ liệu mới bằng cách sử dụng từ khóa struct hoặc class. Dưới đây là một ví dụ về việc tạo một kiểu dữ liệu mới có tên là Student:

Trong ví dụ trên, Student là một kiểu dữ liệu mới mà chúng ta đã định nghĩa. Nó bao gồm ba thành phần: name (kiểu std::string), age (kiểu int), và gpa (kiểu float). Bây giờ, chúng ta có thể sử dụng Student như một kiểu dữ liệu trong chương trình của mình. Điều này giúp cho việc tổ chức và quản lý dữ liệu trở nên dễ dàng hơn.

</div><span class="text_page_counter">Trang 6</span><div class="page_container" data-page="6">

// Lấy chữ số cuối cùng của số int lastDigit = num % 10;

// Loại bỏ chữ số cuối cùng khỏi số int remainingNum = num / 10;

</div><span class="text_page_counter">Trang 7</span><div class="page_container" data-page="7">

// Hàm tìm ước số chung lớn nhất (USCLN) int gcd(int a, int b) {

</div><span class="text_page_counter">Trang 8</span><div class="page_container" data-page="8">

cout << "Boi so chung nho nhat cua " << a << " va " << b << " la: " << lcm(a, b)

// Tính tổng của n phần tử đầu tiên bằng cách cộng phần tử thứ n với tổng của n-1 phần tử đầu tiên

return (double)n/(n+1) + sum(n-1);

</div><span class="text_page_counter">Trang 9</span><div class="page_container" data-page="9">

// Loại bỏ chữ số cuối cùng và tìm chữ số lớn nhất trong số còn lại int remainingMax = findLargestDigit(n / 10);

</div><span class="text_page_counter">Trang 11</span><div class="page_container" data-page="11">

cout << "Nhap so nguyen n: ";

thapHaNoi(n - 1, cotA, cotC, cotB);

cout << "Chuyen dia " << n << " tu cot " << cotA << " sang cot " << cotC <<

</div><span class="text_page_counter">Trang 12</span><div class="page_container" data-page="12">

<b>1. Đối với các ngôn ngữ thông dụng hiện nay, cấu trúc lưu trữ của mảng là cấu trúc nào?2. Khi lập trình bằng ngôn ngữ C, nếu ta khai báo một mảng hai chiều A theo kiểu </b>

A[1...5,1…4] kiểu int thì máy sẽ lưu trữ như thế nào? Minh họa cấu trúc lưu trữ bằng hình vẽ.

<b>3. Giả sử 3 từ máy mới lưu trữ được một phần tử của ma trận B[1..6,1..3]. Hãy tính địa </b>

chỉ của B[4,2] biết rằng ma trận được lưu trữ theo thứ tự ưu tiên hàng và địa chỉ gốc L<small>0</small> = 2000.

<b>BÀI TẬP </b>

<b>1: Viết chương trình nhập vào 1 dãy số nguyên từ bàn phím. Hiển thị dãy số đó theo thứ</b>

tự tăng dần hoặc giảm dần.

<b>2: Viết chương trình nhập vào 2 phân số. Thực hiện các phép tính cộng, trừ, nhân, chia 2</b>

phân số nêu trên. Sau đó, cho biết các phân số đó phân số nào lớn hơn, nhỏ hơn hay bằng nhau.

<b>3: Viết chương trình nhập vào 2 số phức. Thực hiện các phép tính cộng, trừ trên 2 số</b>

phức đó.

<b>4: Nhập vào tọa độ ba đỉnh A(x</b><small>1</small>, y<small>1</small>), B(x<small>2</small>,y<small>2</small>), C(x<small>3</small>,y<small>3</small>).

Xét xem ba điểm A, B, C có thẳng hàng khơng? Nếu khơng thì tính diện tích của tam giác ABC.

<b>5: Viết chương trình cho phép nhập vào dãy số nguyên dương. Xét xem trong dãy có số</b>

ngun tố hay khơng? Nếu có thì hãy in ra số nguyên tố bé nhất.

</div><span class="text_page_counter">Trang 13</span><div class="page_container" data-page="13">

<b>6: Viết chương trình nhập vào dãy số thực. Đưa tất cả các số âm lên đầu, các số dương</b>

xuống cuối và các số 0 đứng giữa, thứ tự các số cùng dấu không thay đổi.

<b>7: Cho ma trận a gồm m hàng n cột. Viết chương trình thực hiện các cơng việc sau:a. Nhập ma trận a từ bàn phím</b>

<b>b. In ma trận vừa nhập lên màn hìnhc. Kiểm tra tính đối xứng của ma trận a.</b>

<b>d. Tìm các điểm yên ngựa của ma trận (Cho biết điểm yên ngựa là điểm mà tại đó nó</b>

có giá trị bé nhất thuộc hàng chứa nó và đồng thời là phần tử lớn nhất của cột chứa nó).

<b>e. Nhập vào hai vị trí cột và đổi chỗ hai cột đó cho nhau. In ma trận vừa đổi cột ra</b>

màn hình.

<b>f. Nhập vào vị trí của một dịng và loại bỏ dịng đó ra khỏi ma trận.</b>

<b>PHẦN ĐÁP ÁN TRẢ LỜI NGẮN</b>

<b>1. Đối với các ngôn ngữ thông dụng hiện nay, cấu trúc lưu trữ của mảng là cấu trúc nào? </b>

Mảng là một cấu trúc dữ liệu tuyến tính, nghĩa là các phần tử trong mảng được lưu trữ liên tiếp trong bộ nhớ. Mỗi phần tử trong mảng có cùng kiểu dữ liệu và có thể được truy cập thông qua chỉ số (index). Chỉ số này thường bắt đầu từ 0 cho phần tử đầu tiên và tăng lên cho mỗi phần tử tiếp theo.

Ví dụ, nếu bạn có một mảng số nguyên int arr[5] = {1, 2, 3, 4, 5};, thì arr[0] sẽ trả về giá trị 1, arr[1] sẽ trả về giá trị 2, và cứ như vậy.

Cấu trúc lưu trữ của mảng giúp việc truy cập và thao tác với các phần tử trong mảng trở nên hiệu quả hơn. Tuy nhiên, do kích thước của mảng được xác định tại thời điểm biên dịch, nên nó khơng thể thay đổi trong q trình chạy chương trình. Điều này có thể hạn chế khả năng linh hoạt của mảng khi làm việc với các tập dữ liệu có kích thước khơng xác định trước.

<b>2. Khi lập trình bằng ngơn ngữ C, nếu ta khai báo một mảng hai chiều A theo kiểu </b>

A[1...5,1…4] kiểu int thì máy sẽ lưu trữ như thế nào? Minh họa cấu trúc lưu trữ bằng hình vẽ.

</div><span class="text_page_counter">Trang 14</span><div class="page_container" data-page="14">

Để minh họa cấu trúc lưu trữ, chúng ta có thể sử dụng một ma trận 5x4 để đại diện cho mảng hai chiều A. Mỗi phần tử trong ma trận tương ứng với một ô nhớ trong bộ nhớ của máy tính.

Dưới đây là một minh họa về cấu trúc lưu trữ mảng A[1...5, 1...4]

<b>3. Giả sử 3 từ máy mới lưu trữ được một phần tử của ma trận B[1..6,1..3]. Hãy tính địa </b>

chỉ của B[4,2] biết rằng ma trận được lưu trữ theo thứ tự ưu tiên hàng và địa chỉ gốc L<small>0</small> = 2000.

để tính địa chỉ của B[4,2], ta có thể sử dụng cơng thức sau: Địa chỉ của B[i,j] = L0 + S * ((i-1)*n + (j-1))

Trong đó

 L0 là địa chỉ gốc (địa chỉ của phần tử đầu tiên trong ma trận)

 S là kích thước của mỗi phần tử trong ma trận (được cho là 3 từ máy)

<b>1: Viết chương trình nhập vào 1 dãy số nguyên từ bàn phím. Hiển thị dãy số đó theo thứ</b>

tự tăng dần hoặc giảm dần. #include <iostream>

</div><span class="text_page_counter">Trang 15</span><div class="page_container" data-page="15">

<b>2: Viết chương trình nhập vào 2 phân số. Thực hiện các phép tính cộng, trừ, nhân, chia 2</b>

phân số nêu trên. Sau đó, cho biết các phân số đó phân số nào lớn hơn, nhỏ hơn hay bằng

</div><span class="text_page_counter">Trang 16</span><div class="page_container" data-page="16">

void nhap(PhanSo &ps) { cout << "Nhap tu so: ";

tong.tu = ps1.tu * ps2.mau + ps2.tu * ps1.mau; tong.mau = ps1.mau * ps2.mau;

</div><span class="text_page_counter">Trang 17</span><div class="page_container" data-page="17">

res.real = real + obj.real; res.imag = imag + obj.imag; return res;

}

</div><span class="text_page_counter">Trang 18</span><div class="page_container" data-page="18">

Complex operator - (Complex const &obj) { Complex res;

res.real = real - obj.real; res.imag = imag - obj.imag; cin >> c1.real >> c1.imag;

cout << "Nhập số phức thứ hai (phần thực và phần ảo): "; cin >> c2.real >> c2.imag;

</div><span class="text_page_counter">Trang 19</span><div class="page_container" data-page="19">

<b>4: Nhập vào tọa độ ba đỉnh A(x</b><small>1</small>, y<small>1</small>), B(x<small>2</small>,y<small>2</small>), C(x<small>3</small>,y<small>3</small>).

Xét xem ba điểm A, B, C có thẳng hàng khơng? Nếu khơng thì tính diện tích của tam giác ABC.

#include<iostream> #include<cmath> using namespace std;

double area(double x1, double y1, double x2, double y2, double x3, double y3) { return abs(0.5 * (x1*(y2-y3) + x2*(y3-y1) + x3*(y1-y2)));

int main() {

double x1, y1, x2, y2, x3, y3; cout << "Nhap toa do diem A: ";

</div><span class="text_page_counter">Trang 20</span><div class="page_container" data-page="20">

<b>5: Viết chương trình cho phép nhập vào dãy số nguyên dương. Xét xem trong dãy có số</b>

ngun tố hay khơng? Nếu có thì hãy in ra số ngun tố bé nhất.

cout << "Nhap day so: "; for(int &num : arr) cin >> num;

int minPrime = INT_MAX; for(int num : arr)

</div><span class="text_page_counter">Trang 21</span><div class="page_container" data-page="21">

<b>6: Viết chương trình nhập vào dãy số thực. Đưa tất cả các số âm lên đầu, các số dương</b>

xuống cuối và các số 0 đứng giữa, thứ tự các số cùng dấu không thay đổi.

cout << "Nhap cac phan tu: "; for (int i = 0; i < n; i++) { cin >> a[i];

</div><span class="text_page_counter">Trang 22</span><div class="page_container" data-page="22">

a.insert(a.end(), b.begin(), b.end()); a.insert(a.end(), c.begin(), c.end()); a.insert(a.end(), d.begin(), d.end());

cout << "Day sau khi sap xep la: "; for (int i = 0; i < n; i++) { <b>c. Kiểm tra tính đối xứng của ma trận a.</b>

<b>d. Tìm các điểm yên ngựa của ma trận (Cho biết điểm yên ngựa là điểm mà tại đó nó</b>

có giá trị bé nhất thuộc hàng chứa nó và đồng thời là phần tử lớn nhất của cột chứa nó).

</div><span class="text_page_counter">Trang 23</span><div class="page_container" data-page="23">

<b>e. Nhập vào hai vị trí cột và đổi chỗ hai cột đó cho nhau. In ma trận vừa đổi cột ra</b>

void inMaTran(const vector<vector<int>>& a) { for (const auto& row : a) {

for (const auto& elem : row) cout << elem << ' ';

cout << '\n'; }

// Hàm kiểm tra tính đối xứng của ma trận_câu c

bool kiemTraDoiXung(const vector<vector<int>>& a) { if (a.size() != a[0].size())

return false;

for (int i = 0; i < a.size(); i++) for (int j = 0; j < i; j++)

</div><span class="text_page_counter">Trang 24</span><div class="page_container" data-page="24">

if (a[i][j] != a[j][i]) return false; return true;

// Hàm tìm điểm yên ngựa của ma trận_câu d

vector<pair<int, int>> timDiemYenNgua(const vector<vector<int>>& a) { vector<pair<int, int>> res;

for (int i = 0; i < a.size(); i++) {

int minRow = *min_element(a[i].begin(), a[i].end()); for (int j = 0; j < a[i].size(); j++) {

if (a[i][j] == minRow) { int maxCol = a[0][j];

for (int k = 1; k < a.size(); k++) maxCol = max(maxCol, a[k][j]);

// Hàm đổi chỗ hai cột_câu e

void doiChoHaiCot(vector<vector<int>>& a, int col1, int col2) { for (auto& row : a)

</div><span class="text_page_counter">Trang 25</span><div class="page_container" data-page="25">

<b>1:Nêu định nghĩa về danh sách liên kết. Vì sao cần phải có cấu trúc dữ liệu động.</b>

<b>2:Nêu tên và vẽ hình minh họa cho các loại danh sách liên kết thông dụng thường dùng.3: Nêu sự khác nhau cơ bản nhất giữa ba loại danh sách liên kết: đơn, đơn nối vòng, và </b>

<b>4:Danh sách liên kết đã khắc phục được những nhược điểm nào của danh sách đặc?BÀI TẬP</b>

<b>1:Cho một danh sách liên kết đơn chứa một dãy số nguyên. Thực hiện các công việc yêu </b>

cầu sau đây:

1. Viết hàm nhập vào một dãy các số nguyên từ bàn phím, lưu trữ nó trong danh sách theo thứ tự nhập vào.

2. Viết hàm nhập vào một dãy các số nguyên từ bàn phím, lưu trữ nó trong danh sách theo thứ tự ngược lại với thứ tự nhập vào.

3. Viết hàm in hai danh sách đã có trong câu 1 và câu 2 ra màn hình.

4. Viết hàm sắp xếp danh sách liên kết theo Bubble Sort (Heap Sort, Select Sort v.v...)

5. Giả sử danh sách nhập vào đã có thứ tự. Viết hàm thêm một phần tử sao cho sau khi thêm vào ta vẫn có một danh sách đã được sắp xếp theo thứ tự như ban đầu.

6. Tìm vị trí các Node có chứa giá trị âm, in các giá trị đó ra màn hình. 7. Viết hàm đếm xem trong danh sách có bao nhiêu Node chứa giá trị âm.

</div><span class="text_page_counter">Trang 26</span><div class="page_container" data-page="26">

8. Viết hàm tính tổng các giá trị có trong danh sách theo hai cách dùng đệ quy và không dùng đệ quy.

9. Viết hàm đảo ngược một danh sách.

10. Viết hàm đảo hai node đầu và cuối cho nhau.

<b>2: Cho một danh sách liên kết đơn nối vòng chứa một dãy số ngun. Viết chương trình </b>

thực hiện các cơng việc sau:

1. Viết hàm nhập n Node của danh sách từ bàn phím.

2. Viết hàm in danh sách đó ra màn hình theo thứ tự đã nhập

3. Viết hàm in danh sách đó ra màn hình theo thứ tự đảo ngược với thứ tự nhập 4. Viết hàm in danh sách đó ra màn hình theo thứ tự: node thứ n rồi đến node thứ 1,

5. Viết in các giá trị lẻ trước, in các giá trị chẵn sau theo thứ tự của chúng có trong danh sách.

6. Đếm số Node có trong danh sách.

7. Tính trung bình cộng của các Node có giá trị chẵn 8. Tính trung bình cộng của các Node có giá trị lẻ

9. Tìm xem trong danh sách có bao nhiêu Node có giá trị X 10. Xóa các Node có giá trị X

<b>PHẦN ĐÁP ÁN TRẢ LỜI NGẮN</b>

<b>1:Nêu định nghĩa về danh sách liên kết. Vì sao cần phải có cấu trúc dữ liệu động.</b>

Danh sách liên kết là một cấu trúc dữ liệu được sử dụng để lưu trữ và tổ chức một tập hợp các phần tử dưới dạng các nút (node) liên kết với nhau thông qua các tham chiếu (references). Mỗi nút chứa một phần tử dữ liệu và một tham chiếu đến nút tiếp theo trong danh sách.

Cấu trúc dữ liệu động là một loại cấu trúc dữ liệu linh hoạt mà cho phép thay đổi kích thước và cấu trúc của dữ liệu trong quá trình chương trình thực thi. Trong trường hợp của danh sách liên kết, cấu trúc dữ liệu động cho phép thêm, xóa và sửa đổi các phần tử trong

</div><span class="text_page_counter">Trang 27</span><div class="page_container" data-page="27">

danh sách một cách linh hoạt mà không yêu cầu cấp phát vùng nhớ liên tục và cố định trước.

Cần phải có cấu trúc dữ liệu động trong danh sách liên kết vì:

1. Linh hoạt về kích thước: Cấu trúc dữ liệu động cho phép thêm và xóa các phần tử trong danh sách một cách dễ dàng, mà không yêu cầu phải cấp phát một vùng nhớ liên tục và lớn hơn khi khởi tạo danh sách ban đầu. Điều này cho phép tiết kiệm bộ nhớ và tăng khả năng tương thích với các yêu cầu thay đổi động trong quá trình chương trình thực thi.

2. Thao tác chèn và xóa hiệu quả: Với danh sách liên kết, việc chèn và xóa phần tử giữa danh sách chỉ yêu cầu thay đổi tham chiếu của các nút liên quan, mà không cần phải di chuyển các phần tử khác. Điều này giúp giảm thiểu thời gian và công sức cần thiết so với việc thực hiện các thao tác tương tự trên mảng (mà đòi hỏi di chuyển các phần tử khác).

3. Định vị dữ liệu linh hoạt: Danh sách liên kết cho phép truy cập dữ liệu theo vị trí linh hoạt thông qua các tham chiếu. Mỗi nút trong danh sách liên kết có thể được truy cập và thao tác riêng lẻ, cho phép truy cập ngẫu nhiên và thực hiện các thao tác truy xuất dữ liệu nhanh chóng.

<b>2:Nêu tên và vẽ hình minh họa cho các loại danh sách liên kết thông dụng thường dùng.</b>

Trong C++, các loại danh sách liên kết thông dụng thường dùng gồm:

1. Danh sách liên kết đơn (Singly Linked List):

 Mỗi nút trong danh sách có hai phần: dữ liệu và con trỏ tới nút tiếp theo.

 Khơng có con trỏ trở lại nút trước đó.

 Thao tác chèn và xóa phần tử dễ dàng, nhưng truy cập ngẫu nhiên chậm hơn.

Ví dụ về danh sách liên kết đơn:

</div><span class="text_page_counter">Trang 28</span><div class="page_container" data-page="28">

 Mỗi nút trong danh sách có ba phần: dữ liệu, con trỏ tới nút tiếp theo và con trỏ tới nút trước đó.

 Có thể truy cập theo cả hai hướng.

 Thao tác chèn và xóa phần tử phức tạp hơn so với danh

3. Danh sách liên kết vòng (Circular Linked List):

 Các nút trong danh sách liên kết vòng được kết nối với

1. Danh sách liên kết đơn (Singly Linked List):

 Mỗi nút trong danh sách liên kết đơn có hai phần: dữ liệu và con trỏ tới nút tiếp theo.

 Khơng có con trỏ trở lại nút trước đó.

 Thao tác chèn và xóa phần tử dễ dàng, nhưng truy cập ngẫu nhiên chậm hơn.

2. Danh sách liên kết đơn nối vòng (Circular Singly Linked List):  Tương tự như danh sách liên kết đơn, nhưng nút cuối cùng

trong danh sách trỏ tới nút đầu tiên, tạo thành một vòng.  Điều này cho phép lặp lại danh sách liên tục và khơng có

nút cuối cùng.

</div><span class="text_page_counter">Trang 29</span><div class="page_container" data-page="29">

 Thao tác chèn và xóa phần tử vẫn dễ dàng, nhưng truy cập ngẫu nhiên chậm hơn.

3. Danh sách liên kết kép (Doubly Linked List):

 Mỗi nút trong danh sách liên kết kép có ba phần: dữ liệu, con trỏ tới nút tiếp theo và con trỏ tới nút trước đó.

 Có thể truy cập theo cả hai hướng.

 Thao tác chèn và xóa phần tử phức tạp hơn so với danh sách liên kết đơn.

 Tuy nhiên, truy cập ngẫu nhiên nhanh hơn vì có thể di chuyển cả hai hướng.

<b>4:Danh sách liên kết đã khắc phục được những nhược điểm nào của danh sách đặc?</b>

Danh sách liên kết đã khắc phục một số nhược điểm của danh sách đặc (array) như sau:

1. Kích thước linh hoạt: Trong danh sách đặc, kích thước của mảng được xác định từ đầu và khơng thể thay đổi. Trong khi đó, danh sách liên kết có thể thay đổi kích thước linh hoạt theo nhu cầu. Bạn có thể thêm hoặc xóa các nút trong danh sách mà khơng cần phải sao chép toàn bộ dữ liệu như trong mảng. 2. Chèn và xóa hiệu quả: Trong danh sách đặc, việc chèn và xóa

phần tử ở giữa mảng địi hỏi phải dịch chuyển các phần tử sau đó. Trong danh sách liên kết, chèn và xóa phần tử có thể được thực hiện hiệu quả bằng cách chỉ thay đổi các con trỏ. Điều này giúp giảm thời gian và tài nguyên cần thiết cho các thao tác này.

3. Truy cập linh hoạt: Trong danh sách đặc, truy cập đến một phần tử cụ thể cần phải dùng chỉ số của mảng. Trong danh sách liên kết, truy cập đến một phần tử cụ thể có thể được thực hiện bằng cách đi qua các nút từ đầu đến cuối hoặc ngược lại. Mặc dù truy cập ngẫu nhiên trong danh sách liên kết chậm hơn so với danh sách đặc, nhưng việc truy cập tuần tự liên tiếp là hiệu quả.

4. Quản lý bộ nhớ: Trong danh sách đặc, kích thước mảng phải được khai báo trước và không thể thay đổi. Điều này có thể dẫn đến việc lãng phí bộ nhớ nếu khơng sử dụng hết dung lượng đã được xác định. Trong danh sách liên kết, bộ nhớ được cấp phát động theo nhu cầu, không gian chỉ được sử dụng khi cần thiết.

<b>BÀI TẬP</b>

</div><span class="text_page_counter">Trang 30</span><div class="page_container" data-page="30">

<b>1:Cho một danh sách liên kết đơn chứa một dãy số nguyên. Thực hiện các công việc yêu </b>

cầu sau đây:

1. Viết hàm nhập vào một dãy các số ngun từ bàn phím, lưu trữ nó trong danh sách theo thứ tự nhập vào.

2. Viết hàm nhập vào một dãy các số nguyên từ bàn phím, lưu trữ nó trong danh sách theo thứ tự ngược lại với thứ tự nhập vào.

3. Viết hàm in hai danh sách đã có trong câu 1 và câu 2 ra màn hình.

4. Viết hàm sắp xếp danh sách liên kết theo Bubble Sort (Heap Sort, Select Sort v.v...)

5. Giả sử danh sách nhập vào đã có thứ tự. Viết hàm thêm một phần tử sao cho sau khi thêm vào ta vẫn có một danh sách đã được sắp xếp theo thứ tự như ban đầu.

6. Tìm vị trí các Node có chứa giá trị âm, in các giá trị đó ra màn hình. 7. Viết hàm đếm xem trong danh sách có bao nhiêu Node chứa giá trị âm.

8. Viết hàm tính tổng các giá trị có trong danh sách theo hai cách dùng đệ quy và không dùng đệ quy.

9. Viết hàm đảo ngược một danh sách.

10. Viết hàm đảo hai node đầu và cuối cho nhau.

// Hàm tạo một Node mới với giá trị cho trước Node* createNode(int value) {

Node* newNode = new Node;

</div><span class="text_page_counter">Trang 31</span><div class="page_container" data-page="31">

newNode->data = value; newNode->next = nullptr; return newNode;

// Hàm thêm một Node vào cuối danh sách liên kết void insertNode(Node*& head, int value) {

Node* newNode = createNode(value); if (head == nullptr) {

head = newNode; } else {

Node* temp = head;

while (temp->next != nullptr) {

</div><span class="text_page_counter">Trang 32</span><div class="page_container" data-page="32">

insertNode(head, num); }

// Hàm nhập vào một dãy số ngun từ bàn phím và lưu trữ nó trong danh sách theo thứ tự ngược lại

void inputReversedList(Node*& head) { Node* temp = head;

while (temp != nullptr) {

std::cout << temp->data << " ";

</div><span class="text_page_counter">Trang 33</span><div class="page_container" data-page="33">

// Hàm sắp xếp danh sách liên kết theo Bubble Sort void bubbleSort(Node*& head) {

if (head == nullptr || head->next == nullptr) {

</div><span class="text_page_counter">Trang 34</span><div class="page_container" data-page="34">

void insertElement(Node*& head, int value) { Node* newNode = createNode(value); if (head == nullptr || value < head->data) { newNode->next = head;

head = newNode; } else {

Node* current = head;

while (current->next != nullptr && current->next->data < value) {

// Hàm tìm và in giá trị âm trong danh sách void findNegativeValues(Node* head) {

std::cout << "Các giá trị âm trong danh sách: "; Node* temp = head;

while (temp != nullptr) {

</div><span class="text_page_counter">Trang 35</span><div class="page_container" data-page="35">

int countNegativeNodes(Node* head) { int count = 0;

Node* temp = head; while (temp != nullptr) {

// Hàm tính tổng các giá trị trong danh sách bằng đệ quy int sumRecursive(Node* head) {

// Hàm tính tổng các giá trị trong danh sách khơng dùng đệ quy int sumIterative(Node* head) {

int sum = 0;

Node* temp = head; while (temp != nullptr) {

</div><span class="text_page_counter">Trang 36</span><div class="page_container" data-page="36">

// Hàm đảo ngược danh sách liên kết void reverseList(Node*& head) { Node* current = head;

Node* prev = nullptr; Node* next = nullptr; while (current != nullptr) {

// Hàm đảo hai node đầu và cuối cho nhau void swapFirstAndLast(Node*& head) {

if (head == nullptr || head->next == nullptr) { return;

}

Node* prevFirst = nullptr; Node* last = head;

while (last->next != nullptr) {

</div><span class="text_page_counter">Trang 37</span><div class="page_container" data-page="37">

first->next = nullptr; }

int main() {

Node* list1 = nullptr; // Danh sách theo thứ tự nhập vào Node* list2 = nullptr; // Danh sách theo thứ tự ngược lại

</div><span class="text_page_counter">Trang 38</span><div class="page_container" data-page="38">

std::cout << "Danh sách 1 sau khi thêm giá trị mới: "; printList(list1);

// Câu 6

findNegativeValues(list1);

// Câu 7

int count = countNegativeNodes(list1);

std::cout << "Số Node chứa giá trị âm: " << count << std::endl;

// Câu 8

int sumRec = sumRecursive(list1); int sumIter = sumIterative(list1);

std::cout << "Tổng các giá trị (đệ quy): " << sumRec << std::endl;

std::cout << "Tổng các giá trị (không đệ quy): " << sumIter << std::endl;

</div><span class="text_page_counter">Trang 39</span><div class="page_container" data-page="39">

<b>2: Cho một danh sách liên kết đơn nối vịng chứa một dãy số ngun. Viết chương trình </b>

thực hiện các công việc sau:

1. Viết hàm nhập n Node của danh sách từ bàn phím.

2. Viết hàm in danh sách đó ra màn hình theo thứ tự đã nhập

3. Viết hàm in danh sách đó ra màn hình theo thứ tự đảo ngược với thứ tự nhập 4. Viết hàm in danh sách đó ra màn hình theo thứ tự: node thứ n rồi đến node thứ 1,

5. Viết in các giá trị lẻ trước, in các giá trị chẵn sau theo thứ tự của chúng có trong danh sách.

6. Đếm số Node có trong danh sách.

7. Tính trung bình cộng của các Node có giá trị chẵn 8. Tính trung bình cộng của các Node có giá trị lẻ

9. Tìm xem trong danh sách có bao nhiêu Node có giá trị X 10. Xóa các Node có giá trị X

</div><span class="text_page_counter">Trang 40</span><div class="page_container" data-page="40">

// Hàm thêm một Node vào cuối danh sách void addNode(Node*& head, int x) { Node* newNode = createNode(x);

if (head == nullptr) { head = newNode; } else {

Node* temp = head;

while (temp->next != nullptr) {

// Hàm nhập n Node của danh sách từ bàn phím void inputList(Node*& head, int n) {

for (int i = 0; i < n; i++) { void printList(Node* head) {

Node* temp = head;

</div>

×