二叉樹之線索連結串列
阿新 • • 發佈:2019-02-04
上一章介紹了二叉樹的二叉連結串列的實現,並給出了相關遍歷演算法,但是,當以二叉連結串列作為二叉樹的儲存結構時,無法直接得到結點在任一序列中的前驅與後繼資訊。為了規避這個弊端,本章將引入線索二叉樹的概念 ,並給出相關Java實現。
為了得到前驅與後繼的資訊,最直觀的想法是在每個結點上新增兩個指標域,分別指向結點在遍歷時的前驅及後繼。然而,這樣會使儲存密度大大降低,而在有n個結點的二叉樹中有n+1個空鏈域,故可利用這些空鏈域來存放結點的前驅及後繼資訊,並作如下規定:若結點有左子樹,則其leftChild域指向其左孩子,否則,令leftChild指向其前驅;若結點有右子樹,則其rightChild域指向其右孩子,否則,令rightChild指向其後繼。
為了避免混淆,現對上章中的結點結構作適當改動,改動後如下:
leftChilt | leftTag | element | rightTag | rightChild |
---|
並規定:leftTag取值為false時,指示結點的左孩子,取值為true時,指示結點的前驅;rightTag取值為false時,指示結點的右孩子,取值為true時,指示結點的後繼。
線索連結串列:以上述結點結構構成的二叉連結串列作為二叉樹的儲存結構,其中指示結點前驅及後繼資訊的指標稱作線索,加上線索的二叉樹稱為線索二叉樹。
線索化:對二叉樹以某種次序遍歷使其變為線索二叉樹的過程。
線索化的實質:修改空指標(將二叉連結串列中的空指標改為指向前驅或後繼的線索)。
下面給出相關java程式碼實現:
1. 二叉連結串列結點
package org.sky.tree;
/**
*線索連結串列結點
*
* @author sky
*
* @param <E>
*/
public class ThreadedBinaryTreeNode<E> {
E element;
//左右孩子指標(引用)
ThreadedBinaryTreeNode<E> leftChild;
ThreadedBinaryTreeNode<E> rightChild;
//左右線索標識: true為線索,false為指標
boolean leftTag;
boolean rightTag;
public ThreadedBinaryTreeNode() {
super();
this.element = null;
this.leftChild = null;
this.rightChild = null;
this.leftTag = false; //預設值一定要設為false
this.rightTag = false; //預設值一定要設為false
}
public ThreadedBinaryTreeNode(E element) {
super();
this.element = element;
this.leftChild = null;
this.rightChild = null;
this.leftTag = false; //預設值一定要設為false
this.rightTag = false; //預設值一定要設為false
}
public ThreadedBinaryTreeNode(E element,
ThreadedBinaryTreeNode<E> leftChild,
ThreadedBinaryTreeNode<E> rightChild, boolean leftTag,
boolean rightTag) {
super();
this.element = element;
this.leftChild = leftChild;
this.rightChild = rightChild;
this.leftTag = leftTag;
this.rightTag = rightTag;
}
public E getElement() {
return element;
}
public void setElement(E element) {
this.element = element;
}
public ThreadedBinaryTreeNode<E> getLeftChild() {
return leftChild;
}
public void setLeftChild(ThreadedBinaryTreeNode<E> leftChild) {
this.leftChild = leftChild;
}
public ThreadedBinaryTreeNode<E> getRightChild() {
return rightChild;
}
public void setRightChild(ThreadedBinaryTreeNode<E> rightChild) {
this.rightChild = rightChild;
}
public boolean isLeftTag() {
return leftTag;
}
public void setLeftTag(boolean leftTag) {
this.leftTag = leftTag;
}
public boolean isRightTag() {
return rightTag;
}
public void setRightTag(boolean rightTag) {
this.rightTag = rightTag;
}
}
2. 線索連結串列實現
package org.sky.tree;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
/**
* 二叉樹的線索連結串列實現
*
* @author sky
*
* @param <E>
*/
public class ThreadedBinaryTree<E> {
private ThreadedBinaryTreeNode<E> root; //二叉連結串列根結點
private ThreadedBinaryTreeNode<E> threadedRoot; //線索連結串列頭結點
private ThreadedBinaryTreeNode<E> preVisit = null; //線索化時用,用於指向剛剛訪問過的結點
public ThreadedBinaryTree() {
super();
this.root = new ThreadedBinaryTreeNode<E>();
}
public boolean isEmpty() {
if (root == null) {
return true;
}
return false;
}
public ThreadedBinaryTreeNode<E> getThreadedRoot() {
return threadedRoot;
}
public ThreadedBinaryTreeNode<E> getRoot() {
return root;
}
/**
* 將傳入的資料隨機分配在二叉樹的結點,以生成二叉連結串列
* @param node
* @param element
*/
public void createTreeRandomly(ThreadedBinaryTreeNode<E> node, E element){
if(root == null){
root = new ThreadedBinaryTreeNode<E>(element);
}else{
if(Math.random() > 0.5){
if(node.leftChild == null){
node.leftChild = new ThreadedBinaryTreeNode<E>(element);
}else{
createTreeRandomly(node.leftChild,element);
}
}else{
if(node.rightChild == null){
node.rightChild = new ThreadedBinaryTreeNode<E>(element);
}else{
createTreeRandomly(node.rightChild,element);
}
}
}
}
/**
* 根據傳入的集合建立完全二叉樹
* 此處利用了完全二叉樹父結點和子結點間的關係:如果i=1,則結點i為根結點,否則其雙親結點為[i/2];
* 如果2i > n,則結點i無左孩子,否則其左孩子結點為2i;
* 如果2i+1 > n,則結點i無右孩子,否則右孩子結點為2i+1;
* @param c
*/
public void createCompleteBinaryTree(Collection<? extends E> c){
if(c != null && c.size() > 0){
List<ThreadedBinaryTreeNode<E>> treeList = new LinkedList<ThreadedBinaryTreeNode<E>>();
for(Object o : c){
ThreadedBinaryTreeNode<E> threadedBinaryTreeNode = new ThreadedBinaryTreeNode<E>((E)o);
treeList.add(threadedBinaryTreeNode);
}
LinkedBlockingDeque<ThreadedBinaryTreeNode<E>> queue = new LinkedBlockingDeque<ThreadedBinaryTreeNode<E>>();
//對前treeList.size()/2 - 1個父節點按照父節點與孩子節點的數字關係建立二叉樹
for(int parentIndex = 0; parentIndex < treeList.size()/2; parentIndex++){
if(parentIndex == 0){
root = treeList.get(parentIndex);
//左子樹
root.leftChild = treeList.get(parentIndex*2 + 1);
queue.add(root.leftChild);
//右子樹
root.rightChild = treeList.get(parentIndex*2 +2);
queue.add(root.rightChild);
}else{
if(!queue.isEmpty() && parentIndex*2+1 < treeList.size()){
ThreadedBinaryTreeNode<E> node = (ThreadedBinaryTreeNode<E>) queue.poll();
if(parentIndex*2+1 < treeList.size()){
//左子樹
node.leftChild = treeList.get(parentIndex*2 + 1);
queue.add(node.leftChild);
}
if(parentIndex*2+2 < treeList.size()){
//右子樹
node.rightChild = treeList.get(parentIndex*2 + 2);
queue.add(node.rightChild);
}
}else{
return ;
};
}
}
}
}
/**
* 中序遍歷二叉樹,並將其中序線索化
* 注:線索化的過程即為遍歷的過程中修改空指標的過程
*/
public void inOrderThreading(ThreadedBinaryTreeNode<E> root){
if(threadedRoot == null){
threadedRoot = new ThreadedBinaryTreeNode<E>();
}
threadedRoot.leftTag = false;
threadedRoot.rightTag = true;
threadedRoot.rightChild = threadedRoot; //右指標回指
if(root == null){
threadedRoot.leftChild = threadedRoot;
}else{
threadedRoot.leftChild = root;
preVisit = threadedRoot;
inThreading(root); //中序線索化
preVisit.rightTag = true;
preVisit.rightChild = threadedRoot;
threadedRoot.rightChild = preVisit;
}
}
//中序遍歷進行中序線索化
public void inThreading(ThreadedBinaryTreeNode<E> currentVisit){
if(currentVisit != null){
inThreading(currentVisit.leftChild); //左子樹線索化
if(currentVisit.leftChild == null){ //前驅線索
currentVisit.leftTag = true;
currentVisit.leftChild = preVisit;
}
if(preVisit.rightChild == null){ //後繼線索
preVisit.rightTag = true;
preVisit.rightChild = currentVisit;
}
preVisit = currentVisit; //保持preVisit指向剛剛訪問過的結點
inThreading(currentVisit.rightChild); //右子樹線索化
}
}
/**
* 中序遍歷線索二叉樹
*/
public void inOrderTraverse(ThreadedBinaryTreeNode<E> threadedRoot){
ThreadedBinaryTreeNode<E> pointer = threadedRoot.leftChild; //指向根結點
while(pointer != threadedRoot){
while(pointer.leftTag == false){
pointer = pointer.leftChild;
}
visit(pointer); //訪問左子樹為空的結點
while(pointer.rightTag == true && pointer.rightChild != threadedRoot){
//訪問後繼結點
pointer = pointer.rightChild;
visit(pointer);
}
pointer = pointer.rightChild;
}
}
/**
* 訪問當前結點
* @param current
*/
public void visit(ThreadedBinaryTreeNode<E> current){
if(current != null && current.element != null){
System.out.println(current.element);
}else{
System.out.println("null");
}
}
}