数据结构-树的那些事(四)

简介: 树是一种类似于链表的数据结构,链表是以线性的方式简单地指向其后端地节点,而树的一个节点可以指向许多个节点,树是一种典型的非线性结构。树是一种表达具有层次特征的图结构的一种方法。

一、树


   树是一种类似于链表的数据结构,链表是以线性的方式简单地指向其后端地节点,而树的一个节点可以指向许多个节点,树是一种典型的非线性结构。树是一种表达具有层次特征的图结构的一种方法。


二、树的基本术语

  1005447-20170810132146995-2004919523.png

 

  上图是一张树的结构,针对这张图我们来理解下树的基本术语;

  根节点:其中的A点就是树跟结点,A点的特征就是没有双亲结点,另外一个点就是一棵树只有一个根结点;

   叶子结点:其中K,L,F,G,M,I,J是叶子节点,没有孩子的结点就是叶子结点;

   兄弟结点:拥有共同的双亲结点的孩子结点就是叶子结点,其中B,C,D是A的兄弟结点;

   结点的大小:结点的大小是子孙的个数包括自身结点,其中C的大小为2;

   树的层:位于相同深度的所层叫做树的层,上图就是3层树,层是从0开始计算的;

   树的高度:树中所有结点高度的最大值,树的深度所有结点深度的最大值,一棵树深度和高度是相同的,但是针对于子节点来说不一定相同的;

   斜树:每个结点自有一个孩子结点的树叫做斜树,只有左孩子结点的叫做左斜树,同理叫做右斜树;


三、理解下树的结构问题


   在这之前我们讨论过顺序存储和链表存储的结构,这里我们借助于二叉树来讨论下树的存储结构,

   1005447-20170810134823433-819016268.png

   上图就是二叉树,我左边已经给出最好的数据结构类型,不够还是要来说下为什么不用顺序存储二叉树,现在假设A左子树是不存在的,按照顺序存储该二叉树的话我们需要2的5次方减去1才能将这二叉树完整的还原,在空间上就有了很大的浪费,所以我们选择链表的结构来存储二叉树,例如左图,还是同样的假设A的左子树只是指向一个空结点就够了,在空间基本没有浪费,就可以将完整的二叉树还原;


四、谈一下二叉树


   上面我们已经引入二叉树的概念,什么是二叉树?如果一棵树每个结点最多拥有2个孩子结点,那么这个树就是二叉树,就算只有根节点也是满足二叉树的;

1005447-20170810140039370-1216182675.png

   上图是几种不同的二叉树,根据子节点的不同可以分成以上几种不同的二叉树,这里解释一下完全二叉树,如果所有的孩子结点深度为h或者h-1,并且结点的编号没有漏调任数字,就叫做完全二叉树,这里还要引入一个严格二叉树的概念,每个结点要么有2个孩子结点,要么没有孩子结点,来几句绕的话,满二叉树一定是完全二叉树,也一定是严格二叉树,完全二叉树不一定是严格二叉树;

    接下来我们谈一下二叉树的遍历,根据结点处理顺序的不同我们分为3种遍历的方法:

    前序遍历:根左右

    中序遍历:左根右

    后序遍历:左右根

1005447-20170811134056538-928288251.png

    一看图就明白了,这里我想说下在实现3中遍历的时候我使用了递归和非递归的方法,先说下递归和迭代个各自的优缺点:

    递归

    1)当满足条件时候,递归终址;

    2)每次调用递归都需要额外的空间用于内存的开销;

    3)如果出现无穷递归程序会内存耗尽,导致栈溢出;

    4)有些情况使用递归可以更容易解决;

    迭代

     1)当满足条件的时候,迭代终止;

     2)每次迭代不需要额外的空间开销;

     3)一旦出现死循环由于没有额外的空间开销,程序不会报错但会一直循环执行;

     4)有些情况可能使用迭代的时候代码不好写;

     基本情况介绍到这里希望大家循环和迭代的时候考虑好,还需说明一下我使用非递归进行遍历的时候的一些想法和思路,在进行前序和中序遍历借助栈的先进后出的特点进行遍历的,后序遍历稍显不一样使用的是两个栈进行的遍历;

      前序:

      首先遍历根节点,然后将右子树压栈,最后将左子树压栈,因为后进先出所以首先出栈的是左子树;

      中序:

       首先将根结点和左子树进行压栈,然后出栈,在出栈的同时将右子树进行压栈;

       后序:

       首先定义两个栈,一个用于输出显示,另外一个用于保存过渡,声明以后按照根左右的顺序依次将结点进行压栈,导致保存过渡的出栈顺序成为右左根,然而在进行循环保存过渡栈的时候,会进行对输出栈进行压栈,所以就导致输出栈入栈的顺序是根右左,出栈的时候是左右根;

       以上是非递归遍历的时候注意点,另外我还使用了一种层次遍历,是借助于队列的特征,按照根左右的特点进行入队,最后就根左右的顺序出队,下面上代码;

       结点的定义:

