数据结构与算法之九 树结构

简介: 数据结构与算法之九 树结构

视频课堂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;
                        }
                }
            }
        }
    }
}



目录
相关文章
|
6月前
|
算法 Python
Python 数据结构和算法:在 Python 中如何实现链表和树结构?
Python 数据结构和算法:在 Python 中如何实现链表和树结构?
69 0
|
5月前
数据结构篇:链表和树结构的操作方法
数据结构篇:链表和树结构的操作方法
49 0
|
5月前
|
存储 算法
数据结构学习记录——集合及运算(集合的表示、并查集、树结构表示集合、集合运算、查找函数、并运算)
数据结构学习记录——集合及运算(集合的表示、并查集、树结构表示集合、集合运算、查找函数、并运算)
33 0
|
6月前
|
存储 算法 Java
【数据结构】树结构(B树、23树、B+树)
【数据结构】树结构(B树、23树、B+树)
135 0
【数据结构】树结构(B树、23树、B+树)
|
6月前
|
搜索推荐
【数据结构】树结构应用(堆排序、赫夫曼树、赫夫曼编码)
【数据结构】树结构应用(堆排序、赫夫曼树、赫夫曼编码)
58 0
|
存储 算法
数据结构与算法(四):树结构
前面讲到的 顺序表、 栈和队列都是一对一的线性结构,这节讲一对多的线性结构——树。「一对多」就是指一个元素只能有一个前驱,但可以有多个后继。
82 0
数据结构121-树结构的认识
数据结构121-树结构的认识
43 0
数据结构121-树结构的认识
数据结构122-树结构的优点
数据结构122-树结构的优点
88 0
数据结构122-树结构的优点
数据结构124-树结构的表示
数据结构124-树结构的表示
50 0
数据结构124-树结构的表示
|
存储 算法 JavaScript
基础数据结构(五):树结构 Tree(TS版)
基础数据结构(五):树结构 Tree(TS版)
基础数据结构(五):树结构 Tree(TS版)
下一篇
无影云桌面