二叉樹的深度、寬度遍歷及平衡樹

何畢之發表於2018-06-11
namespace Val_Tree
{
    public class Node
    {
        //成員變數
        private object _data; //資料
        private Node _left; //左孩子
        private Node _right; //右孩子
        private int _bf;

        public int BF
        {
            get { return _bf; }
            set { _bf = value; }
        }

        public object Data
        {
            get { return _data; }
            set { _data = value; }
        }
        public Node Left //左孩子
        {
            get { return _left; }
            set { _left = value; }
        }
        public Node Right //右孩子
        {
            get { return _right; }
            set { _right = value; }
        }
        //構造方法
        public Node(object data)
        {
            _data = data;
        }
        public override string ToString()
        {
            return _data.ToString();
        }
    }
}

using System;
using System.Collections;

namespace Val_Tree
{
    public class BinaryTree 
    {
        private Node _head; //頭指標
        private string cStr; //用於構造二叉樹的字串
        public Node Head //頭指標
        {
            get { return _head; }
        }
        //構造方法
        public BinaryTree(string constructStr)
        {
            cStr = constructStr;
            _head = new Node(cStr[0]); //新增頭結點
            Add(_head, 0); //給頭結點新增孩子結點
        }
        private void Add(Node parent, int index)
        {
            int leftIndex = 2 * index + 1; //計算左孩子索引
            if (leftIndex < cStr.Length) //如果索引沒超過字串長度
            {
                if (cStr[leftIndex] != '#') //'#'表示空結點
                {   //新增左孩子
                    parent.Left = new Node(cStr[leftIndex]);
                    //遞迴呼叫Add方法給左孩子新增孩子節點
                    Add(parent.Left, leftIndex);
                }
            }
            int rightIndex = 2 * index + 2;
            if (rightIndex < cStr.Length)
            {
                if (cStr[rightIndex] != '#')
                {   //新增右孩子
                    parent.Right = new Node(cStr[rightIndex]);
                    //遞迴呼叫Add方法給右孩子新增孩子節點
                    Add(parent.Right, rightIndex);
                }
            }
        }
        public void PreOrder(Node node) //先序遍歷
        {
            if (node != null)
            {
                Console.Write(node.ToString()); //列印字元
                PreOrder(node.Left); //遞迴
                PreOrder(node.Right); //遞迴
            }
        }
        public void MidOrder(Node node) //中序遍歷
        {
            if (node != null)
            {
                MidOrder(node.Left); //遞迴
                Console.Write(node.ToString()); //列印字元
                MidOrder(node.Right); //遞迴
            }
        }
        public void AfterOrder(Node node) //後繼遍歷
        {
            if (node != null)
            {
                AfterOrder(node.Left); //遞迴
                AfterOrder(node.Right); //遞迴
                Console.Write(node.ToString()); //列印字元
            }
        }

        public void LevelOrder() //寬度優先遍歷
        {
            Queue queue = new Queue(); //宣告一個隊例
            queue.Enqueue(_head); //把根結點壓入佇列
            while (queue.Count > 0) //只要佇列不為空
            {
                Node node = (Node)queue.Dequeue(); //出隊
                Console.Write(node.ToString()); //訪問結點
                if (node.Left != null) //如果結點左孩子不為空
                {   //把左孩子壓入佇列
                    queue.Enqueue(node.Left);
                }
                if (node.Right != null) //如果結點右孩子不為熔
                {   //把右孩子壓入佇列
                    queue.Enqueue(node.Right);
                }
            }
        }
    }
}
namespace Val_Tree
{
    public interface IBinaryTree
    {
        Node Head { get; }

        bool Add(int value); //新增一個元素

        bool Remove(int value);//刪除指定值

        void RemoveNode(Node node);//刪除指定節點

    }
}

using System;
using System.Collections;

namespace Val_Tree
{