/**
 * Created by wangt on 2017/8/1.
 * 树节点的定义
 */
public class Node<T> {
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
    public Node<T> getLchild() {
        return lchild;
    }
    public void setLchild(Node<T> lchild) {
        this.lchild = lchild;
    }
    public Node<T> getRchilid() {
        return rchilid;
    }
    public void setRchilid(Node<T> rchilid) {
        this.rchilid = rchilid;
    }
    //数据
    private  T data;
    //左子树
    private Node<T> lchild;
    //右子树
    private Node<T> rchilid;
    public Node(T data)
    {
        this.data=data;
    }
    public  Node(T data,Node<T> lchild,Node<T> rchilid)
    {
        this.data=data;
        this.lchild=lchild;
        this.rchilid=rchilid;
    }
}

   二叉树的遍历以及基本操作:

public class BinaryTree<T> {
    public Node<T> getRoot() {
        return root;
    }
    private Node<T> root;
    public BinaryTree(T data) {
        this.root=new Node<T>(data);
    }
    /*
    判断是否为空
     */
    public boolean isEmpty() {
        return  this.root==null;
    }
    /*
    在某P节点左子树下插入data
     */
    public void insertLeft(Node<T> p,T data)
    {
        Node<T> tempNode=new Node<T>(data);
        tempNode.setLchild(p.getLchild());
        p.setLchild(tempNode);
    }
    /*
    在某P节点右子树下插入data
     */
    public void insertRight(Node<T> p,T data)
    {
        Node<T> tempNode=new Node<T>(data);
        tempNode.setRchilid(p.getRchilid());
        p.setRchilid(tempNode);
    }
    /*
    删除左子树
     */
    public Node<T> removeLeft(Node<T> p)
    {
        if(p==null||p.getLchild()==null)
        {
            return null;
        }
        Node<T> tempNode=p.getLchild();
        p.setLchild(null);
        return tempNode;
    }
    /*
    删除右子树
     */
    public Node<T> removeRight(Node<T> p)
    {
        if (p==null||p.getRchilid()==null)
        {
            return null;
        }
        Node<T> tempNode=p.getRchilid();
        p.setRchilid(null);
        return  tempNode;
    }
    /*
    下面3个方法是递归遍历得前 中 后 遍历
     */
    public void preOrder(Node<T> node)
    {
        if(node!=null)
        {
            // 根->左->右
            System.out.print(node.getData()+" ");
            preOrder(node.getLchild());
            preOrder(node.getRchilid());
        }
    }
    public void midOrder(Node<T> node)
    {
        if(node!=null)
        {
            // 左->根->右
            midOrder(node.getLchild());
            System.out.print(node.getData()+" ");
            midOrder(node.getRchilid());
        }
    }
    public  void postOrder(Node<T> node)
    {
        if (node != null)
        {
            // 左->右->根
            postOrder(node.getLchild());
            postOrder(node.getRchilid());
            System.out.print(node.getData() + " ");
        }
    }
    /*
    下面3个方法是非递归遍历
     */
    //前序
    public void preOrderNoRecurise(Node<T> node)
    {
        if(node==null)
        {
            return;
        }
        Stack<Node<T>> stack=new Stack<Node<T>>();
        stack.push(node);
        Node<T> tempNode=null;
        while (stack.size()>0)
        {
            //利用stack后进先出得特点进行前序遍历
            tempNode=stack.pop();
            System.out.print(tempNode.getData());
            if (tempNode.getRchilid()!=null)
            {
                stack.push(node.getLchild());
            }
            if (tempNode.getLchild()!=null)
            {
                stack.push(node.getLchild());
            }
        }
    }
    //中序
    public  void midOrderNoRecurise(Node<T> node)
    {
        if (node==null)
        {
            return;
        }
        Stack<Node<T>> stack=new Stack<Node<T>>();
        Node<T> tempNode=node;
        while (stack.size()>0||tempNode!=null)
        {
            //先将左子树压栈
            while (tempNode!=null)
            {
                stack.push(tempNode);
                tempNode=tempNode.getLchild();
            }
            //左子树出栈
            tempNode=stack.pop();
            //跟
            System.out.print(tempNode.getData());
            //右
            tempNode=tempNode.getRchilid();
        }
    }
    //后序遍历
    public void postOrderNoRecurise(Node<T> node)
    {
        if (node==null)
        {
            return;
        }
        //定义2个栈一个出栈一个入栈
        Stack<Node<T>> stackIn=new Stack<Node<T>>();
        Stack<Node<T>> stackOut=new Stack<Node<T>>();
        Node<T> currentNode =null;
        //跟先压栈
        stackIn.push(node);
        while (stackIn.size()>0)
        {
            currentNode=stackIn.pop();
            stackOut.push(currentNode);
            //左子树压栈
            while (currentNode.getLchild()!=null)
            {
                stackIn.push(currentNode.getLchild());
            }
            //右子树压栈
            while (currentNode.getRchilid()!=null)
            {
                stackIn.push(currentNode.getRchilid());
            }
        }
        while (stackOut.size()>0)
        {
            Node<T> outNode=stackOut.pop();
            System.out.print(outNode.getData());
        }
    }
    //层次遍历
    public void levelOrder(Node<T> node)
    {
        if (node==null)
        {
            return;
        }
        LinkedList<Node<T>> queue=new LinkedList<Node<T>>();
        queue.offer(node);
        Node<T> tempNode=null;
        while (queue.size()>0)
        {
            tempNode=queue.poll();
            System.out.print(tempNode.getData());
            if (tempNode.getLchild()!=null)
            {
                queue.offer(tempNode.getLchild());
            }
            if (tempNode.getRchilid()!=null)
            {
                queue.offer(tempNode.getRchilid());
            }
        }
    }
}

   测试代码:

