Mục tiêu của học phần: - Kiến thức: • Phân biệt các loại cấu trúc dữ liệu cơ bản; • Trình bày cấu trúc dữ liệu của cấu trúc cây và một số thao tác trên cây; • Ước lượng thời gian thực
GIỚI THIỆU
Chương này giải thích tầm quan trọng của cấu trúc dữ liệu và giải thuật và tầm quan trọng của việc phân tích giải thuật khi giải quyết một số bài toán tin học vừa và nhỏ
- Nhận biết các loại cấu trúc dữ liệu cơ bản
- Ước lượng thời gian thực hiện cho một số thuật toán đơn giản
BIẾN, KIỂU DỮ LIỆU, CẤU TRÚC DỮ LIỆU
Trước khi hiểu biến là gì, hãy liên hệ tới một biểu thức toán học rất quen thuộc:
Chúng ta không cần lo lắng về cách sử dụng công thức này Điều quan trọng là cần hiểu trong biểu thức này có hai tên (x và y) dùng để chứa giá trị (dữ liệu) Tức x và y là nơi để lưu trữ giá trị Tương tự, trong lập trình khoa học máy tính chúng ta dùng biến (variables) để lưu trữ dữ liệu
Kiểu dữ liệu (Data types)
Dữ liệu cần lưu trữ vào máy tính có rất nhiều loại như số, chữ, hình ảnh, âm thanh,…Tính đa dạng của dữ liệu đòi hỏi phải tổ chức và phân phối bộ nhớ thích hợp để lưu trữ, xử lý tốt các dữ liệu Các dữ liệu được chia thành từng nhóm riêng trên đó xây dựng một số phép toán, tạo nên các kiểu dữ liệu khác nhau Mỗi kiểu dữ liệu là một tập hợp các giá trị Kiểu dữ liệu gồm có 2 thành phần:
• Miền giá trị của kiểu dữ liệu
• Các toán tử được thực hiện trên giá trị này
Trong biểu thức toán học đã được đề cập ở trên, hai biến x và y có thể chứa bất kỳ giá trị gì như số nguyên (10, 20), số thực (0.23, 5.5) Để giải được thì chúng ta cần biết loại giá trị mà x, y có thể nhận Các kiểu dữ liệu cơ bản của ngôn ngữ lập trình như: kiểu số nguyên (interger), kiểu số thực (floating point), kiểu kí tự (charactrer), kiểu chuỗi (string), kiểu luận lý (Boolean), kiểu cấu trúc (struct), kiểu lớp (class), kiểu con trỏ (pointer)
Tùy vào từng kiểu dữ liệu mà các toán tử có thể thực thực hiện trên các giá trị là khác nhau Ví dụ không thể thực hiện các phép toán số học như cộng, trừ nhân, chia trên dữ liệu kiểu chuỗi, phép modulo (phép chia lấy số dư) không thể thực hiện trên kiểu số thực
Mỗi một loại dữ liệu khi được lưu trữ thì sẽ chiếm dung lượng bộ nhớ khác nhau phụ thuộc vào ngôn ngữ lập trình, trình biên dịch và Hệ điều hành Ví dụ số nguyên (interger) chiếm 2 bytes hoặc 4 bytes, kiểu kí tự chiếm 1byte (kiểu char, lưu mã ASCII của ký tự) hoặc 2 byte (kiểu wchar_t, lưu trữ dựa trên bảng mã quốc tế UTF-16 – một dạng mã unicode), …
Có hai loại kiểu dữ liệu:
• Built-in data type: là kiểu dữ liệu cơ sở, do ngôn ngữ lập trình cung cấp
• User-defined data type: là Kiểu dữ liệu do người dùng tự định nghĩa, do lập trình viên tự lập trình để sử dụng
Ngoài ra, trong NET CLR (Common Language Runtime) kiểu dữ liệu còn có thể phân chia thành hai loại khác là:
• Value type (kiểu giá trị)
• Reference type (Kiểu tham chiếu)
Hai kiểu giá trị này khác nhau về cách thức mà trình biên dịch thực hiện lưu trữ các dữ liệu này trong bộ nhớ như sau:
• Kiểu giá trị được lưu trữ trong vùng nhớ tĩnh (static memory) gọi là vùng nhớ stack (ngăn xếp)
• Kiểu tham chiếu được lưu trữ hai nơi: địa chỉ của biến kiểu tham chiếu thì được lưu trữ trong vùng nhớ stack nhưng đối tượng thật lại được lưu trữ trong vùng nhớ heap
Hình 1 Bộ nhớ Stack và Heap
1.1.2.3 | Kiểu dữ liệu cơ sở
Một số kiểu dữ liệu cơ sở được cung cấp trong nhiều ngôn ngữ lập trình là int, float, char, double, bool, Số lượng bit được cấp phát cho mỗi kiểu dữ liệu phụ thuộc vào ngôn ngữ, trình biên dịch và hệ điều hành Phụ thuộc vào kích thước của kiểu dữ liệu mà miền giá trị (domain) của biến sẽ thay đổi
Ví dụ kiểu int chiếm 2 bytes hoặc 4 bytes Nếu chiếm 2 bytes (16 bits) thì miền giá trị của một biến của int từ -32,768 đến 32,767 (tức từ −2 15 đế𝑛 2 15 − 1) Nhưng nếu chiếm 4 bytes (32 bits) thì miền giá trị biến từ -2,147,483,648 đến 2,147,483,647 (tức từ −2 31 𝑡ớ𝑖 2 31 − 1) Tương tự với một số kiểu dữ liệu khác
1.1.2.4 | Kiểu dữ liệu do người dùng tự định nghĩa
Nếu với những bài toán phức tạp, có những dữ liệu thực tế chúng ta cần lưu trữ vào máy tính nhưng kiểu dữ liệu cơ sở không đáp ứng đủ thì khi đó hầu hết các ngôn ngữ lập trình đều cho phép người dùng định nghĩa kiểu dữ liệu để đáp ứng nhu cầu lưu trữ những đối tượng phức tạp trên thực tế
Trong ngôn ngữ C# thì để định nghĩa kiểu dữ liệu mới có thể dùng từ khóa struct, class và cú pháp khai báo như sau: struct Struct_Name
field_name;
function_name([parameters]); // Các hàm tạo
attribute_name;
method_name([parameters]){ };
Các access_modifier trong class sử dụng được tất cả các bổ từ truy xuất như private, public, protected, internal, protected internal
Các access_modifier trong struct là public
Cấu trúc dữ liệu (data structures) là cách lưu trữ và tổ chức dữ liệu trong máy tính sao cho dữ liệu được sử dụng một cách hiệu quả nhất Một số loại cấu trúc dữ liệu như arrays, linked lists, stacks, queues, trees, graphs,
Phụ thuộc vào cách tổ chức của các phần tử mà cấu trúc dữ liệu được chia thành hai loại:
• Cấu trúc dữ liệu tuyến tính (linear data structures): Các phần tử được truy cập tuần tự Ví dụ như Linked Lists, Stacks và Queues
• Cấu trúc dữ liệu phi tuyến tính (non- linear data structures): các phần tử được truy cập không theo trật tự tuyến tính Ví dụ như Trees và graphs
KIỂU DỮ LIỆU TRỪU TƯỢNG (ADTS)
Trước khi tìm hiểu về kiểu dữ liệu trừu tượng thì cần hiểu rõ về một số khái niệm sau:
Trừu tượng hóa (Abstraction): là quá trình tách biệt thiết kế chi tiết với cách sử dụng (tức người sử dụng không cần biết thiết kế chi tiết bên dưới nhưng vẫn có thể sử dụng dễ dàng)
Trừu tượng hóa chức năng (Function abstraction): là quá trình tách biệt cài đặt chi tiết các chức năng với cách sử dụng các chức năng đó
Trừu tượng hóa dữ liệu (Data abstraction): là quá trình tách biệt cài đặt chi tiết của cấu trúc dữ liệu với cách sử dụng cấu trúc dữ liệu đó (ví dụ người sử dụng dùng cấu trúc List để lưu trữ dữ liệu và không cần biết bên trong nó được lưu trữ bằng cách nào) Đối với kiểu dữ liệu cơ sở, mặc định đã được hệ thống hỗ trợ các thao tác trên toán tử số học như +, -, *, /, … Nhưng đối với các kiểu dữ liệu do người dùng định nghĩa thì các thao tác trên những kiểu dữ liệu này không được cung cấp sẵn, vì vậy chúng cũng cần định nghĩa các thao tác Nói tóm lại thì khi người dùng tự định nghĩa thêm một kiểu dữ liệu thì phải định nghĩa luôn cả tập các thao tác hợp lệ đi cùng với kiểu dữ liệu đó
Vậy việc kết hợp cấu trúc dữ liệu cùng với các thao tác trên loại cấu trúc đó (tức là sự kết hợp data abstraction và function abstraction) tạo thành kiểu dữ liệu trừu tượng (Abstract Data Types – ADTs) Một ADT gồm hai phần:
Một vài ADTs thông dụng như: Linked Lists, Stacks, Queues, Priority Queues, Binary Trees,
KHÁI NIỆM GIẢI THUẬT VÀ PHÂN TÍCH GIẢI THUẬT
Khái niệm giải thuật Để giải quyết một vấn đề (problem) chúng ta cần đưa ra từng bước để từ dữ liệu ban đầu cho ra được kết quả mong đợi Vậy giải thuật có thể được phát biểu như sau:
Một giải thuật là tập hợp các bước rõ ràng, xác định để giải quyết một bài toán cho trước
MỘT SỐ GIẢI THUẬT TÌM KIẾM
Tìm kiếm là một trong những giải thuật cốt lõi của khoa học máy tính Chúng ta biết ngày nay rất nhiều thông tin được lưu trữ trên máy tính Để truy xuất những thông tin này một cách hiệu quả thì chúng ta rất cần những giải thuật tìm kiếm hiệu quả Trong chương này sẽ tìm hiểu hai giải thuật tìm kiếm cơ bản thường hay được sử dụng hiện nay
- Mô tả các bước giải thuật
- So sánh các giải thuật tìm kiếm
- Cài đặt một số giải thuật tìm kiếm
- Rèn luyện tính tỉ mỉ, cẩn thận
Tìm kiếm là quá trình tìm kiếm một phần tử (có thể chỉ là thuộc tính của phần tử) trong tập hợp các phần tử Những phần tử này được lưu trong một cơ sở dữ liệu, những phần tử trong mảng, văn bản trong file, phần tử (node) trong cây, đỉnh và cạnh của đồ thị,
Ví dụ: Tìm kiếm một số nguyên trong dãy các số nguyên hoặc tìm kiếm đối tượng sinh viên có các dữ liệu {maSV, hoTen, diaChi, …} Khi đó tìm kiếm trên danh sách sinh viên thì khóa thường chọn là maSV hoặc hoTen
Có rất nhiều cách tổ chức lưu trữ dữ liệu để cải thiện quá trình tìm kiếm Vì vậy phụ thuộc vào tình trạng của dữ liệu mà chúng ta sẽ có những giải thuật tìm kiếm phù hợp và hiệu quả Thông thường người ta phân làm hai loại tìm kiếm:
• Tìm kiếm tuyến tính hay còn gọi là tìm kiếm tuần tự (linear search, sequence search);
• Tìm kiếm nhị phân cho tập dữ liệu đã được sắp xếp (binary search)
Input: Tập dữ liệu được lưu trữ là dãy phần tử a1, a2, , an Giả sử chọn cấu trúc dữ liệu mảng để lưu trữ dãy gồm n số trong bộ nhớ, có khai báo: int arrInt[n]; Khoá cần tìm là key có kiểu nguyên: int key
Lưu ý: Nếu tập dữ liệu là dãy phần tử có dữ liệu phức tạp hơn, ví dụ như danh sách n sinh viên, khi đó mảng khai báo: SinhVien arr[n] và khóa cần tìm ví dụ là họ tên thì khai báo khóa như sau: string hoTen Lúc này kiểu dữ liệu của khóa và kiểu dữ liệu của phần tử trong danh sách không trùng nhau mà chỉ trùng với một trường dữ liệu của phần tử đó mà thôi
Output: Trả về vị trí tìm thấy key trong dãy (nếu tìm thấy) Nếu không tìm thấy thì trả về -1
TÌM KIẾM TUẦN TỰ (SEQUENCE SEARCH)
Giải thuật tìm kiếm tuần tự (Linear Search hay Sequence Search) có thể sử dụng cho dãy phần tử có trật tự bất kỳ Trong trường hợp dãy chưa được sắp xếp thì chúng ta phải duyệt toàn bộ dãy để kiểm tra xem khóa cần tìm có trong dãy hay không
18 Ý tưởng giải thuật: Xuất phát từ đầu dãy, duyệt tuần tự từng phần tử từ đầu đến cuối dãy để tìm kiếm khóa cần tìm Trên đường đi nếu tìm thấy khóa thì trả về vị trí xuất hiện của khóa trong dãy Nếu đi đến cuối dãy mà không tìm thấy thì trả về -1
Ngoài cách duyệt tuần tự từ đầu tới cuối danh sách thì có thể duyệt ngược lại từ cuối về đầu danh sách
Mô phỏng tìm kiếm trên dãy số nguyên với khóa cần tìm key = 8
- Trường hợp tìm thấy khóa key = 8
- Trường hợp không tìm thấy khóa key = 8
- Bước 2: Kiểm tra điều kiện i < arr.Length Nếu đúng thực hiện bước 3 Nếu sai trả về -1 và kết thúc
- Bước 3: So sánh arr[i] == key hay không
Nếu đúng trả về vị trí i, kết thúc Nếu sai: i++ và quay lại Bước 2
* Tim kiem sinh vien theo ho ten
*/ static int UnOrderLinearSearch(int[] arr, int key)
Một ví dụ khác là tìm kiếm họ tên sinh viên trong danh sách n sinh viên, khi đó mã nguồn được viết như sau:
* Tim kiem sinh vien theo ho ten
*/ static int UnOrderLinearSearch(SinhVien[] arr, string key)
{ if (arr[i].hoTen == key) return i;
} Độ phức tạp thời gian: O(n) trong trường hợp xấu nhất chúng ta cần duyệt toàn bộ dãy mới tìm thấy phần tử
Nếu các phần tử của mảng đã được sắp xếp thì nhiều trường hợp chúng ta không phải quét toàn bộ dãy để tìm phần tử Trong thuật giải ở dưới, khi khóa cần tìm nhỏ hơn arrInt[i] thì chúng ta không cần tìm dãy còn lại với giả sử dãy được sắp xếp tăng dần
* Tim kiem sinh vien theo ho ten
20 static int OrderLinearSearch(int[] arr, int key)
{ if (arr[i] == key) return i; else if (arr[i] > key) break;
} Độ phức tạp: Trong trường hợp xấu nhất vẫn là O(n) vì chúng ta có thể phải duyệt toàn bộ dãy trong trường hợp khóa nằm ở cuối dãy hoặc khóa không có trong dãy Nhưng trường hợp trung bình thì vẫn giảm độ phức tạp hơn so với cách trên
Nhận xét: Khi danh sách đã được sắp xếp theo khóa (tăng dần hoặc giảm dần) thì việc tìm kiếm phần tử dựa trên thuật toán tìm kiếm tuần tự có thể bỏ chi phí nhiều khi khóa cần tìm ở cuối danh sách và khi số phần tử danh sách rất lớn Vì vậy để giảm chi phí tìm kiếm trong trường hợp này, chúng ta sẽ sử dụng giải thuật tìm kiếm nhị phân được trình bày ở mục 2.3
TÌM KIẾM NHỊ PHÂN (Binary search)
Giải thuật tìm kiếm nhị phân hoạt động giống như vấn đề tìm kiếm một từ trong một cuốn từ điển Ý tưởng giải thuật: Bước đầu nhảy trực tiếp tới giữa cuốn và so sánh Nếu từ cần tìm được tìm thấy ngay ở vị trí này thì kết thúc việc tìm kiếm Ngược lại nếu từ cần tìm nhỏ hơn (theo thứ tự từ điển) thì ta chỉ cần tìm trên nửa trái của cuốn từ điển hoặc nếu từ cần tìm lớn hơn thì chỉ cần tìm trên nửa phải Việc tìm kiếm cho nửa trái hoặc phải được áp dụng giống như trên
Mô phỏng: với khóa nKey = 8 left data to be searched mid = (left + right) / 2 right
Sau đây là hàm tìm kiếm nhị phân áp dụng trên dãy số nguyên được sắp xếp tăng dần
Bước 1: Xét đoạn mảng [left…right] cần tìm kiếm phần tử x:
Nếu đoạn còn phần tử (left x Chỉ thực hiện tìm kiếm trên đoạn arr[left…mid-1]: right = mid – 1 Quay lại bước 1
* Tim kiem sinh vien theo ho ten
*/ static int BinarySearch (int[] arrInt, int key)
{ int left = 0; int right = arrInt.Length - 1; int mid; while (left 0 ta gọi a1 là phần tử đầu tiên và an là phần tử cuối cùng của danh sách Số phần tử của danh sách được gọi là độ dài của danh sách Trong phạm vi của giáo trình này chỉ trình bày về danh sách đặc và danh sách liên kết
Một tính chất quan trọng của danh sách là các phần tử của danh sách có thứ tự tuyến tính theo vị trí (position) xuất hiện của các phần tử Ta nói ai đứng trước ai+1, với i từ 1 đến n-1; Tương tự ta nói ai là phần tử đứng sau ai-1, với i từ 2 đến n Chúng ta cũng nói ai là phần tử tại vị trí thứ i, hay phần tử thứ i của danh sách
Ví dụ thực tế thường gặp những danh sách sau:
• Danh mục sách trong thư viện
• Danh sách các nhân viên trong công ty
• Danh sách các cuộc gọi video đang chờ được xử lý
Có 2 loại danh sách tuyến tính đó là danh sách đặc (mảng các phần tử) và danh sách liên kết (linked list)
Danh sách đặc (array): Các phần tử được bố trí nằm liên tục với nhau, là dãy các ô nhớ liên tiếp nên được gọi là danh sách đặc
Danh sách liên kết (linked list): Một Linked list là một cấu trúc dữ liệu được sử dụng để lưu trữ tập hợp các phần tử liên kết với nhau bằng con trỏ
Các tính chất của Linked list:
• Các phần tử trong danh sách liên kết tự quản lý nhau bằng cách sử dụng con trỏ Phần tử thứ nhất sẽ có thành phần con trỏ để liên kết (quản lý ô nhớ) của phần tử đứng liền sau nó trong danh sách
• Thành phần con trỏ của phần tử cuối cùng bằng NULL (dấu hiệu kết thúc danh sách)
• Có thể thay đổi kích thước (gia tăng phần tử hoặc giảm phần tử) trong quá trình thực thi
• Có thể tăng độ dài của danh sách theo yêu cầu (cho đến khi bộ nhớ của hệ thống hết)
• Không chiếm dụng lãng phí bộ nhớ (nhưng với mỗi phần tử phải lưu trữ thêm con trỏ)
Tùy thuộc vào mức độ và cách thức kết nối mà danh sách liên kết có thể chia ra nhiều loại khác nhau:
• Danh sách liên kết đơn;
• Danh sách liên kết đôi/kép;
• Danh sách liên kết vòng (vòng đơn, vòng đôi)
Mỗi loại danh sách sẽ có cách biểu diễn các phần tử (cấu trúc dữ liệu) riêng và các thao tác trên đó Trong tài liệu này, trong ba loại danh sách liên kết trên chúng ta chỉ tìm hiểu về danh sách liên kết đơn
Một kiểu dữ liệu được trang bị tập thao tác trên nó thì được gọi là kiểu dữ liệu trừu tượng ADT (Abstract Data Type) Linked list ADT sẽ có các thao tác sau:
• Chèn (insert): Chèn một phần tử vào danh sách
• Xóa (delete): Xóa phần tử ra khỏi danh sách
• Xóa danh sách (delete List): Xóa toàn bộ phần tử của danh sách
• Đếm: Trả về số phần tử có trong danh sách
• Tìm: Tìm phần tử (node) thứ n của danh sách
TỔNG QUAN VỀ SÁCH LIÊN KẾT MÓC NỐI (LINKED LIST) VÀ MẢNG
Mảng là một khối vùng nhớ được cấp phát cho toàn bộ mảng để lưu trữ dữ liệu Các phần tử trong mảng được truy cập trực tiếp một cách nhanh chóng thông qua chỉ số của mỗi phần tử như trong hình sau: Ưu điểm:
• Đơn giản và dễ sử dụng
• Truy cập phần tử nhanh (constant access)
• Cố định kích thướng: kích thước của mảng là tĩnh vì phải được chỉ định trước khi sử dụng
• Việc cấp phát phải nguyên một khối liên tục: Việc cấp phát vùng nhớ cho mảng đôi khi sẽ không thực hiện được khi kích thước lớn
• Thao tác chèn, xóa phức tạp: Để chèn 1 phần tử vào một vị trí trong mảng, chúng ta cần di chuyển tất cả các phần tử khác (bắt đầu từ vị trí cần chèn cho
58 đến hết mảng) sang bên phải để có chỗ trống thêm phần tử vào đúng vị trí Thao tác này càng trở nên phức tạp nếu vị trí cần chèn ở đầu mảng
Danh sách liên kết (Linked List)
Danh sách liên kết cũng có ưu điểm và hạn chế: Ưu điểm:
Danh sách liên kết có thể được mở rộng rất nhanh (constant time) So sánh với mảng thì để tạo một mảng thì chúng ta phải cấp phát vùng nhớ với một số lượng phần tử cho trước Để thêm nhiều phần tử vào mảng khi mảng đầy thì chúng ta phải tạo một mảng mới và sao chép mảng cũ vào mảng mới Điều này sẽ làm mất rất nhiều thời gian
Chúng ta có thể khắc phục trường hợp trên bằng cách cấp phát nhiều không gian từ lúc ban đầu cho mảng, nhưng có thể sẽ rơi vào tình trạng cấp nhiều hơn lượng thực tế cần và sẽ lãng phí bộ nhớ Với linked list, có thể bắt đầu với việc cấp phát không gian cho một phần tử và sẽ thêm phần tử mới một cách dễ dàng mà không cần phải sao chép hay cấp pháp lại vùng nhớ
Có một số vấn đề với linked list Hạn chế chính của linked list là thời gian truy cập tới mỗi phần tử Mảng là truy cập ngẫu nhiên (random-access), có nghĩa là chỉ mất O(1) để truy xuất tới bất kỳ phần tử nào trong danh sách Linked list trong trường hợp xất nhất phải mất O(n) cho việc truy cập tới mỗi phần tử Mảng được định nghĩa như là một khối ô nhớ liên tục, và các phần tử của mảng sẽ nằm bên cạnh nhau, vì thế thời gian truy cập của mảng rất nhanh (do truy cập với không gian cục bộ)
Mặc dù việc cấp phát động vùng nhớ là một ưu điểm lớn, nhưng với việc lưu trữ và truy xuất dữ liệu thì sẽ phải trả chi phí lớn Thỉnh thoảng linked list khó thao tác Nếu phần tử cuối cùng bị xóa, trước khi xóa thì cần phải chỉ ra được con trỏ sẽ lưu trữ tham chiếu NULL, khi đó đòi hỏi phải duyệt toàn bộ linked list mới tới liên kết đó
Cuối cùng, linked list phải mất thêm vùng nhớ cho mỗi phần tử vì phải chứa thêm con trỏ
Bảng sau thể hiện sự so sánh giữa Linked list, Array:
O(1) O(n), nếu mảng không đầy để có thể di chuyển được phần tử
Chèn ở cuối O(n) O(1), nếu mảng không đầy
Chèn ở giữa O(n) O(n), nếu mảng không đầy để có thể di chuyển phần tử Xóa ở giữa O(n) O(n), nếu mảng không đầy để có thể di chuyển phần tử
CÀI ĐẶT DANH SÁCH ĐẶC (MẢNG)
Khai báo cấu trúc dữ liệu
Sau đây là cấu trúc danh sách được khai báo để lưu trữ số nguyên: class ArrayList
//mảng chứa các phần tử của danh sách private int[] _items;
//số phần tử tối đa danh sách có thể chứa private int _capacity;
//số phần tử hiện có trong danh sách private int _size;
* Hàm tạo và khởi tạo ban đầu cho 1 danh sách
Các thuộc tính hỗ trợ
* Thuộc tính Count trả về số phần tử hiện có trong danh sách
* Thuộc tính Capacity trả về số phần tử tối đa mà danh sách có thể chứa
* Có thể gán số phần tử tối đa mới cho danh sách
* Thông báo ngoại lệ khi giá trị mới nhỏ hơn số phần tử hiện tại trong danh sách
{ throw new Exception("Capacity moi nho hon so phan tu hien co");
} if (value > 0 && value != this._items.Length){
_capacity = value; int[] tempArray = new int[_capacity]; if (_size > 0) {
Array.Copy(_items, 0, tempArray, 0, _size);
* Lấy hoặc gán giá trị cho một phần tử ở vị trí index trong danh sách bằng toán tử []
*/ public int this[int index]
{ throw new Exception("Index Argument Out Of Range");
{ if (index < 0 || index >= this._size)
{ throw new Exception("Index Argument Out Of Range");
* Kiểm tra danh sách có rỗng hay không
* return: true nếu danh sách rỗng, false nếu danh sách khác rỗng
* Kiểm tra danh sách có đầy hay không
* return: true nếu danh sách đầy, flase nếu danh sách chưa đầy
/* Thêm một phần tử vào cuối danh sách
* Tham số data: dữ liệu cần thêm
*/ public void Add(int data)
/*Chèn thêm một phần tử vào danh sách
* position: là vị trí cần chèn
* data: dữ liệu cần chèn
*/ public void Insert(int position, int data)
{ if (position >= 0 && position = 0 && position < _size && !IsEmpty())
* Cho biết vị trí đầu tiên xuất hiện giá trị value trong danh sách
* Tham số value: giá trị cần tìm
* Return: vị trí đầu tiên của value, -1 nếu không tìm thấy giá trị value
*/ public int IndexOf(int value)
{ return Array.IndexOf(_items, value, 0, _size);
* Xóa phần tử đầu tiên có giá trị là value ra khỏi danh sách
* Tham số value: là giá trị cần xóa
*/ public void Remove(int value)
{ int index = IndexOf(value); if (index < 0)
* Kiểm tra giá trị value có tồn tại trong danh sách hay không
* Tham số value: giá trị cần tìm
* Return: true nếu tồn tại giá trị value, false nếu không tồn tại giá trị value
*/ public bool Contains(int value)
{ for (int index = 0; index < _size; ++index)
* Cho biết vị trí cuối cùng xuất hiện giá trị value trong danh sách
* Tham số value: giá trị cần tìm
* Return: vị trí cuối cùng của value, -1 nếu không tìm thấy giá trị value
*/ public int LastIndexOf(int value)
} return Array.LastIndexOf(_items, value, _size-1, _size);
// In danh sách public void PrintList()
//Hủy bỏ danh sách public void Clear()
CÀI ĐẶT DANH SÁCH LIÊN KẾT ĐƠN
Khai báo cấu trúc dữ liệu
Danh sách liên kết đơn bao gồm nhiều phần tử (gọi là node) trong đó mỗi node có hai thành phần đó là dữ liệu và một con trỏ next theo sau mỗi phần tử Nhiệm vụ của con trỏ next này dùng để quản lý vùng nhớ của phần tử kế tiếp trong danh sách Nếu là phần tử cuối cùng thì next là NULL báo hiệu kết thúc danh sách Để quản lý toàn bộ danh sách thì chỉ cần một con trỏ first trỏ vào phần tử đứng đầu danh sách Tuy nhiên để thuận tiện cho một vài thao tác ở cuối danh sách chúng ta có thể bổ sung thêm một con trỏ last, last này luôn luôn chỉ vào phần tử đứng cuối danh sách first
Cú pháp khai báo cấu trúc dữ liệu của một phần tử struct ElementType
// định nghĩa các trường dữ liệu
Sau đây là khai báo kiểu dữ liệu của một phần tử trong danh sách liên kết chứa các số nguyên: public class Node
{ internal int Data; internal Node Next; public Node(int d)
Cú pháp khai báo cấu trúc dữ liệu của một danh sách liên kết đơn public class LinkedList
{ private Node _first; private Node _last; private int _size;
* Khoi tao danh sach last first
//Tra ve so phan tu cua danh sach public int Count
//Tra ve node dau tien trong danh sach public Node First
//Tra ve node cuoi cung trong danh sach public Node Last