首页 > 编程语言 >《数据结构与算法》之树

《数据结构与算法》之树

时间:2023-06-11 20:22:26浏览次数:38  
标签:左子 结点 遍历 中序 算法 BT 之树 二叉树 数据结构

导言:

我们在前面的学习中认识到了栈还有队列这些线性的数据存储结构,而现在我们要了解的数据结构却不是线性的了,我们试想线性的结构最大的缺点查询不方便,不管你是从前往后开始查找数据,还是从后往前开始查找数据都是一个一个的比对,

效率很低,所以不推荐使用,那么我们的树结构来存储的话,查找数据会不会被优化呢?

一.树的概念

什么是树?

 从更广义的角度上来说,树状结构(Tree structure),又可称为树形结构,或称树状图,其是一种将层次结构式的构造性质,以图象方式表现出来的方法。

以树的象征来表现出构造之间的关系,不过在图象的呈现上,它是一个上下颠倒的树,其根部在上方,是内容的开头,而下方的内容称为枝干与叶子。

  •  树中有一个被称为根的特殊结点(根Root)
  • 其余的结点互不相交集合,且本身又是一个树

 我么现在定义的树都是无相交集合的,也就是在各个子节点之间是无法直接相连的,而且一个子节点无法同时被两个父节点指向,但是后面会有图等数据结构它又是可以的,注意区分

树的一些基本术语:

 结点的度:结点的子树的个数

如:A结点的度为3,D结点的度为1

树的度:树的结点中最大的度数

如:上面的树结点最大的度是A的3,所以树的度为3

叶节点:度为0的结点

如:结点E,FI,H都是度为0的结点

父结点:如果此结点向下一层可以找的结点,那么此结点即为下一层结点的父结点

如:结点G相对于结点I,结点G为它的上一层,所以G是I的父节点

子结点:如果此节点向上一层可以找的结点,那么对于上一层的结点,自己为子节点,也称自己为孩子结点

如:结点G相对于结点C,结点G为子结点

 兄弟结点:有同一父结点的子节点互称兄弟结点

如:结点E,F相对于结点B都是它的子结点,所以结点E,F互称兄弟结点

路径与路径长度:路径是一个结点到另一个结点所经过的结点序列,它是节点的集合,而路径长度指的是结点到结点边的个数,是边的集合

如:结点A到结点I,它的路径也就是结点序列是: {  A,C,G,I  } 即为4 , 它的路径长度也就是边的个数是: { A -> C  ,  C -> G  ,  G -> I  }即为3

结点的层次:规定根结点在 1 层,其它任意结点的层数是其父结点的层数加 1

如:结点 G的层数是:根节点 A (1)+父节点 C(1)+ 1 即为第 3 层

树的深度:树中的所有结点的最大层次就是树的深度

如:此树的最大深度就是 A  ->  I 为  4  层 

 树的表示:

我们常用的表示方法是:儿子-兄弟表示方法

 它的实现方式是靠每个结点使用两个指针域来标识结点,一个标识的是最左边的孩子结点,一个是标识自己同层次的兄弟结点,事实证明它的空间浪费是最小的,所以使用最多

 这就是使用儿子-兄弟结点实现的上面的那一棵树

二.特别树结构之二叉树

什么是二叉树?

二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成。

二叉树的关键是,如果树存在,那么一定是父结点只有左子结点和右子结点两个结点,并且两个结点可以独立存在,不一定要成对出现它的子结点区间一定是  0 <= n  <=2

 上面的就是一棵很正常的二叉树

在二叉树存在的情况下,我们有的父结点右两个结点,有的只有一个结点,有的没有结点被称为叶子结点

我们来看看几种特殊的二叉树:

 这种叫斜二叉树,它的结点都向一边倾斜

图中的是往左子结点倾斜,其实全部往右倾斜也是一样的,都是斜二叉树,它的结构就和线性结构差不多了,跟链表也差不多

但是它们都是二叉树的一种结构,也是符合二叉书的特点的

 这种是满二叉树,也是很完美的二叉树,

它的子结点都是均匀分布在左右的,而且空间浪费是二叉树中最少的

后面的学习多向满二叉树发展

二叉树的顺序存储:

由于二叉树的规律很明显,即树存在时,一个父结点只有最多有两个结点

且我们的完全二叉树是最适合利用数组来存储的,它的结构特特征很符合数组,并且可以很快的找到父结点和子结点

 从上面的图中我们可以很快的推算出子结点和父结点的位置

父结点:当前结点 / 2(取整)

子结点:左子节点,当前结点 *2 ,右子结点,当前结点 *2 + 1 ,计算的结果要小于 n  (结点的个数),否则无子结点

