数据结构---树

一、定义:(递归的方法)

树(Tree)是n(n≥0)个结点的有限集。n=0时称为空树。在任意一棵非空树中:(1)有且仅有一个特定的称为根(Root)的结点;(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T、T2、……、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。

两点注意:1.n>0时,根节点是唯一的;
2.m>0时,子树的个数没有限制,但彼此之间互不相交

1、结构特点:一对多
2、结点的度:结点拥有的子树个数称为结点的度。
树的度:树内各结点的度的最大值。
3、结点间的关系:结点的子树的根称为该结点的孩子;该结点称为孩子的双亲。
4、结点的层次:从根开始定义,根为第一层。树中结点的最大层次称为树的深度或者高度。

如果将树中结点的各子树看成从左至右是有次序的,不能互换的,则称该树是有序树

5、森林:m(m>=0)棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。

二、树的存储结构(待更新。。。)

三、二叉树的定义

引入:1.折半查找算法
2.对于在某个阶段都是两种结果的情形,比如开和关、0和1、真和假、上和下、对和错、正与反等,都适合用二叉树来建模。

1、定义

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

2、二叉树特点
  • 每个结点最多2棵子树,不存在度大于2的结点;
  • 左子树和右子树是有次序的,不能任意颠倒;
  • 需要区分左子树还是右子树。

二叉树有5种形态:

  • 空二叉树
  • 只有一个根节点
  • 根节点只有左子树
  • 根节点只有右子树
  • 根节点既有左子树又有右子树
3、特殊二叉树
3.1、斜树

分为左斜树和右斜树。结点的个数与二叉树的深度相同。

3.2、满二叉树

定义:

1.如果所有的分支结点都存在左子树和右子树;2.所有叶子结点都在同一层上

特点:

(1)叶子结点只能在最下一层;
(2)非叶子结点的度是2;
(3)在同样深度的二叉树中,满二叉树的结点数最多,叶子结点数最多。

3.3、完全二叉树

定义:

对一棵具有n个结点的二叉树按层序编号,如果编号为i (1<i<n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树,如图所示。
在这里插入图片描述
满二叉树一定是一棵完全二叉树;反之不一定成立。

特点:

(1)叶子结点只能出现在最下两层
(2)最下层的叶子一定集中在左部连续位置
(3)倒数二层,若有叶子结点,一定都在右部连续位置。
(4)如果结点度为1,则该结点只有左孩子,即不存在只有右子树的情况。
(5)同样结点数的二叉树,完全二叉树的深度最小。

4、二叉树的性质

性质1:
在二叉树的第i层上至多有2**(i-1)个结点(i≥1)。
在这里插入图片描述
第一层是根结点,只有一个,所以2**(1-1)=2**0=1。

第二层有两个,2**(2-1)=2**1=2。

第三层有四个,2**(3-1)=2**2=4。
。。。

性质2:
深度为k的二叉树至多有2**k-1个结点(k≥1)。

性质3:
对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1。终端结点数就是叶子结点数。

性质4:
具有n个结点的完全二叉树的深度为[log2(n)]+1([x]表示不大于x的最大整数)。

性质5:
如果对一棵有n个结点的完全二叉树(其深度为[Log2(n)]+1)的结点按层序编号(从第1层到第[Log2(n)]+1层,每层从左到右),对任一结点i (1≤i≤n)有:
在这里插入图片描述
1.如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2]。
2.如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。
3.如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。

5、二叉树的存储结构
5.1、二叉树的顺序存储结构

二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置,也就是数组的下标要能体现结点之间的逻辑关系,比如双亲与孩子的关系,左右兄弟的关系等。
在这里插入图片描述
在这里插入图片描述
当然对于一般的二叉树,尽管层序编号不能反映逻辑关系,但是可以将其按完全二叉树编号,只不过,把不存在的结点设置为“^”而已。 ^表示空档不存在。
顺序存储结构一般只用于完全二叉树。

5.2、二叉链表

既然顺序存储适用性不强,我们就要考虑链式存储结构。
二叉树每个结点最多有两个孩子,所以为它设计一个数据域两个指针域是比较自然的想法,我们称这样的链表叫做二叉链表
在这里插入图片描述

6、二叉树的遍历(定义是递归的方式,因此遍历也可以采用递归)

遍历是二叉树最重要的一门学问。

二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次

树的结点之间不存在唯一的前驱和后继关系。

对于计算机来说,它只有循环、判断等方式来处理;也就是说,它只会处理线性序列。遍历方法都是在把树中的结点变成某种意义的线性序列。

6.1、前序遍历

次序:根节点->左子树->右子树

function preOrder(root){
  let result = [];
  if(root==null){
    return 
  }
  // 前序遍历位置
  result.push(root.val)
  preOrder(root.left)
  preOrder(root.right)
}

序列化(打平到一个字符串):

let sep=","
let NULL="#"

// 主函数,序列化为字符串
function serialize(root){
  let sb = new StringBuilder()//StringBuilder可以用于高效拼接字符串
  help(root,sb)
  return sb.toString();
}
//辅助函数,将二叉树存入StringBuilder
function help(root,sb){
  if(root==null){
    sb.append(NULL).append(sep)
    return
  }

  // 前序遍历的位置
  sb.append(root.val).append(sep)

  help(root.left,sb)
  help(root.right,sb)
}
6.2、中序遍历

次序:左子树->根节点->右子树

function preOrder(root){
  let result = [];
  if(root==null){
    return 
  }  
  preOrder(root.left)
  // 中序遍历位置
  result.push(root.val)
  preOrder(root.right)
}
6.3、后序遍历

次序:左子树->右子树->根节点

function preOrder(root){
  let result = [];
  if(root==null){
    return 
  }  
  preOrder(root.left)
  preOrder(root.right)
  // 后序遍历位置
  result.push(root.val)
}
6.4、层序遍历

次序:从上至下,从左到右

第一种:一层一层输出

function traverse(root){
  if(root==null){
    return []
  }
  let result=[]
  //初始化队列,将root入队
  let list = new Array();
  list.push(root)
  
  while(list.length!==0){
    let len=list.length
    // 层序遍历的位置
    let temp=[]
    for(let i=0;i<len;i++){
      let node=list.shift()
      temp.push(node.val)
      if(node.left!=null){
        list.push(node.left)
      }  
      if(node.right!=null){
        list.push(node.right)
      }
    }
    result.push(temp)
  }
  return result
}

第二种:标准的二叉树层级遍历框架

function traverse(root){
  if(root==null){
    return 
  }
  let result=[]
  //初始化队列,将root入队
  let list = new Array();
  list.push(root)
  
  while(list.length!==0){
    // 层序遍历的位置
      let node=list.shift()
      result.push(node.val)
      if(node.left!=null){
        list.push(node.left)
      }  
      if(node.right!=null){
        list.push(node.right)
      }
  }
  return result
}

第三种:带空指针的层序遍历(以便于后期反序列化)

function serialize(root){
  if(root==null){
    return [null]
  }
  let result=[]
  //初始化队列,将root入队
  let list = new Array();
  list.push(root)
  
  while(list.length!==0){
    // 层序遍历的位置
      let node=list.shift()
      if(node==null){
      	result.push(null)
      	continue
      }
      result.push(node.val)     
      list.push(node.left)          
      list.push(node.right)
  }
  return result
}
6.5、反序列化(由序列推出二叉树结构)

原理: 先确定根节点root,然后遵循遍历规则,递归生成左右子树。

三种遍历都是从根结点开始,前序遍历是先打印再递归左和右。
已知前序和中序,可以唯一确定一棵二叉树;
已知后序和中序,可以唯一确定一棵二叉树。
注意:已知前序和后序,是不能确定一棵二叉树的。

情况一:如何通过二叉树的前序遍历结果还原一棵二叉树?

注意:一般语境下,单单前序遍历结果是不能还原二叉树结构的,因为缺少空指针的信息。但是,如果前序遍历列表包含空指针的信息,就可以还原。

let sep=","
let NULL="#"
// 主函数,将字符串反序列化为二叉树结构
function deserialize(data){
  // 将字符串转化为列表
  let nodes=new Array()
  for(let s of data.split(sep)){   //以逗号分隔转化成列表
    nodes.push(s)
  }
  return help(nodes)
}
// 辅助函数,通过nodes列表构造二叉树
function help(nodes){
  if(nodes.length==0){
    return null
  }

  // 前序遍历位置
  // 列表最左侧就是根节点
  let first=nodes.shift()
  if(first==NULL){
    return null
  }
  let root=new TreeNode(parseInt(first))

  root.left=help(nodes)
  root.right=help(nodes)
  return root
}

情况二:如何通过二叉树的后序遍历结果还原一棵二叉树? 难点

**deserialize方法原理:**先确定根节点root,然后遵循遍历规则,递归计算生成左右子树。
核心思想:后序遍历结果中,root的值是列表中最后一个元素。我们应该从后往前取出列表元素,先用最后一个元素构造出root,再递归调用生成root的左右子树。从后往前的意思是:先用最后一个元素构造出root,然后先构造出 root.right子树,最后构造出 root.left子树。

let sep=","
let NULL="#"
// 主函数,将字符串反序列化为二叉树结构
function deserialize(data){
  // 将字符串转化为列表
  let nodes=new Array()
  for(let s of data.split(sep)){
    nodes.push(s)
  }
  return help(nodes)
}
// 辅助函数,通过nodes列表构造二叉树
function help(nodes){
  if(nodes.length==0){
    return null
  }

  // 后序遍历位置
  // 列表最右侧就是根节点
  let last=nodes.pop()
  if(last==NULL){
    return null
  }
  let root=new TreeNode(parseInt(last))
  // 先构造出右子树,后构造出左子树
  root.right=help(nodes)
  root.left=help(nodes)
  return root
}

情况三:如何通过二叉树的层序遍历结果还原一棵二叉树?
队列进行层级遍历,同时用索引 i 记录对应子节点的位置:

function deserialize(result){
  if(result==[null]){
    return null
  }
  // 第一个元素就是root的值
  let first=result.shift()
  let root=new TreeNode(first)

  //初始化队列,记录父节点
  let list = new Array();
  list.push(root)

  for(let i=0;i<result.length;){
    //队列中存的都是“父”节点
    let parent=list.shift()
    //左侧结点的值
    let left=result[i++]
    if(left==null){
      parent.left=null 
    }else{
      parent.left=new TreeNode(left)
      list.push(parent.left)
    }
    //右侧结点的值
    let right=result[i++]
    if(right!=null){
      parent.right=new TreeNode(right)
      list.push(parent.right)
    }else{
      parent.right=null
    }
  }
  return root
}

二叉树的相关知识暂时更新到这。
点击这里🔍几种特殊的二叉树查看一些特殊二叉树的相关知识。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值