You are on page 1of 21

Chương 3: Danh Sách Liên Kết

Nội dung:
I/. Giới thiệu Danh sách liên kết (Linked List)
II/. Danh sách liên kết và mảng
III/. Mô tả DSLK đơn
IV/. Các thao tác trên DSLK đơn (Singly Linked List)
V/. Danh Sách Liên Kết Vòng (Circular Linked List)
VI/. Danh Sách Liên Kết Đôi (Doubly Linked List)

1
I/. Giới thiệu Danh sách liên kết (Linked List):
Một danh sách liên kết là một cấu trúc dữ liệu tuyến tính, trong đó các phần tử không được lưu trữ tại
các vị trí bộ nhớ tiếp giáp nhau. Các phần tử trong danh sách liên kết được liên kết bằng cách sử dụng
con trỏ như được hiển thị trong hình dưới đây:

Một góc nhìn khác, một danh sách liên kết bao gồm các Node trong đó mỗi Node chứa một thành phần
dữ liệu và một thành phần tham chiếu (liên kết) đến Node tiếp theo trong danh sách.
Danh sách liên kết theo hình trên là DSLK đơn II/.
Danh sách liên kết và mảng:
Cả hai mảng và danh sách liên kết đều có thể được sử dụng để lưu trữ dữ liệu tuyến tính, nhưng mỗi
thứ đều có một số ưu điểm và nhược điểm so với nhau.

Ưu điểm của DSLK:


• Kích thước của mảng là cố định và thường phải chuẩn bị sẵn một cách dư thừa, còn DSLK thì
có kích thước động, khi cần có thể mở rộng thêm bất kỳ.
• Chèn 1 phần tử vào giữa mảng cần tốn nhiều thao tác (vì phải dịch chuyển các phần tử để tạo
chỗ trống chèn vào), còn DSLK thì không tốn nhiều thao tác như vậy. Tương tự cho thao tác
xóa trong mảng.

2
Khuyết điểm của DSLK:
• Truy cập ngẫu nhiên là không thể trong DSLK, phải truy cập từ Node đầu tiên trở đi.
• Cần thêm không gian cấp cho con trỏ của mỗi phần tử (Node).
• Mảng có vị trí liên tục nên thuận lợi cho bộ nhớ Cache tốt hơn DSLK và mảng có hiệu suất cao
hơn DSLK.
III/. Mô tả DSLK đơn:
Một danh sách liên kết được biểu diễn bằng một con trỏ chỉ đến Node đầu tiên của danh sách liên kết.
Node đầu tiên được gọi là Head (đầu). Nếu danh sách liên kết trống, thì giá trị của đầu là NULL.
Mỗi nút trong danh sách bao gồm ít nhất hai phần:

• Phần dữ liệu
• Phần liên kết dùng tham chiếu đến một node tiếp theo
Trong Java, LinkedList có thể được biểu diễn như một lớp (Class) và một Node như một lớp riêng biệt.
Lớp LinkedList chứa một tham chiếu của kiểu lớp Node.
class LinkedList
{ Node head; // head của DSLK
// Khai báo Linked list Node
class Node
{ int data; //phần dữ liệu
Node next; //phần tham chiếu đến Node kế
// Hàm Constructor dùng khởi tạo 1 node mới
// phần tham chiếu được mặc định là NULL
Node(int d)
{ data = d;}
}
Các hàm thao tác khác (các phương thức)…
}

Ví dụ: Chương trình tạo 1 DSLK đơn giản

class LinkedList
{ Node head; // phần tử head của DSLK
/* Khai báo lớp cho Node */
3
static class Node
{ int data;
Node next;

Node(int d) // Hàm Constructor


{ data = d; next=null; }
}
/* Hàm main tạo DSLK có 3 node */
public static void main(String[] args)
{ LinkedList llist = new LinkedList(); //tạo DSLK rỗng
llist.head = new Node(1); //tạo 1 node
Node second = new Node(2);
Node third = new Node(3);
llist.head.next = second; // Link first node with the second node
second.next = third; // Link second node with the third node
}
}

Hình minh họa cho DSLK theo ví dụ:

1 2 3

IV/. Các thao tác trên DSLK đơn:

1/. Định nghĩa DSLK: định nghĩa Node, định nghĩa DSLK và các phương thức / hàm cơ bản
A/. Phần khai báo cho Node: là phần khai báo cho 1 phần tử gọi là node, còn cần thêm các hàm
Constructor, setlink, setdata, getlink, getdata,… Tạo 1 Class mô tả cho Node.

/* Khai báo Class Node */ class Nodes


