数据结构-二叉树和二叉查找树
先按樹-二叉樹-二叉查找樹的順序解釋會比較清楚。
一,樹
樹(Tree)是n(n≥0)個結點的有限集。在任意一棵非空樹中:
(1)有且僅有一個特定的被稱為根(Root)的結點;
(2)當n>1時,其余結點可分為m(m>0)個互不相交的有限集T1,T2,…,Tm,其中每一個集合本身又是一棵樹,并且稱為根的子樹(SubTree)。
結點的度(Degree):結點擁有的子樹數稱為結點的度(Degree)。度為0的結點稱為葉子(Leaf)或終端結點。度不為0的結點稱為非終端結點或分支結點。樹的度:是樹內各結點的度的最大值。
孩子和雙親:結點的子樹的根稱為該結點的孩子(Child),相應地,該結點稱為孩子的雙親(Parent)。
結點的層次(Level):是從根結點開始計算起,根為第一層,根的孩子為第二層,依次類推。樹中結點的最大層次稱為樹的深度(Depth)或高度。
如果將樹中結點的各子樹看成從左至右是有次序的(即不能互換),則稱該樹為有序樹,否則稱為無序樹。
二,二叉樹
二叉樹(Binary Tree)的特點是每個結點至多具有兩棵子樹(即在二叉樹中不存在度大于2的結點),并且子樹之間有左右之分。
二叉樹的性質:
(1)、在二叉樹的第i層上至多有2i-1個結點(i≥1)。
(2)、深度為k的二叉樹至多有2k-1個結點(k≥1)。
(3)、對任何一棵二叉樹,如果其終端結點數為n0,度為2的結點數為n2,則n0=n2+1。
有很多關于樹的術語,在這里不做過多的文字解釋,不想畫圖了。下面我找了圖來說明,圖參考來自http://blog.csdn.net/u012152619/article/details/42059325,通過它可以直觀地理解樹的路徑、根、父節點、子節點、葉節點、子樹、層等概念
三,二叉查找樹(左<中<右)
我們從一種特殊的、使用很廣泛的二叉樹入手:二叉查找樹。
二叉查找樹的性質:
(1)、若它的左子樹不為空,則左子樹上所有結點的值均小于它的根結點的值;
(2)、若它的右子樹不為空,則右子樹上所有結點的值均大于它的根結點的值;
(3)、它的左、右子樹也分別為二叉查找樹。
用一句話概括,二叉查找樹的特點是,一個節點的左子節點的關鍵字值小于這個節點,右子節點的關鍵字值大于或等于這個父節點。
二叉查找樹的基本操作是查找,插入,刪除,遍歷,下面一一介紹:
1,查找(search)
我們已經知道,二叉搜索樹的特點是左子節點小于父節點,右子節點大于或等于父節點。查找某個節點時,先從根節點入手,如果該元素值小于根節點,則轉向左子節點,否則轉向右子節點,以此類推,直到找到該節點,或者到最后一個葉子節點依然沒有找到,則證明樹中沒有該節點
代碼是:
/** 查找元素,返回true */public boolean search(E e) {TreeNode<E> current = root; // 從根元素開始while (current != null) {if (e.compareTo(current.element) < 0) {//如果比當前元素值小,就指向當前元素的左子樹current = current.left;}else if (e.compareTo(current.element) > 0) {//如果比當前元素值大,就指向當前元素的右子樹current = current.right;}else // element等于 current.elementreturn true; //發現元素,返回true}return false;}
2,插入(insert)
插入一個新節點首先要確定插入的位置,關鍵思路是確定新節點父節點所在的位置。
代碼:
/** 插入元素,成功返回true */public boolean insert(E e) {if (root == null)root = createNewNode(e); // 如果是樹空則創造一個跟節點else {// 標記當前父節點位置TreeNode<E> parent = null;TreeNode<E> current = root;while (current != null)if (e.compareTo(current.element) < 0) {parent = current;current = current.left;}else if (e.compareTo(current.element) > 0) {parent = current;current = current.right;}elsereturn false; // 有重復節點,不能被插入// 創建一個新節點掛在父節點下if (e.compareTo(parent.element) < 0)parent.left = createNewNode(e);elseparent.right = createNewNode(e);}size++;return true; // 插入成功}
刪除BST中的一個節點是最麻煩的操作,總結一下大概下面兩種方法:
Case 1:刪除點沒有左孩子,這是只需要將該節點的父節點和當前節點的有孩子相連即可
Case2:刪除點有左孩子.這種情況下先找到當前節點的左子樹的最右節點,因為一個節點的左子樹的最右節點也比右子樹的最左節點小,把最右節點復制給刪除點,然后刪除最右節點
代碼:
/** 刪除節點,刪除成功返回true,不在樹中返回false*/public boolean delete(E e) {// 標記被刪除的節點和該節點的父節點位置TreeNode<E> parent = null; TreeNode<E> current = root;while (current != null) {if (e.compareTo(current.element) < 0) {parent = current;current = current.left;}else if (e.compareTo(current.element) > 0) {parent = current;current = current.right;}elsebreak; // 元素在這個樹中}if (current == null)return false; // 元素不在樹中if (current.left == null) { // 第一種情況:元素沒有左子樹,把當前節點的右子樹直接掛在其父節點的右子樹上// 把當前節點的右子樹直接掛在其父節點的右子樹上if (parent == null) {root = current.right;}else {if (e.compareTo(parent.element) < 0)parent.left = current.right;elseparent.right = current.right;}}else { // 第二種情況:元素有左子樹,先找到當前節點的左子樹的最右節點//標記當前節點的左子樹的父節點和最右節點TreeNode<E> parentOfRightMost = current;TreeNode<E> rightMost = current.left;//一直向右,找到最右端的節點,因為一個節點的左子樹的最右節點也比右子樹的最左節點小while (rightMost.right != null) {parentOfRightMost = rightMost;rightMost = rightMost.right; // 一直向右}/** 以上代碼的目的是要找到刪除節點的左子樹最右節點 ,因為一個節點的左子樹的最右節點也比右子樹的最左節點小*/// 找到最右節點后,放到當前要刪除的位置current.element = rightMost.element;// 消除最右節點if (parentOfRightMost.right == rightMost)parentOfRightMost.right = rightMost.left;//把最右節點的左子樹掛在其父節點的右子樹上else// 具體情況: parentOfRightMost == currentparentOfRightMost.left = rightMost.left; }size--;return true; // 刪除成功}
下面介紹一下二叉樹的構成:
Tree.java
package com.hust.cn; public interface Tree<E extends Comparable<E>> {//查找元素 public boolean search(E e);//插入元素public boolean insert(E e);//刪除元素public boolean delete(E e);//中序遍歷public void inorder();//后序遍歷public void postorder();//前序遍歷public void preorder();//返回大小public int getSize();//判空public boolean isEmpty();//返回樹的迭代器public java.util.Iterator iterator(); } AbstractTree.java
package com.hust.cn; public abstract class AbstractTree<E extends Comparable<E>>implements Tree<E> {//中序遍歷public void inorder() {}//后序遍歷public void postorder() {}//前序遍歷public void preorder() {}//判空public boolean isEmpty() {return getSize() == 0;}//返回樹的迭代器public java.util.Iterator iterator() {return null;} } BinaryTree.java
package com.hust.cn; public class BinaryTree<E extends Comparable<E>>extends AbstractTree<E> {protected TreeNode<E> root;//節點類,是內部類protected int size = 0;/** 構造函數 */public BinaryTree() {}/** 對象數組創建一個二叉查找樹 */public BinaryTree(E[] objects) {for (int i = 0; i < objects.length; i++)insert(objects[i]);}/** 查找元素,返回true */public boolean search(E e) {TreeNode<E> current = root; // 從根元素開始while (current != null) {if (e.compareTo(current.element) < 0) {//如果比當前元素值小,就指向當前元素的左子樹current = current.left;}else if (e.compareTo(current.element) > 0) {//如果比當前元素值大,就指向當前元素的右子樹current = current.right;}else // element等于 current.elementreturn true; //發現元素,返回true}return false;}/** 插入元素,成功返回true */public boolean insert(E e) {if (root == null)root = createNewNode(e); // 如果是樹空則創造一個跟節點else {// 標記當前父節點位置TreeNode<E> parent = null;TreeNode<E> current = root;while (current != null)if (e.compareTo(current.element) < 0) {parent = current;current = current.left;}else if (e.compareTo(current.element) > 0) {parent = current;current = current.right;}elsereturn false; // 有重復節點,不能被插入// 創建一個新節點掛在父節點下if (e.compareTo(parent.element) < 0)parent.left = createNewNode(e);elseparent.right = createNewNode(e);}size++;return true; // 插入成功}/*創建一個新節點*/protected TreeNode<E> createNewNode(E e) {return new TreeNode<E>(e);}/** 中序遍歷*/public void inorder() {inorder(root);}/** 從根節點中序遍歷 ,遞歸方法*/protected void inorder(TreeNode<E> root) {if (root == null) return;inorder(root.left);System.out.print(root.element + " ");inorder(root.right);}/**后序遍歷 */public void postorder() {postorder(root);}/**從根節點后序遍歷,遞歸方法 */protected void postorder(TreeNode<E> root) {if (root == null) return;postorder(root.left);postorder(root.right);System.out.print(root.element + " ");}/**前序遍歷 */public void preorder() {preorder(root);}/** 從根節點前序遍歷,遞歸方法 */protected void preorder(TreeNode<E> root) {if (root == null) return;System.out.print(root.element + " ");preorder(root.left);preorder(root.right);}/** 返回樹的大小 */public int getSize() {return size;}/** 返回根節點 */public TreeNode getRoot() {return root;}/** 返回從根節點到一個具體元素的路徑 */public java.util.ArrayList<TreeNode<E>> path(E e) {java.util.ArrayList<TreeNode<E>> list =new java.util.ArrayList<TreeNode<E>>();//用數組存放路徑上的元素TreeNode<E> current = root; // 從根節點開始while (current != null) {list.add(current); // 添加當前元素到數組里if (e.compareTo(current.element) < 0) {current = current.left;}else if (e.compareTo(current.element) > 0) {current = current.right;}elsebreak;}return list; // 返回節點數組}/** 刪除節點,刪除成功返回true,不在樹中返回false*/public boolean delete(E e) {// 標記被刪除的節點和該節點的父節點位置TreeNode<E> parent = null; TreeNode<E> current = root;while (current != null) {if (e.compareTo(current.element) < 0) {parent = current;current = current.left;}else if (e.compareTo(current.element) > 0) {parent = current;current = current.right;}elsebreak; // 元素在這個樹中}if (current == null)return false; // 元素不在樹中if (current.left == null) { // 第一種情況:元素沒有左子樹,把當前節點的右子樹直接掛在其父節點的右子樹上// 把當前節點的右子樹直接掛在其父節點的右子樹上if (parent == null) {root = current.right;}else {if (e.compareTo(parent.element) < 0)parent.left = current.right;elseparent.right = current.right;}}else { // 第二種情況:元素有左子樹,先找到當前節點的左子樹的最右節點//標記當前節點的左子樹的父節點和最右節點TreeNode<E> parentOfRightMost = current;TreeNode<E> rightMost = current.left;//一直向右,找到最右端的節點,因為一個節點的左子樹的最右節點也比右子樹的最左節點小while (rightMost.right != null) {parentOfRightMost = rightMost;rightMost = rightMost.right; // 一直向右}/** 以上代碼的目的是要找到刪除節點的左子樹最右節點 ,因為一個節點的左子樹的最右節點也比右子樹的最左節點小*/// 找到最右節點后,放到當前要刪除的位置current.element = rightMost.element;// 消除最右節點if (parentOfRightMost.right == rightMost)parentOfRightMost.right = rightMost.left;//把最右節點的左子樹掛在其父節點的右子樹上else// 具體情況: parentOfRightMost == currentparentOfRightMost.left = rightMost.left; }size--;return true; // 刪除成功}/** 獲得中序迭代器 */public java.util.Iterator iterator() {return inorderIterator();}/** 創建一個迭代器類*/public java.util.Iterator inorderIterator() {return new InorderIterator();}// 中序迭代器類,內部類class InorderIterator implements java.util.Iterator {// 存儲元素的數組private java.util.ArrayList<E> list =new java.util.ArrayList<E>();private int current = 0; //數組中當前元素的位置public InorderIterator() {inorder(); // 中序遍歷二叉樹}/** 從根部中序遍歷*/private void inorder() {inorder(root);}/** 中序遍歷子樹 */private void inorder(TreeNode<E> root) {if (root == null)return;inorder(root.left);list.add(root.element);inorder(root.right);}/** 遍歷下一個元素*/public boolean hasNext() {if (current < list.size())return true;return false;}/** 獲得當前元素,并把指針指向另一個元素 */public Object next() {return list.get(current++);}/** 移出當前元素 */public void remove() {delete(list.get(current)); //刪除當前元素list.clear(); //清理數組inorder(); //重新中序遍歷數組}}/** 清楚樹的所有元素 */public void clear() {root = null;size = 0;}/** 內部類,樹的節點類 */public static class TreeNode<E extends Comparable<E>> {E element;TreeNode<E> left;TreeNode<E> right;public TreeNode(E e) {element = e;}}} TestBinaryTree.java
package com.hust.cn; public class TestBinaryTree {public static void main(String[] args) {// 創建一個二叉查找樹BinaryTree<String> tree = new BinaryTree<String>();tree.insert("George");tree.insert("Michael");tree.insert("Tom");tree.insert("Adam");tree.insert("Jones");tree.insert("Peter");tree.insert("Daniel");// 遍歷樹System.out.println("Inorder (sorted): ");tree.inorder();System.out.println("\nPostorder: ");tree.postorder();System.out.println("\nPreorder: ");tree.preorder();System.out.println("\nThe number of nodes is " + tree.getSize());// 查找一個元素System.out.println("\nIs Peter in the tree? " + tree.search("Peter"));// 從root到peter的一條路徑System.out.println("\nA path from the root to Peter is: ");java.util.ArrayList<BinaryTree.TreeNode<String>> path = tree.path("Peter");for (int i = 0; path != null && i < path.size(); i++)System.out.print(path.get(i).element + " ");//利用數組構建一個二叉查找樹,并中序遍歷Integer[] numbers = {2, 4, 3, 1, 8, 5, 6, 7};BinaryTree<Integer> intTree = new BinaryTree<Integer>(numbers);System.out.println("\nInorder (sorted): ");intTree.inorder();} }
測試結果:
總結
以上是生活随笔為你收集整理的数据结构-二叉树和二叉查找树的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据结构-堆实现优先队列(java)
- 下一篇: 数据结构之排序算法(一)-堆排序