數據結構之樹學習筆記

一.樹中的節點關係和一些概念node

1.基本概念數組

樹中節點數可使用n來表示this

空樹:n爲0的樹spa

節點的度:指節點的子節點數目,如上圖中B節點爲一度,D節點爲三度3d

父子兄弟關係:如上圖中D是G的父節點,H是D的子節點,G是H的兄弟節點,I是J的堂兄弟節點(這個概念不重要)code

樹的層次:仍是如上圖,A節點爲第一層,B、C節點爲第二層,D、E、F節點爲第三層,G、H、I、J節點爲第四層blog

樹的深度:仍是上圖中,總共有四個層次的節點,稱樹的深度爲4排序

樹中的一些注意事項:1)一個子節點只能有一個父節點,可是一個父節點的子節點數目沒有限制遞歸

          2)子樹之間不能相交ci

          3)樹只能有一個根節點

2.樹的存儲

樹能夠採用鏈式存儲,能夠採用如下的一些方式存儲:

1)在節點中記錄父節點的位置,即父節點存儲法。這個方式能夠很方便找到父節點甚至父節點的父節點,可是尋找子節點時卻須要遍歷查找;

2)在節點中記錄全部子節點的位置,即子節點存儲法。這個方式一樣方便找子節點,可是尋找父節點一樣須要遍歷;

3)在節點中記錄第一個子節點和其餘兄弟節點的位置,即孩子兄弟存儲法。這個方法找子節點和兄弟節點很方便,可是找父節點一樣要遍歷。

 

二.二叉樹

二叉樹中每一個節點的子節點最多隻能有兩個,稱爲左右子節點,左右子節點下方的樹稱爲左右子樹。

1.一些特殊的二叉樹

左斜樹:全部節點只有左子節點的樹。

右斜樹:全部節點只有右子節點的樹。

滿二叉樹:對於一個k層的二叉樹,k-1層的節點都有兩個子節點,第k層的節點都有0個子節點,這樣的二叉樹稱爲滿二叉樹。

徹底二叉樹:對一個有k層的滿二叉樹,第k層的節點有2k-1個,若是將這些節點從右向左連續刪除0-2k-1個後就獲得一個徹底二叉樹。滿二叉樹是特殊的徹底二叉樹。下圖分別是4層的滿樹從右向左連續刪除6個節點和3個節點獲得的兩個徹底二叉樹。

2.二叉樹的存儲

對一個k層的滿二叉樹,每一層的節點數分別是20、21、......、2k-1,總節點數是2k-1。以下圖,將這個滿二叉樹的全部節點一層一層從左到右編號,能夠發現一個有趣的現象:每一個節點的父節點編號都是這個節點編號除以2的商,每一個節點的子節點編號都是這個節點編號乘以2的積(左子節點)或者乘以2加1的和(右子節點)。

根據這個特性可使用順序的方式存儲二叉樹,給每一個節點編號,每一個節點的子節點和父節點均可以經過編號運算獲得。對於非徹底二叉樹,編號時將空缺的位置一同編號便可。下面是三個非徹底二叉樹的編號方式:

 

所以二叉樹的存儲可使用數組進行順序存儲,空缺的節點置爲空便可。同時也因爲空缺的節點都置爲空,所以像右斜樹等不徹底二叉樹會形成嚴重的空間浪費,所以鏈表存儲的方式也能夠採用。

3.二叉樹的遍歷

1)前序遍歷:從根節點開始,先輸出當前節點的數據,再依次遍歷輸出左節點和右節點。

2)中序遍歷:從根節點開始,先輸出當前左節點的數據,再輸出當前節點的數據,最後輸出當前的右節點的數據。

3)後序遍歷:從根節點開始,先輸出當前節點左節點的數據,再輸出當前節點右節點的數據,最後輸出當前節點的數據。

4)層序遍歷:從樹的第一層開始,從上到下逐層遍歷,每一層從左到右依次輸出。

