树结构基础
一、概述
为什么需要树这种数据结构:
- 树存储方式能够提高数据存储、读取的效率,比如利用**二叉排序树(Binary Sort Tree),**既可以保证数据的检索速度,同时可以保证数据的插入、删除、修改的速度
树的常用术语:
- 节点的权(节点值)
- 路径(从root到大该节点的路线)
- 树的高度(最大层数)
- 森林:多颗子树结构构成森林
二、二叉树
1. 概述
- 每个节点最多只能有两个子节点的一种形式
- 二叉树的子节点分为左节点和右节点
- 如果该二叉树的所有节点都在最后一层,并且节点总数为2^n-1(n为层数),则称它为满二叉树
- 如果该二叉树所有叶子节点都在最后一层或者倒数第二层,而且最后一层叶子节点在左边连续,倒数第二层的叶子节点在右边连续我们称为完全二叉树
2. 二叉树的遍历
使用前序、中序、后序来遍历二叉树:
- 前序遍历:先输出父节点,再遍历左子树和右子树
- 中序遍历:先遍历左子树,再输出父节点,再遍历右子树
- 后序遍历:先遍历右子树,再遍历右子树,最后输出父节点
class Node{
private int no;
private String name;
private Node left;
private Node right;
public Node(int no, String name) {
this.no = no;
this.name = name;
}
public void setLeft(Node left) {
this.left = left;
}
public void setRight(Node right) {
this.right = right;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Node{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
/**
* 前序遍历
*/
public void preOrder(){
System.out.println(this);
if(this.left!=null){
this.left.preOrder();
}
if(this.right!=null){
this.right.preOrder();
}
}
/**
* 中序遍历
*/
public void infixOrder(){
if(this.left!=null){
this.left.infixOrder();
}
System.out.println(this);
if(this.right!=null){
this.right.infixOrder();
}
}
/**
* 后序遍历
*/
public void postOrder(){
if(this.left!=null){
this.left.postOrder();
}
if(this.right!=null){
this.right.postOrder();
}
System.out.println(this);
}
}
3. 查找
class Node{
/**
* 前序遍历查找
*
* @param no
* @return
*/
public Node preOrderSearch(int no) {
if (root != null) {
return root.preOrderSearch(no);
} else {
return null;
}
}
/**
* 中序遍历查找
*
* @param no
* @return
*/
public Node infixOrderSearch(int no) {
if (root != null) {
return root.infixOrderSearch(no);
} else {
return null;
}
}
/**
* 后序遍历查找
*
* @param no
* @return
*/
public Node postOrderSearch(int no) {
if (root != null) {
return root.postOrderSearch(no);
} else {
return null;
}
}
}
4. 删除
说明:
- 如果删除的节点是叶子节点,则删除该节点
- 如果删除的是非叶子节点,则删除该子树
class Node{
/**
* 删除节点
* @param no 节点编号
*/
public void delNode(int no){
if(this.left!=null&&this.left.no==no){
this.left=null;
return;
}
if(this.right!=null&&this.right.no==no){
this.right=null;
return;
}
if(this.left!=null){
this.left.delNode(no);
}
if(this.right!=null){
this.right.delNode(no);
}
}
}
三、顺序存储二叉树
1. 概述
说明:
从数据存储来看,数组存储方式和树存储方式可以相互转换。即数组可以转换为树,树也可以转换为数组。
特点:
- 顺序二叉树通常只考虑完全二叉树
- 第n个元素的左子节点为2*n+1
- 第n个元素的右子节点为2*n+2
- 第n个元素的父节点为(n-1)/2
- n:表示二叉树中的第几个元素(按0开始编号)
2. 代码演示
/**
* @author DELL
* @Date 2020/2/6 17:19
**/
public class ArrayBinaryTreeDemo {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6, 7};
ArrayBinaryTree arrayBinaryTree = new ArrayBinaryTree(arr);
arrayBinaryTree.preOrder();
}
}
class ArrayBinaryTree {
private int[] arr;
public ArrayBinaryTree(int[] arr) {
this.arr = arr;
}
/**
* 重载preOrder方法
*/
public void preOrder() {
this.preOrder(0);
}
/**
* @param index 数组的下标
*/
public void preOrder(int index) {
if (arr == null || arr.length == 0) {
System.out.print("数组为空!");
}
System.out.println(arr[index]);
if ((index * 2 + 1) < arr.length) {
preOrder(2 * index + 1);
}
if ((index * 2 + 2) < arr.length) {
preOrder(2 * index + 2);
}
}
}
四、线索化二叉树
1. 概述
- n个节点的二叉链表中含有n+1【(2n-(n-1))】个空指针域。利用二叉链表中的空指针域存放指向该节点在某种遍历次序下的前驱和后继节点的指针
- 加了线索的二叉链表称为线索二叉链表,相应的二叉树称为线索二叉树,根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树、后序线索二叉树三种
- 一个节点的前一个节点称为前驱节点
- 一个节点的后一个节点称为后继节点
2. 应用案例
- 将二叉树进行中序遍历,例如:一个二叉树中序遍历的结果为:{ 8 , 3 , 10 , 1 , 14 , 6 }
- 线索化后Node节点的属性left指向的是左子树也可能指向的是前驱节点,同样属性right指向的是右子树也可能指向后继节点
class ThreadedBinaryTree {
private Node root;
private Node pre=null;
public void setRoot(Node root) {
this.root = root;
}
public void threadNodes(){
this.threadNodes(root);
}
/**
* 线索化
* @param node
*/
public void threadNodes(Node node){
if(node==null){
return;
}
//线索化左子树
threadNodes(node.getLeft());
//线索化当前节点
if(node.getLeft()==null){
node.setLeft(pre);
node.setLeftType(1);
}
//处理后继节点
if(pre!=null&&pre.getRight()==null){
//让前驱节点的有指针指向当前节点
pre.setRight(node);
pre.setRightType(1);
}
pre=node;
//线索化右子树
threadNodes(node.getRight());
}
}
3. 遍历
class Node{
/**
* 遍历线索化二叉树
*/
public void threadedList(){
Node node=root;
while(node!=null){
//循环找到leftType==1的节点,当leftType==1说明
// 该节点是按照线索化处理后的有效节点
while(node.getLeftType()==0){
node=node.getLeft();
}
System.out.println(node.toString());
//如果当前节点的右指针指向后继节点就一直输出
while(node.getRightType()==1){
//获取当前节点的后继节点
node=node.getRight();
System.out.println(node.toString());
}
//替换节点
node=node.getRight();
}
}
}