Panduan untuk Pokok AVL di Jawa

1. Pengenalan

Dalam tutorial ini, kami akan memperkenalkan Pokok AVL dan kami akan melihat algoritma untuk memasukkan, menghapus, dan mencari nilai.

2. Apa itu Pokok AVL?

Pokok AVL, dinamakan sempena penciptanya Adelson-Velsky dan Landis, adalah pokok carian binari yang mengimbangkan diri (BST).

Pokok pengimbangan diri adalah pokok carian binari yang menyeimbangkan ketinggian selepas penyisipan dan penghapusan mengikut beberapa peraturan pengimbangan.

Kerumitan masa terburuk BST adalah fungsi ketinggian pokok. Khususnya, jalan terpanjang dari akar pokok ke simpul. Untuk nod BST dengan N, katakan bahawa setiap nod hanya mempunyai nol atau satu anak. Oleh itu ketinggiannya sama dengan N, dan masa pencarian dalam keadaan terburuk adalah O (N). Oleh itu, matlamat utama kami dalam BST adalah memastikan ketinggian maksimum hampir dengan log (N).

Faktor keseimbangan nod N adalah tinggi (kanan (N)) - tinggi (kiri (N)) . Dalam Pokok AVL, faktor keseimbangan nod hanya boleh menjadi satu daripada 1, 0, atau -1 nilai.

Mari tentukan objek Node untuk pokok kami:

public class Node { int key; int height; Node left; Node right; ... }

Seterusnya, mari kita tentukan AVLTree :

public class AVLTree { private Node root; void updateHeight(Node n) { n.height = 1 + Math.max(height(n.left), height(n.right)); } int height(Node n) { return n == null ? -1 : n.height; } int getBalance(Node n) { return (n == null) ? 0 : height(n.right) - height(n.left); } ... }

3. Bagaimana Mengimbangkan Pokok AVL?

Pokok AVL memeriksa faktor keseimbangan nodnya selepas penyisipan atau penghapusan nod. Sekiranya faktor keseimbangan nod lebih besar daripada satu atau kurang daripada -1, pokok itu mengimbangi semula dirinya.

Terdapat dua operasi untuk mengimbangkan semula pokok:

  • putaran kanan dan
  • putaran kiri.

3.1. Putaran Kanan

Mari mulakan dengan putaran yang betul.

Anggaplah kita mempunyai BST yang disebut T1, dengan Y sebagai simpul akar, X sebagai anak kiri Y, dan Z sebagai anak kanan X. Memandangkan ciri-ciri BST, kita tahu bahawa X <Z <Y.

Setelah putaran kanan Y, kami mempunyai sebatang pokok bernama T2 dengan X sebagai akar dan Y sebagai anak kanan X dan Z sebagai anak kiri Y. T2 masih BST kerana menjaga urutan X <Z <Y .

Mari lihat operasi putaran yang betul untuk AVLTree kami :

Node rotateRight(Node y) { Node x = y.left; Node z = x.right; x.right = y; y.left = z; updateHeight(y); updateHeight(x); return x; }

3.2. Operasi Putaran Kiri

Kami juga mempunyai operasi putaran kiri.

Anggaplah BST yang disebut T1, dengan Y sebagai simpul akar, X sebagai anak kanan Y, dan Z sebagai anak kiri X. Dengan ini, kita tahu bahawa Y <Z <X.

Selepas putaran kiri Y, kami mempunyai sebatang pokok bernama T2 dengan X sebagai akar dan Y sebagai anak kiri X dan Z sebagai anak kanan Y. T2 masih merupakan BST kerana menjaga urutan Y <Z <X .

Mari lihat operasi putaran kiri untuk AVLTree kami :

Node rotateLeft(Node y) { Node x = y.right; Node z = x.left; x.left = y; y.right = z; updateHeight(y); updateHeight(x); return x; }

3.3. Teknik Mengimbangi Semula

Kita boleh menggunakan operasi putaran kanan dan putaran kiri dalam kombinasi yang lebih kompleks untuk memastikan AVL Tree seimbang setelah ada perubahan pada nodnya . Dalam struktur yang tidak seimbang, sekurang-kurangnya satu simpul mempunyai faktor keseimbangan sama dengan 2 atau -2. Mari lihat bagaimana kita dapat mengimbangkan pokok dalam keadaan seperti ini.

Apabila faktor keseimbangan nod Z adalah 2, subtree dengan Z sebagai akar berada di salah satu daripada dua keadaan ini, menganggap Y sebagai anak Z yang tepat.

Untuk kes pertama, ketinggian anak kanan Y (X) lebih besar daripada ketinggian anak kiri (T2). Kita dapat mengimbangi semula pokok dengan mudah dengan putaran kiri Z.

Untuk kes kedua, ketinggian anak kanan Y (T4) kurang daripada ketinggian anak kiri (X). Keadaan ini memerlukan gabungan operasi putaran.

