1. 程式人生 > >資料結構與演算法之九 樹結構

資料結構與演算法之九 樹結構

視訊課堂https://edu.csdn.net/course/play/7621
在本章中,你將學習: 在樹中儲存資料 實現二叉樹 實現二叉搜尋樹 假設你被要求呈現作業系統的目錄結構。 目錄結構含有不同的資料夾和檔案。一個資料夾可能含有更多的子資料夾和檔案。 在這種情況下,要用線型結構來表示這種結構幾乎是不可能的,因為所有的專案之間都有層級 關係。 要表示這樣的結構,就需要一種非線型的資料儲存機制。

樹是非線性儲存的結構(物理結構),同時也是邏輯結構; 棧、佇列:邏輯結構;是線性儲存,採用陣列/連結串列來實現; 應用:作業系統目錄、樹形控制元件、樹形選單.通過樹形控制元件、樹形選單來實現行政區域圖表示,部門級別管理等等。樹形結構元素之間的關係是有層次的。 二叉樹的實際應用!求助 今天在做MIS系統時遇到一個問題,一個部門的資料庫的設計!部門的組織結構圖很明顯是資料結構中的樹的結構,對於樹的三種儲存結構來說(雙親,孩子,孩子-兄弟表示法),該採用那種資料結構呢?
說明:我做的這個部門表因為設計的部門多,修改頻繁,所以可能雙親方法不是最好的!

樹被應用於資料元素之間的關係以層級關係來表示的應用程式中。


最頂層的節點被稱為根(根節點)。


樹中的每一個節點在其層級下可能有子樹。


葉子節點:指沒有子節點的節點。


子樹: 是樹結構的一部分, 但它本身也可被看作一個樹結構,這就是子樹。 子樹也可以含有葉子節點。


邊: 從父節點到子節點的連線被稱為一個邊。


兄弟:
它指同一個節點的子節點。


節點的層級: 它指一個節點與根節點之間的距離(到根節點的邊的數目)。 根節點永遠位於 0 級。 當你將樹移至低處,層級增加 1


樹結構的深度: 指一個樹結構的最大層級 +1 下面的樹結構的深度是 4

定義二叉樹

一個二叉樹就是每個節點 只能 最多擁有 2 個子節點的樹結構。這些子節
點一般被視為左子節點和右子節點。 二叉樹有各種型別: 嚴格二叉樹 滿二叉樹
完整二叉樹

滿二叉樹 深度 d 的二叉樹擁有剛好 2d 1 個節點。

完整二叉樹: 指有 n 個節點且深度為 d ,且其節點對應深度為 k 的完整二叉樹中序號從 0 n 1 的節點。


二叉樹的陣列表示: 所有節點被表示為陣列中的元素。


二叉樹的連結表現形式: 使用連結列表來實現一個二叉樹。 連結表示中的每個節點都具有以下資訊: 資料 對左子節點的引用 對右子節點的引用 如果一個節點不含有左子節點或右子節點,或一個子節點都沒有,相應的左(右) 子節點欄位就指向 NULL


