数据结构之树学习笔记

一.树中的节点关系和一些概念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);
                }
            }
        }
    }