树
特点
- 任何一个非空树只有一个根节点
- 一棵树的任意两个节点有且仅有唯一的一条路径连通
- 一棵树如果有n个节点,那么它一定恰好有n-1条边
- 一棵树不包含回路
元素
根节点、子结点、叶子节点,层数(从上到下)、高度(从下到上)、深度(从上到下从0开始)
二叉树
概念: 每个结点最多只能有两个子结点的叫做二叉树
满二叉树
二叉树所有的叶子结点都在最后一层,并且
结点总数=2^(n-1)
,则称为满二叉树,也就是每一层节点都达到了最大值。
完全二叉树
该二叉树的所有叶子结点都在最后一层或者倒数第二层,而且最后一层的叶子结点在左边连续,倒数第二层的叶子结点在左边连续,倒数第二层的叶子结点在右边连续,称之为完全二叉树
其余层都是满节点,最后一层或满或右边空缺,特点是父结点与子结点的序号有对应关系(父:i,左子:2i,右子:2i+1)
平衡二叉树
是一颗二叉排序树
- 特点:可以是一棵空树,左右两个子树的高度差绝对值不超过1
- 常用实现方法:红黑树、AVL树、替罪羊树、加权平均树、伸展树
二叉搜索树
特点:
- 每个结点最多拥有两个子结点,即每个结点都是一个小的二叉树
- 比父结点小的放左边
- 比父结点大的放右边
优点: 拥有链表的快速删除,增加的特性,同时具有快速查找的优势
缺点: 由于数据原因,可能会造成左右两边深度过大,不平衡。甚至可能变成斜树,即退化成链表。
二叉树创建和构成
二叉树的存储
- 链式存储:通过链表将各个节点串联
每个节点:数据data,左节点指针left,有结点指针right
private class Node {
// 结点内容
T val;
// 左右孩子
Node left;
Node right;
public Node(T val) {
this.val = val;
this.left = null;
this.right = null;
}
}
- 顺序存储:通过数组将二叉树一层一层的存储,如果有空位,那就空着,每个位置只存储data,那么左子节点为父结点的2i,右节点为2i+1
private class Node {
// 结点内容
T val;
// 左右孩子
Node left;
Node right;
public Node(T val) {
this.val = val;
this.left = null;
this.right = null;
}
}
Node root; // 根结点
int size; // 树中结点的个数
public Tree() {
this.root = null;
this.size = 0;
}
// 获取树中结点的个数
public int getSize() {
return this.size;
}
// 判断是否为空树
public boolean isEmpty() {
return this.root == null;
}
// 向树中添加结点
public void add(T val) {
this.root = add(root, val);
}
private Node add(Node node, T val) {
// 递归终止条件
if (node == null) {
this.size++;
return new Node(val);
}
// 递归操作
if (node.val.compareTo(val) > 0) {
node.left = add(node.left, val);
} else {
node.right = add(node.right, val);
}
return node;
}
/**
* 查询val在是否存在于树中
*
* @param val 查询的值
* @return boolean
*/
public boolean find(T val) {
return find(root, val);
}
private boolean find(Node node, T val) {
// 1、递归终止条件
if (node == null) {
return false;
}
// 2、 递归操作
if (node.val.compareTo(val) == 0) {
return true;
} else if (node.val.compareTo(val) < 0) {
return find(node.right, val);
} else {
return find(node.left, val);
}
}
// 从二分搜索树中寻找最小元素所在的结点
public Node findMixNode() {
if (root == null) {
return null;
}
Node cur = root;
while (cur.left != null) {
cur = cur.left;
}
return cur;
}
public Node findMixNodeDG() {
if (root == null) {
return null;
}
return findMixNodeDG(root);
}
// 从以node为根的二分搜索树中查找最小元素所在的结点
private Node findMixNodeDG(Node node) {
// 1、递归终止的条件
if (node.left == null) {
return node;
}
// 2、递归操作
return findMixNodeDG(node.left);
}
// 从二分搜索树中删除最小元素所在的结点
public void removeMinNode() {
Node minNode = findMixNodeDG();
if (minNode == null) {
return;
}
// 进行删除
root = removeMinNode(root);
}
// 从以node 为根的二分搜索树中删除最小结点
private Node removeMinNode(Node node) {
// 1、递归终止的条件
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
this.size--;
return rightNode;
}
// 2、递归操作
node.left = removeMinNode(node.left);
return node;
}
二叉树的遍历
- 三种遍历方式:前序,中序,后序
前序遍历
先输出父结点,再遍历左子树和右子树
// 前序遍历
public List<T> preTraverse() {
List<T> list = new ArrayList<>();
preTraverse(root, list);
return list;
}
private void preTraverse(Node node, List<T> list) {
// 1、递归终止的条件
if (node == null) {
return;
}
// 2、递归操作
list.add(node.val);
preTraverse(node.left, list);
preTraverse(node.right, list);
}
中序遍历
先遍历左子树,再输出父结点,再遍历右子树
//中续遍历
public List<T> middleTraverse() {
List<T> list = new ArrayList<>();
middleTraverse(root, list);
return list;
}
private void middleTraverse(Node node, List<T> list) {
// 1、递归终止的条件
if (node == null) {
return;
}
// 2、递归操作
middleTraverse(node.left, list);
list.add(node.val);
middleTraverse(node.right, list);
}
后序遍历
先遍历左子树,再遍历右子树,然后输出父结点
//后续遍历
public List<T> afterTraverse() {
List<T> list = new ArrayList<>();
afterTraverse(root, list);
return list;
}
private void afterTraverse(Node node, List<T> list) {
// 1、递归终止的条件
if (node == null) {
return;
}
// 2、递归操作
afterTraverse(node.left, list);
afterTraverse(node.right, list);
list.add(node.val);
}
层序遍历
利用队列的性质将每层的结点存储到一个集合中,然后分别判断其左右子树,直到循环结束
/**
* 层序遍历---借助队列实现
*
* @return
*/
public List<T> levelTraver() {
List<T> list = new ArrayList<>();
Queue<Node> queue = new LinkedList<>();
if (this.root != null) {
queue.offer(this.root);
}
while (!queue.isEmpty()) {
Node temp = queue.poll();
list.add(temp.val);
if (temp.left != null) {
queue.offer(temp.left);
}
if (temp.right != null) {
queue.offer(temp.right);
}
}
return list;
}
平衡二叉树
特点:
- 是一棵空树或它的左右两个子树的高度差的绝对值不超过1,超过1就会失衡。并且左右两个子树都是一棵平衡二叉树。
获取平衡因子
// 获取平衡因子
public int getBalance(Node node) {
if (node == null) {
return 0;
}
return getHeight(node.left) - getHeight(node.right);
}
添加节点:
- 往平衡二叉树中添加节点很可能会导致二叉树失去平衡,所以我们需要在每次插入结点后进行平衡的维护操作。插入结点破坏平衡性有如下四种情况:
- 右右旋转
- 左左旋转
- 左右旋转
- 右左旋转
// 向树中添加结点
public void add(T val) {
if (find(val) != null) {
return;
}
this.root = add(root, val);
}
// 在以node为根的二分搜索树中添加val
private Node add(Node node, T val) {
// 1、递归终止的条件
if (node == null) {
this.size++;
return new Node(val);
}
// 2、递归操作
if (node.val.compareTo(val) > 0) {
node.left = add(node.left, val);
} else {
node.right = add(node.right, val);
}
// 更新节点高度
node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
//以node节点为根的树不平衡,就要调整
Node resultNode = node;
if (getBalance(node) > 1 && getBalance(node.left) >= 0) {
resultNode = rightRotate(node);
} else if (getBalance(node) < -1 && getBalance(node.right) <= 0) {
resultNode = leftRotate(node);
} else if (getBalance(node) > 1 && getBalance(node.left) < 0) {
node.left = leftRotate(node.left);
resultNode = rightRotate(node);
} else if (getBalance(node) < -1 && getBalance(node.right) > 0) {
node.right = rightRotate(node.right);
resultNode = leftRotate(node);
}
return resultNode;
}
删除操作
public void remove(T ele) {
Node delNode = find(ele);
if (delNode == null) {
return;
}
// 进行删除操作
root = remove(root, ele);
}
// 从以node为根的二分搜索树中删除值为ele的结点
private Node remove(Node node, T ele) {
Node resultNode = null;
if (node.val.compareTo(ele) == 0) {
// 情况1: 只存在右子树
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
this.size--;
resultNode = rightNode;
} else if (node.right == null) {
Node leftNode = node.left;
node.left = null;
this.size--;
resultNode = leftNode;
} else {
// 左右子树都不为空
// 1>从右子树中查找最小元素所属结点
Node suffixNode = findMixNodeDG(node.right);
// 2> 后继结点的右子树:从原右子树中删除最小元素结点之后新树的根结点
suffixNode.right = remove(node.right, suffixNode.val);
// 3> 后继结点的左子树: 删除结点的左子树
suffixNode.left = node.left;
node.left = node.right = null;
resultNode = suffixNode;
}
} else if (node.val.compareTo(ele) > 0) {
node.left = remove(node.left, ele);
resultNode = node;
} else {
node.right = remove(node.right, ele);
resultNode = node;
}
// 删除的是叶子节点
if (resultNode == null) {
return null;
}
// 删除之后,可能改变了树的平衡,因此需要进行调整
resultNode.height = Math.max(getHeight(resultNode.left), getHeight(resultNode.right)) + 1;
Node result = resultNode;
if (getBalance(resultNode) > 1 && getBalance(resultNode.left) >= 0) {
result = rightRotate(resultNode);
} else if (getBalance(resultNode) < -1 && getBalance(resultNode.right) <= 0) {
result = leftRotate(resultNode);
} else if (getBalance(resultNode) > 1 && getBalance(resultNode.left) < 0) {
resultNode.left = leftRotate(resultNode.left);
result = rightRotate(resultNode);
} else if (getBalance(resultNode) < -1 && getBalance(resultNode.right) > 0) {
resultNode.right = rightRotate(resultNode.right);
result = leftRotate(resultNode);
}
return result;
}
// 判断方法: 判断是否为二分搜索树
public boolean isBinarySearchTree() {
if (this.root == null) {
return true;
}
// 使用中序遍历的性质
List<T> list = middleTraverse();
for (int i = 1; i < list.size(); i++) {
if (list.get(i - 1).compareTo(list.get(i)) > 0) {
return false;
}
}
return true;
}
// 判断方法: 判断是否为AVL树
public boolean isBalanceTree() {
return isBalanceTree(this.root);
}
private boolean isBalanceTree(Node node) {
if (node == null) {
return true;
}
int balance = Math.abs(getBalance(node));
if (balance > 1) {
return false;
}
return isBalanceTree(node.left) && isBalanceTree(node.right);
}
// 层序遍历,并打印平衡因子
public void levelTraverse() {
LinkedList<Node> queue = new LinkedList<>();
if (this.root == null) {
return;
}
queue.add(this.root);
while (!queue.isEmpty()) {
Node node = queue.poll();
System.out.println(node.val + "<---->" + getBalance(node));
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
}
/**
* 右旋
* y
* x -----> x
* z z y
*/
private Node rightRotate(Node y) {
Node x = y.left;
// 1、保存x的右子树
Node t3 = x.right;
// 2、将y作为x的右子树
x.right = y;
// 3、将t3作为y的左子树
y.left = t3;
// 更新高度
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x;
}
/**
* 左旋
* <p>
* y
* x ----> x
* z y z
*/
private Node leftRotate(Node y) {
Node x = y.right;
// 1、保存x的左孩子
Node t2 = x.left;
// 2、将y作为x的左树
x.left = y;
// 3、将t2作为y的右树
y.right = t2;
// 更新高度
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x;
}