C#數據結構-線索化二叉樹

為什麼線索化二叉樹?

對於二叉樹的遍歷,我們知道每個節點的前驅與後繼,但是這是建立在遍歷的基礎上,否則我們只知道後續的左右子樹。現在我們充分利用二叉樹左右子樹的空節點,分別指向當前節點的前驅、後繼,便於快速查找樹的前驅後繼。

不多說,直接上程式碼:

/// <summary>
/// 線索二叉樹 節點
/// </summary>
/// <typeparam name="T"></typeparam>
public class ClueTreeNode<T>
{
    /// <summary>
    /// 內容
    /// </summary>
    public T data { get; set; }
    /// <summary>
    /// 左樹
    /// </summary>
    public ClueTreeNode<T> leftNode { get; set; }
    /// <summary>
    /// 右樹
    /// </summary>
    public ClueTreeNode<T> rightNode { get; set; }
    /// <summary>
    /// 0 標識左樹 1 標識 當前節點的前驅
    /// </summary>
    public int leftTag { get; set; }
    /// <summary>
    /// 0標識右樹 1 標識 當前節點的後繼
    /// </summary>
    public int rightTag { get; set; }

    public ClueTreeNode()
    {
        data = default(T);
        leftNode = null;
        rightNode = null;
    }

    public ClueTreeNode(T item)
    {
        data = item;
        leftNode = null;
        rightNode = null;
    }
}
/// <summary>
/// 線索化 二叉樹 
/// 
/// 為什麼線索化二叉樹?
/// 第一:對於二叉樹,如果有n個節點,每個節點有指向左右孩子的兩個指針域,所以一共有2n個指針域。
/// 而n個節點的二叉樹一共有n-1條分支線數,也就是說,其實是有 2n-(n-1) = n+1個空指針。
/// 這些空間不存儲任何事物,白白浪費記憶體的資源。
/// 第二:對於二叉樹的遍歷,我們知道每個節點的前驅與後繼,但是這是建立在遍歷的基礎上。
/// 否則我們只知道後續的左右子樹。
/// 第三:對於二叉樹來說,從結構上來說是單向鏈表,引入前驅後繼後,線索化二叉樹可以認為是雙向鏈表。
/// </summary>
/// <typeparam name="T"></typeparam>
public class ClueBinaryTree<T>
{
    /// <summary>
    /// 樹根節
    /// </summary>
    private ClueTreeNode<T> head { get; set; }
    /// <summary>
    /// 線索化時作為前驅轉存
    /// </summary>
    private ClueTreeNode<T> preNode { get; set; }

    public ClueBinaryTree(){
        head = new ClueTreeNode<T>();
    }
    public ClueBinaryTree(T val){
        head = new ClueTreeNode<T>(val);
    }


    public ClueTreeNode<T> GetRoot(){
        return head;
    }

    /// <summary>
    /// 插入左節點
    /// </summary>
    /// <param name="val"></param>
    /// <param name="node"></param>
    /// <returns></returns>
    public ClueTreeNode<T> AddLeftNode(T val, ClueTreeNode<T> node){
        if (node == null)
            throw new ArgumentNullException("參數錯誤");
        ClueTreeNode<T> treeNode = new ClueTreeNode<T>(val);
        ClueTreeNode<T> childNode = node.leftNode;
        treeNode.leftNode = childNode;
        node.leftNode = treeNode;
        return treeNode;
    }

    /// <summary>
    /// 插入右節點
    /// </summary>
    /// <param name="val"></param>
    /// <param name="node"></param>
    /// <returns></returns>
    public ClueTreeNode<T> AddRightNode(T val, ClueTreeNode<T> node){
        if (node == null)
            throw new ArgumentNullException("參數錯誤");
        ClueTreeNode<T> treeNode = new ClueTreeNode<T>(val);
        ClueTreeNode<T> childNode = node.rightNode;
        treeNode.rightNode = childNode;
        node.rightNode = treeNode;
        return treeNode;
    }
    /// <summary>
    /// 刪除當前節點的 左節點
    /// </summary>
    /// <param name="node"></param>
    /// <returns></returns>
    public ClueTreeNode<T> DeleteLeftNode(ClueTreeNode<T> node){
        if (node == null || node.leftNode == null)
            throw new ArgumentNullException("參數錯誤");
        ClueTreeNode<T> leftChild = node.leftNode;
        node.leftNode = null;
        return leftChild;
    }

    /// <summary>
    /// 刪除當前節點的 右節點
    /// </summary>
    /// <param name="node"></param>
    /// <returns></returns>
    public ClueTreeNode<T> DeleteRightNode(ClueTreeNode<T> node){
        if (node == null || node.rightNode == null)
            throw new ArgumentNullException("參數錯誤");
        ClueTreeNode<T> rightChild = node.rightNode;
        node.rightNode = null;
        return rightChild;
    }



    /// <summary>
    /// 中序遍歷線索化二叉樹
    /// </summary>
    public void MiddlePrefaceTraversal(){
        ClueTreeNode<T> node = head;
        while (node != null)
        {
            //判斷是否是
            while (node.leftTag == 0)
            {
                node = node.leftNode;
            }
            Console.Write($" {node.data}");
            while (node.rightTag == 1)
            {
                node = node.rightNode;
                Console.Write($" {node.data}");
            }
            node = node.rightNode;
        }
    }
    /// <summary>
    /// 線索化二叉樹
    /// </summary>
    /// <param name="node"></param>
    public void MiddleClueNodes(ClueTreeNode<T> node){
        if (node == null)
        {
            return;
        }
        //線索化左子樹
        MiddleClueNodes(node.leftNode);
        //當左樹為空時,指向前驅,標識為 1
        if (node.leftNode == null)
        {
            node.leftNode = preNode;
            node.leftTag = 1;
        }
        //如果 前驅的右樹不為空
        if (preNode != null && preNode.rightNode == null)
        {
            preNode.rightNode = node;
            preNode.rightTag = 1;
        }
        preNode = node;
        //線索化右子樹
        MiddleClueNodes(node.rightNode);
    }
}

 

 

 

 

 

 現在我們測試:

//創建樹
ClueBinaryTree<string> clueBinaryTree = new ClueBinaryTree<string>("A");
ClueTreeNode<string> tree1 = clueBinaryTree.AddLeftNode("B", clueBinaryTree.GetRoot());
ClueTreeNode<string> tree2 = clueBinaryTree.AddRightNode("C", clueBinaryTree.GetRoot());
ClueTreeNode<string> tree3 = clueBinaryTree.AddLeftNode("D", tree1);
clueBinaryTree.AddRightNode("E", tree1);
clueBinaryTree.AddLeftNode("F", tree2);
clueBinaryTree.AddRightNode("G", tree2);

clueBinaryTree.MiddleClueNodes(clueBinaryTree.GetRoot());

Console.Write("中序遍歷");
clueBinaryTree.MiddlePrefaceTraversal();

列印結果:

中序遍歷 D B E A F C G