此外,我们还有一些非完全二叉树,对于它们使用数组来存储的话需要利用null值来补满成完全二叉树,但是会有空间浪费

 二叉树的链表实现:

 三.二叉树的遍历

 二叉树的先序遍历:

遍历过程为:

  1. 先访问根结点
  2. 先序遍历左子树
  3. 先序遍历右子树

 先序遍历部分的代码(递归实现):

void PreOrderTraversal(TreeNode *BT){
    if(BT){ //结点存在 
        printf("%d",BT->Data);
        PreOrderTraversal(BT->Left);
        PreOrderTraversal(BT->Right);
    }
}

 

先序遍历的结果是:A , B , E , F , C ,  G

二叉树的中序遍历:

遍历过程为:

  1. 中序遍历其左子树
  2. 访问根节点
  3. 中序遍历其右子树

 中序遍历部分代码实现(递归):

void PreOrderTraversal(TreeNode *BT){
    if(BT){ //结点存在 
        PreOrderTraversal(BT->Left);
        printf("%d",BT->Data);
        PreOrderTraversal(BT->Right);
    }
}

 

中序遍历的结果是:

E , B , F ,  A  ,  C  ,  G

二叉树的后序遍历:

遍历过程为:

  1. 后序遍历其左子树
  2. 后序遍历其右子树
  3. 访问根结点

 后序遍历部分代码实现(递归):

void PreOrderTraversal(TreeNode *BT){
    if(BT){ //结点存在 
        PreOrderTraversal(BT->Left);
        PreOrderTraversal(BT->Right);
        printf("%d",BT->Data);
    }
}

 

后序遍历的结果:

E  ,  F  ,  B  ,   G  ,  C  ,  A

总结:

其实三种遍历方式都是对根结点位置的描述,然后遵循先左子树后右子树的特点

先序遍历:根节点,左子树,右子树

中序遍历:左子树,根节点,右子树

后序遍历:左子树,右子树,根节点

中序非递归遍历:

中序的非递归遍历使用的是循环的思想,然后使用堆栈作为数据存储结构

我们根据中序遍历的思路,它是先遍历了左子树,然后访问根节点,遍历右子树

所以说我们可以先让根结点入栈,然后一直让左子树的左结点入栈,直到当前的结点没有左节点时,把指针转向它的它的右节点,然后出栈左结点和它的父结点

我们再使用同样的方法入栈当前的右节点的左节点,如果没有左节点就出栈,没有此结点就下一次循环了

 上面是图片解析我们的中序非递归遍历

然后是代码实现:

void InOrderTraversal(TreeNode *BT){
    TreeNode *T=BT;
    Stack s = CreateStack(Maxsize);
    //开辟一个数组栈空间 
    While(T || !IsEmpty(s)){
        // 树结点不为空,并且栈内还有元素 
        while(T){
            //树结点存在 
            push(s,T);
            T=T->Left;
        }
        if(!isEmpty(s)){
            //栈不为空 
            T=pop(s); 
            printf("%d\n",T->Data);
            T=T->Right;
        }
    }
}

中序遍历也可以改为先序遍历,只需要把输出函数放在,入栈函数push()后面即可

层序遍历:

层序遍历的思想是使用队列加循环完成的,它和后面要学习的图的遍历很像

由于队列的特点是先进先出,所以它的遍历是一层一层向下进行的

队列实现:遍历从根结点开始,首先将根结点入队,然后开始执行循环:结点出队,访问此结点,入队它的孩子结点

 使用队列来遍历二叉树,很显然它是一层一层的输出结果的

然后代码实现:

void LevelOrderTraversal(TreeNode *BT){
    Queue Q;
    TreeNode *T;
    if(!BT)
    //空树直接返回 
    return;
    Q = CreateQueue(Maxsize);
    //创建一个队列 
    AddQ(Q,BT);
    //把根结点先入队
    while(!isEmptyQ(Q)){
        T = Delete(Q); 
        printf("%d\n",T->Data);
        //出队结点 
        if(T->Left)
        AddQ(Q,T->Left);
        //入队左子结点 
        if(T->Right)
        AddQ(Q,T->Right);
        //入队右子结点 
    }
}

 

已知两个遍历序列,来求二叉树

已知先序序列:a,b,c ,d,e,f,g,h,i,j

已知中序序列:c,b,e,d,a,h,g,i,j,f

首先我们要根据先序序列,推出我们的根结点为a,那么再中序序列中,a左边的就是左子树,右边的就是右子树

 然后根据中序序列也可以把先序学列划分成左子树和右子树,根据长度划分,左子树b,c ,d,e,右子树f,g,h,i,j,然后各个子树的第一个元素又是当前子树的根结点,又可以再已划分的中序序列中去继续找左子树和右子树

 我们看到左子树,在先序序列中找到e元素和d元素,先序序列中它的顺序是d,e,那么第一个元素就是根结点即d为根结点,e为子结点,有根据中序序列的e,d,说明先访问子结点,那么e一定是左子结点

