1. Code
  2. Coding Fundamentals

Daftar Tertaut (Linked List)

Scroll to top
This post is part of a series called Data Structures Succinctly: Part 1.
Sorting Algorithms

Indonesian (Bahasa Indonesia) translation by Danish Kalesaran (you can also view the original English article)

Struktur data pertama yang akan kita pelajari adalah Daftar Tertaut, dan ini bukan tanpa alasan. Selain merupakan struktur yang ada hampir di mana saja mulai dari sistem operasi sampai video game, struktur ini juga merupakan dasar untuk membangun banyak struktur data lainnya.

Ikhtisar

Dalam pemahaman yang sangat luas, tujuan dari daftar tertaut adalah untuk menyediakan mekanisme yang konsisten untuk menyimpan dan mengakses sejumlah data yang terpisah. Itu dilakukan dengan menghubungkan datanya ke dalam daftar.

Sebelum kita masuk ke dalam artinya, mari kita mulai dengan meninjau bagaimana data disimpan di dalam satu array.

Data integer disimpan di dalam satu array
Data integer disimpan di dalam satu array

Seperti yang ditampilkan di gambar, data array disimpan sebagai satu potong memori yang teralokasi secara berurutan yang terbagi secara logis. Data yang disimpan di dalam array ditempatkan di dalam salah satu dari bagian-bagian itu dan dirujuk dengan lokasinya atau indeksnya di dalam array.

Ini adalah cara yang baik untuk menyimpan data. Kebanyakan bahasa pemrograman memudahkan kegiatan alokasi array dan operasi pada konten dari array itu. Penyimpanan data yang berurutan memberikan keuntungan secara performa (yaitu lokalitas data), kemudahan dalam melakukan iterasi pada data, dan data itu bisa diakses secara langsung melalui indeksnya (akses acak) secara konstan.

Tapi ada saat di mana array bukanlah solusi yang ideal.

Misalnya program yang memiliki tuntutan sebagai berikut:

  1. Baca integer dengan jumlah yang tidak diketahui dari sumber input (method NextValue) hingga bilangan 0xFFFF ditemui.
  2. Sampaikan semua integer yang sudah dibaca (dalam sekali pemanggilan) ke method ProcessItems.

Karena tuntutan dari program itu mengindikasikan bahwa beberapa value harus disampaikan ke method ProcessItems dalam satu pemanggilan, satu solusi yang paling jelas adalah menggunakan array integer. Contohnya:

1
void LoadData()
2
{
3
	// Asumsikan bahwa 20 sudah cukup untuk menampung nilainya.

4
	int[] values = new int[20];
5
	for (int i = 0; i < values.Length; i++)
6
	{
7
		if (values[i] == 0xFFFF)
8
		{
9
			break;
10
		}
11
12
		values[i] = NextValue();
13
	}
14
15
	ProcessItems(values);
16
}
17
18
void ProcessItems(int[] values)
19
{
20
	// ... Proses data.

21
}

Solusi ini memiliki beberapa kendala, tapi yang paling kentara adalah ketika menerima lebih dari 20 nilai. Dalam program itu sekarang, nilai dari 21 sampai n diabaikan begitu saja. Ini bisa diringankan dengan mengalokasikan lebih dari 20 nilai, mungkin 200 atau 2000. Mungkin ukurannya bisa dikonfigurasi oleh user, atau jika arraynya sudah penuh array yang lebih besar bisa dialokasikan dan semua data sudah ada dikopi ke situ. Pada akhirnya solusi-solusi itu menciptakan kerumitan dan membuang memori.

Apa yang kita perlukan adalah kumpulan yang memampukan kita untuk menambahkan sejumlah integer yang tidak ditentukan kemudian melakukan enumerasi terhadap integer-integer itu sesuai urutan masuknya masing-masing integer. Ukuran maksimum dari kumpulan itu tidak ditentukan dan indeksasi untuk akses acak bukanlah keharusan. Untuk itu kita perlu daftar tertaut.

Sebelum kita melanjutkan dan mempelajari bagaimana daftar tertaut dirancang dan diterapkan, kita tinjau dulu solusi akhir kita.

