二叉树、二叉搜索树、AVL树的java实现

来源:互联网 发布:西门淘宝店 编辑:程序博客网 时间:2024/05/21 10:55


数据结构一直都是断断续续的看,总是觉得理解的不够深入,特别是对树的理解,一直都很浅显,今儿又看了一遍,来做个总结吧。


首先,树中的一些概念:

1、树的节点包含一个数据元素,以及若干指向其子树的分支。节点拥有的子树的数量称为节点的度。节点的最大层次称为树的深度或高度

2、二叉树是一种树形结构,其特点是每个节点至多有两棵子树,且子树有左右之分,次序不能随意颠倒。

3、满二叉树:一棵深度为k且有2^k - 1个节点的二叉树,称之为满二叉树。

4、完全二叉树:对一个深度为k,节点个数为n的二叉树,当且仅当每个节点都与深度为k的满二叉树中编号从1至n的节点一一对应时,称之为完全二叉树。

5、哈夫曼树:又称最优树,是一类带权路径长度最短的树。

6、二叉查找树(Binary Search Tree):对任一个节点,左子树的所有值都小于它,右子树的所有值都大于它。

7、AVL树:带有平衡条件的二叉查找树。

8、伸展树:任一个节点被访问后,它就要经过一系列的AVL树的旋转,将该节点放到根上。

9、B-树:另外一种常用的查找树。有几个特点,对于M阶的B树,首先,根节点如果不是叶子,则其子节点的数量在2-M之间。其次,除根之外,所有的非叶子节点的子节点数在M/2 - M之间。最后,所有的叶子节点有相同的深度。


概念讲到这儿,下面来实战几种树。这里主要讲三种,分别是:

1、普通二叉树

2、二叉搜索树

3、AVL树


详细的代码下载地址:https://github.com/BLYang7/DataStructure/tree/master/BinNode


普通二叉树:讲一些树结构的定义,普通生成、前序中序后序遍历的实现(递归实现和用栈来实现)、层次遍历、获取二叉树的深度、判断是否平衡、寻找两个节点最近的共同父节点。  

