大二那会根本没蹋下心来看,觉得天书一般,连旋转都没搞明白。
今天仔细看了书,发现真的一点不难啊,鄙视自己……
首先是概念:
平衡二叉树是为了解决前面二叉排序树不均衡的问题,而加入了一种平衡机制。所以,平衡二叉树是一种特殊的二叉排序树(BST)!
AVL树查找的平均和最差复杂度都是O(logn) !!!(BST的最坏是O(n))
AVL树的插入复杂度是O(logn)。
平衡二叉树(简称AVL树):对任意一个结点,它的左子树和又子树都是平衡二叉树(左子树都小于结点,右子树都大于结点),且左子树深度和右子树深度不超过1。
书上那个平衡因子的概念只是为了辅助书上的实现方法,没有太大意义。
一般是在AVL树的构造(插入)阶段,做树的平衡处理(左、右旋),使得它称为一个平衡二叉树。
可以证明,AVL树的深度是logN。
中序遍历AVL树(其实这是二叉排序树),可以从小到大还原出一个序列。
下面的图摘自董师兄的博客《数据结构之AVL树》
一般,我们在插入时会遇到4种不平衡情况:
(1)Insert在左子树的左子树,此时需要根右旋转。
(2)Insert在左子树的右子树,此时先要对左子树左旋转,然后对根右旋转。
(3)Insert在右子树的右子树,此时需要根左旋转。
(4)Insert在右子树的左子树,此时先要对右子树右旋转,然后对根左旋转。
我们的实现中,没有用平衡因子,而是直接记录每个结点子树的深度,如下:
public class AVLNode { public AVLNode(int data) { this.data = data; } public int data; public AVLNode left = null; public AVLNode right = null; public int depth = 0; }
为了方便处理null结点的深度,我们定义如下深度函数:
当空结点为-1时,它的父亲结点+1正好是0。
public static int Depth(AVLNode node) { if (node == null) { return -1; } else { return node.depth; } }
然后是左旋操作:
特别注意:更新结点时要先更新下面的(原tree结点),再更新新的mid。
返回要返回mid。
public static AVLNode RotateLeft(AVLNode tree) { // Rotate right AVLNode mid = tree.right; tree.right = mid.left; mid.left = tree; // Update depth (Notify the sequence) tree.depth = Math.max(Depth(tree.left), Depth(tree.right)) + 1; mid.depth = Math.max(Depth(mid.left), Depth(mid.right)) + 1; return mid; }
同理,右旋操作:
public static AVLNode RotateRight(AVLNode tree) { // Rotate left AVLNode mid = tree.left; tree.left = mid.right; mid.right = tree; // Update depth (Notify the sequence) tree.depth = Math.max(Depth(tree.left), Depth(tree.right)) + 1; mid.depth = Math.max(Depth(mid.left), Depth(mid.right)) + 1; return mid; }
好了,下面是插入的代码:
特别注意,Java中的引用实际是C++中的指针,即对函数传入的“临时变量”修改,将不会影响外围原变量!
因此,我们每次需要返回这个树引用。调用时也应该是
tree = Insert(tree, data);
public static AVLNode Insert(AVLNode tree, int data) { if (tree == null) { tree = new AVLNode(data); } else { if (data < tree.data) { // Insert at left child tree.left = Insert(tree.left, data); if (Depth(tree.left) - Depth(tree.right) == 2) { // Need balance if (data < tree.left.data) { // Insert at left child's left child // Left-Rotate tree = RotateRight(tree); } else if (data > tree.left.data) { // Insert at left child's right child // First Left-rotate tree's left child // Then Right-rotate tree tree.left = RotateLeft(tree.left); tree = RotateRight(tree); } } } else if (data > tree.data) { // Insert at right child tree.right = Insert(tree.right, data); if (Depth(tree.left) - Depth(tree.right) == -2) { if (data > tree.right.data) { // Insert at right child's right // Left-Rotate tree = RotateLeft(tree); } else if (data < tree.right.data) { // Insert at right child's left // First Right-rotate on tree's right child tree.right = RotateRight(tree.right); // Then Left-rotate tree tree = RotateLeft(tree); } } } } // Update depth tree.depth = Math.max(Depth(tree.left), Depth(tree.right)) + 1; return tree; }
查找和排序二叉树一样的,如下:
public static boolean Find(AVLNode tree, int key) { AVLNode ptr = tree; while (true) { if (ptr == null) { break; } if (key == ptr.data) { return true; } else if (key < ptr.data) { ptr = ptr.left; } else { ptr = ptr.right; } } return false; }
好了,上面的版本有点土,用了Java还用static,下面这个版本是将AVLNode封装在了类内。调用时候不用在保存更新的AVLNode了,内部处理了,如下:
import java.util.Random; public class AVLTree { private int Depth(AVLNode node) { if (node == null) { return -1; } else { return node.depth; } } private AVLNode RotateRight(AVLNode tree) { // Rotate left AVLNode mid = tree.left; tree.left = mid.right; mid.right = tree; // Update depth (Notify the sequence) tree.depth = Math.max(Depth(tree.left), Depth(tree.right)) + 1; mid.depth = Math.max(Depth(mid.left), Depth(mid.right)) + 1; return mid; } private AVLNode RotateLeft(AVLNode tree) { // Rotate right AVLNode mid = tree.right; tree.right = mid.left; mid.left = tree; // Update depth (Notify the sequence) tree.depth = Math.max(Depth(tree.left), Depth(tree.right)) + 1; mid.depth = Math.max(Depth(mid.left), Depth(mid.right)) + 1; return mid; } private AVLNode Insert(AVLNode tree, int data) { if (tree == null) { tree = new AVLNode(data); } else { if (data < tree.data) { // Insert at left child tree.left = Insert(tree.left, data); if (Depth(tree.left) - Depth(tree.right) == 2) { // Need balance if (data < tree.left.data) { // Insert at left child's left child // Left-Rotate tree = RotateRight(tree); } else if (data > tree.left.data) { // Insert at left child's right child // First Left-rotate tree's left child // Then Right-rotate tree tree.left = RotateLeft(tree.left); tree = RotateRight(tree); } } } else if (data > tree.data) { // Insert at right child tree.right = Insert(tree.right, data); if (Depth(tree.left) - Depth(tree.right) == -2) { if (data > tree.right.data) { // Insert at right child's right // Left-Rotate tree = RotateLeft(tree); } else if (data < tree.right.data) { // Insert at right child's left // First Right-rotate on tree's right child tree.right = RotateRight(tree.right); // Then Left-rotate tree tree = RotateLeft(tree); } } } } // Update depth tree.depth = Math.max(Depth(tree.left), Depth(tree.right)) + 1; return tree; } public void Insert(int data) { root = Insert(root, data); } private void Print(AVLNode tree) { if (tree != null) { Print(tree.left); System.out.println(tree.data); Print(tree.right); } } public void Print() { Print(root); } public boolean Find(int key) { AVLNode ptr = root; while (true) { if (ptr == null) { break; } if (key == ptr.data) { return true; } else if (key < ptr.data) { ptr = ptr.left; } else { ptr = ptr.right; } } return false; } private AVLNode root = null; public static void main(String[] args) { // AVLTree tree = new AVLTree(); // for (int i = 0; i < 1000; i++) { // tree.Insert(i); // } // tree.Print(); // int key = 1000; // System.out.println("Find: " + key + "? " + tree.Find(key)); // AVLTree tree = new AVLTree(); // int arr[] = { 13, 24, 37, 90, 53 }; // for (int data : arr) { // tree.Insert(data); // } // tree.Print(); AVLTree tree = new AVLTree(); Random rand = new Random(); for (int i = 0; i < 10000; i++) { tree.Insert(rand.nextInt(100000)); } tree.Print(); } }