1
static void LoadItems()
2
{
3
	LinkedList list = new LinkedList();
4
	while (true)
5
	{
6
		int value = NextValue();
7
		if (value != 0xFFFF)
8
		{
9
			list.Add(value);
10
		}
11
		else
12
		{
13
			break;
14
		}
15
	}
16
17
	ProcessItems(list);
18
}
19
20
static void ProcessItems(LinkedList list)
21
{
22
		// ... Proses data.

23
}

Perhatikan bahwa semua kendala dari solusi dengan array yang sebelumnya tidak lagi ada. Persoalan array yang tidak cukup besar atau pengalokasian yang lebih banyak dari yang diperlukan tidak ada lagi.

Anda juga harus menyadari beberapa keputusan desain yang akan kita buat nanti yang diungkapkan oleh solusi ini, yaitu bahwa class LinkedList menerima argumen dengan tipe yang generik dan menerapkan interface IEnumerable.


Menerapkan Class LinkedList

Node

Inti dari struktur data daftar tertaut adalah class Node. Suatu node adalah wadah yang menyediakan kemampuan untuk menyimpan data dan berhubungan dengan node yang lain.

Suatu node daftar tertaut memuat data dan satu property yang mengarah ke node yang berikutnyaSuatu node daftar tertaut memuat data dan satu property yang mengarah ke node yang berikutnyaSuatu node daftar tertaut memuat data dan satu property yang mengarah ke node yang berikutnya
Suatu node daftar tertaut memuat data dan satu property yang mengarah ke node yang berikutnya

Dalam bentuknya yang paling sederhana, satu class Node yang memuat integer bisa berbentuk seperti ini:

1
public class Node
2
{
3
	public int Value { get; set; }
4
	public Node Next { get; set; }
5
}

Dengan ini kita bisa menciptakan daftar tertaut yang sangat sederhana. Pada contoh berikut, kita akan mengalokasikan tiga node (first, middle, dan last) dan kemudian menautkan semuanya ke dalam satu daftar.

1
// +-----+------+
2
// |  3  | null +
3
// +-----+------+
4
Node first = new Node { Value = 3 };
5
6
// +-----+------+    +-----+------+
7
// |  3  | null +    |  5  | null +
8
// +-----+------+    +-----+------+
9
Node middle = new Node { Value = 5 };
10
11
// +-----+------+    +-----+------+
12
// |  3  |  *---+--->|  5  | null +
13
// +-----+------+    +-----+------+
14
first.Next = middle;
15
16
// +-----+------+    +-----+------+   +-----+------+
17
// |  3  |  *---+--->|  5  | null +   |  7  | null +
18
// +-----+------+    +-----+------+   +-----+------+
19
Node last = new Node { Value = 7 };
20
21
// +-----+------+    +-----+------+   +-----+------+
22
// |  3  |  *---+--->|  5  |  *---+-->|  7  | null +
23
// +-----+------+    +-----+------+   +-----+------+
24
middle.Next = last;

Sekarang kita memiliki daftar tertaut yang dimulai dengan node first dan diakhiri dengan node last property Next untuk node last mengarah pada null yang merupakan penanda akhir dari daftar (end-of-list). Dengan daftar ini, kita bisa melakukan operasi dasar. Misalnya, nilai dari property Data dari masing-masing node:

1
private static void PrintList(Node node)
2
{
3
	while (node != null)
4
	{
5
		Console.WriteLine(node.Value);
6
		node = node.Next;
7
	}
8
}

Cara kerja dari method PrintList adalah melakukan iterasi terhadap tiap node di dalam daftar itu, menampilkan nilai dari node yang kini, kemudian berpindah ke node yang dirujuk oleh property Next.

Dengan pemahaman terhadap perkiraan bentuk dari node daftar tertaut, mari kita lihat class LinkedListNode yang sebenarnya.