Dalam kes ini, pertama-tama kita memutar Y ke kanan, sehingga pokoknya sama bentuknya dengan casing sebelumnya. Kemudian kita dapat mengimbangkan semula pokok dengan putaran kiri Z.

Juga, apabila faktor keseimbangan nod Z adalah -2, subtree berada di salah satu daripada dua keadaan ini, jadi kita menganggap Z sebagai akar dan Y sebagai anak kirinya.

Ketinggian anak kiri Y lebih besar daripada anak kanannya, jadi kita mengimbangkan pokok dengan putaran kanan Z.

Atau dalam kes kedua, anak kanan Y mempunyai ketinggian yang lebih besar daripada anak kirinya.

Jadi, pertama sekali, kita mengubahnya menjadi bentuk sebelumnya dengan putaran kiri Y, kemudian kita mengimbangkan pokok dengan putaran kanan Z.

Mari lihat operasi pengimbangan semula untuk AVLTree kami :

Node rebalance(Node z) { updateHeight(z); int balance = getBalance(z); if (balance > 1) { if (height(z.right.right) > height(z.right.left)) { z = rotateLeft(z); } else { z.right = rotateRight(z.right); z = rotateLeft(z); } } else if (balance  height(z.left.right)) z = rotateRight(z); else { z.left = rotateLeft(z.left); z = rotateRight(z); } } return z; }

Kami akan menggunakan keseimbangan semula setelah memasukkan atau menghapus nod untuk semua node di jalan dari nod yang diubah ke root.

4. Masukkan Node

Apabila kita akan memasukkan kunci di pokok, kita mesti mencari kedudukannya yang tepat untuk melepasi peraturan BST. Oleh itu, kita mulakan dari akar dan membandingkan nilainya dengan kunci baru. Sekiranya kuncinya lebih besar, kita terus ke kanan - jika tidak, kita pergi ke anak kiri.

Setelah kita menemui simpul induk yang betul, maka kita menambah kunci baru sebagai simpul ke kiri atau kanan, bergantung pada nilainya.

Setelah memasukkan node, kami mempunyai BST, tetapi mungkin bukan Pohon AVL. Oleh itu, kami memeriksa faktor keseimbangan dan mengimbangi semula BST untuk semua nod di jalan dari simpul baru ke akar.

Mari lihat operasi sisipan:

Node insert(Node node, int key) { if (node == null) { return new Node(key); } else if (node.key > key) { node.left = insert(node.left, key); } else if (node.key < key) { node.right = insert(node.right, key); } else { throw new RuntimeException("duplicate Key!"); } return rebalance(node); }

Penting untuk diingat bahawa kunci unik di pokok - tidak ada dua nod yang mempunyai kunci yang sama.

Kerumitan masa algoritma sisipan adalah fungsi ketinggian. Oleh kerana pokok kita seimbang, kita dapat menganggap bahawa kerumitan masa dalam keadaan terburuk adalah O (log (N)).

5. Padamkan Node

Untuk memadam kunci dari pokok, pertama-tama kita mesti mencarinya di BST.

Setelah kita menemui simpul (disebut Z), kita harus memperkenalkan calon baru untuk menjadi penggantinya di pokok. Sekiranya Z adalah daun, calon kosong. Sekiranya Z hanya mempunyai satu anak, anak ini adalah calon, tetapi jika Z mempunyai dua anak, prosesnya sedikit lebih rumit.

Assume the right child of Z called Y. First, we find the leftmost node of the Y and call it X. Then, we set the new value of Z equal to X ‘s value and continue to delete X from Y.

Finally, we call the rebalance method at the end to keep the BST an AVL Tree.

Here is our delete method:

Node delete(Node node, int key) { if (node == null) { return node; } else if (node.key > key) { node.left = delete(node.left, key); } else if (node.key < key) { node.right = delete(node.right, key); } else { if (node.left == null || node.right == null) { node = (node.left == null) ? node.right : node.left; } else { Node mostLeftChild = mostLeftChild(node.right); node.key = mostLeftChild.key; node.right = delete(node.right, node.key); } } if (node != null) { node = rebalance(node); } return node; }

The time complexity of the delete algorithm is a function of the height of the tree. Similar to the insert method, we can assume that time complexity in the worst case is O(log(N)).

6. Search for a Node

Searching for a node in an AVL Tree is the same as with any BST.

Start from the root of the tree and compare the key with the value of the node. If the key equals the value, return the node. If the key is greater, search from the right child, otherwise continue the search from the left child.

Kerumitan masa pencarian adalah fungsi ketinggian. Kita boleh menganggap bahawa kerumitan masa dalam keadaan terburuk adalah O (log (N)).

Mari lihat contoh kod:

Node find(int key) { Node current = root; while (current != null) { if (current.key == key) { break; } current = current.key < key ? current.right : current.left; } return current; }

7. Kesimpulannya

Dalam tutorial ini, kami telah melaksanakan AVL Tree dengan operasi memasukkan, menghapus, dan mencari.

Seperti biasa, anda boleh mendapatkan kod di Github.