• Kích thước có thể thay đổi Hàm cấp phát bộ hớ của C void *mallocsize; //Trả về con trỏ chỉ đến một vung nhớ //size byte vừa được cấp phát void *callocn,size; //Trả về con trỏ chỉ đến
Trang 1CON TRỎ VÀ DANH SÁCH LIÊN KẾT
I Kiễu dữ liệu con trỏ
1 Biến không động
• Khai báo tường minh
• Tồn tại khi vào phạm vi được khai báo và chỉ mất khi ra khỏi phạm vi này
• Được cấp phát vùng nhớ
• Kích thước không thay đổi trong suốt quá trình
VD:
int a;
char b[10];
//a b là các biến không động
2 Kiểu con trỏ
• Một con trỏ hay một biến con trỏ là:
✓ một biến chiếu đến một ô nhớ
✓ nó lưu vị trí/địa chỉ của ô nhớ đó
• Hai ứng dụng chính:
✓ Truy nhập gián tiếP
✓ Bộ nhớ động
VD :
int x, y ; //cho y=5 thi x bang 5
int* p ; //p la bien con tro
p = &y ; //tro p toi y (p luu dia chi cua y)
*p = x ; //ghi gia tri bien x vao bo nho tro toi p
• Đọc *p là biến mà p trỏ tới
Đọc &x là địa chỉ của x
& là toán tử địa chỉ (address of operator)
* là toán tử thâm nhập (dereferencing operator)
• Giả sử p1 = &x và p2 = &y, thì p1 trỏ tới x và p2 trỏ tới y
p1 = p2 Không tương đương với *p1 = *p2 p1 = p2 có hiệu quả trỏ p1 tới y,lệnh đó không thay đổi x Lệnh *p1 = *p2 ; tương đương với x=y;
• Cú pháp định nghĩa một kiểu con trỏ trong C
typedef <Kiểu con trỏ> *<kiểu cơ sở>
Trang 2int *p, *q;
Ta có thể viết
typedef int* IntPtr;
IntPtr p, q; //p q deu la con tro
• Khi một biến con trỏ p lưu địa chỉ của đối tượng x ta nói p trỏ đến x
3 Biến động
• Không khai báo tường minh
• Có thể cấp phát hoặc giải phóng bộ nhớ theo yêu cầu
• Không theo quy tắc phạm vi
• Vung nhớ được cấp phát trong heap
heap: vùng bộ nhớ đặc biệt dành riêng cho các biến động Để tạo một biến động mới, hệ thống cấp phát không gian từ heap Nếu không còn bộ nhớ,
new không thể cấp phát bộ nhớ thì nó trả về gia trị Null Thực ra, NULL là giá trị 0, nhưng ta coi
nó là một giá trị đặc biệt vì còn sử
dụng cho trường hợp đặc biệt: con trỏ "rỗng"
• Kích thước có thể thay đổi
Hàm cấp phát bộ hớ của C
void *malloc(size); //Trả về con trỏ chỉ đến một vung nhớ
//size byte vừa được cấp phát
void *calloc(n,size); //Trả về con trỏ chỉ đến một vùng nhớ
//vừa được cấp phát gồm n phần tử
//Mỗi phần tử có kích thước size byte
Hàm cấp phát bộ hớ của C++
p = new int; //Cap phat mot o nho chua so nguyen va p tro toi o nho do
Hàm hủy một biến động do p chỉ đến
• Hàm free(p) hủy do lệnh malloc hoặc calloc
• Hàm delete p hủy do lệnh new
VD:
delete p;
➢ Trả lại vùng bộ nhớ trỏ bởi p, nhưng không sửa giá tri của p
Trang 3➢ Sau khi thực thi delete p, giá trị của p không xác định
VD:
int* p1, p2;//cap phat vung nho cho mot bien dong kieu int
p1 = (int*)malloc(sizeof(int));
p1* = 5; //dat gia tri 5 cho bien dong p1
p2 = (int*)calloc(10, sizeof(int)); //cap phat bien dong kieu mang gom 10 phan tu keu int
(p2 + 3) *= 0; //dat gia tri 0 cho phan tu thu 4 cua mang p2
free(p1);
free(p2);
VD:
#include <iostream>
int main()
{
int *p1, *p2;
p1 = new int;
*p1 = 10;
p2 = p1;
cout << "*p1 = " << *p1 << endl; //*p1=10
cout << "*p2 = " << *p2 << endl << endl; //*p2=10
*p2 = 30;
cout << "*p1 = " << *p1 << endl; //*p1=30
cout << "*p2 = " << *p2 << endl << endl; //*p1=30
p1 = new int;
*p1 = 40;
cout << "*p1 = " << *p1 << endl; //*p1=40
cout << "*p2 = " << *p2 << endl << endl; //*p1=30
}
II Danh sách kiên kết
Các hình thức tổ chức danh sách
Có nhiều kiểu tổ chức liên kết giữa các phần tử trong danh sách như :
-Danh sách liên kết đơn: Mỗi phần tử liên kết với phần tử đứng sau nó trong danh sách
-Danh sách liên kết kép: Mỗi phần tử liên kết với phần tử đứng trước và sau nó trong danh sách
Trang 4A B C D
-Danh sách liên kết vòng:Phần tử cuối danh sách liên kết với phần tử đầu danh sách
-Danh sách tổng quát
Hình thức liên kết này cho phép các thao tác thêm, huỷ phần tử trên danh sách được thực hiện dễ dàng hơn, phản ánh được bản chất linh động của danh sách
A Danh sách liên kết đơn
1 Kiến thức
Mỗi phần tử của danh sách đơn là một cấu trúc chứa hai thông tin :
• Thành phần dữ liệu: lưu trữ các thông tin về bản thân phần tử
• Thành phần mối liên kết: lưu trữ địa chỉ của phần tử kế tiếp trong danh sách, hoặc lưu trữ giá trị NULL nếu là phần tử cuối danh sách
Ta có định nghĩa tổng quát
struct Node
{
Data Info; //data là kiểu dữ liệu đã định nghĩa trước
Node* Next; //con trỏ chỉ đến cấu trúc Node
};
Trang 5• Một phần tử trong danh sách đơn là một biến động sẽ được yêu cầu cấp phát khi cần Và
danh sách đơn chính là sự liên kết các biến động này với nhau do vậy đạt được sự linh động khi thay đổi số lượng các phần tử
• Nếu biết được địa chỉ của phần tử đầu tiên trong danh sách đơn thì có thể dựa vào thông tin next của nó để truy xuất đến phần tử thứ hai trong xâu, và lại dựa vào thông tin next của phần tử thứ 2 để truy xuất đến phần tử thứ 3, Nghĩa là để quản lý một xâu đơn chỉ cần biết địa chỉ phần tử đầu xâu Thường một con trỏ head sẽ được dùng để lưu trữ địa chỉ phần tử đầu xâu, ta gọi head là đầu xâu Ta có khai báo:
Node *head;
• Tuy về nguyên tắc chỉ cần quản lý xâu thông qua đầu xâu head, nhưng thực tế có nhiều trường hợp cần làm việc với phần tử cuối xâu, khi đó mỗi lần muốn xác định phần tử cuối xâu lại phải duyệt từ đầu xâu Ðể tiện lợi, có thể sử dụng thêm một con trỏ tail giữ địa chỉ phần tử cuối xâu Khai báo tail như sau :
Node *tail;
Xâu sẽ có
2 Các thao tác cơ bản trên danh sách liên kết đơn
✓ Giả sử có định nghĩa nút trong danh sách
✓ Giả sử có định nghĩa kiểu danh sách liên kết
1 Tạo phần tử
2 Khởi tạo danh sách rỗng
3 Kiểm tra danh sách rỗng
4 Duyệt danh sách
5 Tìm phần tử có thành phần dữ liệu x trong danh sách
6 Chèn một phần tử vào danh sách
7 Hủy một phần tử khỏi danh sách liên kết
8 Sắp xếp danh sách
Trang 69 Một số cấu trúc đặc biệt của danh sách liên kết đơn
//Giả sử có định nghĩa nút trong danh sách :
C1
struct Node //Khai báo một cấu trúc dữ liệu
{
int info;
node *pNext;
};
C2
typedef struct Node
{
int info;
Node *pNext;
}Node; //Kiểu của một phần tử trong danh sách
//Giả sử có định nghĩa kiểu danh sách liên kết :
struct list //Kiểu danh sách liên kết
{
Node *pHead; //con trỏ lưu địa chỉ đầu danh sách liên kết Node *pTail; //con trỏ lưu địa chỉ cuối danh sách liên kết
};
VD:
//Kiểu dữ liệu sinh viên
struct SinhVien
{
char Ten[30];
int MSSV;
};
//Kiểu phần tử trong danh sách
struct SinhVienNode
{
SinhVien Info;
SinhVien* pNext;
};
//Đổi lại tên kiểu phần tử trong danh sách
typedef SinhVien SVNode;
//Định nghĩa danh sách liên kết
Trang 7struct List
{
SVNode* pHead;
SVNode* pTail;
};
Hoặc
//Kiểu dữ liệu sinh viên
struct SinhVien
{
char Ten[30];
int MSSV;
};
//Kiểu phần tử trong danh sách
struct SinhVienNode
{
SinhVien Info;
SinhVien* pNext;
};
//Định nghĩa danh sách liên kết
struct List
{
SinhVienNode* pHead;
SinhVienNode* pTail;
};
1.Tạo phần tử
// Hàm CreateNode(x): tạo một nút trong DSLK vời thành phần dữ liệu x, hàm trả về con trỏ lưu trữ địa chỉ của phần tử vừa tạo (nếu thành công)
Node* GetNode(int x)
{
Node *p;
p = new Node; //Cấp phát bộ nhớ cho phần tử
//Gộp Node* p=new Node;
if (p == NULL)
{
cout << "Khong du bo nho"; exit(1);
}
p->Info = x; //Gán thông tin cho phần tử p
p->pNext = NULL;
Trang 8return p;
}
Sử dụng Node* New_ele = GetNode(x); : gán giá trị của hàm (là con trỏ) cho 1 biến con trỏ kiểu Node, phần tử này đặt tên là New_ele, giữ địa chỉ của phần tử đã tạo
2.Khởi tạo danh sách rỗng
//Hàm khởi tạo danh sách rỗng
void CreatList(List &l)
{
l.pHead = l.pTail = NULL;
}
3.Kiểm tra danh sách rỗng
//Kiểm tra danh sách rỗng, trả về 0 nếu rỗng trả về 1 nếu không rỗng
int IsEmpty(List )
{
if (l.pHead = NULL) //Danh sách rỗng
return 0;
return 1;
}
4.Duyệt danh sách
• Đếm các phần tử của danh sách
• Xuất dữ liệu các phần tử trong DS ra màn hình
• Tìm tất cả các phần tử thỏa điều kiện
• Hủy toàn bộ danh sách (và giải phóng bộ nhớ)
Thuật toán
B1: p = Head; //Cho p trỏ đến phần tử đầu danh sách
B2: Trong khi (Danh sách chưa hết) thực hiện
• B2.1 : Xử lý phần tử p;
• B2.2 : p=p->pNext; // Cho p trỏ tới phần từ kế tiếp
//Hàm duyệt danh sách
void ProcessList(List l)
{
Node* p;
p = l.pHead;
while (p != NULL)
Trang 9{
ProcessNode(p); //Xử lí cụ thể từng hàm
p = p->pNext;
}
}
//Hàm xuất dữ liệu ra màn hình
void Output(List )
{
Node* p;
if (IsEmpty(l)==0)
{
cout << "Danh sach rong"; return;
}
cout << "Du lieu danh sach";
p = l.pHead;
while (p!= NULL)
{
cout << p->info;
p = p->pNext;
}
}
4.Tìm phần tử có thành phần dữ liệu x trong danh sách
B1: p = Head; //Cho p trỏ đến phần tử đầu danh sách B2: Trong khi (p != NULL) và (p->Info != x ) thực hiện:
p = p->Next;// Cho p trỏ tới phần tử kế tiếp
B3:
Nếu p != NULL thì p trỏ tới phần tử cần tìm
Ngược lại: không có phần tử cần tìm
//Hàm tìm phần tử có trong danh sách
Node* Search(List , int x)
{
Node* p;
p = l.pHead;
while (p != NULL && p->info != x)
p = p->pNext;
return p;
}
Trang 106 Chèn một phần tử vào danh sách
Có ba vị trí để có thể chèn một phần tử New_ele vào danh sách:
• Chèn phần tử vào đầu danh sách
• Chèn phần tử vào cuối danh sách
• Chèn phần tử vào danh sách sau một phần tử q
C1: Chèn vào đầu danh sách
pHead
pTail
X
.
Giải thuật :
Nếu danh sách rỗng thì
B1.1: Head = New_elelment;
B1.2: Tail = Head;
Ngược lại
B2.1: New_ele->pNext = Head;
B2.1: Head = New_ele;
//Ham chen một phần tử vào đầu danh sách
void AddFirst(list &l, node *New_ele)
{
if (l.pHead == NULL) //xau rong
{
l.pHead = New_ele;
l.pTail = l.pHead;
}
else
{
New_ele->pNext = l.pHead;
l.pHead = New_ele;
}
}
//Tạo nút có dữ liệu x, sau đó chèn nút vừa tạo vào đầu danh sách, hàm trả về con trỏ lưu vị trí nút vừa tạo
Node* InsertHead(List &l, int x)
{
Node* New_ele = GetNode(x);
if (New_ele == NULL)
return NULL;
Trang 11if (l.pHead == NULL)
{
l.pHead = New_ele;
l.pTail = l.pHead;
}
else
{
New_ele->pNext = l.pHead;
l.pHead = New_ele;
}
return New_ele;
}
Hoặc
Node* InsertHead(List &l, int x)
{
Node* New_ele = GetNode(x);
if (New_ele == NULL)
return NULL; AddHead(l, New_ele);
return New_ele;
}
C2: Chèn vào cuối danh sách
Thuật toán
Nếu danh sách rỗng thì
B1.1: Head = New_ele;
B1.2: Tail = Head;
Ngược lại
B2.1: Tail ->pNext = New_ele;
B2.2: Tail = New_ele ;
pHead
pTail
X
void AddTail(List &l, Node* New_ele)
{
Trang 12if (l.pHead == NULL)
{
l.pHead = New_ele;
l.pTail = l.pHead;
}
else
{
l.pTail->pNext = New_ele;
l.pTail = New_ele;
}
}
Tạo nút có dữ liệu x, sau đó chèn nút vừa tạo vào cuối danh sách, hàm trả về con trỏ lưu vị trí nút vừa tạo
Node* InsertTail(List &l, int x)
{
Node* New_ele = GetNode(x);
if (New_ele == NULL)
return NULL;
if (l.pHead == NULL)
{
l.pHead = New_ele;
l.pTail = l.pHead;
}
else
{
l.pTail->pNext = New_ele;
l.pTail = New_ele;
}
return New_ele;
}
Hoặc
Node* InsertTail(List &l, int x)
{
Node* New_ele = GetNode(x);
if (New_ele == NULL)
return NULL; AddTail(l, New_ele);
return New_ele;
}
C2: Chèn một phần tử x vào danh sách sau một phần tử q
Trang 13Nếu ( q != NULL) thì
B1: New_ele -> pNext = q->pNext;
B2: q->pNext = New_ele;
7 Hủy một phần tử khỏi danh sách liên kết
• Hủy phần tử đầu danh sách
• Hủy một phần tử đứng sau phần tử q trong danh sách
• Hủy một phần tử có dữ liệu x
8 Sắp xếp danh sách
9 Một số cấu trúc đặc biệt của danh sách liên kết đơn 9.1
A Danh sách liên kết kép
➢ Chèn một phần tử vào DSLK kép
1 Chèn đầu danh sách
2 Chèn cuối danh sách
3 Chèn nút vào sau một phần tử p
4 Chèn nút vào trước phần tử p
➢ Hủy một phần tử ra khỏi danh sách kép
1 Hủy phần tử đầu danh sách
2 Hủy phần tử đầu danh sách
3 Hủy phần tử sau phần tử q
4 Hủy phần tử trước phần tử q
Trang 145 Hủy phần tử có khóa k
➢ Sắp xếp trên danh sách liên kết kép