1
public class LinkedListNode
2
{
3
	/// 

4
	/// Bentuk node baru dengan nilai yang ditentukan.

5
	/// 

6
	public LinkedListNode(T value)
7
	{
8
		Value = value;
9
	}
10
11
	/// 

12
	/// Nilai dari node.

13
	/// 

14
	public T Value { get;  internal set; }
15
16
	/// 

17
	/// Node yang berikutnya dalam daftar tertaut (null jika merupakan node terakhir).

18
	/// 

19
	public LinkedListNode Next { get;  internal set; }
20
}

Class LinkedList

Sebelum menerapkan class LinkedList, kita perlu memikirkan apa yang kita harapkan dari daftar itu.

Sebelumnya kita sudah melihat bahwa kumpulan itu harus bisa mendukung data dengan tipe yang ditentukan jadi kita tahu yang kita inginkan adalah menciptakan interface yang generik.

Karena kita menggunakan framework .NET untuk menerapkan daftar itu, maka wajar jika kita ingin class itu untuk bisa bertindak sebagaimana tipe data kumpulan lainnya yang sudah ada di dalam framework. Cara termudah untuk melakukan ini adalah menerapkan interface ICollection<T>. Perhatikan bahwa saya memilih ICollection<T> bukannya IList<T>. Ini karena interface IList<T> memberikan kemampuan untuk mengakses nilai berdasarkan indeks. Pengindeksan secara langsung biasanya berguna tapi tidak bisa diterapkan secara efisien di dalam daftar tertaut.

Dengan mengetahui persyaratan ini kita bisa menciptakan dasar dari class, kemudian melengkapi method-method ini nanti.

1
public class LinkedList : 
2
	System.Collections.Generic.ICollection
3
{
4
	public void Add(T item)
5
	{
6
		throw new System.NotImplementedException();
7
	}
8
9
	public void Clear()
10
	{
11
		throw new System.NotImplementedException();
12
	}
13
14
	public bool Contains(T item)
15
	{
16
		throw new System.NotImplementedException();
17
	}
18
19
	public void CopyTo(T[] array, int arrayIndex)
20
	{
21
		throw new System.NotImplementedException();
22
	}
23
24
	public int Count
25
	{
26
		get;
27
		private set;
28
	}
29
30
	public bool IsReadOnly
31
	{
32
		get { throw new System.NotImplementedException(); }
33
	}
34
35
	public bool Remove(T item)
36
	{
37
		throw new System.NotImplementedException();
38
	}
39
40
	public System.Collections.Generic.IEnumerator GetEnumerator()
41
	{
42
		throw new System.NotImplementedException();
43
	}
44
45
	System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
46
	{
47
		throw new System.NotImplementedException();
48
	}
49
}

Add

Perilaku
Menambahkan nilai yang disediakan ke akhir dari daftar tertaut.
Performa 0(1)

Menambhkan sebuah item pada sebuah linked list melibatkan tiga langkah:

  1. Alokasikan instans LinkedListNode yang baru.
  2. Temukan node terakhir dari daftar yang ada.
  3. Arahkan properti Next dari node terakhir ke node yang baru.

Intinya adalah mengetahui node mana yang merupakan node terakhir di dalam daftar. Ada dua cara bagi kita untuk mengetahui ini. Yang pertama adalah mengawasi node yang pertama (node "head") dan menelusuri daftar sampai kita menemukan node terakhir. Pendekatan ini tidak mengharuskan kita untuk mengawasi node yang terakhir, yang menyimpan memori sebesar satu rujukan (apapun ukuran dari platform pointer anda), tapi mengharuskan kita untuk menjalani daftar setiap kali node yang baru ditambahkan. Ini akan membuat Add menjadi operasi 0(n).

Pendekatan yang kedua adalah menjejaki node terakhir (node "tail") di dalam daftar dan ketika kita menambahkan node yang baru kita hanya perlu mengakses rujukan yang sudah kita simpan secara langsung. Ini merupakan algoritma 0(1), dan dengan begitu merupakan pendekatan yang lebih disukai.

Hal pertama yang perlu kita lakukan adalah menambahkan dua field private ke class LinkedList: rujukan kepada node yang pertama (kepala) dan node yang terakhir (tail).

1
private LinkedListNode _head;
2
private LinkedListNode _tail;

