線索二叉樹:
按照某種方式對二叉樹進行遍歷,可以把二叉樹中所有結點排序爲一個線性序列,在該序列中,除第一個結點外每個結點有且僅有一個直接前驅結點;除最後一個結點外每一個結點有且僅有一個直接後繼結點;
在有N個節點的二叉樹中需要利用N+1個空指針添加線索,這是因爲在N個節點的二叉樹中,每個節點有2個指針,所以一共有2N個指針,除了根節點以外,每一個節點都有一個指針從它的父節點指向它,所以一共使用了N-1個指針,所以剩下2N-(N-1)也就是N+1個空指針;
若能利用這些空指針域來存放指向該節點的直接前驅或是直接後繼的指針,則可由此信息直接找到在該遍歷次序下的前驅結點或後繼結點,從而比遞歸遍歷提高了遍歷速度,節省了建立系統棧所使用的存儲空間;
這些被重新利用起來的空指針就被稱爲線索(Thread),加上了這些線索的二叉樹就是線索二叉樹;如圖:
根據線索性質的不同,線索二叉樹可分爲前序線索二叉樹、中序線索二叉樹和後序線索二叉樹三種;
記node指向二叉鏈表中的一個結點,以下是建立線索的規則:
(1)如果node的左指針域爲空,則存放指向某種遍歷序列中該結點的前驅結點,這個結點稱爲node的前驅;
(2)如果node的右指針域爲空,則存放指向中序遍歷序列中該結點的後繼結點。這個結點稱爲node的後繼;
Java代碼:
結點類:
package tree.thread;
public class Node
{
private int data;
private Node left;
private boolean leftIsThread; // 左孩子是否爲線索
private Node right;
private boolean rightIsThread; // 右孩子是否爲線索
public Node(int data)
{
this.data = data;
this.left = null;
this.leftIsThread = false;
this.right = null;
this.rightIsThread = false;
}
public int getData()
{
return data;
}
public void setData(int data)
{
this.data = data;
}
public Node getLeft()
{
return left;
}
public void setLeft(Node left)
{
this.left = left;
}
public boolean isLeftIsThread()
{
return leftIsThread;
}
public void setLeftIsThread(boolean leftIsThread)
{
this.leftIsThread = leftIsThread;
}
public Node getRight()
{
return right;
}
public void setRight(Node right)
{
this.right = right;
}
public boolean isRightIsThread()
{
return rightIsThread;
}
public void setRightIsThread(boolean rightIsThread)
{
this.rightIsThread = rightIsThread;
}
@Override
public boolean equals(Object obj)
{
if (obj instanceof Node)
{
Node temp = (Node) obj;
if (temp.getData() == this.data)
{
return true;
}
}
return false;
}
@Override
public int hashCode()
{
return super.hashCode() + this.data;
}
}
線索二叉樹類:
package tree.thread;
public class ThreadTree
{
private Node root; // 根節點
private int size; // 大小
private Node pre = null; // 線索化的時候保存前驅
public ThreadTree()
{
this.root = null;
this.size = 0;
this.pre = null;
}
public ThreadTree(int[] data)
{
this.pre = null;
this.size = data.length;
this.root = createTree(data, 1); // 創建二叉樹
}
/**
* 創建二叉樹
*
*/
public Node createTree(int[] data, int index)
{
if (index > data.length)
{
return null;
}
Node node = new Node(data[index - 1]);
Node left = createTree(data, 2 * index);
Node right = createTree(data, 2 * index + 1);
node.setLeft(left);
node.setRight(right);
return node;
}
/**
* 將以root爲根節點的二叉樹線索化
*
*/
public void inThread(Node root)
{
if (root != null)
{
inThread(root.getLeft()); // 線索化左孩子
if (null == root.getLeft()) // 左孩子爲空
{
root.setLeftIsThread(true); // 將左孩子設置爲線索
root.setLeft(pre);
}
if (pre != null && null == pre.getRight()) // 右孩子爲空
{
pre.setRightIsThread(true);
pre.setRight(root);
}
pre = root;
inThread(root.getRight()); // 線索化右孩子
}
}
/**
* 中序遍歷線索二叉樹
*
*/
public void inThreadList(Node root)
{
if (root != null)
{
while (root != null && !root.isLeftIsThread()) // 如果左孩子不是線索
{
root = root.getLeft();
}
do
{
System.out.print(root.getData() + ",");
if (root.isRightIsThread()) // 如果右孩子是線索
{
root = root.getRight();
}
else // 有右孩子
{
root = root.getRight();
while (root != null && !root.isLeftIsThread())
{
root = root.getLeft();
}
}
} while (root != null);
}
}
/**
* 前序遍歷遞歸算法
*
*/
public void preList(Node root)
{
if (root != null)
{
System.out.print(root.getData() + ",");
preList(root.getLeft());
preList(root.getRight());
}
}
/**
* 中序遍歷
*
*/
public void inList(Node root)
{
if (root != null)
{
inList(root.getLeft());
System.out.print(root.getData() + ",");
inList(root.getRight());
}
}
public Node getRoot()
{
return root;
}
public void setRoot(Node root)
{
this.root = root;
}
public int getSize()
{
return size;
}
public void setSize(int size)
{
this.size = size;
}
}
測試類:
package tree.thread;
public class ThreadTreeTest
{
public static void main(String[] args)
{
int[] data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
ThreadTree threadTree = new ThreadTree(data); // 創建普通二叉樹
threadTree.inList(threadTree.getRoot()); // 中序遞歸遍歷二叉樹
System.out.println();
threadTree.inThread(threadTree.getRoot()); // 採用中序遍歷將二叉樹線索化
threadTree.inThreadList(threadTree.getRoot()); // 中序遍歷線索化二叉樹
}
}
由於它充分利用了空指針域的空間(等於節省了空間),又保證了創建時的一次遍歷就可以終生受用前驅後繼的信息(這意味着節省了時間),所以在實際問題中,如果所使用的二叉樹需要經常遍歷或查找結點時需要某種遍歷序列中的前驅和後繼,那麼採用線索二叉鏈表的存儲結構就是不錯的選擇;