顺序存储二叉树
顺序存储二叉树是指用一个数组存储的二叉树,一般用于完全二叉树,物理上用数组存储逻辑上是一个树结构。
- 第n个元素的左节点索引2n+1
- 第n个元素的右节点索引2n+2
- 第n个元素的父节点为(n-1)/2
- n为元素在数组中的索引
代码示例
class Node {
Node left;
Node right;
int id;
Object data;
public Node(int id, Object data) {
this.data = data;
this.id = id;
}
@Override
public String toString() {
return "Node{" +
"id=" + id +
", data=" + data +
'}';
}
/**
* 前序遍历 先输出中间节点 再输出左节点 再出输出又节点
*/
public void preOrderTraversal() {
System.out.print(this + "\t");
if (this.left != null) {
this.left.preOrderTraversal();
}
if (this.right != null) {
this.right.preOrderTraversal();
}
}
/**
* 中序遍历 先左 再中 后右
*/
public void inOrderTraversal() {
if (this.left != null) {
this.left.inOrderTraversal();
}
System.out.print(this + "\t");
if (this.right != null) {
this.right.inOrderTraversal();
}
}
/**
* 后续遍历 左 -> 右 -> 中
*/
public void postOrderTraversal() {
if (this.left != null) {
this.left.postOrderTraversal();
}
if (this.right != null) {
this.right.postOrderTraversal();
}
System.out.print(this + "\t");
}
/**
* 前序遍历查找
*
* @param id
*/
public Node preOrderFind(int id) {
if (this.id == id) {
return this;
}
Node temp = null;
if (this.left != null) {
temp = this.left.preOrderFind(id);
}
// 左递归没找到尝试右递归
if (temp == null) {
if (this.right != null) {
temp = this.right.preOrderFind(id);
}
}
return temp;
}
}
/**
* 顺序存储二叉树一般只考虑完全二叉树
* 第n个元素的左节点为2n+1
* 第n个元素的右节点为2n+2
* 第n个元素的父节点为(n-1)/2
* n为数组的索引
*/
class ArrayBinaryTree {
public Node[] tree;
public ArrayBinaryTree(Node[] tree) {
this.tree = tree;
}
public void preOrderTraversal() {
this.preOrderTraversal(0);
}
/**
* 前序遍历 顺序存储二叉树
*
* @param index
*/
public void preOrderTraversal(int index) {
if (tree == null || tree.length <= 0 || index < 0) {
throw new RuntimeException(" error tree or index!");
}
System.out.println(tree[index]);
// 遍历左子树 2* index+1 为左子节点在数组中的索引
int leftNodeIndex = 2 * index + 1;
if (leftNodeIndex < tree.length && tree[leftNodeIndex] != null) {
preOrderTraversal(leftNodeIndex);
}
// 遍历由子树 2* index + 2 为右节点在数组中的位置
int rightNodeIndex = 2 * index + 2;
if (rightNodeIndex < tree.length && tree[rightNodeIndex] != null) {
preOrderTraversal(rightNodeIndex);
}
}
public void inOrderTraversal() {
this.inOrderTraversal(0);
}
/**
* 中序遍历 顺序存储二叉树
* @param index
*/
public void inOrderTraversal(int index) {
int leftIndex = 2 * index + 1;
int rightIndex = 2 * index + 2;
if (leftIndex < tree.length && tree[leftIndex] != null) {
inOrderTraversal(leftIndex);
}
System.out.println(tree[index]);
if (rightIndex < tree.length && tree[rightIndex] != null) {
inOrderTraversal(rightIndex);
}
}
public void postOrderTraversal() {
this.postOrderTraversal(0);
}
/**
* 后序遍历 顺序存储二叉树
* @param index
*/
public void postOrderTraversal(int index) {
int leftIndex = 2 * index + 1;
int rightIndex = 2 * index + 2;
if (leftIndex < tree.length && tree[leftIndex] != null) {
postOrderTraversal(leftIndex);
}
if (rightIndex < tree.length && tree[rightIndex] != null) {
postOrderTraversal(rightIndex);
}
System.out.println(tree[index]);
}
}
线索二叉树
二叉树的叶子节点会有一些空的指针,如果有n个节点则会有n+1个空指针域,将这些空指针域利用起来存在该二叉树为某一种遍历次序下的前驱节点和后继节点的二叉树成为线索二叉树。根据线索二叉树性质不同分为前序线索二叉树,中序线索二叉树,后续线索二叉树。
一个节点的前一个节点为前驱节点,一个节点的后一个节点为后继节点
核心思想
- 当线索化二叉树的时候left和right就分为两种情况
- left指向左子树或者指向前驱节点
- right指向右子树或者后继节点
- 实现线索化二叉树的需要对之前Node节点实体进行增强添加一个标记字段来记录left和right指针指向的是子树还是前驱或者后继节点
- 在线索化二叉树的之后需要记录当前节点的上一个节点用于连接后继节点
- 因为二叉树线索化之后left和right的定义已经改变所以遍历的方式也不同,用之前的方式遍历会出现死循环
代码示例
class ThreadedBinaryTree {
Node root;
Node pre = null;// 上一个节点初始化为null
public ThreadedBinaryTree(Node root) {
this.root = root;
}
public void postOrderTraversal() {
Node temp = root;
// 定位到树最左端
while (temp.left != null && !temp.isLeftThread) {
temp = temp.left;
}
while (temp != null) {
// temp left是后继节点
if (temp.isRightThread) {
System.out.println(temp);
pre = temp;
temp = temp.right;
} else {
// 后继节点之后的那一颗 子树需要特殊处理 必须拿到该子树的父节点然后继续
if (temp.right == pre) {
System.out.println(temp);
pre = temp;
temp = temp.parent;
} else {
temp = temp.right;
while (temp.left != null && !temp.isLeftThread) {
temp = temp.left;
}
}
}
}
}
// 后续线索化
public void postOrderThread(Node node) {
if (node == null) {
return;
}
postOrderThread(node.left);
postOrderThread(node.right);
if (node.left == null) {
node.left = pre;
node.isLeftThread = true;
}
if (pre != null && pre.right == null) {
pre.right = node;
pre.isRightThread = true;
}
pre = node;
}
// 前序线索化
public void preOrderThreaded(Node node) {
if (node == null) {
// 为null不能线索化
return;
}
// 先将当前节点复制pre
if (node.left == null) {
node.left = pre;
node.isLeftThread = true;
}
if (pre != null && pre.right == null) {
pre.right = node;
pre.isRightThread = true;
}
pre = node;
// 左递归 必须加上该条件 必然会死循环
if (!node.isLeftThread) {
preOrderThreaded(node.left);
}
// 右递归
preOrderThreaded(node.right);
}
public void preOrderTraversal() {
Node temp = root;
while (temp != null) {
while (!temp.isRightThread && !temp.isLeftThread) {
System.out.println(temp);
temp = temp.left;
}
System.out.println(temp);
temp = temp.right;
}
}
// 中序线索化
public void inOrderThreaded(Node node) {
if (node == null) {
// 为null不能线索化
return;
}
// 左递归
inOrderThreaded(node.left);
// 左子树为空 进行线索化指向前驱节点
if (node.left == null) {
node.left = pre;
// 记得把node leftThread的属性修改成true 表示left指向的是一个前驱节点而不是子树
node.isLeftThread = true;
}
// 线索化右指针 处理右指针的时候需要特殊处理,因为如果已经遍历到当前节点发现可以线索化因为现在树是单向的所以无法将指针指向下一个节点
// 采用上一个节点的指针right如果是null说明可以被线索化 指向当前node 即可完成线索化
if (pre != null && pre.right == null) {
pre.right = node;
pre.isRightThread = true;
}
// 最终要移动pre 指向当前node 意思是pre下移
pre = node;
// 右递归
inOrderThreaded(node.right);
}
// 中序遍历
public void inOrderTraversal() {
if (root == null) {
System.out.println("tree is empty!");
return;
}
Node temp = root;
while (temp != null) {
// 左节点判断如果是正常子节点往下
while (!temp.isLeftThread) {
temp = temp.left;
}
// 打印节点
System.out.println(temp);
// right如果是线索节点的话就直接指向了下一个节点所以直接打印即可
while (temp.isRightThread) {
temp = temp.right;
System.out.println(temp);
}
// 找节点的右边
temp = temp.right;
}
}
}