Kemudian kita perlu menambahkan method yang menjalankan tiga langkah yang tadi.

1
public void Add(T value)
2
{
3
	LinkedListNode node = new LinkedListNode(value);
4
	
5
	if (_head == null)
6
	{
7
		_head = node;
8
		_tail = node;
9
	}
10
	else
11
	{
12
		_tail.Next = node;
13
		_tail = node;
14
	}
15
	
16
	Count++;
17
}

Yang pertama adalah melakukan alokasi instans LinkedListNode yang baru. Kemudian memeriksa apakah daftar itu kosong. Jika daftar itu kosong, node yang baru ditambahkan dengan memasukkan rujukan _head dan _tail ke node yang baru. Node yang baru itu sekarang merupakan node yang pertama dan kedua dalam daftar. Jika daftar itu tidak kosong, node itu ditambahkan ke akhir dari daftar dan rujukan _tail diperbarui untuk merujuk pada akhir yang baru dari daftar.

Properti Count dinaikkan ketika suatu node ditambahkan untuk memastikan properti ICollection<T>. Count mengembalikan nilai yang akurat.

Remove

Perilaku
Menghapus node pertama di daftar yang nilainya sama dengan nilai yang diberikan. Method ini mengembalikan true jika ada nilai yang dihapus. Jika tidak ada maka yang dikembalikan adalah false.
Performa O(n)

Sebelum kita lanjut dengan algoritma Remove, mari kita tinjau apa yang hendak diselesaikannya. Ada empat node di dalam daftar pada daftar berikut. Kita hendak menghapus node yang bernilai tiga.

Daftar tertaut dengan empat nilaiDaftar tertaut dengan empat nilaiDaftar tertaut dengan empat nilai
Daftar tertaut dengan empat nilai

Ketika penghapusan sudah dilakukan, daftar itu akan dimodifikasi sehingga properti Next pada node yang bernilai dua mengharah pada node yang bernilai empat.

Daftar tertaut dengan node 3 terhapusDaftar tertaut dengan node 3 terhapusDaftar tertaut dengan node 3 terhapus
Daftar tertaut dengan node 3 terhapus

Algoritma dasar untuk penghapusan node adalah:

  1. Temukan node yang akan dihapus.
  2. Perbarui properti Next dari node yang mendahului node yang dihapus untuk mengarah pada node setelah node yang dihapus.

Seperti biasanya, yang sulit adalah detailnya. Ada beberapa situasi yang harus kita pertimbangkan ketika menghapus suatu node:

  • Daftar bisa saja kosong, atau nilai yang hendak kita hapus bisa saja tidak ada di dalam daftar. Dalam situasi ini daftar itu tidak akan berubah.
  • Node yang akan dihapus bisa saja merupakan satu-satunya node di dalam daftar. Dalam situasi ini kita hanya perlu mengatur field _head dan _tail menjadi null.
  • Node yang akan dihapus bisa saja merupakan node yang pertama. Dalam situasi ini tidak ada node sebelum node ini, jadi kita perlu memperbarui field _head untuk mengarah pada node head yang baru.
  • Node itu bisa saja berada di tengah daftar.
  • Node itu bisa saja merupakan node terakhir di daftar. Dalam situasi ini kita perbarui field _tail untuk merujuk pada node kedua dari belakang dalam daftar dan mengatur properti Next-nya menjadi null.