你可以在二叉樹上執行各種操作。 在二叉樹上最常見的操作是遍歷。 遍歷指的是訪問樹中所有節點一次的過程。 ( 依次訪問 ) 遍歷二叉樹有三種方式: 中序遍歷( Inorder traversal 前序遍歷( Preorder traversal 後序遍歷( Postorder traversal


中序遍歷一個二叉樹所需的步驟如下:       1. 遍歷左子樹       2. 訪問根節點       3. 遍歷右子樹 讓我們考慮一個示例。


按前序遍歷一個二叉樹的順序如下:       1. 訪問根節點       2. 遍歷左子樹       3. 遍歷右子樹


在二叉樹中進行後序遍歷的步驟如下: 1.   遍歷左子樹 2. 遍歷右子樹 3. 訪問根節點


實現一個二叉搜尋樹

假設有一家行動電話公司儲存著它遍及全世界的數百萬的客戶資訊。每個客戶都分配有一個唯一的身份號(id)。可以通過各自id來讀取每個客戶的記錄。這些id需要以排序後的方式進行儲存,這樣你就能輕鬆對這些資料進行事務操作,如搜尋、插入和刪除



你會用什麼資料結構來儲存客戶的 id 你可以使用一個數組嗎? 在陣列中搜索操作是很快的。 然而,在陣列中插入和刪除卻是很複雜的。 在這種情況下,要儲存的客戶 id 的數量是很大的。因此,插入和刪除將會非常耗時。 你可以使用一個連結列表嗎? 在連結列表中插入和刪除操作是很快的。 然而,連結列表僅允許順序搜尋。


如果你需要訪問一個特定客戶
id (位於列表末端),那麼就要求你訪問所有前面的節 點,這也將非常耗時。

二叉搜尋樹是每個節點都滿足以下條件的二叉樹: 節點的左子樹的所有值小於該節點的值。 ( 左子樹的所有節點)

節點的右子樹的所有值大於該節點的值。
( 右子樹的所有節點)

你可以在二叉搜尋樹上執行各種操作: 遍歷 搜尋 插入

刪除

要搜尋特定值,你需要執行以下步驟: 1. currentNode 指向根節點。 2. 如果 currentNode null a. 顯示 沒發現 b. 退出 3. 將需要搜尋的值與 currentNode 的值進行比較。取決於比較結果,有三種可能性: a. 如果該值等於 currentNode 的值:  i.      顯示 發現  ii.     退出 b. 如果該值小於 currentNode 的值:  i.      currentNode 指向它的左子節點  ii.     跳轉到步驟 2 c. 如果該值大於 currentNode 的值:  i.      currentNode 指向它的右子節點           ii.     跳轉到步驟 2


在實施插入操作前,需要首先檢查樹是否為空。 如果樹是空的,新節點將成為根節點。 如果樹不是空的,就需要為要插入的新節點找到合適的位置。 這就要求你查詢要插入的新節點的父節點。 一旦找到父節點,新節點就作為父節點的左子節點或右子節點被插入。 要找到要插入的新節點的父節點,你需要在樹中執行搜尋操作。

活動:實現一個二叉搜尋樹
問題描述: 編寫程式來實現在含有字典中單詞的二叉搜尋樹上的插入和遍歷操作。 小結
在本章中,你已經學到: 一個樹結構就是以非線型資料結構來表示不同資料元素之間的層級關係。 一個二叉樹就是一個特定型別的樹,其中的每個節點最多隻能有 2 個子節點。 二叉樹可以使用陣列來實施,也可以使用連結列表,取決於需求。 樹的遍歷操作就是訪問樹中所有節點一遍。有三種類型的遍歷,中序、前序和後 序遍歷。 二叉搜尋樹的特點就是樹中節點的左子節點的值永遠小於該節點的值,而節點的 右子節點的值永遠大於該節點。

向二叉搜尋樹插入節點需要首先找到節點中適於插入的位置。 需要在從二叉搜尋樹中刪除節點前,檢查下列三個條件: 要刪除的節點是否為葉子節點 要刪除的節點是否只有一個子節點(左或右子節點) 要刪除的節點是否含有兩個子節點

using System;
using System.Collections.Generic;
using System.Text;

namespace BinarySearchTree
{
    // 二叉排序樹節點類
    public class Node
    {
        public string info;//存放節點的值
        public Node lchild;//存放左子樹的引用
        public Node rchild;//存放右子樹的引用

        public Node(string info, Node lchild, Node rchild)
        {
            this.info = info;
            this.lchild = lchild;
            this.rchild = rchild;
        }
    }

    // 二叉排序樹類
    public class BinaryTree
    {
        //存放二叉排序樹的根節點
        public Node root;

        
        public BinaryTree()
        {
            root = null;//表示二叉排序樹是一棵空樹
        }

        //在查詢某個元素或插入某個元素時使用
        public void find(string element, ref Node parent, ref Node currentNode)
        {
            currentNode = root;
            parent = null;

            /*
             在二叉排序樹查詢是否有節點的值等於element。
             
             如果存在,當退出此迴圈時currentNode就指向這個要查詢的節點,
             parent指向此節點的父親節點;
             
             如果不存在,當退出此迴圈時currentNode就指null,
             parent指向要插入節點的父親節點;
             
            */
            while (currentNode != null)
            {
                //讓父親指向當前節點
                parent = currentNode;

                //如果要查詢元素的值小於當前節點的值
                if (string.Compare(element , currentNode.info)<0)
                {
                    //讓當前節點指向當前節點左子樹
                    currentNode = currentNode.lchild;
                }
                //如果要查詢元素的值大於當前節點的值
                else if (string.Compare(element, currentNode.info) > 0)
                {
                    //讓當前節點指向當前節點右子樹
                    currentNode = currentNode.rchild;
                }
                else//如果要查詢元素的值等於當前節點的值
                {
                    //表明找到,退出迴圈
                    break;
                }
            }
        }

        //在二叉排序樹中插入元素
        public void insert(string element)
        {

            Node tmp;//存放要插入的節點
            Node parent = null;//存放要插入節點的父親節點
            Node currentNode = null;//存放當前節點

            //第一步,找到要插入元素的父親節點            
            find(element, ref parent, ref currentNode);

            //第二步,在二叉排序樹中插入新節點

            //如果要插入元素不在二叉排序樹,就可以插入此元素,
            //currentNode等於null,parent就指向要插入元素的父親節點
            if (currentNode == null)
            {
                //建立新節點物件
                tmp = new Node(element, null, null);

                //如果是空樹
                if (parent == null)
                {
                    root = tmp;
                }
                else
                {
                    //如果要插入元素的值小於父親節點的值
                    if (string.Compare(element, parent.info) < 0)
                    {
                        //將新節點插入到父親節點的左子樹
                        parent.lchild = tmp;
                    }
                    else//如果要插入元素的值大於父親節點的值
                    {
                        //將新節點插入到父親節點的右子樹
                        parent.rchild = tmp;
                    }
                }

            }
            //如果要插入元素已經在二叉排序樹,currentNode就不等於null
            else
            {
                throw new Exception("二叉樹中已經存在值為 " + element +" 的節點");
            }
        }

        //將節點值等於element的節點從二叉排序樹中刪除
        public void delete(string element)
        {
            Node  parent = null, currentNode = null;

            //第一步:找到要刪除的節點
            find(element, ref parent, ref currentNode);

            //如果currentNode != null,表明找到要刪除的節點
            if (currentNode != null)
            {
                //如果要刪除的節點為葉子節點
                if ((currentNode.lchild == null) && (currentNode.rchild == null))
                {
                    //如果要刪除的節點為根節點
                    if (currentNode == root)
                    {
                        //刪除後,就為空樹
                        root = null;
                    }
                    //如果要刪除的節點是父親節點的左子樹
                    else if (currentNode == parent.lchild)
                    {
                        //將父親節點的左子樹置null
                        parent.lchild = null;
                    }
                    //如果要刪除的節點是父親節點的右子樹
                    else
                    {
                        //將父親節點的右子樹置null
                        parent.rchild = null;
                    }
                }
                else if ((currentNode.lchild != null) && (currentNode.rchild != null))//要刪除的節點有兩個子節點
                {
                    //中序繼任節點
                    Node inorder_suc;
                    //中序繼任節點的父節點
                    Node inorder_suc_parent;

                    //讓中序繼任節點指向要刪除節點的右子樹
                    inorder_suc = currentNode.rchild;
                    //讓中序繼任節點的父節點指向要刪除節點
                    inorder_suc_parent = currentNode;

					//如果中序繼任節點不是要刪除節點的右兒子節點
                    if (inorder_suc.lchild != null)
                    {
                        
                        //找到中序繼任節點,
                        //說白了就是不斷左移的過程,
                        //直到找到最左邊的葉子節點或者只有右子樹的節點
                        while (inorder_suc.lchild != null)
                        {
                            //讓中序繼任節點的父節點指向中序繼任節點
                            inorder_suc_parent = inorder_suc;
                            //讓中序繼任節點指向中序繼任節點的左子節點
                            inorder_suc = inorder_suc.lchild;
                        }

                        //用中序繼任節點的值替換要刪除節點的值
                        currentNode.info = inorder_suc.info;
          
						//刪除中序繼任節點
                        inorder_suc_parent.lchild = inorder_suc.rchild;
                        
                    }
                    //如果中序繼任節點是要刪除節點的右兒子節點
                    else
                    {
						//用中序繼任節點的值替換要刪除節點的值
                        currentNode.info = inorder_suc.info;

						//刪除中序繼任節點
                        inorder_suc_parent.rchild = inorder_suc.rchild;
                    }

                }
                else//要刪除的節點只有一個子節點
                {
                    Node child;//要刪除節點的子節點

                    //如果要刪除的節點只有左子樹
                    if (currentNode.lchild != null)
                    {
                        //將要刪除節點的左子樹賦給child
                        child = currentNode.lchild;
                    }
                    //如果要刪除的節點只有右子樹
                    else
                    {
                        //將要刪除節點的右子樹賦給child
                        child = currentNode.rchild;
                    }

                    //如果要刪除的節點為父節點的左子節點
                    if (currentNode == parent.lchild)
                    {
                        //讓父節點左子節點引用指向child
                        parent.lchild = child;
                    }
                    //如果要刪除的節點為父節點的右子節點
                    else
                    {
                        //讓父節點右子節點引用指向child
                        parent.rchild = child;
                    }
                }
            }
            //如果currentNode == null,表明沒有找到要刪除的節點
            else
            {
                throw new Exception("沒有找到要刪除的節點");
            }
        }

        //中序遍歷,輸入引數為要遍歷樹的根節點
        public void inorder(Node ptr)
        {
            if (root == null)
            {
                Console.WriteLine("tree is empty");
            }
            if(ptr != null)
            {
                //中序遍歷左子樹
                inorder(ptr.lchild);

                //訪問根節點的值
                Console.Write(ptr.info + "  ");

                //中序遍歷右子樹
                inorder(ptr.rchild);
            }

        }

        //先序遍歷,輸入引數為要遍歷樹的根節點
        public void preorder(Node ptr)
        {
            if (root == null)
            {
                Console.WriteLine("tree is empty");
            }
            if (ptr != null)
            {
                //訪問根節點的值
                Console.Write(ptr.info + "  ");

                //先序遍歷左子樹
                preorder(ptr.lchild);

                //先序遍歷右子樹
                preorder(ptr.rchild);
            }

        }

        //後序遍歷,輸入引數為要遍歷樹的根節點
        public void postorder(Node ptr)
        {
            if (root == null)
            {
                Console.WriteLine("tree is empty");
            }
            if (ptr != null)
            {
                //後序遍歷左子樹
                postorder(ptr.lchild);

                //後序遍歷右子樹
                postorder(ptr.rchild);

                //訪問根節點的值
                Console.Write(ptr.info + "  ");
            }

        }
    }
}


using System;
using System.Text;

namespace BinarySearchTree
{
    /* A Node class consists of three things, the information, reference to the 
       right child, and reference to the left child. */
    class Node
    {
        public string info;
        public Node lchild;
        public Node rchild;

        public Node(string i, Node l, Node r) /* Constructor for the Node class */
        {
            info = i;
            lchild = l;
            rchild = r;
        }
    }
    class BinaryTree
    {
        public Node ROOT;
        public BinaryTree()
        {
            ROOT = null; /* Initializing ROOT to null */
        }
        public void insert(string element) /* Inserts a Node in the Binary Search Tree */
        {
            Node tmp, parent = null, currentNode = null;
            find(element, ref parent, ref currentNode);
            if (currentNode != null) /* Checks if the node to be inserted is already present or not */
            {
                Console.WriteLine("Duplicates words not allowed");
                return;
            }
            else /* If the specified Node is not present */
            {
                tmp = new Node(element, null, null); /* creates a Node */
                if (parent == null) /* If the tree is empty */
                    ROOT = tmp;
                else
                    if (String.Compare(element,parent.info) < 0)
                        parent.lchild = tmp;
                    else
                        parent.rchild = tmp;
            }
        }
        public void find(string element, ref Node parent, ref Node currentNode)
        {
            /* This function finds the currentNode of the specified Node as well as the 
               currentNode of its parent. */

            currentNode = ROOT;
            parent = null;
            while ((currentNode != null) && (currentNode.info != element))
            {
                parent = currentNode;
                if (String.Compare(element,currentNode.info)<0)
                    currentNode = currentNode.lchild;
                else
                    currentNode = currentNode.rchild;
            }
        }

        public void inorder(Node ptr) /* Performs the inorder traversal of the tree */
        {
            if (ROOT == null)
            {
                Console.WriteLine("Tree is empty");
                return;
            }
            if (ptr != null)
            {
                inorder(ptr.lchild);
                Console.Write(ptr.info + "   ");
                inorder(ptr.rchild);
            }
        }

        public void preorder(Node ptr) /* Performs the preorder traversal of the tree */
        {
            if (ROOT == null)
            {
                Console.WriteLine("Tree is empty");
                return;
            }
            if (ptr != null)
            {
                Console.Write(ptr.info + "   ");
                preorder(ptr.lchild);
                preorder(ptr.rchild);
            }
        }

        public void postorder(Node ptr) /* Performs the postorder traversal of the tree */
        {
            if (ROOT == null)
            {
                Console.WriteLine("Tree is empty");
                return;
            }
            if (ptr != null)
            {
                postorder(ptr.lchild);
                postorder(ptr.rchild);
                Console.Write(ptr.info + "   ");
            }
        }

        public void remove() /* Deletes the specified Node from the tree */
        {
            if (ROOT == null) /* Checks whether the tree is empty */
            {
                Console.WriteLine("Tree is empty");
                return;
            }
            Node parent = null, currentNode = null;
            string element;
            Console.Write("Enter the word to be deleted: ");
            element = Console.ReadLine();
            find(element, ref parent, ref currentNode); /* Finds the currentNode of the Node and its parent */
            if (currentNode == null)
            {
                Console.WriteLine("\nWord not found in the dictionary");
                return;
            }
            /* Depending upon the status of the child nodes, the lines of code below
               call the appropriate function for performing the deletion of the specified
               node from the tree. */
            if (currentNode.lchild == null && currentNode.rchild == null)
                case_1(ref parent, ref currentNode);
            else if (currentNode.lchild != null && currentNode.rchild == null)
                case_2(ref parent, ref currentNode);
            else if (currentNode.lchild == null && currentNode.rchild != null)
                case_2(ref parent, ref currentNode);
            else
                case_3(ref parent, ref currentNode);
        }

        public void case_1(ref Node parent, ref Node currentNode) /* This function is invoked if the Node to be deleted is the leaf Node */
        {
            if (parent == null)
                ROOT = null;
            else
            {
                if (currentNode == parent.lchild)
                    parent.lchild = null;
                else
                    parent.rchild = null;
            }
        }
        public void case_2(ref Node parent, ref Node currentNode) /* This function is invoked if the node to be deleted has one child (left or right) */
        {
            Node child;
            if (currentNode.lchild != null)
                child = currentNode.lchild;
            else
                child = currentNode.rchild;
            if (parent == null)
                ROOT = child;
            else
                if (currentNode == parent.lchild)
                    parent.lchild = child;
                else
                    parent.rchild = child;
        }
        public void case_3(ref Node parent, ref Node currentNode) /* This function is invoked when the Node to be deleted has two children */
        {
            Node inorder_suc, inorder_parent;
            inorder_parent = currentNode;
            inorder_suc = currentNode.rchild;
            while (inorder_suc.lchild != null)
            {
                inorder_parent = inorder_suc;
                inorder_suc = inorder_suc.lchild;
            }

            currentNode.info = inorder_suc.info;

            if (inorder_suc.lchild == null && inorder_suc.rchild == null)
                case_1(ref inorder_parent, ref inorder_suc);
            else
                case_2(ref inorder_parent, ref inorder_suc);
        }

        static void Main(string[] args)
        {
            BinaryTree b = new BinaryTree();
            while (true)
            {
                Console.WriteLine("\nMenu");
                Console.WriteLine("1. Implement insert operation");
                Console.WriteLine("2. Perform inorder traversal");
                Console.WriteLine("3. Perform preorder traversal");
                Console.WriteLine("4. Perform postorder traversal");
                Console.WriteLine("5. Implement delete operation");
                Console.WriteLine("6. Exit");
                Console.Write("\nEnter your choice (1-6): ");
                char ch = Convert.ToChar(Console.ReadLine());
                Console.WriteLine();
                switch (ch)
                {
                    case '1':
                        {
                            Console.Write("Enter a word: ");
                            string word = Console.ReadLine();
                            b.insert(word);
                        }
                        break;
                    case '2':
                        {
                            b.inorder(b.ROOT);                            
                        }
                        break;
                    case '3':
                        {
                            b.preorder(b.ROOT);                          
                        }
                        break;
                    case '4':
                        {
                            b.postorder(b.ROOT);                           
                        }
                        break;
                    case '5':
                        {
                            b.remove();
                        }
                        break;
                    case '6':
                        return;
                    default:
                        {
                            Console.WriteLine("Invalid option");
                            break;
                        }
                }

            }
        }
    }
}