Algoritme dan struktur data di Java, Bagian 5: Daftar yang memiliki kaitan ganda

Meskipun daftar tertaut tunggal memiliki banyak kegunaan, daftar tersebut juga memiliki beberapa batasan. Untuk satu hal, daftar tertaut tunggal membatasi traversal node ke satu arah: Anda tidak dapat melintasi daftar tertaut tunggal ke belakang kecuali Anda terlebih dahulu membalikkan tautan node-nya, yang membutuhkan waktu. Jika Anda melakukan traversal terbalik dan perlu memulihkan node-traversal ke arah semula, Anda harus mengulangi inversi, yang membutuhkan lebih banyak waktu. Daftar tertaut tunggal juga membatasi penghapusan node. Dalam jenis daftar ini, Anda tidak dapat menghapus node arbitrer tanpa akses ke node sebelumnya.

Untungnya, Java menawarkan beberapa jenis daftar yang dapat Anda gunakan untuk mencari dan mengurutkan data yang disimpan di program Java Anda. Tutorial terakhir dalam seri Struktur dan algoritma data ini memperkenalkan pencarian dan pengurutan dengan daftar tertaut ganda dan daftar tertaut melingkar. Seperti yang akan Anda lihat, kedua kategori struktur data ini dibangun di atas daftar tertaut tunggal untuk menawarkan perilaku pencarian dan penyortiran yang lebih luas dalam program Java Anda.

Daftar yang ditautkan ganda

Sebuah doubly-linked list adalah daftar link dari node di mana setiap node memiliki sepasang bidang hubungan. Satu bidang tautan memungkinkan Anda melintasi daftar ke arah depan, sedangkan simpul lainnya memungkinkan Anda melintasi daftar ke arah belakang. Untuk arah maju, variabel referensi menyimpan referensi ke node pertama. Setiap node terhubung ke node berikutnya melalui kolom link "berikutnya", kecuali node terakhir, yang kolom link "berikutnya" -nya berisi referensi null untuk menandai akhir daftar (dalam arah maju). Arah mundur bekerja dengan cara yang sama. Variabel referensi menyimpan referensi ke node terakhir arah maju, yang Anda interpretasikan sebagai node pertama. Setiap node terhubung ke node sebelumnya melalui kolom link "sebelumnya". Node pertama "sebelumnya"bidang tautan berisi nol untuk menandakan akhir daftar.

Coba pikirkan daftar tertaut ganda sebagai sepasang daftar tertaut tunggal, masing-masing menghubungkan node yang sama. Diagram pada Gambar1 menunjukkan topForwarddaftar topBackwardtertaut tunggal yang direferensikan dan direferensikan.

Operasi CRUD dalam daftar tertaut ganda

Membuat, menyisipkan, dan menghapus node adalah operasi umum dalam daftar tertaut ganda. Mereka mirip dengan operasi yang Anda pelajari untuk daftar tertaut tunggal. (Ingat bahwa daftar tertaut ganda hanyalah sepasang daftar tertaut tunggal yang menghubungkan node yang sama.) Pseudocode berikut menunjukkan pembuatan dan penyisipan node ke dalam daftar tertaut ganda yang ditunjukkan pada Gambar 1. Pseudocode juga menunjukkan node penghapusan:

 DECLARE CLASS Node DECLARE STRING name DECLARE Node next DECLARE Node prev END DECLARE DECLARE Node topForward DECLARE Node temp DECLARE Node topBackward topForward = NEW Node topForward.name = "A" temp = NEW Node temp.name = "B" topBackward = NEW Node topBackward.name = "C" // Create forward singly-linked list topForward.next = temp temp.next = topBackward topBackward.next = NULL // Create backward singly-linked list topBackward.prev = temp temp.prev = topForward topForward.prev = NULL // Delete Node B. temp.prev.next = temp.next; // Bypass Node B in the forward singly-linked list. temp.next.prev = temp.prev; // Bypass Node B in the backward singly-linked list. END 

Contoh penerapan: CRUD dalam daftar tertaut ganda

Contoh aplikasi Java DLLDemomenunjukkan cara membuat, menyisipkan, dan menghapus node dalam daftar tertaut ganda. Kode sumber aplikasi ditunjukkan pada Daftar 1.