1
public bool Remove(T item)
2
{
3
	LinkedListNode previous = null;
4
	LinkedListNode current = _head;
5
	
6
	// 1: Daftar kosong: Jangan lakukan apa-apa.

7
	// 2. Node tunggal: Jadikan nilai sebelumnya null.

8
	// 3: Node banyak:

9
	//		a: Node yang hendak dihapus adalah node pertama.

10
	// 		b: Node yang hendak dihapus ada di tengah atau di akhir.

11
	
12
	while (current != null)
13
	{
14
		if (current.Value.Equals(item))
15
		{
16
			// Ini adalah node di tengah atau di akhir.

17
			if (previous != null)
18
			{
19
				// Situasi 3b.

20
				
21
				// Sebelum: Head -> 3 -> 5 -> null

22
				// Sesudah: Head -> 3 ------> null

23
				previous.Next = current.Next;
24
				
25
				// Node terakhir, jadi perbarui _tail.

26
				if (current.Next == null)
27
				{
28
					_tail = previous;
29
				}
30
			}
31
			else
32
			{
33
				// Situasi 2 atau 3a.

34
				
35
				// Sebelum: Head -> 3 -> 5

36
				// Sesudah: Head ------> 5

37
				
38
				// Head -> 3 -> null

39
				// Head ------> null

40
				_head = _head.Next;
41
				
42
				// Apakah daftarnya sekarang kosong?

43
				if (_head == null)
44
				{
45
					_tail = null;
46
				}
47
			}
48
			Count--;
49
			
50
			return true;
51
		}
52
		
53
		previous = current;
54
		current = current.Next;	
55
	}
56
	
57
	return false;
58
}

Properti Count dikurangi ketika suatu node dihapus untuk memastikan properti ICollection<T>. Count mengembalikan nilai yang akurat.

Contains

Perilaku
Mengembalikan Boolean yang mengindikasikan apakah nilai yang diberikan ada di dalam daftar tertaut.
Performa O(n)

Method Contains cukup sederhana. Dia melihat ke setiap node dalam daftar, dari yang pertama hingga yang terakhir, dan mengembalikan true ketika suatu node yang cocok dengan parameter sudah ditemukan. Jika akhir dari daftar ditemukan dan node tidak ditemukan, method ini mengembalikan false.

1
public bool Contains(T item)
2
{
3
	LinkedListNode current = _head;
4
	while (current != null)
5
	{
6
		if (current.Value.Equals(item))
7
		{
8
			return true;
9
		}
10
		
11
		current = current.Next;
12
	}
13
	
14
	return false;
15
}

GetEnumerator

Perilaku Mengembalikan satu instans dari IEnumerator yang memampukan untuk enumerasi terhadap nilai dari daftar tertaut dari pertama hingga terakhir.
Performa Mengembalikan instans dari enumerator adalah operasi O(1). Melakukan enumerasi terhadap setiap item adalah operasi O(n).

GetEnumerator diterapkan dengan melakukan enumerasi terhadap daftar dari node pertama hingga terakhir dan menggunakan keyword C# yield untuk mengembalikan nilai dari node yang sekarang pada pengguna.

Perhatikan bahwa LinkedList menerapkan perilaku iterasi di dalam versi IEnumerable<T> dari method GetEnumerator dan mengikuti perilaku ini dalam versi IEnumerable.

1
IEnumerator IEnumerable.GetEnumerator()
2
{
3
	LinkedListNode current = _head;
4
	while (current != null)
5
	{
6
		yield return current.Value;
7
		current = current.Next;
8
	}
9
}
10
11
IEnumerator IEnumerable.GetEnumerator()
12
{
13
	return ((IEnumerable)this).GetEnumerator();
14
}

Clear

Perilaku Menghapus semua item dari daftar.
Performa O(1)

Method Clear mengatur field _head dan _tail ke null untuk membersihkan daftar. Karena .NET memiliki garbage collector, node-node itu tidak harus dihapus secara eksplisit. Memastikan bahwa jika node memuat rujukan IDisposable maka harus dibuang bukanlah tanggung jawab dari daftar tertaut tapi dari pengguna.

1
public void Clear()
2
{
3
	_head = null;
4
	_tail = null;
5
	Count = 0;
6
}

CopyTo

Perilaku Mengkopi isi dari daftar tertaut dari awal hingga akhir ke dalam array yang disediakan, dimulai dari index array yang ditentukan.
Performa O(n)

Method CopyTo melakukan iterasi terhadap isi dari daftar dan menggunakan penempatan sederhana untuk mengkopi item ke array. Memastikan array sasaran untuk memiliki ruangan kosong yang cukup untuk memuat semua item dalam daftar merupakan tanggung jawab dari pengguna.

1
public void CopyTo(T[] array, int arrayIndex)
2
{
3
	LinkedListNode current = _head;
4
	while (current != null)
5
	{
6
		array[arrayIndex++] = current.Value;
7
		current = current.Next;
8
	}
9
}