public class BinaryTest {
    public static void main(String[] args) {
        BinaryTree<String> binaryTree=new BinaryTree<String>("A");
        Node<String> rootNode=binaryTree.getRoot();
        binaryTree.insertLeft(rootNode,"B");
        binaryTree.insertRight(rootNode,"C");
        Node<String> nodeB=rootNode.getLchild();
        binaryTree.insertLeft(nodeB,"D");
        binaryTree.insertRight(nodeB,"E");
        Node<String> nodeC=rootNode.getRchilid();
        binaryTree.insertRight(nodeC,"F");
        binaryTree.levelOrder(rootNode);
        binaryTree.midOrder(rootNode);
    }
}


五、暂停数据结构和集合介绍


    最近进行了SSH的学习,但是发现由于工作中一直使用C#语言做开发,所以导致会忘记,所以准备做一下Meavn整合SSH系列文章,然后在进行数据结构和集合系列,SSH系列的基本想法就是将基本的1对多,多对多这些关系做一下,还有就是框架整合时候的注意点等等,我这边进行思考下,在下片开头会进行详细介绍,至于题目我想暂定.Net开发玩转Java开发;      

相关文章
|
2月前
|
算法
数据结构之博弈树搜索(深度优先搜索)
本文介绍了使用深度优先搜索(DFS)算法在二叉树中执行遍历及构建链表的过程。首先定义了二叉树节点`TreeNode`和链表节点`ListNode`的结构体。通过递归函数`dfs`实现了二叉树的深度优先遍历,按预序(根、左、右)输出节点值。接着,通过`buildLinkedList`函数根据DFS遍历的顺序构建了一个单链表,展示了如何将树结构转换为线性结构。最后,讨论了此算法的优点,如实现简单和内存效率高,同时也指出了潜在的内存管理问题,并分析了算法的时间复杂度。
65 0
|
6天前
|
存储 C++
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
38 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
6天前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
31 12
|
6天前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
32 10
|
6天前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
25 2
|
3月前
|
存储 算法 搜索推荐
探索常见数据结构:数组、链表、栈、队列、树和图
探索常见数据结构:数组、链表、栈、队列、树和图
135 64
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
81 5
|
2月前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
119 16
|
2月前
|
算法
数据结构之文件系统模拟(树数据结构)
本文介绍了文件系统模拟及其核心概念,包括树状数据结构、节点结构、文件系统类和相关操作。通过构建虚拟环境,模拟文件的创建、删除、移动、搜索等操作,展示了文件系统的基本功能和性能。代码示例演示了这些操作的具体实现,包括文件和目录的创建、移动和删除。文章还讨论了该算法的优势和局限性,如灵活性高但节点移除效率低等问题。
72 0
|
3月前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
37 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)