1. Pengenalan
Dalam tutorial ini, kita akan melihat pelaksanaan senarai berangkai pekeliling di Java.
2. Senarai Berkaitan Pekeliling
Senarai terpaut bulat adalah variasi senarai terpaut di mana node terakhir menunjuk ke nod pertama, melengkapkan bulatan penuh nod . Dengan kata lain, variasi senarai terpaut ini tidak mempunyai unsur nol pada akhirnya.
Dengan perubahan sederhana ini, kami mendapat beberapa faedah:
- Mana-mana nod dalam senarai terpaut bulat boleh menjadi titik permulaan
- Akibatnya, keseluruhan senarai dapat dilalui bermula dari sebarang nod
- Oleh kerana simpul terakhir senarai terpaut bulat mempunyai penunjuk ke simpul pertama, mudah untuk melakukan operasi enqueue dan dequeue
Secara keseluruhan, ini sangat berguna dalam pelaksanaan struktur data antrian.
Dari segi prestasi, ia sama dengan pelaksanaan senarai terpaut yang lain kecuali satu perkara: Melintasi dari simpul terakhir ke nod kepala dapat dilakukan dalam masa yang tetap . Dengan senarai terpaut konvensional, ini adalah operasi linear.
3. Pelaksanaan di Jawa
Mari mulakan dengan membuat kelas Node tambahan yang akan menyimpan nilai int dan penunjuk ke nod seterusnya :
class Node { int value; Node nextNode; public Node(int value) { this.value = value; } }
Sekarang mari buat nod pertama dan terakhir dalam senarai terpaut bulat, biasanya disebut kepala dan ekor:
public class CircularLinkedList { private Node head = null; private Node tail = null; // .... }
Pada subseksyen berikutnya, kita akan melihat operasi yang paling biasa yang boleh kita lakukan pada senarai yang berkaitan dengan pekeliling.
3.1. Memasukkan Elemen
Operasi pertama yang akan kita lindungi adalah memasukkan nod baru. Semasa memasukkan elemen baru, kita perlu menangani dua kes:
- The kepala nod adalah batal , yang tidak ada unsur-unsur telah ditambah. Dalam kes ini, kita akan membuat simpul baru yang kita tambahkan sebagai kepala dan ekor senarai kerana hanya ada satu simpul
- The kepala nod tidak null , iaitu, terdapat satu atau lebih unsur-unsur telah dimasukkan dalam senarai tersebut. Dalam kes ini, ekor yang ada harus menunjuk ke simpul baru dan simpul yang baru ditambahkan akan menjadi ekor
Dalam kedua kes di atas, NextNode untuk ekor akan menunjuk ke kepala
Mari buat kaedah addNode yang mengambil nilai untuk dimasukkan sebagai parameter:
public void addNode(int value) { Node newNode = new Node(value); if (head == null) { head = newNode; } else { tail.nextNode = newNode; } tail = newNode; tail.nextNode = head; }
Sekarang kita boleh menambahkan beberapa nombor ke senarai terpaut pekeliling kami:
private CircularLinkedList createCircularLinkedList() { CircularLinkedList cll = new CircularLinkedList(); cll.addNode(13); cll.addNode(7); cll.addNode(24); cll.addNode(1); cll.addNode(8); cll.addNode(37); cll.addNode(46); return cll; }
3.2. Mencari Elemen
Operasi seterusnya yang akan kita lihat adalah mencari untuk menentukan apakah ada unsur dalam senarai.
Untuk ini, kami akan membetulkan simpul dalam senarai (biasanya kepala ) sebagai currentNode dan melintasi keseluruhan senarai menggunakan NextNode simpul ini , sehingga kami menemui elemen yang diperlukan.
Mari kita menambah satu kaedah baru containsNode yang mengambil searchValue sebagai parameter:
public boolean containsNode(int searchValue) { Node currentNode = head; if (head == null) { return false; } else { do { if (currentNode.value == searchValue) { return true; } currentNode = currentNode.nextNode; } while (currentNode != head); return false; } }
Sekarang, mari kita tambahkan beberapa ujian untuk mengesahkan bahawa senarai yang dibuat di atas mengandungi elemen yang kita tambahkan dan tidak ada yang baru:
@Test public void givenACircularLinkedList_WhenAddingElements_ThenListContainsThoseElements() { CircularLinkedList cll = createCircularLinkedList(); assertTrue(cll.containsNode(8)); assertTrue(cll.containsNode(37)); } @Test public void givenACircularLinkedList_WhenLookingForNonExistingElement_ThenReturnsFalse() { CircularLinkedList cll = createCircularLinkedList(); assertFalse(cll.containsNode(11)); }
3.3. Memadamkan Elemen
Seterusnya, kita akan melihat operasi hapus. Sama dengan penyisipan, kita mempunyai beberapa kes (tidak termasuk kes di mana senarai itu sendiri kosong) yang perlu kita perhatikan.
- Elemen untuk dihapuskan adalah kepala itu sendiri . Dalam kes ini, kita perlu mengemas kini kepala sebagai simpul kepala semasa yang seterusnya , dan simpul ekor seterusnya sebagai kepala baru
- Elemen untuk dihapuskan adalah unsur lain selain kepala . Dalam kes ini, kita hanya perlu mengemas kini simpul simpul sebelumnya sebagai simpul simpul seterusnya yang perlu dihapuskan
Kami sekarang akan menambahkan kaedah baru deleteNode yang mengambil nilaiToDelete sebagai parameter:
public void deleteNode(int valueToDelete) { Node currentNode = head; if (head != null) { if (currentNode.value == valueToDelete) { head = head.nextNode; tail.nextNode = head; } else { do { Node nextNode = currentNode.nextNode; if (nextNode.value == valueToDelete) { currentNode.nextNode = nextNode.nextNode; break; } currentNode = currentNode.nextNode; } while (currentNode != head); } } }
Mari sekarang tambahkan ujian mudah untuk mengesahkan bahawa penghapusan berfungsi seperti yang diharapkan untuk semua kes:
@Test public void givenACircularLinkedList_WhenDeletingElements_ThenListDoesNotContainThoseElements() { CircularLinkedList cll = createCircularLinkedList(); assertTrue(cll.containsNode(13)); cll.deleteNode(13); assertFalse(cll.containsNode(13)); assertTrue(cll.containsNode(1)); cll.deleteNode(1); assertFalse(cll.containsNode(1)); assertTrue(cll.containsNode(46)); cll.deleteNode(46); assertFalse(cll.containsNode(46)); }
3.4. Melintasi Senarai
Kami akan melihat rentetan senarai pautan pekeliling kami di bahagian akhir ini . Sama seperti operasi carian dan hapus, untuk melintasi kita menetapkan currentNode sebagai kepala dan melintasi seluruh senarai menggunakan NextNode nod ini.
Mari tambah kaedah traverseList baru yang mencetak elemen yang ditambahkan ke senarai:
public void traverseList() { Node currentNode = head; if (head != null) { do { LOGGER.info(currentNode.value + " "); currentNode = currentNode.nextNode; } while (currentNode != head); } }
Seperti yang dapat kita lihat, dalam contoh di atas, semasa melintasi, kita hanya mencetak nilai setiap node, sehingga kita kembali ke node kepala.
4. Kesimpulan
Dalam tutorial ini, kami telah melihat bagaimana menerapkan senarai berangkai pekeliling di Java dan meneroka beberapa operasi yang paling biasa.
Pertama, kami mengetahui apa sebenarnya senarai terpaut bulat termasuk beberapa ciri dan perbezaan yang paling biasa dengan senarai pautan konvensional. Kemudian, kami melihat cara memasukkan, mencari, menghapus dan melintasi item dalam pelaksanaan senarai terpaut pekeliling kami.
Seperti biasa, semua contoh yang digunakan dalam artikel ini terdapat di GitHub.