Count

Perilaku Mengembalikan sebuah integer yang mengindikasikan jumlah item yang ada di dalam daftar. Jika daftar itu kosong, maka nilai yang dikembalikan adalah 0.
Performa O(1)

Count hanyalah properti yang diterapkan secara otomatis dengan getter publick dan setter private. Perilaku yang sebenarnya terjadi di dalam method Add, Remove, dan Clear.

1
public int Count
2
{
3
	get;
4
	private set;
5
}

IsReadOnly

Perilaku Mengembalikan false jika daftar itu tidak read-only.
Performa O(1)
1
public bool IsReadOnly
2
{
3
	get { return false; }
4
}

Daftar Tertaut Ganda

Class LinkedList yang baru kita buat dikenal sebagai daftar tertaut tunggal. Ini berarti hanya ada terdapat satu tautan searah di antara satu node dan node berikutnya di dalam daftar. Ada variasi yang umum dari daftar tertaut yang memampukan pengguna untuk mengakses daftar tertaut dari dua arah. Variasi ini dikenal sebagai daftar tertaut ganda.

Untuk menciptakan daftar tertaut ganda pertama kita harus memodifikasi class LinkedListNode kita untuk memiliki satu properti baru yang bernama Previous. Previous berlaku seperti Next tapi mengarah pada node sebelumnya di dalam daftar.

Daftar tertaut ganda yang menggunakan properti node PreviousDaftar tertaut ganda yang menggunakan properti node PreviousDaftar tertaut ganda yang menggunakan properti node Previous
Daftar tertaut ganda yang menggunakan properti node Previous

Seksi berikut akan menjelaskan perubahan dari daftar tertaut tunggal dan daftar tertaut ganda.

Class Node

Satu-satunya perubahan terhadap class LinkedListNode adalah penambahan properti baru yang bernama Previous yang mengarah pada LinkedListNode yang sebelumnya di dalam daftar tertaut, atau mengembalikan null jika merupakan node pertama dalam daftar.

1
public class LinkedListNode
2
{
3
	///

4
	/// Bentuk node baru dengan nilai yang diberikan

5
	///

6
	///

7
	public LinkedListNode(T value)
8
	{
9
		Value = value;
10
	}
11
	
12
	///

13
	/// Nilai dari node.

14
	///

15
	public T Value { get; internal set; }
16
	
17
	///

18
	/// Node berikutnya dalam daftar tertaut (null jika node terakhir)

19
	///

20
	public LinkedListNode Next { get; internal set; }
21
	
22
	///

23
	/// Node sebelumnya dalam linked list (null jika node pertama)

24
	///

25
	public LinkedListNode Previous { get; internal set; }
26
}

Add

Sementara daftar tertaut tunggal hanya menambahkan node ke akhir dari daftar, daftar tertaut ganda bisa menambahkan node ke awal dan akhir dari daftar dengan menggunakan AddFirst dan AddLast. Method ICollection<T>. Add akan mengikuti method AddLast untuk mempertahankan kompatibilitas dengan class List dari daftar tertaut tunggal.

AddFirst

Perilaku Menambahkan nilai yang diberikan ke depan daftar.
Performa O(1)

Menambahkan node ke depan daftar sangat mirip dengan menambahkan node ke daftar tertaut tunggal.

  1. Set properti Next dari node yang baru ke node head yang lama.
  2. Set properti Previous dari node head yang lama ke node yang baru.
  3. Perbarui field _tail (jika diperlukan) dan tambahkan Count.
1
public void AddFirst(T value)
2
{
3
	LinkedListNode node = new LinkedListNode(value);
4
	
5
	// Simpan dulu head node yang lama supaya tidak hilang.

6
	LlinkedListNode temp = _head;
7
	
8
	// Arahkan head ke node yang baru.

9
	_head = node;
10
	
11
	// Masukkan sisa dari daftar ke belakang head.

12
	_head.Next = temp;
13
	
14
	if (Count == 0)
15
	{
16
		// Jika daftar kosong maka head dan tail harus

17
		// merujuk pada node yang baru.

18
		_tail = _head;
19
	}
20
	else
21
	{
22
		// Sebelum: head -------> 5 <-> 7 -> null

23
		// Sesudah: head -> 3 <-> 5 <-> 7 -> null

24
		temp.Previous = head;
25
	}
26
	
27
	Count++;
28
}