右边,先序序列是g,h,i,j,说明g是下一层的根结点,由于中序序列它们的父结点是后访问,说明g,h,i,j都位于左子结点,

 我们可以看到,左子树已经推算完了,然后就是右子树,

肯定是一个相对于g的左子结点,主要是i,j,我们看看先序序列也是i,j,说明i是根节点,由于中序遍历的i,j说明先访问根结点,那么j一定是i的右子结点

 这样一棵二叉树就被我们给推算出来了

 

-

-

-

-

-

-

博客编辑于

---------------------浙江大学陈越老师《数据结构与算法》

 

标签:左子,结点,遍历,中序,算法,BT,之树,二叉树,数据结构
From: https://www.cnblogs.com/5ran2yl/p/17472781.html

相关文章

  • 基于半监督学习的单体型组装算法
    基于半监督学习的单体型组装算法李明阳湖南师范大学摘要:单体型组装(HaplotypeAssembly)是根据测序得到的DNA片段通过各种模型算法来重建出生物个体的单体型。随着人类基因组计划(HumanGenomeProject,HGP)的逐渐完成,人们已经认识到个体之间基因序列的差异是造成个体之间......
  • 0001. Kmeans聚类算法
    一、Kmeans原理Kmeans算法是一种常见的聚类算法,用于将数据集划分成k个不重叠的簇。其主要思想是通过迭代的方式将样本电话分到不同的簇中,使得同一簇内的样本点相似度较高,不同簇之间的相似度较低。Kmeans算法的详细步骤:初始化:选择k个初始聚类中心,可以是随机选择或者根据某种启......
  • 【数据结构】查找
    基本概念查找表 由同一类型的数据元素(记录)构成的集合。所谓集合指记录间不存在前驱后继关系,因此查找表是一种应用灵便的结构。静态查找表 只对查找表做查找操作,即只查询某个记录是否在表中,或只检索某个记录的各种属性。或者说:查找表加上不会使该表的内容发生变化的查找操作,称作......
  • Redis学习笔记4-脚本、持久化和集群 Redis学习笔记1-基础命令及数据结构: http://blog.
        Redis学习笔记4-脚本、持久化和集群Redis学习笔记1-基础命令及数据结构:http://blog.guoyb.com/2016/07/21/learn-redis-basic-commands/Redis学习笔记2-事务与过期时间:http://blog.guoyb.com/2016/08/23/learn-redis-adv/Redis学习笔记3-排序与消息通知:http://blog......
  • 算法题总结-分组背包
    原题有N件物品和一个容量为V的背包。第i件物品的费用是Ci,价值是Wi。这些物品被划分为K组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。由于截止目前,没有刷到对应的经典题目,以下以依赖背包的转化题......
  • 关于RL 和DRL中的算法总结
    其中:RL分为基于价值的学习和基于策略的学习和AC架构的价值学习DQNDQN=Q_learing+网络使用了价值网络q(..w)DQN训练的过程基础的DQN就是训练Q网络更新w参数代码中梯度下降用的是下面这一张这里有个问题下面这张图片中有不一样的地方即Gradientdescent......
  • 算法题总结-分组背包与依赖背包
    原题https://www.nowcoder.com/practice/f9c6f980eeec43ef85be20755ddbeaf4?tpId=37&tqId=21239&rp=1&ru=/exam/oj/ta&qru=/exam/oj/ta&sourceUrl=%2Fexam%2Foj%2Fta%3Fdifficulty%3D1%26page%3D1%26pageSize%3D50%26search%3D%26tpId%3D37%26type%3D37&am......
  • 算法学习day52动态规划part13-674、300、718
    packageLeetCode.DPpart13;/***674.最长连续递增序列*给定一个未经排序的整数数组,找到最长且连续递增的子序列,并返回该序列的长度。*连续递增的子序列可以由两个下标l和r(l<r)确定,*如果对于每个l<=i<r,都有nums[i]<nums[i+1],*那么子序列[nums[......
  • 算法学习day53动态规划part14-1143、53、1035
    packageLeetCode.DPpart14;/***1143.最长公共子序列*给定两个字符串text1和text2,返回这两个字符串的最长公共子序列的长度。*如果不存在公共子序列,返回0。*一个字符串的子序列是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些......
  • 或许是一个新的算法方向?
    动动发财的小手,点个赞吧!今日谷歌DeepMind使用深度强化学习发现更快的排序算法,相关论文成果已经发表在Nature上。据报道:该算法可以提速70%,相比之下,快了3倍之多。摘要排序或散列等基本算法在任何给定的一天都会被使用数万亿次。随着计算需求的增长,让这些算法尽可能高效变得至......