{
protected int data; protected Nodes link;

4
/* Hàm Constructor */
public Nodes()
{
link = null;
data = 0;
}
/* Hàm Constructor */
public Nodes(int d,Nodes n)
{
data = d;
link = n;
}
/* Hàm đặt liên kết đến Node kế */
public void setLink(Nodes n)
{
link = n;
}
/* Hàm gán dữ liệu cho Node hiện hành */
public void setData(int d)
{
data = d;
}
/* Hàm trả về Link đến node kế */
public Nodes getLink()
{
return link;
}
/* Hàm trả về dữ liệu của node hiện hành */
public int getData()
{
return data;
}
}

B/. Phần khai báo cho DSLK: để quản lý DSLK, người cần nắm giữ phần tử start, end và sử dụng
thêm thuộc tính size để theo dõi số phần tử trong DSLK.

Tạo 1 Class định nghĩa danh sách liên kết (đầy đủ)

class linkedLists
{
protected Nodes start; //Chỉ đến phần tử đầu của DSLK
protected Nodes end ; //Chỉ đến phần tử cuối của DSLK public
int size ; //cho biết kích thước của DSLK
5
/* Hàm Constructor */
public linkedLists() //Khởi tạo DSLK rỗng
{
start = null;
end = null; size
= 0;
}
/* Hàm kiểm tra DSLK rỗng */
public boolean isEmpty()
{
return start == null;
}
/* Hàm trả về kích thước của DSLK */
public int getSize()
{
return size;
}
/* Định nghĩa các hàm thực hiện các thao tác trên DSLK*/
}

Sinh viên vẽ hình minh họa 1 DSLK

5 8 12 … 72

data next null

start
end
Size: 9

2/. Khởi tạo DSLK:


public linkedLists() //Khởi tạo DSLK rỗng
{
start = null;
end = null; size
= 0;
}

6
3/. Kiểm tra DSLK rỗng:

/* Hàm kiểm tra DSLK rỗng */ public


boolean isEmpty()
{
return start == null;
}

4/. Lấy kích thước DSLK:

/* Hàm trả về kích thước của DSLK */


public int getSize()
{ return size;
}

5/. Chèn 1 phần tử vào đầu DSLK:


/* Thêm 1 phần tử vào đầu DSLK */
public void insertAtStart(int val)
{
Nodes nptr = new Nodes(val,
null); size++ ;
if(start == null)
{
start = nptr;
end = start;
}
else
{
nptr.setLink(start);
start = nptr;
}
}

7
6/. Chèn 1 phần tử vào đuôi DSLK:
/* Thêm 1 phần tử vào đuôi DSLK */
public void insertAtEnd(int val)
{
Nodes nptr = new
Nodes(val,null); size++ ;
if(start == null)
{
start = nptr;
end = start;
}
else
{
end.setLink(nptr);
end = nptr;
}
}

8
7/. Chèn 1 phần tử tại vị trí Pos trong DSLK:
/* Thêm 1 phần tử tại vị trí pos */
public void insertAtPos(int val , int pos)
{
Nodes nptr = new Nodes(val, null); //khởi tạo Node mới
Nodes ptr = start;
pos = pos - 1 ;
for (int i = 1; i < size; i++) // vị trí tính là 1,2,3…
{
if (i == pos)
{
Nodes tmp = ptr.getLink() ;
ptr.setLink(nptr); //ptr trỏ đến nptr
nptr.setLink(tmp); //nptr trỏ đến tmp
break;
}
ptr = ptr.getLink(); //tiến tới node kế
}
size++ ; }

8/. Xóa 1 phần tử tại vị trí Pos trong DSLK:


/* Hàm xóa 1 phần tử tại vị trí pos */
public void deleteAtPos(int pos)
{
if (pos == 1) //xóa tại vị trí start
{

9
start = start.getLink();
size--; //giảm kích thước 1
return ;
}
if (pos == size) //xóa tại vị trí end
{
Nodes s = start;
Nodes t = start;
while (s != end) //đi dần đến phần tử end
{
t = s;
s = s.getLink();
} end
= t;
end.setLink(null);
size --; //giảm kích thước 1
return;
}
Nodes ptr = start; //xóa tại vị trí giữa
pos = pos - 1 ;
for (int i = 1; i < size - 1; i++)
{
if (i == pos)
{
Nodes tmp =
ptr.getLink(); tmp =
tmp.getLink();
ptr.setLink(tmp); break;
}
ptr = ptr.getLink();
}
size-- ; //giảm kích thước 1
}
Vẽ hình ở đây: Sinh viên tự vẽ hình cho trường hợp xóa ở đầu, cuối ptr: phần tử
đứng trước phần tử cần xóa; tmp là phần tử (vị trí pos) cần xóa