4.二叉樹的代碼實現(C#),這裏使用順序存儲實現了二叉樹的數值添加和幾種遍歷,沒有實現刪除的方法。

    class BiTree<T>
    {
        private T[] data;
        private int count = 0;          //當前二叉樹存儲的數據量
        public BiTree(int capacity)     //當前二叉樹的數據容量
        {
            data = new T[capacity];
        }
        /// <summary>
        /// 向二叉樹中添加數據或者修改二叉樹中的數據
        /// </summary>
        /// <param name="item"></param>須要存儲的數據
        /// <param name="index"></param>須要存儲的數據的編號
        /// <returns></returns>
        public bool Add(T item,int index)
        {
            //校驗二叉樹是否存滿
            if (count >= data.Length)
                return false;
            data[index - 1] = item;
            count++;
            return true;
        }

        public void Traversal()
        {
            FirstTravalsal(1);
            Console.WriteLine();
            MiddleTravalsal(1);
            Console.WriteLine();
            LastTravalsal(1);
            Console.WriteLine();
            LayerTravalsal();
        }

        /// <summary>
        /// 前序遍歷
        /// </summary>
        /// <param name="index"></param>遍歷的數據的編號
        private void FirstTravalsal(int index)
        {
            //校驗編號是否存在
            if (index > data.Length)
                return;
            //校驗數據是否存在,當前位置沒有數據則存儲爲-1
            if (data[index - 1].Equals(-1))
                return;
            //輸出當前數據
            Console.Write(data[index - 1] + " ");
            //計算左右子節點的下標
            int leftNumber = index * 2;
            int rightNumber = index * 2 + 1;
            //遞歸遍歷左右子節點
            FirstTravalsal(leftNumber);
            FirstTravalsal(rightNumber);
        }
        /// <summary>
        /// 中序遍歷
        /// </summary>
        /// <param name="index"></param>遍歷的數據的編號
        private void MiddleTravalsal(int index)
        {
            //校驗編號是否存在
            if (index > data.Length)
                return;
            //校驗數據是否存在,當前位置沒有數據則存儲爲-1
            if (data[index - 1].Equals(-1))
                return;

            //計算左右子節點的下標
            int leftNumber = index * 2;
            int rightNumber = index * 2 + 1;
            //遞歸遍歷左子節點
            FirstTravalsal(leftNumber);
            //輸出當前數據
            Console.Write(data[index - 1] + " ");
            //遞歸遍歷右子節點
            FirstTravalsal(rightNumber);
        }
        /// <summary>
        /// 後序遍歷
        /// </summary>
        /// <param name="index"></param>遍歷的數據的編號
        private void LastTravalsal(int index)
        {
            //校驗編號是否存在
            if (index > data.Length)
                return;
            //校驗數據是否存在,當前位置沒有數據則存儲爲-1
            if (data[index - 1].Equals(-1))
                return;

            //計算左右子節點的下標
            int leftNumber = index * 2;
            int rightNumber = index * 2 + 1;
            //遞歸遍歷左子節點
            FirstTravalsal(leftNumber);
            //遞歸遍歷右子節點
            FirstTravalsal(rightNumber);
            //輸出當前數據
            Console.Write(data[index - 1] + " ");
        }
        /// <summary>
        /// 層序遍歷
        /// </summary>
        private void LayerTravalsal()
        {
            for(int i = 0;i < data.Length;i++)
            {
                //校驗當前數據是否爲空
                if (data[i].Equals(-1))
                    continue;
                //輸出遍歷的數據
                Console.Write(data[i] + " ");
            }
        }
    }

三.二叉排序樹

二叉排序樹的節點位置和節點的大小有關,從根節點開始判斷,比當前節點小就往當前節點的左子樹上移動,反之往當前節點的右子樹上移動,一直判斷直到移動到的位置沒有節點,這個位置就是節點的放置位置。以下圖所示,鏈接線上的數字是節點的放置順序:

 

 能夠看到,一樣的數據,最後存儲出來的二叉樹形狀和數據的放置順序有關。

下面是二叉排序樹的實現(C#),這裏使用鏈表實現了二叉排序樹的節點添、查找和刪除。

   class BSNode
    {
        public BSNode LeftChild{get;set;}
        public BSNode RightChild { get; set; }
        public BSNode Parent { get; set; }

        public int Data { get; set; }

        public BSNode()
        {

        }
        public BSNode(int item)
        {
            this.Data = item;
        }
    }
    class BSTree
    {
        //記錄根節點位置
        public BSNode Root { get; set; }
        /// <summary>
        /// 添加數據
        /// </summary>
        /// <param name="item"></param>要添加的數據,以int爲例,也能夠拓展爲一個泛型
        public void AddNode(int item)
        {
            //新建一個node
            BSNode newNode = new BSNode(item);
            //判斷樹中有沒有節點,沒有節點當前節點做爲根節點,有節點將數據放入節點中
            if (Root == null)
                Root = newNode;
            else
            {
                //定義一個臨時節點記錄當前正在訪問的節點位置
                BSNode temp = Root;
                //死循環,須要不斷判斷節點應該往當前節點左邊仍是右邊放置,且不知道要循環判斷多少次
                while (true)
                {
                    //若是要放置的數據大於當前節點,說明要放置的節點應該往右邊放
                    if(item >= temp.Data)
                    {
                        //判斷當前節點的右邊子節點位置是否已經有節點,若是沒有直接放置而後跳出循環
                        if (temp.RightChild == null)
                        {
                            temp.RightChild = newNode;
                            newNode.Parent = temp;
                            break;
                        }
                        //當前節點右節點位置有數據的狀況下,將臨時節點置爲當前節點的右節點,繼續循環判斷應該往這個節點的哪一邊放置
                        else
                        {
                            temp = temp.RightChild;
                        }
                    }
                    //若是要放置的數據不是大於當前節點,說明要放置的節點應該往左邊放
                    else
                    {
                        if (temp.LeftChild == null)
                        {
                            temp.LeftChild = newNode;
                            newNode.Parent = temp;
                            break;
                        }
                        else
                        {
                            temp = temp.LeftChild;
                        }
                    }
                }
            }
        }
        /// <summary>
        /// 採用中序遍歷能夠實現數據由小到大輸出
        /// </summary>
        /// <param name="node"></param>遍歷輸出node節點及其子節點
        public void MiddleTraversal(BSNode node)
        {
            if (node == null)
                return;

            MiddleTraversal(node.LeftChild);
            Console.Write(node.Data + " ");
            MiddleTraversal(node.RightChild);
        }
        /// <summary>
        /// 查找數據是否在樹中(遞歸方式)
        /// </summary>
        /// <param name="item"></param>要查找的數據
        /// <param name="node"></param>在node節點及其子孫節點中查找
        /// <returns></returns>
        public bool Find1(int item,BSNode node)
        {
            //校驗當前節點是否爲空
            if (node == null)
                return false;
            //判斷節點的數據是否和要查找的數據相同
            else if (item == node.Data)
                return true;
            //判斷要查找的數據和當前數據的大小,決定是繼續在左子樹中查找仍是在右子樹中查找
            else if (item > node.Data)
                return Find1(item, node.RightChild);
            else
                return Find1(item, node.LeftChild);
        }
        /// <summary>
        /// 查找數據是否在樹中(循環方式)
        /// </summary>
        /// <param name="item"></param>要查找的數據
        /// <param name="node"></param>在node節點及其子孫節點中查找
        /// <returns></returns>
        public bool Find2(int item, BSNode node)
        {
            BSNode temp = node;
            while (true)
            {
                //校驗當前節點是否爲空
                if (temp == null)
                    return false;
                //判斷節點的數據是否和要查找的數據相同
                else if (item == temp.Data)
                    return true;
                //判斷要查找的數據和當前數據的大小,決定是繼續在左子樹中查找仍是在右子樹中查找
                else if (item > temp.Data)
                    temp = temp.RightChild;
                else
                    temp = temp.LeftChild;
            }
        }
        /// <summary>
        /// 根據數據查找並刪除存儲數據的節點
        /// </summary>
        /// <param name="item"></param>要刪除的數據
        /// <returns></returns>
        public bool DeleteNode(int item)
        {
            //首先須要查找要刪除的節點是否存在,使用臨時節點temp記錄,若是存在才能刪除,不然不能刪除
            BSNode temp = Root;
            while (true)
            {
                //校驗當前節點是否爲空
                if (temp == null)
                    return false;
                //判斷節點的數據是否和要查找的數據相同,相同就須要刪除這個節點
                else if (item == temp.Data)
                {
                    DeleteNode(temp);
                    return true;
                }
                //判斷要查找的數據和當前數據的大小,決定是繼續在左子樹中查找仍是在右子樹中查找
                else if (item > temp.Data)
                    temp = temp.RightChild;
                else
                    temp = temp.LeftChild;
            }
        }
        /// <summary>
        /// 刪除指定的節點
        /// </summary>
        /// <param name="node"></param>要刪除的節點
        private void DeleteNode(BSNode node)
        {
            //判斷當前節點是否爲根節點,不是根節點刪除時須要修改當前節點的父節點的引用指向,是根節點須要修改Root的值
            if (node.Parent != null)
            {
                //分爲四種狀況,分別是這個節點沒有子節點、只用左子節點、只有右子節點和有左右兩個子節點
                //沒有子節點直接修改父節點的引用,並刪除節點
                if (node.LeftChild == null && node.RightChild == null)
                {
                    if (node.Parent.RightChild == node)
                        node.Parent.RightChild = null;
                    else
                        node.Parent.LeftChild = null;

                    node = null;
                }
                //只有左子節點或者右子節點須要更改父節點的引用,並更改相應子節點的父節點引用
                else if (node.LeftChild == null && node.RightChild != null)
                {
                    if (node.Parent.RightChild == node)
                        node.Parent.RightChild = node.RightChild;
                    else
                        node.Parent.LeftChild = node.RightChild;

                    node.RightChild.Parent = node.Parent;

                    node = null;
                }
                else if (node.LeftChild != null && node.RightChild == null)
                {
                    if (node.Parent.RightChild == node)
                        node.Parent.RightChild = node.LeftChild;
                    else
                        node.Parent.LeftChild = node.LeftChild;

                    node.LeftChild.Parent = node.Parent;

                    node = null;
                }
                //左右子節點都有的狀況下將右子樹的最小值的數據複製到當前節點,而後去刪除右子樹的最小值
                else
                {
                    BSNode temp = node.RightChild;
                    while (true)
                    {
                        if (temp.LeftChild != null)
                            temp = temp.LeftChild;
                        else
                            break;
                    }

                    node.Data = temp.Data;
                    DeleteNode(temp);
                }
            }
            //當前節點是根節點的狀況下和不是根節點的刪除相似,也是四種狀況,不過須要修改的是Root的引用和子節點的父節點引用,不用管父節點
            else
            {
                if (node.LeftChild == null && node.RightChild == null)
                {
                    node = null;
                    Root = null;
                }
                else if (node.LeftChild == null && node.RightChild != null)
                {
                    node.RightChild.Parent = null;
                    Root = node.RightChild;

                    node = null;
                }
                else if (node.LeftChild != null && node.RightChild == null)
                {
                    node.LeftChild.Parent = null;
                    Root = node.LeftChild;

                    node = null;
                }
                else
                {
                    BSNode temp = node.RightChild;
                    while (true)
                    {
                        if (temp.LeftChild != null)
                            temp = temp.LeftChild;
                        else
                            break;
                    }

                    node.Data = temp.Data;
                    DeleteNode(temp);
                }
            }
        }
    }
相關文章
相關標籤/搜索