    public class BinarySearchTree : IBinaryTree //實現畫樹介面
    {    //成員變數
        private Node _head; //頭指標
        private Node[] path = new Node[32]; //記錄訪問路徑上的結點
        private int p; //表示當前訪問到的結點在_path上的索引
        Node IBinaryTree.Head //顯式介面實現
        {
            get { return (Node)_head; }
        }
        public bool Add(int value) //新增一個元素
        {   //如果是空樹,則新結點成為二叉排序樹的根
            if (_head == null)
            {
                _head = new Node(value);
                _head.BF = 0;
                return true;
            }
            p = 0;
            //prev為上一次訪問的結點,current為當前訪問結點
            Node prev = null, current = _head;
            while (current != null)
            {
                path[p++] = current; //將路徑上的結點插入陣列
                //如果插入值已存在,則插入失敗
                if ((int)current.Data == value)
                {
                    return false;
                }
                prev = current;
                //當插入值小於當前結點,則繼續訪問左子樹,否則訪問右子樹
                current = (value < (int)prev.Data) ? prev.Left : prev.Right;
            }
            current = new Node(value); //建立新結點
            current.BF = 0;
            if (value < (int)prev.Data) //如果插入值小於雙親結點的值
            {
                prev.Left = current; //成為左孩子
            }
            else //如果插入值大於雙親結點的值
            {
                prev.Right = current; //成為右孩子
            }
            path[p] = current; //將新元素插入陣列path的最後
            //修改插入點至根結點路徑上各結點的平衡因子
            int bf = 0;
            while (p > 0)
            {   //bf表示平衡因子的改變數,當新結點插入左子樹,則平衡因子+1
                //當新結點插入右子樹,則平衡因子-1
                bf = (value < (int)path[p - 1].Data) ? 1 : -1;
                path[--p].BF += bf; //改變當父結點的平衡因子
                bf = path[p].BF; //獲取當前結點的平衡因子
                //判斷當前結點平衡因子,如果為0表示該子樹已平衡,不需再回溯
                //而改變祖先結點平衡因子,此時新增成功,直接返回
                if (bf == 0)
                {
                    return true;
                }
                else if (bf == 2 || bf == -2) //需要旋轉的情況
                {
                    RotateSubTree(bf);
                    return true;
                }
            }
            return true;
        }
        //刪除指定值
        public bool Remove(int value)
        {
            p = -1;
            //parent表示雙親結點,node表示當前結點
            Node node = _head;
            //尋找指定值所在的結點
            while (node != null)
            {
                path[++p] = node;
                //如果找到,則呼叫RemoveNode方法刪除結點
                if (value == (int)node.Data)
                {
                    RemoveNode(node);//現在p指向被刪除結點
                    return true; //返回true表示刪除成功
                }
                if (value < (int)node.Data)
                {   //如果刪除值小於當前結點,則向左子樹繼續尋找
                    node = node.Left;
                }
                else
                {   //如果刪除值大於當前結點,則向右子樹繼續尋找
                    node = node.Right;
                }
            }
            return false; //返回false表示刪除失敗
        }
        //刪除指定結點
        public void RemoveNode(Node node)
        {
            Node tmp = null;
            //當被刪除結點存在左右子樹時
            if (node.Left != null && node.Right != null)
            {
                tmp = node.Left; //獲取左子樹
                path[++p] = tmp;
                while (tmp.Right != null) //獲取node的中序遍歷前驅結點,並存放於tmp中
                {   //找到左子樹中的最右下結點
                    tmp = tmp.Right;
                    path[++p] = tmp;
                }
                //用中序遍歷前驅結點的值代替被刪除結點的值
                node.Data = tmp.Data;
                if (path[p - 1] == node)
                {
                    path[p - 1].Left = tmp.Left;
                }
                else
                {
                    path[p - 1].Right = tmp.Left;
                }
            }
            else //當只有左子樹或右子樹或為葉子結點時
            {   //首先找到惟一的孩子結點
                tmp = node.Left;
                if (tmp == null) //如果只有右孩子或沒孩子
                {
                    tmp = node.Right;
                }
                if (p > 0)
                {
                    if (path[p - 1].Left == node)
                    {   //如果被刪結點是左孩子
                        path[p - 1].Left = tmp;
                    }
                    else
                    {   //如果被刪結點是右孩子
                        path[p - 1].Right = tmp;
                    }
                }
                else  //當刪除的是根結點時
                {
                    _head = tmp;
                }
            }
            //刪除完後進行旋轉,現在p指向實際被刪除的結點
            int data = (int)node.Data;
            while (p > 0)
            {   //bf表示平衡因子的改變數,當刪除的是左子樹中的結點時,平衡因子-1
                //當刪除的是右子樹的孩子時,平衡因子+1
                int bf = (data <= (int)path[p - 1].Data) ? -1 : 1;
                path[--p].BF += bf; //改變當父結點的平衡因子
                bf = path[p].BF; //獲取當前結點的平衡因子
                if (bf != 0) //如果bf==0,表明高度降低,繼續後上回溯
                {
                    //如果bf為1或-1則說明高度未變,停止回溯,如果為2或-2,則進行旋轉
                    //當旋轉後高度不變,則停止回溯
                    if (bf == 1 || bf == -1 || !RotateSubTree(bf))
                    {
                        break;
                    }
                }
            }
        }
        //旋轉以root為根的子樹,當高度改變,則返回true;高度未變則返回false
        private bool RotateSubTree(int bf)
        {
            bool tallChange = true;
            Node root = path[p], newRoot = null;
            if (bf == 2) //當平衡因子為2時需要進行旋轉操作
            {
                int leftBF = root.Left.BF;
                if (leftBF == -1) //LR型旋轉
                {
                    newRoot = LR(root);
                }
                else if (leftBF == 1)
                {
                    newRoot = LL(root); //LL型旋轉
                }
                else //當旋轉根左孩子的bf為0時,只有刪除時才會出現
                {
                    newRoot = LL(root);
                    tallChange = false;
                }
            }
            if (bf == -2) //當平衡因子為-2時需要進行旋轉操作
            {
                int rightBF = root.Right.BF; //獲取旋轉根右孩子的平衡因子
                if (rightBF == 1)
                {
                    newRoot = RL(root); //RL型旋轉
                }
                else if (rightBF == -1)
                {
                    newRoot = RR(root); //RR型旋轉
                }
                else //當旋轉根左孩子的bf為0時,只有刪除時才會出現
                {
                    newRoot = RR(root);
                    tallChange = false;
                }
            }
            //更改新的子樹根
            if (p > 0)
            {
                if ((int)root.Data < (int)path[p - 1].Data)
                {
                    path[p - 1].Left = newRoot;
                }
                else
                {
                    path[p - 1].Right = newRoot;
                }
            }
            else
            {
                _head = newRoot; //如果旋轉根為AVL樹的根,則指定新AVL樹根結點
            }
            return tallChange;
        }
        //root為旋轉根,rootPrev為旋轉根雙親結點
        private Node LL(Node root) //LL型旋轉,返回旋轉後的新子樹根
        {
            Node rootNext = root.Left;
            root.Left = rootNext.Right;
            rootNext.Right = root;
            if (rootNext.BF == 1)
            {
                root.BF = 0;
                rootNext.BF = 0;
            }
            else //rootNext.BF==0的情況,刪除時用
            {
                root.BF = 1;
                rootNext.BF = -1;
            }
            return rootNext; //rootNext為新子樹的根
        }
        private Node LR(Node root) //LR型旋轉,返回旋轉後的新子樹根
        {
            Node rootNext = root.Left;
            Node newRoot = rootNext.Right;
            root.Left = newRoot.Right;
            rootNext.Right = newRoot.Left;
            newRoot.Left = rootNext;
            newRoot.Right = root;
            switch (newRoot.BF) //改變平衡因子
            {
                case 0:
                    root.BF = 0;
                    rootNext.BF = 0;
                    break;
                case 1:
                    root.BF = -1;
                    rootNext.BF = 0;
                    break;
                case -1:
                    root.BF = 0;
                    rootNext.BF = 1;
                    break;
            }
            newRoot.BF = 0;
            return newRoot; //newRoot為新子樹的根
        }
        private Node RR(Node root) //RR型旋轉,返回旋轉後的新子樹根
        {
            Node rootNext = root.Right;
            root.Right = rootNext.Left;
            rootNext.Left = root;
            if (rootNext.BF == -1)
            {
                root.BF = 0;
                rootNext.BF = 0;
            }
            else //rootNext.BF==0的情況,刪除時用
            {
                root.BF = -1;
                rootNext.BF = 1;
            }
            return rootNext; //rootNext為新子樹的根
        }
        private Node RL(Node root) //RL型旋轉,返回旋轉後的新子樹根
        {
            Node rootNext = root.Right;
            Node newRoot = rootNext.Left;
            root.Right = newRoot.Left;
            rootNext.Left = newRoot.Right;
            newRoot.Right = rootNext;
            newRoot.Left = root;
            switch (newRoot.BF) //改變平衡因子
            {
                case 0:
                    root.BF = 0;
                    rootNext.BF = 0;
                    break;
                case 1:
                    root.BF = 0;
                    rootNext.BF = -1;
                    break;
                case -1:
                    root.BF = 1;
                    rootNext.BF = 0;
                    break;
            }
            newRoot.BF = 0;
            return newRoot; //newRoot為新子樹的根
        }