package com.BinNode;import java.util.LinkedList;import java.util.Queue;import java.util.Stack;/** * 树结构 */public class TreeNode {/** * 定义一棵二叉树 */private static class BinNode{public int Element;public BinNode Left;public BinNode Right;public BinNode(int element){this.Element = element;}public BinNode(int element, BinNode left, BinNode right){this.Element = element;this.Left = left;this.Right = right;}public boolean isLeaf(){return this.Left == null && this.Right == null;}}/** * 测试数据的构建 */public static BinNode root = new BinNode(0);public static BinNode left = new BinNode(1);public static BinNode right = new BinNode(2);public static BinNode leftLeft = new BinNode(3);public static BinNode leftRight = new BinNode(4);public static BinNode rightLeft = new BinNode(5);public static BinNode rightRight = new BinNode(6);static{root.Left = left;root.Right = right;left.Left = leftLeft;left.Right = leftRight;right.Left = rightLeft;right.Right = rightRight;}/** * 前序遍历,先遍历跟节点,再遍历左子树,后遍历右子树 * @param root 待遍历的根节点 */private static void preOrder(BinNode root){if( root == null ){return;}System.out.print(root.Element + "  "); //遍历打印输出preOrder(root.Left);preOrder(root.Right);}/** * 中序遍历,先遍历左节点,再遍历跟节点,后遍历右子树 * @param root 待遍历的根节点 */private static void middleOrder(BinNode root){if( root == null ){return;}middleOrder(root.Left);System.out.print( root.Element + "  " );middleOrder(root.Right);}/** * 后序遍历, 先遍历左节点,再遍历右节点,最后遍历根节点 * @param root 待遍历的根节点 */private static void postOrder(BinNode root){if( root == null ){return;}postOrder(root.Left);postOrder(root.Right);System.out.print( root.Element + "  " );}/** * 前序遍历,不用递归实现,使用栈来实现 */private static void preOrder1(BinNode root){Stack stack = new Stack();BinNode temp = root;//入栈while( temp != null ){System.out.print( temp.Element + "  " );if( temp.Right != null ){stack.push(temp.Right);}temp = temp.Left;}//出栈while( stack.size() > 0 ){temp = (BinNode) stack.pop();while( temp != null ){System.out.print(temp.Element + "  " );if( temp.Right != null ){stack.push(temp.Right);}temp = temp.Left;}}}/** * 中序遍历,不用递归实现,使用栈区来实现 * @param root */private static void middleOrder1( BinNode root ){Stack stack = new Stack();BinNode temp = root;//入栈while( temp != null ){if( temp != null ){stack.push(temp);}temp = temp.Left;}//出栈while( stack.size() > 0 ){temp = (BinNode) stack.pop();System.out.print( temp.Element + "  ");if( temp.Right != null ){temp = temp.Right;stack.push(temp);while( temp != null ){if( temp.Left != null){stack.push(temp.Left);}temp = temp.Left;}}}}/** * 后序遍历,不用递归实现,使用栈来记录。这里有点繁,根节点还是要重新装载进栈 * @param root */private static void postOrder1( BinNode root ){Stack stack = new Stack();BinNode temp = root;//入栈while(temp != null){if(temp != null){stack.push(temp);}temp = temp.Left;}//出栈while( stack.size() > 0 ){BinNode lastVisit = temp;temp = (BinNode) stack.pop();if(temp.Right == null || temp.Right == lastVisit){System.out.print( temp.Element + "  " );} else if ( temp.Left == lastVisit ){stack.push(temp);  //再装载进去temp = temp.Right;stack.push(temp);while( temp != null ){if( temp.Left != null ){stack.push(temp.Left);}temp = temp.Left;}}}}/** * 逐层打印二叉树的每一层 层次遍历 * 采用广度搜索优先的遍历方法,利用队列的方式实现 */private static void printTree( BinNode root ){if( root == null ){return;}BinNode tmp = null;Queue<BinNode> queue = new LinkedList<BinNode>();//队列中把根节点加进去queue.add(root);//遍历整棵二叉树while( !queue.isEmpty() ){tmp = (BinNode) queue.poll();System.out.print( tmp.Element + "  ");//将左节点加入到队列中if( tmp.Left != null ){queue.add(tmp.Left);}//将右节点加入到队列中if( tmp.Right != null ){queue.add(tmp.Right);}}}/** * 获取二叉树的深度 */private static int getDepth(BinNode root){if( root == null ){return 0;}int leftLength = getDepth(root.Left);int rightLength = getDepth(root.Right);return (leftLength > rightLength ? leftLength : rightLength) + 1;}/** * 判断是否为平衡二叉树 * 平衡的条件是:左右子树的深度相差不超过1 */private static boolean isBalanceTree(BinNode root){if( root == null ){return true;}int leftLength = getDepth(root.Left);int rightLength = getDepth(root.Right);int distance = (leftLength > rightLength) ? (leftLength - rightLength) : (rightLength - leftLength);if( distance > 1 ){return false;} else {return isBalanceTree(root.Left) && isBalanceTree(root.Right);}}/** * 寻找两个节点最近的共同父节点 * 将两个节点的父节点栈进行对比,一次弹出相同的节点,最后一次弹出的相同节点即为最近的公共父节点 */private static BinNode findParentNode(BinNode root, BinNode node1, BinNode node2){java.util.Stack stack1 = new java.util.Stack();getPositionByNode(root, node1, stack1);java.util.Stack stack2 = new java.util.Stack();getPositionByNode(root, node2, stack2);BinNode tempNode = null;while( stack1.peek() == stack2.peek() ){tempNode = (BinNode) stack1.pop();stack2.pop();}return tempNode;}//把某个节点的所有父节点依次添加到栈中private static boolean getPositionByNode(BinNode root, BinNode node, java.util.Stack stack){if( root == null ){return false;}else if( root == node ){stack.push(root);return true;}else if( getPositionByNode(root.Left, node, stack) || getPositionByNode(root.Right, node, stack) ){stack.push(root);return true;}return false;}//测试public static void main(String[] args) {//前序遍历,递归调用preOrder(root);System.out.println();//中序遍历,递归调用middleOrder(root);System.out.println();//后序遍历,递归调用postOrder(root);System.out.println();//前序遍历,栈来实现preOrder1(root);System.out.println();//中序遍历,栈来实现middleOrder1(root);System.out.println();//后序遍历,栈来实现postOrder1(root);System.out.println();//层次遍历printTree(root);System.out.println();//获取二叉树的深度System.out.println(getDepth(root));//判断是否为平衡二叉树System.out.println(isBalanceTree(root));//寻找两个节点最近的共同父节点System.out.println(findParentNode(root, leftLeft, leftRight).Element);}}


二叉搜索树:这里重点是节点的插入和节点的删除,请看代码:

package com.BinNode;/**  * 二叉搜索树 Binary Search Tree *  * 此程序实现一个二叉查找树的功能,可以进行动态插入、删除关键字;  * 查询给定关键字、最小关键字、最大关键字;转换为有序列表(用于排序)  */import java.util.ArrayList;import java.util.List;public class BinarySearchTree {// 树的根结点private TreeNode root = null;// 遍历结点列表private List<TreeNode> nodelist = new ArrayList<TreeNode>();// 树的节点类private class TreeNode {private int key;private TreeNode leftChild;private TreeNode rightChild;private TreeNode parent;public TreeNode(int key, TreeNode leftChild, TreeNode rightChild,TreeNode parent) {this.key = key;this.leftChild = leftChild;this.rightChild = rightChild;this.parent = parent;}public int getKey() {return key;}public String toString() {String leftkey = (leftChild == null ? "" : String.valueOf(leftChild.key));String rightkey = (rightChild == null ? "" : String.valueOf(rightChild.key));return "(" + leftkey + " , " + key + " , " + rightkey + ")";}}/** * isEmpty: 判断二叉查找树是否为空;若为空,返回 true ,否则返回 false . *  */public boolean isEmpty() {if (root == null) {return true;} else {return false;}}/** * TreeEmpty: 对于某些二叉查找树操作(比如删除关键字)来说,若树为空,则抛出异常。 */public void TreeEmpty() throws Exception {if (isEmpty()) {throw new Exception("树为空!");}}/** * search: 在二叉查找树中查询给定关键字 *  * @param key 给定关键字 * @return 匹配给定关键字的树结点 */public TreeNode search(int key) {TreeNode pNode = root;while (pNode != null && pNode.key != key) {if (key < pNode.key) {pNode = pNode.leftChild;} else {pNode = pNode.rightChild;}}return pNode;}/** * minElemNode: 获取二叉查找树中的最小关键字结点 *  * @return 二叉查找树的最小关键字结点 * @throws Exception 若树为空,则抛出异常 */public TreeNode minElemNode(TreeNode node) throws Exception {if (node == null) {throw new Exception("树为空!");}TreeNode pNode = node;while (pNode.leftChild != null) {pNode = pNode.leftChild;}return pNode;}/** * maxElemNode: 获取二叉查找树中的最大关键字结点 *  * @return 二叉查找树的最大关键字结点 * @throws Exception 若树为空,则抛出异常 */public TreeNode maxElemNode(TreeNode node) throws Exception {if (node == null) {throw new Exception("树为空!");}TreeNode pNode = node;while (pNode.rightChild != null) {pNode = pNode.rightChild;}return pNode;}/** * successor: 获取给定结点在中序遍历顺序下的后继结点 *  * @param node 给定树中的结点 * @return 若该结点存在中序遍历顺序下的后继结点,则返回其后继结点;否则返回 null * @throws Exception */public TreeNode successor(TreeNode node) throws Exception {if (node == null) {return null;}// 若该结点的右子树不为空,则其后继结点就是右子树中的最小关键字结点if (node.rightChild != null) {return minElemNode(node.rightChild);}// 若该结点右子树为空TreeNode parentNode = node.parent;while (parentNode != null && node == parentNode.rightChild) {node = parentNode;parentNode = parentNode.parent;}return parentNode;}/** * precessor: 获取给定结点在中序遍历顺序下的前趋结点 *  * @param node  给定树中的结点 * @return 若该结点存在中序遍历顺序下的前趋结点,则返回其前趋结点;否则返回 null * @throws Exception */public TreeNode precessor(TreeNode node) throws Exception {if (node == null) {return null;}// 若该结点的左子树不为空,则其前趋结点就是左子树中的最大关键字结点if (node.leftChild != null) {return maxElemNode(node.leftChild);}// 若该结点左子树为空TreeNode parentNode = node.parent;while (parentNode != null && node == parentNode.leftChild) {node = parentNode;parentNode = parentNode.parent;}return parentNode;}/** * insert: 将给定关键字插入到二叉查找树中 *  * @param key 给定关键字 */public void insert(int key) {TreeNode parentNode = null;TreeNode newNode = new TreeNode(key, null, null, null);TreeNode pNode = root;if (root == null) {root = newNode;return;}while (pNode != null) {parentNode = pNode;if (key < pNode.key) {pNode = pNode.leftChild;} else if (key > pNode.key) {pNode = pNode.rightChild;} else {// 树中已存在匹配给定关键字的结点,则什么都不做直接返回return;}}if (key < parentNode.key) {parentNode.leftChild = newNode;newNode.parent = parentNode;} else {parentNode.rightChild = newNode;newNode.parent = parentNode;}}/** * insert: 从二叉查找树中删除匹配给定关键字相应的树结点 *  * @param key 给定关键字 */public void delete(int key) throws Exception {TreeNode pNode = search(key);if (pNode == null) {throw new Exception("树中不存在要删除的关键字!");}delete(pNode);}/** * delete: 从二叉查找树中删除给定的结点. *  * @param pNode 要删除的结点。前置条件是给定结点在二叉查找树中已经存在 * @throws Exception */private void delete(TreeNode pNode) throws Exception {if (pNode == null) {return;}if (pNode.leftChild == null && pNode.rightChild == null) { // 该结点既无左孩子结点,也无右孩子结点TreeNode parentNode = pNode.parent;if (pNode == parentNode.leftChild) {parentNode.leftChild = null;} else {parentNode.rightChild = null;}return;}if (pNode.leftChild == null && pNode.rightChild != null) { // 该结点左孩子结点为空,右孩子结点非空TreeNode parentNode = pNode.parent;if (pNode == parentNode.leftChild) {parentNode.leftChild = pNode.rightChild;pNode.rightChild.parent = parentNode;} else {parentNode.rightChild = pNode.rightChild;pNode.rightChild.parent = parentNode;}return;}if (pNode.leftChild != null && pNode.rightChild == null) { // 该结点左孩子结点非空,右孩子结点为空TreeNode parentNode = pNode.parent;if (pNode == parentNode.leftChild) {parentNode.leftChild = pNode.leftChild;pNode.rightChild.parent = parentNode;} else {parentNode.rightChild = pNode.leftChild;pNode.rightChild.parent = parentNode;}return;}// 该结点左右孩子结点均非空,则删除该结点的后继结点,并用该后继结点取代该结点TreeNode successorNode = successor(pNode);delete(successorNode);pNode.key = successorNode.key;}/** * inOrderTraverseList: 获得二叉查找树的中序遍历结点列表 *  * @return 二叉查找树的中序遍历结点列表 */public List<TreeNode> inOrderTraverseList() {if (nodelist != null) {nodelist.clear();}inOrderTraverse(root);return nodelist;}/** * inOrderTraverse: 对给定二叉查找树进行中序遍历 *  * @param root 给定二叉查找树的根结点 */private void inOrderTraverse(TreeNode root) {if (root != null) {inOrderTraverse(root.leftChild);nodelist.add(root);inOrderTraverse(root.rightChild);}}/** * toStringOfOrderList: 获取二叉查找树中关键字的有序列表 *  * @return 二叉查找树中关键字的有序列表 */public String toStringOfOrderList() {StringBuilder sbBuilder = new StringBuilder(" [ ");for (TreeNode p : inOrderTraverseList()) {sbBuilder.append(p.key);sbBuilder.append(" ");}sbBuilder.append("]");return sbBuilder.toString();}/** * 获取该二叉查找树的字符串表示 */public String toString() {StringBuilder sbBuilder = new StringBuilder(" [ ");for (TreeNode p : inOrderTraverseList()) {sbBuilder.append(p);sbBuilder.append(" ");}sbBuilder.append("]");return sbBuilder.toString();}public TreeNode getRoot() {return root;}public static void testNode(BinarySearchTree bst, TreeNode pNode)throws Exception {System.out.println("本结点: " + pNode);System.out.println("前趋结点: " + bst.precessor(pNode));System.out.println("后继结点: " + bst.successor(pNode));}public static void testTraverse(BinarySearchTree bst) {System.out.println("二叉树遍历:" + bst);System.out.println("二叉查找树转换为有序列表: " + bst.toStringOfOrderList());}// 测试public static void main(String[] args) {try {BinarySearchTree bst = new BinarySearchTree();System.out.println("查找树是否为空? " + (bst.isEmpty() ? "是" : "否"));int[] keys = new int[] { 15, 6, 18, 3, 7, 13, 20, 2, 9, 4 };for (int key : keys) {bst.insert(key);}System.out.println("查找树是否为空? " + (bst.isEmpty() ? "是" : "否"));TreeNode minkeyNode = bst.minElemNode(bst.getRoot());System.out.println("最小关键字: " + minkeyNode.getKey());testNode(bst, minkeyNode);TreeNode maxKeyNode = bst.maxElemNode(bst.getRoot());System.out.println("最大关键字: " + maxKeyNode.getKey());testNode(bst, maxKeyNode);System.out.println("根结点关键字: " + bst.getRoot().getKey());testNode(bst, bst.getRoot());testTraverse(bst);System.out.println("****************************** ");testTraverse(bst);} catch (Exception e) {System.out.println(e.getMessage());e.printStackTrace();}}}



AVL树:平衡二叉树,对于这种树,涉及到旋转。当新插入一个节点时,要判断节点插入之后当前树是否平衡,如果不平衡,肯定是当前插入的节点破坏了原来节点的平衡关系。需要讲三层的子树旋转成两层。具体的几种旋转如图示:



上代码:

package com.BinNode;/** * AVL树 * */public class AVLTree<T extends Comparable<? super T>> {/** * AVL树节点的定义 */private static class AvlNode<T> {AvlNode(T theElement) {this(theElement, null, null);}AvlNode(T theElement, AvlNode<T> lt, AvlNode<T> rt) {element = theElement;left = lt;right = rt;height = 0;}T element; // 节点中的数据AvlNode<T> left; // 左儿子AvlNode<T> right; // 右儿子int height; // 节点的高度}// avl树根private AvlNode<T> root;public AVLTree() {root = null;}// 在avl树中插入数据,重复数据复略public void insert(T x) {root = insert(x, root);}// 在avl中删除数据,没有实现public void remove(T x) {System.out.println("Sorry, remove unimplemented");}// 在avl树中找最小的数据public T findMin() throws Exception {if (isEmpty())throw new Exception("空");return findMin(root).element;}// 在avl树中找最大的数据public T findMax() throws Exception {if (isEmpty())throw new Exception("空");return findMax(root).element;}// 搜索public boolean contains(T x) {return contains(x, root);}public void makeEmpty() {root = null;}public boolean isEmpty() {return root == null;}// 排序输出avl树public void printTree() {if (isEmpty())System.out.println("Empty tree");elseprintTree(root);}/** * AVL树的插入,重点就是这儿了 * @param x 要插入的元素 * @param t 树的根节点 * @return */private AvlNode<T> insert(T x, AvlNode<T> t) {if (t == null){return new AvlNode<T>(x, null, null);}int compareResult = x.compareTo(t.element);if (compareResult < 0) {t.left = insert(x, t.left);// 将x插入左子树中// 打破平衡if (height(t.left) - height(t.right) == 2){// LL型(左左型)if (x.compareTo(t.left.element) < 0){t = rotateWithLeftChild(t);}// LR型(左右型)else{t = doubleWithLeftChild(t);}}} else if (compareResult > 0) {// 将x插入右子树中t.right = insert(x, t.right);// 打破平衡if (height(t.right) - height(t.left) == 2){// RR型(右右型)if (x.compareTo(t.right.element) > 0){t = rotateWithRightChild(t);}else{// RL型t = doubleWithRightChild(t);}}} else{; // 重复数据,什么也不做}t.height = Math.max(height(t.left), height(t.right)) + 1;// 更新高度return t;}// 带左子树旋转,适用于LL型private AvlNode<T> rotateWithLeftChild(AvlNode<T> k2) {AvlNode<T> k1 = k2.left;k2.left = k1.right;k1.right = k2;k2.height = Math.max(height(k2.left), height(k2.right)) + 1;k1.height = Math.max(height(k1.left), k2.height) + 1;return k1;}// 带右子树旋转,适用于RR型private AvlNode<T> rotateWithRightChild(AvlNode<T> k1) {AvlNode<T> k2 = k1.right;k1.right = k2.left;k2.left = k1;k1.height = Math.max(height(k1.left), height(k1.right)) + 1;k2.height = Math.max(height(k2.right), k1.height) + 1;return k2;}// 双旋转,适用于LR型private AvlNode<T> doubleWithLeftChild(AvlNode<T> k3) {k3.left = rotateWithRightChild(k3.left);return rotateWithLeftChild(k3);}// 双旋转,适用于RL型private AvlNode<T> doubleWithRightChild(AvlNode<T> k1) {k1.right = rotateWithLeftChild(k1.right);return rotateWithRightChild(k1);}// 找最小private AvlNode<T> findMin(AvlNode<T> t) {if (t == null)return t;while (t.left != null)t = t.left;return t;}// 找最大private AvlNode<T> findMax(AvlNode<T> t) {if (t == null)return t;while (t.right != null)t = t.right;return t;}// 搜索(查找)private boolean contains(T x, AvlNode<T> t) {while (t != null) {int compareResult = x.compareTo(t.element);if (compareResult < 0)t = t.left;else if (compareResult > 0)t = t.right;elsereturn true; // Match}return false; // No match}// 前序遍历avl树private void printTree(AvlNode<T> t) {if (t != null) {System.out.println(t.element);printTree(t.left);printTree(t.right);}}// 求高度private int height(AvlNode<T> t) {return t == null ? -1 : t.height;}// Test programpublic static void main(String[] args) {AVLTree<Integer> t = new AVLTree<Integer>();final int NUMS = 100;final int GAP = 37;for (int i = GAP; i != 0; i = (i + GAP) % NUMS){t.insert(i);}t.printTree();}}

至此,三种常见的树就完成了



0 0
原创粉丝点击