节点
package com.young.tree;
/**
*
* Title:树节点:二叉链表结构
*
*
* @Author: yangyongbing
* @Date: 2023-04-18 13:25
* @version: v1.0
*/
public class Node<T> {
public Node<T> lChild;
private T data;
public Node<T> rChild;
public Node() {
lChild=null;
data=null;
rChild=null;
}
public Node(T data) {
this.data = data;
this.lChild=null;
this.rChild=null;
}
public Node<T> getlChild() {
return lChild;
}
public void setlChild(Node<T> lChild) {
this.lChild = lChild;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Node<T> getrChild() {
return rChild;
}
public void setrChild(Node<T> rChild) {
this.rChild = rChild;
}
}
二叉树
package com.young.tree;
/**
*
* Title: 二叉树
*
*
* @Author: yangyongbing
* @Date: 2023-04-18
* @version: v1.0
*/
public class BinaryTree<T> {
private final int maxNodes = 100;
// 根节点
public Node<T> root;
// 创建一棵空二叉树
public BinaryTree() {
this.root = new Node<>();
}
// 创建一棵以数据元素x为根节点的二叉树
public BinaryTree(T x) {
this.root = new Node<>(x);
}
/*在当前二叉树的parent结点中插入一个新的左子结点,
若已存在左子树,则将该左子树变成新左子结点的左孩子树*/
public boolean addLeft(T x, Node<T> parent) {
if (parent == null) {
return false;
}
// 创建一个空节点
Node<T> p = new Node<>(x);
// 如果父节点的左子树为空,则直接将数据素x赋给父节点的左孩子节点
if (parent.lChild != null) {
// 将父节点的左子树赋给这个新节点左子节点
p.lChild = parent.lChild;
}
// 将新节点赋给父节点的左孩子节点
parent.lChild = p;
return true;
}
/*在当前二叉树的parent结点中插入一个新的右子节点,
若已存在右子树,则将该右子树变成新右子节点的右孩子树*/
public boolean addRight(T x, Node<T> parent) {
if (parent == null) {
return false;
}
// 创建一个空节点
Node<T> p = new Node<>(x);
if (parent.rChild != null) {
p.rChild = parent.rChild;
}
parent.rChild = p;
return true;
}
// 删除当前二叉树的parent节点中的左子树
public boolean deleteLeft(Node<T> parent) {
if (parent == null) {
return false;
} else {
parent.lChild = null;
return true;
}
}
// 删除当前二叉树的parent节点中的右子树
public boolean deleteRight(Node<T> parent) {
if (parent == null) {
return false;
} else {
parent.rChild = null;
return true;
}
}
// 先序遍历
public void preorder(Node<T> node) {
if (node != null) {
// 访问根节点
visit(node.getData());
// 先序遍历左子树
preorder(node.getlChild());
// 先序遍历右子树
preorder(node.getrChild());
}
}
// 中序遍历
public void inorder(Node<T> node) {
if (node != null) {
// 中序遍历左子树
inorder(node.lChild);
// 访问根节点
visit(node.getData());
// 中序遍历右子树
inorder(node.rChild);
}
}
// 后序遍历
public void postorder(Node<T> node) {
if (node != null) {
// 后续遍历左子树
postorder(node.lChild);
// 后续遍历右子树
postorder(node.rChild);
// 访问根节点
visit(node.getData());
}
}
// 按层次遍历
public void levelOrder() {
// 节点数组,用于存放节点
Node<T>[] queue = new Node[this.maxNodes];
if (this.root == null) {
return;
}
// 定义队首和队尾指针
int front, rear;
// 队列为空,对首指针不指向任何一个数组元素
front = -1;
// 队列为空,对尾指针指向数组第一个位置
rear = 0;
// 根节点入队
queue[rear] = this.root;
while (front != rear) {
// 访问对首节点的数据域
front++;
visit(queue[front].getData());
// 左节点入队
if (queue[front].lChild != null) {
rear++;
queue[rear] = queue[front].lChild;
}
// 右节点入队
if (queue[front].rChild != null) {
rear++;
queue[rear] = queue[front].rChild;
}
}
}
private void visit(T x) {
System.out.println(x);
}
// 在当前二叉树中查找数据x
public boolean search(Node<T> node,T x) {
if (node != null) {
// 访问根节点
T t = node.getData();
if(x==t){
return true;
}
// 先序遍历左子树
search(node.getlChild(),x);
// 先序遍历右子树
search(node.getrChild(),x);
}
return false;
}
// 按某种方式遍历二叉树中的所有节点
//按指定方式遍历二叉树
//i=0表示先序遍历,=1表示中序遍历,=2表示后序遍历,=3表示层次遍历
public void traversal(int i)
{
switch(i)
{
case 0: preorder(this.root);break;
case 1: inorder(this.root);break;
case 2: postorder(this.root);break;
default: levelOrder();
}
}
// 求当前二叉树的高度
public int getHeight(Node<T> parent) {
int lh,rh,max;
if(parent!=null){
lh=getHeight(parent.lChild);
rh=getHeight(parent.rChild);
max= Math.max(lh, rh);
return max+1;
}
return 0;
}
}
树(Tree)是若干个结点组成的有限集合,其中必须有一个结点是根结点,其余结点划分为若干个互不相交的集合,每一个集合还是一棵树,但被称为根的子树。注意,当树的结点个数为0时,我们称这棵树为空树,记为Φ。
特点:
二叉树
二叉树(Binary Tree)是一种每结点最多拥有2个子树的树结构,其中第1个子树被称为左子树,第2个子树被称为右子树。注意,当二叉树的结点个数为0时,我们称这个二叉树为空二叉树,记为Φ。二叉树是有序的,即若将其左、右子树颠倒,就成为另一棵不同的二叉树。即使树中结点只有一棵子树,也要区分它是左子树,还是右子树。因此二叉树具有五种基本形态。
满二叉树
在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子结点都在同一层上,这样的一棵二叉树称作满二叉树。(a)图就是一棵满二叉树,(b)图则不是满二叉树,因为该二叉树的D,F,G,H,I叶子结点未在同一层上。
完全二叉树
完全二叉树是一种叶子结点只能出现在最下层和次下层且最下层的叶子结点集中在树的左边的特殊二叉树。图5.5(a)所示为一棵完全二叉树,图5.4(b)和图5.5(b)都不是完全二叉树。对比图5.4(a)和图5.5(a)可以发现,满二叉树与完全二叉树存在如下关系:当树的深度相同时,若对树的结点按从上至下、从左到右的顺序进行编号,则在两种树上同一个位置上的结点的编号相同。显然,一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。
二叉树性质
性质1 一棵非空二叉树的第i层上最多有2i-1个结点(i≥1)。
性质2 一棵深度为k的二叉树中,最多具有2k-1个结点。
性质3 对于一棵非空的二叉树,如果叶子结点数为n0,度数为2的结点数为n2,则有:n0=n2+1。
性质4 具有n个结点的完全二叉树的深度k为[Log2n]
性质5 对于具有n个结点的完全二叉树,如果按照从上至下和从左到右的顺序对二叉树中的所有结点从1开始顺序编号,则对于任意的序号为i的结点,有:
(1)如果i>1,则序号为i的结点的父结点的序号为[插图];如果i=1,则该结点是根结点,无父结点。
(2)如果2i≤n,则序号为i的结点的左子结点的序号为2i;如果2i>n,则序号为i的结点无左子结点。
(3)如果2i+1≤n,则序号为i的结点的右子结点的序号为2i+1;如果2i+1>n,则序号为i的结点无右子结点。+1。
class Node<T>
{
public Node<T> lChild; //左孩子
private T data; //数据域
public Node<T> rChild; //右孩子
public Node() //构造函数,创建一个空节点
{
data = null;
lChild = null;
rChild = null;
}
public Node(T x) //重载构造函数,创建一个数据值为x的节点
{
data = x;
lChild = null;
rChild = null;
}
}
二叉链表也可以带头结点的方式存放:
在Java中描述二叉链表的关键是确定二叉树的根,代码如下。
class BinaryTree<T>
{
public Node<T> root; //根结点
public BinaryTree() //创建一棵空二叉树
{
this.root = new Node<T>();
}
public BinaryTree(T x) //创建一棵以数据元素x为根结点的二叉树
{
this.root = new Node<T>(x);
}
//……
}
(2)三叉链表存储:
每个结点由四个域组成,具体结构为:
其中,data、lchild以及rchild三个域的意义同二叉链表结构,parent域为指向该结点双亲结点的指针。这种存储结构既便于查找孩子结点,又便于查找双亲结点,但是,相对于二叉链表存储结构而言,它增加了空间开销。
一棵二叉树的三叉链表表示:
尽管在二叉链表中无法由结点直接找到其双亲,但由于二叉链表结构灵活,操作方便,对于一般情况的二叉树,甚至比顺序存储结构还节省空间。因此,二叉链表是最常用的二叉树存储方式。
class BinaryTree<T>
{
private Node<T> root;
public BinaryTree(){} //创建一棵空二叉树
public BinaryTree(T x){} //创建一棵以数据元素x为根结点的二叉树
/*在当前二叉树的parent结点中插入一个新的左子结点,
若已存在左子树,则将该左子树变成新左子结点的左孩子树*/
public boolean insertLeft(T x, Node<T> parent){ }
/*在当前二叉树的parent结点中插入一个新的右孩子结点,
若已存在右子树,则将该右子树变成新右孩子结点的左子树*/
public boolean insertRight(Node<T> parent){ }
//删除在当前二叉树的parent结点中的左子树
public boolean deleteLeft(Node<T> parent){ }
//删除在当前二叉树的parent结点中的右子树
public boolean deleteRight(Node<T> parent){ }
public boolean search(T x){ } //在当前二叉树中查找数据x
public void traversal(int i){ } //按某种方式遍历当前二叉树的全部结点
public int getHeight(Node<T> parent){ } //求当前二叉树的高度
}
public BinaryTree()
{
this.root = new Node<T>(); //创建根结点,该结点的数据域为空
}
生成一棵二叉树:
public BinaryTree(T x) //创建一棵以数据元素x为根结点的二叉树
{
this.root = new Node<T>(x);
}
向二叉树中插入一个左孩子结点:
//在当前二叉树的parent节点中插入一个新的左孩子结点,若已存在左子树,则将该左子树变成新左孩子结点的左子树
public boolean insertLeft(T x, Node<T> parent)
{
if(parent==null) return false;
Node<T> p= new Node<T>(x); //创建一个新结点
if(parent.lChild==null)
parent.lChild = p; //将新结点直接设置到父结点的左孩子结点
else
{
//先将父结点原来的左子树设置为新结点的左子树
p.lChild = parent.lChild;
//再将新结点设置到父结点的左孩子结点
parent.lChild = p;
}
return true;
}
//注意,若要执行本操作,则必须先确定插入位置,即parent节点
删除二叉树的左子树:
//删除当前二叉树的parent结点中的左子树
public boolean deleteLeft(Node<T> parent)
{
if(parent==null) return false;
else
{
parent.lChild=null;
return true;
}
}
二叉树的遍历是指按照某种顺序访问二叉树中的每个结点,使每个结点被访问一次且仅被访问一次。
遍历是二叉树中经常要用到的一种操作。因为在实际应用问题中,常常需要按一定顺序对二叉树中的每个结点逐个进行访问,查找具有某一特点的结点,然后对这些满足条件的结点进行处理。
通过一次完整的遍历,可使二叉树中结点信息由非线性排列变为某种意义上的线性序列。也就是说,遍历操作使非线性结构线性化。
由二叉树的定义可知,一棵二叉树由根结点、根结点的左子树和根结点的右子树三部分组成。因此,只要依次遍历这三部分,就可以遍历整个二叉树。若以D、L、R分别表示访问根结点、遍历根结点的左子树、遍历根结点的右子树,则二叉树的遍历方式有六种:DLR、LDR、LRD、DRL、RDL和RLD。如果限定先左后右,则只有前三种方式,即DLR(称为先序遍历)、LDR(称为中序遍历)和LRD(称为后序遍历)。
public void preorder(Node<T> node)
{
if(node==null) return;
else
{
visit(node.getData()); //访问根结点
preOrder(node.lChild); //先序遍历左子树
preOrder(node.rChild); //先序遍历右子树
}
}
public void inorder(Node<T> node)
{
if(node==null) return;
else
{
inorder(node.lChild); //中序遍历左子树
visit(node.getData()); //访问根结点
inorder(node.rChild); //中序遍历右子树
}
}
public void postorder(Node<T> node)
{
if(node==null) return;
else
{
postorder(node.lChild); //后序遍历左子树
postorder(node.rChild); //后序遍历右子树
visit(node.getData()); //访问根结点
}
}
public void levelOrder()
{
Node<T>[] queue= new Node[this.maxNodes];//构造一个队列
int front,rear; //队首指针、队尾指针
if (this.root==null) return;
front=-1; //队列暂时为空,队首指针不指向任何一个数组元素
rear=0; //队列暂时为空,队尾指针指向第一个数组元素
queue[rear]=this.root; //二叉树的根结点进队列
while(front!=rear)
{
front++;
visit(queue[front].getData()); /*访问队首结点的数据域*/
/*将队首结点的左孩子结点进队列*/
if (queue[front].lChild!=null)
{
rear++;
queue[rear]=queue[front].lChild;
}
/*将队首结点的右孩子结点进队列*/
if (queue[front].rChild!=null)
{
rear++;
queue[rear]=queue[front].rChild;
}
}
}
package com.young.tree;
/**
*
* Title:树节点:二叉链表结构
*
*
* @Author: yangyongbing
* @Date: 2023-04-18 13:25
* @version: v1.0
*/
public class Node<T> {
public Node<T> lChild;
private T data;
public Node<T> rChild;
public Node() {
lChild=null;
data=null;
rChild=null;
}
public Node(T data) {
this.data = data;
this.lChild=null;
this.rChild=null;
}
public Node<T> getlChild() {
return lChild;
}
public void setlChild(Node<T> lChild) {
this.lChild = lChild;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Node<T> getrChild() {
return rChild;
}
public void setrChild(Node<T> rChild) {
this.rChild = rChild;
}
}
package com.young.tree;
/**
*
* Title: 二叉树
*
*
* @Author: yangyongbing
* @Date: 2023-04-18
* @version: v1.0
*/
public class BinaryTree<T> {
private final int maxNodes = 100;
// 根节点
public Node<T> root;
// 创建一棵空二叉树
public BinaryTree() {
this.root = new Node<>();
}
// 创建一棵以数据元素x为根节点的二叉树
public BinaryTree(T x) {
this.root = new Node<>(x);
}
/*在当前二叉树的parent结点中插入一个新的左子结点,
若已存在左子树,则将该左子树变成新左子结点的左孩子树*/
public boolean addLeft(T x, Node<T> parent) {
if (parent == null) {
return false;
}
// 创建一个空节点
Node<T> p = new Node<>(x);
// 如果父节点的左子树为空,则直接将数据素x赋给父节点的左孩子节点
if (parent.lChild != null) {
// 将父节点的左子树赋给这个新节点左子节点
p.lChild = parent.lChild;
}
// 将新节点赋给父节点的左孩子节点
parent.lChild = p;
return true;
}
/*在当前二叉树的parent结点中插入一个新的右子节点,
若已存在右子树,则将该右子树变成新右子节点的右孩子树*/
public boolean addRight(T x, Node<T> parent) {
if (parent == null) {
return false;
}
// 创建一个空节点
Node<T> p = new Node<>(x);
if (parent.rChild != null) {
p.rChild = parent.rChild;
}
parent.rChild = p;
return true;
}
// 删除当前二叉树的parent节点中的左子树
public boolean deleteLeft(Node<T> parent) {
if (parent == null) {
return false;
} else {
parent.lChild = null;
return true;
}
}
// 删除当前二叉树的parent节点中的右子树
public boolean deleteRight(Node<T> parent) {
if (parent == null) {
return false;
} else {
parent.rChild = null;
return true;
}
}
// 先序遍历
public void preorder(Node<T> node) {
if (node != null) {
// 访问根节点
visit(node.getData());
// 先序遍历左子树
preorder(node.getlChild());
// 先序遍历右子树
preorder(node.getrChild());
}
}
// 中序遍历
public void inorder(Node<T> node) {
if (node != null) {
// 中序遍历左子树
inorder(node.lChild);
// 访问根节点
visit(node.getData());
// 中序遍历右子树
inorder(node.rChild);
}
}
// 后序遍历
public void postorder(Node<T> node) {
if (node != null) {
// 后续遍历左子树
postorder(node.lChild);
// 后续遍历右子树
postorder(node.rChild);
// 访问根节点
visit(node.getData());
}
}
// 按层次遍历
public void levelOrder() {
// 节点数组,用于存放节点
Node<T>[] queue = new Node[this.maxNodes];
if (this.root == null) {
return;
}
// 定义队首和队尾指针
int front, rear;
// 队列为空,对首指针不指向任何一个数组元素
front = -1;
// 队列为空,对尾指针指向数组第一个位置
rear = 0;
// 根节点入队
queue[rear] = this.root;
while (front != rear) {
// 访问对首节点的数据域
front++;
visit(queue[front].getData());
// 左节点入队
if (queue[front].lChild != null) {
rear++;
queue[rear] = queue[front].lChild;
}
// 右节点入队
if (queue[front].rChild != null) {
rear++;
queue[rear] = queue[front].rChild;
}
}
}
private void visit(T x) {
System.out.println(x);
}
// 在当前二叉树中查找数据x
public boolean search(Node<T> node,T x) {
if (node != null) {
// 访问根节点
T t = node.getData();
if(x==t){
return true;
}
// 先序遍历左子树
search(node.getlChild(),x);
// 先序遍历右子树
search(node.getrChild(),x);
}
return false;
}
// 按某种方式遍历二叉树中的所有节点
//按指定方式遍历二叉树
//i=0表示先序遍历,=1表示中序遍历,=2表示后序遍历,=3表示层次遍历
public void traversal(int i)
{
switch(i)
{
case 0: preorder(this.root);break;
case 1: inorder(this.root);break;
case 2: postorder(this.root);break;
default: levelOrder();
}
}
// 求当前二叉树的高度
public int getHeight(Node<T> parent) {
int lh,rh,max;
if(parent!=null){
lh=getHeight(parent.lChild);
rh=getHeight(parent.rChild);
max= Math.max(lh, rh);
return max+1;
}
return 0;
}
}
按照某种遍历方式对二叉树进行遍历,可以把二叉树中所有结点排列为一个线性序列。在该序列中,除第一个结点外,每个结点有且仅有一个直接前驱结点,除最后一个结点外,每个结点有且仅有一个直接后继结点。但是,二叉树中每个结点在这个序列中的直接前驱结点和直接后继结点是什么,二叉树的存储结构中并没有反映出来,只能在对二叉树遍历的动态过程中得到这些信息。为了保留结点在某种遍历序列中直接前驱和直接后继的位置信息,可以利用二叉树的二叉链表存储结构中的那些空指针域来指示。这些指向直接前驱结点和指向直接后继结点的指针被称为线索(thread),加了线索的二叉树称为线索二叉树。线索二叉树将为二叉树的遍历提供许多方便。