        public void LevelOrder() //寬度優先遍歷
        {
            Queue queue = new Queue(); //宣告一個隊例
            queue.Enqueue(_head); //把根結點壓入佇列
            while (queue.Count > 0) //只要佇列不為空
            {
                Node node = (Node)queue.Dequeue(); //出隊
                Console.Write(node.ToString()+"  "); //訪問結點
                if (node.Left != null) //如果結點左孩子不為空
                {   //把左孩子壓入佇列
                    queue.Enqueue(node.Left);
                }
                if (node.Right != null) //如果結點右孩子不為熔
                {   //把右孩子壓入佇列
                    queue.Enqueue(node.Right);
                }
            }
        }
    }
}

using System;

namespace Val_Tree
{
    class Program
    {
        static void Main(string[] args)
        {
            // 使用字串構造二叉樹
            Console.WriteLine("二叉樹遍歷");
            Console.WriteLine("ABCDE#F");
            BinaryTree bTree = new BinaryTree("ABCDE#F");
            Console.WriteLine("先序遍歷:根左右");
            bTree.PreOrder(bTree.Head); //先序遍歷
            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine("中序遍歷:左根右");
            bTree.MidOrder(bTree.Head); //中序遍歷
            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine("後序遍歷:左右根");
            bTree.AfterOrder(bTree.Head); //後序遍歷
            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine("寬度優先遍歷:");
            bTree.LevelOrder();//寬度優先遍歷
            Console.WriteLine();
            Console.WriteLine();

            //構建一顆平衡樹 並放入1-?個元素後 寬度優先遍歷輸出
            BinarySearchTree tree = new BinarySearchTree();
            for (int i = 1; i <= 7; i++)
                tree.Add(i);
            tree.LevelOrder();

            Console.ReadKey();
        }
    }
}



相關文章