Java实现前中后序线索化二叉树以及遍历
文章目錄
- 一、線索化二叉樹的原理
- 二、構建線索化二叉樹
- 三、代碼實現線索二叉樹
一、線索化二叉樹的原理
在前面介紹二叉樹的文章中提到,二叉樹可以使用兩種存儲結構:順序存儲和鏈式存儲,在使用鏈式存儲時,會存在大量的空指針域,n個節點的二叉鏈表中含有n+1[ 2n-(n-1)=n+1 ]個空指針域,為了可以充分利用這些空指針域,引申出了線索化二叉樹,將這些空指針域利用起來,提高檢索效率。
上圖中,紫色區域就是沒有指向的空指針域,從存儲空間的角度來看,這些空指針域浪費了內存資源。
從另外的角度思考,如果需要知道C節點的前驅節點和后繼節點,每次都都需要進行一次遍歷,是否可以提前存儲C節點的前驅節點和后繼節點從而省去遍歷的操作從而提高效率呢?
綜合以上分析,可以通過充分利用二叉鏈表中的空指針域,存入節點在某種遍歷方式下的前驅和后繼節點的指針。
這種指向前驅和后繼的指針成為線索,加上線索的二叉鏈成為線索鏈表,而對應的二叉樹就稱為線索化二叉樹(Threaded Binary Tree)
二、構建線索化二叉樹
- 先對二叉樹進行中序遍歷(不了解二叉樹遍歷的可以參考數據結構:二叉樹),將所有節點右指針為空的指針域指針它的后繼節點,如下圖:
通過中序遍歷到D節點,得到了D節點right指針為空,并且D節點后繼節點為B,于是將D的right節點指向B節點(如上圖中的①操作),而B節點right指針也為空,于是將right指針指向A節點(如上圖中的②操作),以此類推,到F節點的后繼節點為Null,則F的right指針指向Null。
-
接下來將這顆二叉樹所有左指針為空的指針指向它的前驅節點,如下圖:
如上圖,D節點的left指針域指向Null(第⑤部操作),E節點的前驅節點是A,將E的left指針指向A節點,以此類推,F節點的left節點指向C節點。
-
通過上述兩步操作完成了整個二叉樹的線索化。
通過線索化后,可以看出(藍色箭頭表示前驅,粉色箭頭表示后繼)線索化二叉樹相當于是把二叉樹轉換成一個特殊的雙向鏈表,這樣對新增、刪除以及查找節點都帶來了方便,提高了效率,轉換為鏈表后的圖示如下:
線索化需要對節點的結構進行修改,修改之后的數據結構如下:
class Node {private int num;private Object data; //數據域private Node left; //左指針域private Node right; //右指針域/*** leftType == 0表示指向的是左子樹,如果是1表示指向前驅節點*/private int leftType;/*** rightType == 0表示指向的是右子樹,如果是1表示指向后繼節點*/private int rightType; }三、代碼實現線索二叉樹
-
節點結構
class Node {private int num;private Object data;private Node left;private Node right;/*** leftType == 0表示指向的是左子樹,如果是1表示指向前驅節點*/private int leftType;/*** rightType == 0表示指向的是右子樹,如果是1表示指向后繼節點*/private int rightType;public Node(int num, Object data){this.num = num;this.data = data;}/*** 前序遍歷*/public void preOrder(){//先輸出父節點信息System.out.println(this);//判斷此節點的左節點是否為空//如果左節點不為空并且指針類型不是前驅類型if (this.left != null && this.leftType != 1){//向左前序遍歷this.left.preOrder();}//判斷此節點的右節點是否為空//如果右節點不為空并且指針類型不是后繼類型if (this.right != null && this.rightType != 1){//向右前序遍歷this.right.preOrder();}}/*** 中序遍歷線索化二叉樹*/public void infixOrder(){//判斷此節點的左節點是否為空if (this.left != null && this.leftType != 1){//向左中序遍歷this.left.infixOrder();}//輸出父節點信息System.out.println(this);//判斷此節點的右節點是否為空if (this.right != null && this.rightType != 1){//向右中序遍歷this.right.infixOrder();}}/*** 后序遍歷*/public void postOrder(){//判斷此節點的左節點是否為空if (this.left != null && this.leftType != 1){//向左后序遍歷this.left.postOrder();}//判斷此節點的右節點是否為空if (this.right != null && this.rightType != 1){//向右后序遍歷this.right.postOrder();}//輸出父節點信息System.out.println(this);}//get set方法省略@Overridepublic String toString(){return "Node{" +"num=" + num +", data=" + data +'}';} } -
線索化二叉樹結構
/*** 定義ThreadedBinaryTree 實現了線索化功能的二叉樹*/ class ThreadedBinaryTree {/*** 根節點*/private Node root;/*** 為了實現線索化,需要創建要指向當前節點的前驅節點的指針* 在遞歸進行線索化時,pre總是保留前一個節點*/private Node pre = null;public void setRoot(Node root){this.root = root;}public void createBinaryTree(ArrayList<Node> list){this.createBinaryTree(list,0);}/*** 創建二叉樹* @param list 節點列表* @param index 用于創建的索引* @return*/private Node createBinaryTree(ArrayList<Node> list, int index){Node node = null;if (isEmpty()){setRoot(list.get(0));}if (index < list.size()){node = list.get(index);node.setLeft(createBinaryTree(list, (2*index+1)));node.setRight(createBinaryTree(list, (2*index+2)));}return node;}public boolean isEmpty(){return root == null;}public void preThreadNodes(){this.preThreadNodes(root);}public void infixThreadNodes(){this.infixThreadNodes(root);}public void postThreadNodes(){this.postThreadNodes(root);}/*** 前序線索化二叉樹* @param node 當前需要線索化的節點*/private void preThreadNodes(Node node){//遞歸回溯條件if (node == null){return;}//遍歷到葉子節點(前驅指針未利用)if (node.getLeft() == null){node.setLeft(pre);node.setLeftType(1);}//pre.getLeft() != node 一定不可少//否則在第一次回溯后到父節點后,如果不加上此判斷會讓pre節點后驅指針指向該父節點//此時pre的前驅后繼節點均指向該父節點,必然會發生死循環無法結束遞歸if (pre != null && pre.getRight() == null && pre.getLeft() != node){pre.setRight(node);pre.setRightType(1);}pre = node;if (node.getLeftType() == 0){preThreadNodes(node.getLeft());}preThreadNodes(node.getRight());}/*** 中序線索化二叉樹* @param node 當前需要線索化的節點*/private void infixThreadNodes(Node node){if (node == null){return;}//1.遞歸線索化左子樹infixThreadNodes(node.getLeft());//2.線索化當前節點//處理當前節點的前驅節點if (node.getLeft() == null){//讓當前節點的左指針指向前驅節點node.setLeft(pre);//修改當前節點的左指針的類型node.setLeftType(1);}//處理后繼節點if (pre != null && pre.getRight() == null){//讓前驅節點的右指針指向當前節點pre.setRight(node);//修改前驅節點的右指針類型pre.setRightType(1);}//每處理一個節點之后讓當前節點是下一個節點的前驅節點pre = node;//3.遞歸線索化右子樹infixThreadNodes(node.getRight());}/*** 后序線索化二叉樹* @param node 當前需要線索化的節點*/private void postThreadNodes(Node node){if (node == null){return;}postThreadNodes(node.getLeft());postThreadNodes(node.getRight());if (node.getLeft() == null){node.setLeft(pre);node.setLeftType(1);}if ( pre!= null && pre.getRight() == null ){pre.setRight(node);pre.setRightType(1);}pre = node;}/*** 非遞歸方式中序遍歷線索化二叉樹的方法*/public void threadedInfixList(){Node node = root;while (node != null){//循環地找到leftType == 1的節點while (node.getLeftType() == 0){node = node.getLeft();}System.out.println(node);while (node.getRightType() == 1){node = node.getRight();System.out.println(node);}node = node.getRight();}}public void threadedTreePreList(){if (root != null){root.preOrder();}else{System.out.println("二叉樹為空,無法遍歷");}}public void threadedTreeInfixList(){if (root != null){root.infixOrder();}else{System.out.println("二叉樹為空,無法遍歷");}}public void threadedTreePostList(){if (root != null){root.postOrder();}else{System.out.println("二叉樹為空,無法遍歷");}} }
以上。
如有不足或者錯誤歡迎評論指正。
整理不易,留個三連再走吧!
總結
以上是生活随笔為你收集整理的Java实现前中后序线索化二叉树以及遍历的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据结构:二叉树
- 下一篇: Java实现堆排序及详细图解