AddLast

Perilaku Menambahkan nilai yang diberikan ke akhir dari daftar.
Performa O(1)

Menambahkan node ke akhir dari daftar lebih mudah daripada menambahkan node ke awal daftar.

Node yang baru hanya perlu ditambahkan ke akhir dari daftar, perbarui state dari _tail dan _head sebagaimana diperlukan, dan Count ditambahkan.

1
public void AddLast(T value)
2
{
3
	LinkedListNode node = new LinkedListNode(value);
4
	
5
	if (Count == 0)
6
	{
7
		_head = node;
8
	}
9
	else
10
	{
11
		_tail.Next = node;
12
		
13
		// Sebelum: Head -> 3 <-> 5 -> null

14
		// Sesudah: Head -> 3 <-> 5 <-> 7 -> null

15
		// 7.Previous = 5

16
		node.Previous = _tail;
17
	}
18
	
19
	_tail = node;
20
	Count++;
21
}

Seperti sudah disebutkan sebelumnya, ICollection<T>.Add sekarang hanya akan memanggil AddLast.

1
public void Add(T value)
2
{
3
	AddLast(value);
4
}

Remove

Seperti method Add, method Remove akan dimampukan untuk menghapus node dari awal atau akhir dari daftar. Perubahan pada method ICollection<T>.Remove adalah bisa memperbarui properti Previous yang seharusnya.

RemoveFirst

Perilaku Menghapus nilai pertama dari daftar. Jika daftar itu kosong, tidak ada yang perlu dilakukan. Mengembalikan true jika ada nilai yang dihapus. Jika tidak maka mengembalikan false.
Performa O(1)

RemoveFirst membarui daftar dengan menentukan property head dari daftar tertaut menjadi node kedua dalam daftar itu dan membarui property Previous-nya ke null. Ini menghapus semua rujukan ke node head sebelumnya, menghapusnya dari daftar. Jika daftar itu hanya memuat satu singleton, atau kosong, daftar itu akan menjadi kosong (property head dan tail akan menjadi null).

1
public void RemoveFirst()
2
{
3
  if (Count != 0)
4
  {
5
    // Sebelum: Head -> 3 <-> 5

6
    // Sesudah: Head -------> 5

7
    
8
    // Head -> 3 -> null

9
    // Head -------> null

10
    _head = _head.Next;
11
    
12
    Count--;
13
    
14
    if (Count == 0)
15
    {
16
      _tail = null;
17
    }
18
    else
19
    {
20
      // 5.Previous was 3; now it is null.

21
      _head.Previous = null;
22
    }
23
  }
24
}

RemoveLast

Perilaku Menghapus node terakhir dari daftar. Jika daftar kosong, tidak ada yang dilakukan. Mengembalikan true jika satu nilai dihapus. Jika tidak ada nilai yang dihapus maka yang dikembalikan adalah false.
Performa O(1)

RemoveLast

bekerja dengan cara menetapkan property tail dari daftar menjadi node sebelum node tail yang sekarang. Ini menghapus node terakhir dari daftar. Jika daftar itu kosong atau hanya memiliki satu node, ketika method ini mengembalikan property head dan tail, mereka akan menjadi null.
1
public void RemoveLast()
2
{
3
  if (Count != 0)
4
  {
5
    if (Count == 1)
6
    {
7
      _head = null;
8
      _tail = null;
9
    }
10
    else
11
    {
12
      // Sebelum: Head --> 3 --> 5 --> 7

13
      //          Tail = 7

14
      // Sesudah: Head --> 3 --> 5 --> null

15
      //          Tail = 5

16
      // Null-kan property Next dari 5

17
      tail.Previous.Next = null;
18
    }
19
    
20
    Count--;
21
  }
22
}

Remove