10
9/. Duyệt DSLK:
/* Hàm duyệt DSLK */
public void display()
{
System.out.print("\nSingly Linked List = ");
if (size == 0)
{
System.out.print("Danh sách liên kết rỗng!\
n"); return; }
if (start.getLink() == null) //DSLK có 1 phần tử
{
System.out.println(start.getData() );
return;
}
Nodes ptr = start; //duyệt từ start 
end System.out.print(start.getData()+ "-
>"); ptr = start.getLink(); while
(ptr.getLink() != null)
{
System.out.print(ptr.getData()+ "->");
ptr = ptr.getLink();
}
System.out.print(ptr.getData()+ "\n");
}

Bài tập DSLK 1: Viết chương trình cài đặt DSLK đơn, có menu chọn công việc như sau:
1. Thêm phần tử ở đầu
2. Thêm phần tử ở đuôi
3. Thêm phần tử theo vị trí (2,3..)
4. Xóa phần tử theo vị trí
5. Duyệt DSLK
6. Kết thúc

Bài tập DSLK 2: Bài tập Stack 2: Viết chương trình khai báo Stack (dùng danh sách liên kết) có chứa
các số nguyên, với menu thực hiện như sau:
1. Push vào Stack
2. Pop ra khỏi Stack
3. Xem phần tử trên đỉnh Stack
4. Kiểm tra Stack rỗng
5. Số phần tử trong Stack
6. Duyệt Stack
7. Thoát
11
Bài tập DSLK 3: Bài tập Queue 2: Viết chương trình khai báo Queue (dùng danh sách liên kết) có
chứa các số nguyên, với menu thực hiện như sau:
1. Thêm vào Queue (EnQueue)
2. Lấy ra khỏi Queue (DeQueue)
3. Xem phần tử đầu Queue
4. Kiểm tra Queue rỗng
5. Kích thước Queue
6. Duyệt Queue
7. Thoát

12
V/. Danh Sách Liên Kết Vòng (Circular Linked List):
Danh sách liên kết vòng là một biến thể của danh sách liên kết, trong đó tất cả các nút được kết nối để
tạo thành một vòng tròn. Không có null ở cuối. Danh sách liên kết vòng, có thể là danh sách liên kết
vòng đơn (singly circular linked list) hoặc danh sách được liên kết vòng đôi (doubly circular linked
list).

Ưu điểm của Danh sách được Liên kết vòng:


i. Bất kỳ Node nào cũng có thể là điểm bắt đầu. Chúng ta có thể duyệt toàn bộ danh sách
bằng cách bắt đầu từ bất kỳ điểm nào.
ii. Hữu ích cho việc thực hiện hàng đợi (Queue). iii. Danh sách liên kết vòng rất hữu
ích trong các ứng dụng liên tục đi quanh danh sách.
iv. Các danh sách liên kết đôi (kép) được sử dụng để thực hiện các cấu trúc dữ liệu tiên tiến như
Fibonacci Heap.
Sinh viên tìm hiểu thêm về DSLK vòng (bài tập).

VI/. Danh Sách Liên Kết Đôi (Doubly Linked List):


1/. Mô tả DSLK đôi: là DSLK mà mỗi phần tử có 2 liên kết, một chỉ đến phần tử đứng trước và một
chỉ đến phần tử đứng sau.

Xem thêm DSLK vòng đôi (không vẽ hình này)

13
Ưu điểm so với danh sách liên kết đơn:
i. Một DSLK đôi có thể được di chuyển theo cả hai hướng tiến và lùi. ii.
Các hoạt động xóa, chèn thêm trong DSLK đôi là hiệu quả hơn.

2/. Định nghĩa DSLK đôi:


A/. Phần khai báo cho Node: cần thêm các hàm Constructor, setlink, setdata, getlink, getdata,… Tạo
1 Class mô tả cho Node.
/* Khai báo Class Node */ class Node
{
protected int data; //Phần dữ liệu
protected Node next, prev; //phần liên kết sau, trước
/* hàm Constructor */ public
Node()
{
next = null; prev
= null; data = 0;
}
/* hàm Constructor */
public Node(int d, Node n, Node p)
{
data = d; next
= n; prev = p;
}
/* Hàm đặt link đến node kế (next _ sau) */ public void
setLinkNext(Node n)
{
next = n;
}
/* Hàm đặt link đến node trước_previous */ public void
setLinkPrev(Node p)
{
prev = p;
}
/* Hàm trả về node đứng sau (next node) */ public Node
getLinkNext()
{
return next;
}
/* Hàm trả về node đứng trước (previous node) */
public Node getLinkPrev() {

14
return prev;
}
/* Hàm gán giá trị Data cho node */
public void setData(int d)
{
data = d;
}
/* Hàm trả về Data của node */
public int getData()
{
return data;
} }

B/. khai báo cho DSLK: để quản lý DSLK, người cần nắm giữ phần tử start, end và sử dụng thêm
thuộc tính size để theo dõi số phần tử trong DSLK.
Tạo 1 Class định nghĩa DSLK đôi.
/* Class linkedList */ class linkedList
{ protected Node start; //Chỉ đến phần tử đầu của DSLK
protected Node end ; //Chỉ đến phần tử cuối của DSLK public int
size; //Cho biết kích thước của DSLK
/* hàm khởi tạo DSLK rỗng */ public
linkedList()
{
start = null; end =
null; size = 0;
}
/* Hàm kiểm tra DSLK rỗng */ public
boolean isEmpty()
{
return start == null;
}
/* Hàm trả về kích thước của DSLK */ public int
getSize()
{
return size;
}

15
… /* định nghĩa các hàm thao tác khác trên DSLK
đôi*/

Sinh viên vẽ hình minh họa 1 DSLK đôi:


null null

5 16 16 … 58

prev data next

start

end

Size: 10

2/. Khởi tạo DSLK:

public linkedList()
{
start = null; end = null; size = 0;
}

3/. Kiểm tra DSLK rỗng:

public boolean isEmpty()


{
return start == null;
}

4/. Lấy kích thước DSLK:

public int getSize()


{
return size; }

5/. Chèn 1 phần tử vào đầu DSLK:

16
/* Hàm thêm 1 phần tử vào đầu */
public void insertAtStart(int val)
{
Node nptr = new Node(val, null, null);
if(start == null)
{
start = nptr;
end = start;
}
else
{
start.setLinkPrev(nptr);
nptr.setLinkNext(start); start
= nptr;
}
size++;
}

6/. Chèn 1 phần tử vào đuôi DSLK:


/* Hàm thêm 1 phần tử vào đuôi */
public void insertAtEnd(int val)
{
Node nptr = new Node(val, null, null);
if(start == null)
{
start = nptr;
end = start;
}
else
{
nptr.setLinkPrev(end);
end.setLinkNext(nptr); end
= nptr;
}
size++; }
17
7/. Chèn 1 phần tử tại vị trí Pos trong DSLK:
/* Hàm thêm 1 phần tử vào vị trí pos */
public void insertAtPos(int val , int pos)
{
Node nptr = new Node(val, null, null);
if (pos == 1)
{

insertAtStart(val);
return; }
Node ptr = start;
for (int i = 2; i <= size; i++)
{
if (i == pos)
{
Node tmp = ptr.getLinkNext();
ptr.setLinkNext(nptr);
nptr.setLinkPrev(ptr);
nptr.setLinkNext(tmp);
tmp.setLinkPrev(nptr);
}
ptr = ptr.getLinkNext();
}
size++ ;
}

18
8/. Xóa 1 phần tử tại vị trí Pos trong DSLK:
/* Hàm xóa 1 phần tử tại vị trí pos */
public void deleteAtPos(int pos)
{
if (pos == 1)
{
if (size == 1)
{
start = null;
end = null; size
= 0; return;
}
start =
start.getLinkNext();
start.setLinkPrev(null);
size--; return ;
}
if (pos == size)
{
end = end.getLinkPrev();
end.setLinkNext(null); size--
;
}
Node ptr = start.getLinkNext();
for (int i = 2; i <= size; i++)
{
if (i == pos)
{
Node p = ptr.getLinkPrev();
Node n = ptr.getLinkNext();

p.setLinkNext(n);
n.setLinkPrev(p);
19
size-- ;
return;
}
ptr = ptr.getLinkNext();
}
}

9/. Duyệt DSLK:


/* Hàm duyệt DSLK */
public void display()
{
System.out.print("\nDoubly Linked List = ");
if (size == 0)
{
System.out.print("Danh sách liên kết rỗng!\n");
return;
}
if (start.getLinkNext() == null)
{
System.out.println(start.getData());
return;
}
Node ptr = start;
System.out.print(start.getData()+ " <->
"); ptr = start.getLinkNext();
while (ptr.getLinkNext() != null)
{
System.out.print(ptr.getData()+ " <-> ");
ptr = ptr.getLinkNext();
}
System.out.print(ptr.getData()+ "\n");
}

20
Bài tập thêm về DSLK đơn (Điểm A)
1/. Viết hàm đảo ngược DSLK (chỉ dùng 1 DSLK) 2/. Viết hàm sắp xếp danh sách liên kết theo thứ tự
tăng dần (dùng giải thuật bất kỳ)

21

You might also like