Kode 1. Sebuah aplikasi Java yang mendemonstrasikan CRUD dalam daftar tertaut ganda

 public final class DLLDemo { private static class Node { String name; Node next; Node prev; } public static void main(String[] args) { // Build a doubly-linked list. Node topForward = new Node(); topForward.name = "A"; Node temp = new Node(); temp.name = "B"; Node topBackward = new Node(); topBackward.name = "C"; topForward.next = temp; temp.next = topBackward; topBackward.next = null; topBackward.prev = temp; temp.prev = topForward; topForward.prev = null; // Dump forward singly-linked list. System.out.print("Forward singly-linked list: "); temp = topForward; while (temp != null) { System.out.print(temp.name); temp = temp.next; } System.out.println(); // Dump backward singly-linked list. System.out.print("Backward singly-linked list: "); temp = topBackward; while (temp != null) { System.out.print(temp.name); temp = temp.prev; } System.out.println(); // Reference node B. temp = topForward.next; // Delete node B. temp.prev.next = temp.next; temp.next.prev = temp.prev; // Dump forward singly-linked list. System.out.print("Forward singly-linked list (after deletion): "); temp = topForward; while (temp != null) { System.out.print(temp.name); temp = temp.next; } System.out.println(); // Dump backward singly-linked list. System.out.print("Backward singly-linked list (after deletion): "); temp = topBackward; while (temp != null) { System.out.print(temp.name); temp = temp.prev; } System.out.println(); } } 

Susun Daftar 4 sebagai berikut:

 javac DLLDemo.java 

Jalankan aplikasi yang dihasilkan sebagai berikut:

 java DLLDemo 

Anda harus mengamati keluaran berikut:

 Forward singly-linked list: ABC Backward singly-linked list: CBA Forward singly-linked list (after deletion): AC Backward singly-linked list (after deletion): CA 

Mengacak dalam daftar tertaut ganda

Java Collections Framework menyertakan Collectionskelas metode utilitas, yang merupakan bagian dari java.utilpaket. Kelas ini menyertakan void shuffle(List list)metode yang " secara acak mengizinkan daftar yang ditentukan menggunakan sumber default keacakan ." Misalnya, Anda mungkin menggunakan metode ini untuk mengacak setumpuk kartu yang dinyatakan sebagai daftar tertaut ganda ( java.util.LinkedListkelas adalah contohnya). Dalam pseudocode di bawah ini, Anda dapat melihat bagaimana algoritme Acak dapat mengacak daftar tertaut ganda:

 DECLARE RANDOM rnd = new RANDOM DECLARE INTEGER i FOR i = 3 DOWNTO 2 swap(topForward, i - 1, rnd.nextInt(i)) END FOR FUNCTION swap(Node top, int i, int j) DECLARE Node nodei, nodej DECLARE INTEGER k // Locate ith node. Node nodei = top FOR k = 0 TO i - 1 nodei = nodei.next END FOR // Locate jth node. Node nodej = top FOR k = 0 TO i - 1 nodej = nodej.next END FOR // Perform the swap. DECLARE STRING namei = nodei.name DECLARE STRING namej = nodej.name nodej.name = namei nodei.name = namej END FUNCTION END 

Algoritme Acak memperoleh sumber keacakan dan kemudian menelusuri daftar secara mundur, dari simpul terakhir hingga yang kedua. Ini berulang kali menukar node yang dipilih secara acak (yang sebenarnya hanya bidang nama) ke "posisi saat ini." Node dipilih secara acak dari bagian daftar yang berjalan dari node pertama ke posisi saat ini, inklusif. Perhatikan bahwa algoritma ini secara kasar dikutip dari void shuffle(List list)kode sumber.

Pseudocode algoritme Shuffle malas karena hanya berfokus pada daftar tertaut tunggal yang traverse maju. Ini adalah keputusan desain yang masuk akal, tetapi kami membayar harga untuk itu dalam kerumitan waktu. Kompleksitas waktu adalah O ( n 2). Pertama, kita memiliki loop O ( n ) yang memanggil swap(). Kedua, di dalam swap(), kita memiliki dua loop O ( n ) berurutan . Ingat aturan berikut dari Bagian 1:

 If f1(n) = O(g(n)) and f2(n) = O(h(n)) then (a) f1(n)+f2(n) = max(O(g(n)), O(h(n))) (b) f1(n)*f2(n) = O(g(n)*h(n)). 

Bagian (a) membahas algoritma sekuensial. Di sini, kami memiliki dua loop O ( n ). Berdasarkan aturan tersebut, kompleksitas waktu yang dihasilkan adalah O ( n ). Bagian (b) berhubungan dengan algoritma bersarang. Dalam hal ini, kita memiliki O ( n ) dikalikan dengan O ( n ), menghasilkan O ( n 2).

Note that Shuffle's space complexity is O(1), resulting from the helper variables that are declared.

Example application: Shuffling in a doubly-linked list

The Shuffle application in Listing 2 is a demonstration of the Shuffle algorithm.

Listing 2. The Shuffle algorithm in Java

 import java.util.Random; public final class Shuffle { private static class Node { String name; Node next; Node prev; } public static void main(String[] args) { // Build a doubly-linked list. Node topForward = new Node(); topForward.name = "A"; Node temp = new Node(); temp.name = "B"; Node topBackward = new Node(); topBackward.name = "C"; topForward.next = temp; temp.next = topBackward; topBackward.next = null; topBackward.prev = temp; temp.prev = topForward; topForward.prev = null; // Dump forward singly-linked list. System.out.print("Forward singly-linked list: "); temp = topForward; while (temp != null) { System.out.print(temp.name); temp = temp.next; } System.out.println(); // Dump backward singly-linked list. System.out.print("Backward singly-linked list: "); temp = topBackward; while (temp != null) { System.out.print(temp.name); temp = temp.prev; } System.out.println(); // Shuffle list. Random rnd = new Random(); for (int i = 3; i > 1; i--) swap(topForward, i - 1, rnd.nextInt(i)); // Dump forward singly-linked list. System.out.print("Forward singly-linked list: "); temp = topForward; while (temp != null) { System.out.print(temp.name); temp = temp.next; } System.out.println(); // Dump backward singly-linked list. System.out.print("Backward singly-linked list: "); temp = topBackward; while (temp != null) { System.out.print(temp.name); temp = temp.prev; } System.out.println(); } public static void swap(Node top, int i, int j) { // Locate ith node. Node nodei = top; for (int k = 0; k < i; k++) nodei = nodei.next; // Locate jth node. Node nodej = top; for (int k = 0; k < j; k++) nodej = nodej.next; String namei = nodei.name; String namej = nodej.name; nodej.name = namei; nodei.name = namej; } } 

Compile Listing 5 as follows:

 javac Shuffle.java 

Run the resulting application as follows:

 java Shuffle 

You should observe the following output from one run:

 Forward singly-linked list: ABC Backward singly-linked list: CBA Forward singly-linked list: BAC Backward singly-linked list: CAB 

Circular linked lists

The link field in the last node of a singly-linked list contains a null link. This is also true in a doubly-linked list, which contains the link fields in the last nodes of the forward and backward singly-linked lists. Suppose, instead, that the last nodes contained links to the first nodes. In this situation, you would end up with a circular-linked list, which is shown in Figure 2.

Circular-linked lists, also known as circular buffers or circular queues, have many uses. For example, they're used by operating system interrupt handlers to buffer keystrokes. Multimedia applications use circular-linked lists to buffer data (for example, buffering data being written to a sound card). This technique is also used by the LZ77 family of lossless data compression algorithms.

Linked lists versus arrays

Throughout this series on data structures and algorithms, we've considered the strengths and weaknesses of different data structures. Since we've focused on arrays and linked lists, you might have questions about these types specifically. What advantages and disadvantages do linked lists and arrays offer? When do you use a linked list and when do you use an array? Can data structures from both categories be integrated into a useful hybrid data structure? I'll try to answer these questions below.

Linked lists offer the following advantages over arrays:

  • They don't require extra memory to support expansion. In contrast, arrays require extra memory when expansion is necessary. (Once all elements contain data items, no new data items can be appended to an array.)
  • Mereka menawarkan penyisipan / penghapusan node yang lebih cepat daripada operasi berbasis array yang setara. Hanya tautan yang perlu diperbarui setelah mengidentifikasi posisi sisipkan / hapus. Dari perspektif larik, penyisipan item data memerlukan pergerakan semua item data lainnya untuk membuat elemen kosong. Demikian pula, penghapusan item data yang sudah ada memerlukan pergerakan semua item data lainnya untuk menghapus elemen kosong. Semua perpindahan item data membutuhkan waktu.

Sebaliknya, array menawarkan keuntungan berikut dibandingkan daftar tertaut:

  • Elemen array menempati lebih sedikit memori daripada node karena elemen tidak memerlukan bidang tautan.
  • Array menawarkan akses yang lebih cepat ke item data, melalui indeks berbasis integer.