Perilaku Menghapus node pertama dari dalam daftar yang nilainya sama dengan nilai yang diberikan. Method ini mengembalikan true jika ada satu nilai yang dihapus, dan jika tidak ada maka yang dikembalikan adalah false.
Performa O(n)

Method ICollection<T>Remove hampir identik dengan versi yang tertaut tunggal tapi property Previous sekarang diperbarui dalam operasi penghapusan. Untuk menghindari perulangan kode, method ini memanggil RemoveFirst ketika sudah diketahui bahwa node yang akan dihapus adalah node pertama di dalam daftar.

1
public bool Remove(T item)
2
{
3
  LinkedListNode previous = null;
4
  LinkedListNode current = head;l
5
  
6
  // 1: Daftar kosong: Tidak ada yang dilakukan.

7
  // 2: Node tunggal: Null-kan previous.

8
  // 3: Node banyak:

9
  //    a: Node yang akan dihapus adalah node pertama.

10
  //    b: Node yang akan dihapus adalah yang tengah atau yang terakhir.

11
  
12
  while (current != null)
13
  {
14
    // Head -> 3 -> 5 -> 7 -> null

15
    // Head -> 3 ------> 7 -> null

16
    if (Current.Value.Equals(item))
17
    {
18
      // Ini adalah node yang tengah atau yang akhir.

19
      if (previous != null)
20
      {
21
        // Situasi 3b.

22
        previous.Next = current.Next;
23
        
24
        // Merupakan yang ujung, jadi perbarui _tail.

25
        if (current.Next == null)
26
        {
27
          _tail = previous;
28
        }
29
        else
30
        {
31
          // Sebelum: Head -> 3 <-> 5 <-> 7 -> null

32
          // Sesudah: Head -> 3 <-------> 7 -> null

33
          
34
          // previous = 3

35
          // current = 5

36
          // current.Next = 7

37
          // Jadi... 7.Previous = 3

38
          current.Next.Previous = previous;
39
        }
40
        
41
        Count--;
42
      }
43
      else
44
      {
45
        // Situasi 2 atau 3a.

46
        RemoveFirst();
47
      }
48
      
49
      return true;
50
    }
51
    
52
    previous = current;
53
    current = current.Next;
54
  }
55
  
56
  return false;
57
}

Tapi Kenapa?

Kita bisa menambahkan node ke awal dan akhir dari daftar-jadi? Pada posisinya sekaerang, class List yang tertaut ganda ini tidak lebih kuat daripada daftar tertaut tunggal. Tapi hanya dengan satu modifikasi kecil, kita bisa membuka semua perilaku yang memungkinkan. Dengan menjadikan property head dan tail property yang public dan read-only, pengguna daftar tertaut akan bisa menerapkan semua jenis perilaku yang baru.

1
public LinkedListNode Head
2
{
3
  get
4
  {
5
    return _head;
6
  }
7
}
8
9
public LinkedListNode Tail
10
{
11
  get
12
  {
13
    return _tail;
14
  }
15
}

Dengan perubahan sederhana ini kita bisa melakukan enumerasi secara manual terhadap daftar, yang memungkinkan kita untuk melakukan enumerasi dan pencarian terbalik (tail-to-head).

Misalnya, contoh kode berikut menunjukkan cara untuk menggunakan property Tail dan Previous dari daftar untuk mengenumerasi daftar itu secara terbalik dan melakukan pemrosesan pada tiap node.

1
public void ProcessListBackwards()
2
{
3
  LinkedList list = new LinkedList();
4
  PopulateList(list);
5
  
6
  LinkedListNode current = list.Tail;
7
  while (current != null)
8
  {
9
    ProcessNode(current);
10
    current = current.Previous;
11
  }
12
}

Sebagai tambahan, class List yang tertaut ganda juga memampukan kita untuk menciptakan class Deque, yang sendirinya merupakan dasar untuk membangun class lainnya. Kita akan mendiskusikan class ini nanti di bagian lain.

Selanjutnya

Ini menyelesaikan bagian kedua tentang daftar tertaut. Selanjutnya, kita akan membahas daftar array.