2020-11-25
作者:互联网
树形结构
1、二叉树的定义
二叉树(Binary Tree)是有限个节点的集合,这个集合可以是空集,也可以是一个根节点和两颗不相交的子二叉树组成的集合,其中一颗树叫根的左子树,另一颗树叫右子树。所以二叉树是一个递归的概念。
2、两种特殊的二叉树
满二叉树和完全二叉树。
1)、满二叉树(Full Binary Tree)就是高度为k,且拥有(2^k)-1个节点的二叉树。一棵满二叉树每个节点,要么都有两棵子树,要么都没有子树;而且每一层所有的节点之间必须要么都有两棵子树,要么都没子树。
2)、完全二叉树(Complete Binary Tree)
假设完全二叉树高度为k,则完全二叉树需要符合以下两点:
(1)、所有叶子节点都出现在k层或k-1层,并且从1~k-1层必须达到最大节点数。
(2)、第k层可以是不满的,但是第k层的所有节点必须集中在最左边。
3、二叉树的性质
1)在非空二叉树的第i层上,至多有2^(i-1)个结点
2)深度为k的二叉树至多有2^k-1个结点
3)对任何一颗二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0 = n2+1
4)n个结点的完全二叉树深度为⌈log2(n+1)⌉,对以2为底n+1对数进行向上取整(⌈⌉是向上取整符号)
4、二叉树的存储方式
a、顺序存储:
二叉树结点从上到下、从左往右依次存储,n层的二叉树需要2^(n-1)个连续存储空间。特别适合存储完全二叉树和满二叉树,否则浪费大量空间。如下图:
对于完全二叉树用顺序存储方式,若父节点下标为i,则左孩子为2i,右孩子为2i+1(从下标1开始存储节点)。
b、链式存储:
使用二叉链表表示法,如下代码:
二叉链表表示:
public class BinaryTree {
Object data;
BinaryTree leftChild;// 左孩子
BinaryTree rightChild;// 右孩子
public BinaryTree(Object data) {
this.data = data;
}
//还可以其他构造方法
}
5、二叉查找树(二叉搜索树、二叉排序树)
(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
(3)左、右子树也分别为二叉排序树;
(4)没有键值相等的结点。
空树也是二叉排序树!!!
二叉查找树删除的三种情况:
a、删除节点为叶子结点时直接删除
如上图,删除叶子结点1、3、8,可以直接删除。
b、删除节点只有一个孩子结点
如上图,删除节点4(它只有一个左孩子),直接令祖父结点2指向孙子结点3即可。
c、删除节点有两个孩子结点
如上图,删除节点2(它有两个孩子结点),用其右子树的最小结点代替待删除结点的数据,然后递归删除那个右子树最小结点。或者用其左子树的最大结点代替待删除结点的数据,然后递归删除那个右子树最小结点。
// 定义二叉树结构
static class BSTree {
private int value;
private BSTree leftChild;
private BSTree rightChild;
public BSTree(int data) {
this.value = data;
}
public BSTree(int value, BSTree leftChild, BSTree rightChild) {
this.value = value;
this.leftChild = leftChild;
this.rightChild = rightChild;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public BSTree getLeftChild() {
return leftChild;
}
public void setLeftChild(BSTree leftChild) {
this.leftChild = leftChild;
}
public BSTree getRightChild() {
return rightChild;
}
public void setRightChild(BSTree rightChild) {
this.rightChild = rightChild;
}
}
// 二叉查找树root中插入节点data
public static boolean insert(BSTree root, int data) {
BSTree newNode = new BSTree(data, null, null);
if (root == null) {// root为空树,新插入节点为根
root = newNode;
return true;
}
BSTree tree = root;
while (tree != null) {
if (tree.value == data) {
System.out.println("二叉查找树没有键值相等的结点");
return false;
}
if (tree.value > data) {
if (tree.leftChild == null) {
tree.leftChild = newNode;
return true;
}
tree = tree.leftChild;
} else {
if (tree.rightChild == null) {
tree.rightChild = newNode;
return true;
}
tree = tree.rightChild;
}
}
return false;
}
/* 二叉查找树的删除(三种情况)
* 1、删除节点为叶子结点时直接删除
* 2、删除节点只有一个孩子结点
* 3、删除节点有两个孩子结点
*/
public static boolean delete(BSTree root, int data) {
// 1、先找到要删除元素data节点
BSTree treeParent = null;// 为了删除有一个孩子的节点或者叶子节点
BSTree tree = root;
while (tree != null) {
if (tree.value > data) {
treeParent = tree;
tree = tree.leftChild;
} else if (tree.value < data) {
treeParent = tree;
tree = tree.rightChild;
} else {// 相等,找到删除结点tree
break;
}
}
// 2、判断待删除结点tree的情况
if (tree == null) {
System.out.println("二叉查找树中没有该节点,无法删除");
return false;
}
// 删除节点有两个孩子结点【这里用右子树上最小结点替换】
if (tree.leftChild != null && tree.rightChild != null) {
BSTree rightParent = tree;
BSTree rightTree = tree.rightChild;
while (rightTree.leftChild != null) {
// 右子树的最小结点一定在最左边
rightParent = rightTree;
rightTree = rightTree.leftChild;
}
// 右子树最小结点rightTree要替换删除结点tree
tree.value = rightTree.value;
// 删除rightTree
if (rightTree.rightChild != null) {
rightParent.leftChild = rightTree.rightChild;
} else {
rightParent.leftChild = null;
}
return true;
}
// 删除节点为叶子结点/只有一个孩子结点
if (tree.rightChild != null) {
BSTree rightParent = tree;
BSTree rightTree = tree.rightChild;
treeParent.leftChild = rightTree;
return true;
} else if (tree.leftChild != null) {
BSTree rightParent = tree;
BSTree rightTree = tree.leftChild;
treeParent.rightChild = rightTree;
return true;
} else {// tree为叶子节点,直接删除
if (treeParent.value > tree.value) {// tree为左边的叶子节点
treeParent.leftChild = null;
} else {
treeParent.rightChild = null;
}
return true;
}
}
// 中序遍历二叉树,因为二叉排序树的中序结果为有序序列
public static void search(BSTree root) {
if (root == null) {
return;
}
search(root.leftChild);
System.out.print(root.value + " ");
search(root.rightChild);
}
public static void main(String[] args) {
BSTree root = new BSTree(20, null, null);
insert(root, 15);
insert(root, 30);
insert(root, 12);
insert(root, 18);
insert(root, 10);
insert(root, 14);
insert(root, 16);
insert(root, 19);
insert(root, 11);
insert(root, 13);
insert(root, 17);
search(root);
System.out.println("\n-------------------------------------");
// 删除15结点(有两个孩子结点)
// delete(root, 15);
// 删除10节点(有一个右孩子11)
// delete(root, 10);
// 删除14节点(有一个左孩子13)
// delete(root, 14);
// 删除叶子节点11
delete(root, 11);
search(root);
}
示意图如下:
二叉查找树的时间复杂度分析:
一般情况下:插入、删除、查找时间复杂度为O(log n),取决于树的高度!!!
最坏情况下:二叉查找树退化为线性结构(例如:以1、2、3、4等有序序列插入而形成的查找树),它的插入、删除、查找时间复杂度为O(n)。为了防止这种情况,设计了平衡二叉树(AVL树)。
6、平衡二叉树(AVL树、平衡二叉查找树)
平衡二叉树的定义:
(1)、可以是一 棵空树
(2)、如果不是空树,则它的左右两个子树的高度差的绝对值不超过1,并且左右子树都是一棵平衡二叉树。
平衡二叉树的优缺点
很好地解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是 频繁旋转会使插入和删除牺牲掉O(logN)左右的时间
平衡二叉树失衡的四种情况
当插入、删除节点时可能会破坏平衡二叉树的结构,可能有如下四种情形:LL型、RR型、LR型、RL型。参考链接:失衡四种情况解决
(1)、LL型:如下图,当插入结点24,破坏了AVL树的平衡性。规定从插入结点开始,向上寻找第一个不平衡起始结点,发现为结点35。
其一般规律如下图:数字只是标记作用!!!
(2)、RR型:如下图,当插入结点70后破坏了AVL树的平衡。发现60为不平衡起始点。
其一般规律如下图:数字只是标记作用!!!
(3)、LR型:
其一般规律如下图:数字只是标记作用!!!
(4)、RL型:
其一般规律如下图:数字只是标记作用!!!
// 定义平衡二叉树的结构
static class AVLNode {
private int value;
private AVLNode leftChild;
private AVLNode rightChild;
private int height;// 判断结点的高度
public AVLNode(int data) {
this.value = data;
}
public AVLNode(int value, AVLNode leftChild, AVLNode rightChild) {
this.value = value;
this.leftChild = leftChild;
this.rightChild = rightChild;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public AVLNode getLeftChild() {
return leftChild;
}
public void setLeftChild(AVLNode leftChild) {
this.leftChild = leftChild;
}
public AVLNode getRightChild() {
return rightChild;
}
public void setRightChild(AVLNode rightChild) {
this.rightChild = rightChild;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
// 计算某个结点高度
private static int getNodeHeight(AVLNode node) {
if (node == null) {
return 0;
}
return node.getHeight();
}
// 计算结点node左右两个高度中的最大值
private static int getMaxHeight(AVLNode node, int left, int right) {
return Math.max(left, right);
}
/*
* LL旋转步骤:
* 1、让不平衡起始点元素的左孩子成为 新的起始节点
* 2、原来的不平衡起始点作为 新起始点的右孩子
* 3、原来的不平衡起始点的左孩子上的右子树作为 原来的不平衡起始点的左孩子
*
* @param node:失衡结点
* @return :旋转后的根节点
*/
public static AVLNode LL(AVLNode node) {
// 不平衡起始点node的左子树
AVLNode node_left = node.leftChild;
// 原来的不平衡起始点的左孩子上的右子树【可能null】
AVLNode newLeft = node_left.rightChild;
// 开始旋转
node_left.rightChild = node;
if (newLeft != null) {
node.leftChild = newLeft;
}
// 重新计算高度失衡结点和旋转后根节点的高度
node.height = getMaxHeight(node, getNodeHeight(node.leftChild), getNodeHeight(node.rightChild)) + 1;
node_left.height = getMaxHeight(node_left, getNodeHeight(node_left.leftChild),
getNodeHeight(node_left.rightChild)) + 1;
return node_left;// 返回旋转后的根节点
}
/*
* RR
*/
public static AVLNode RR(AVLNode node) {
AVLNode node_right = node.rightChild;
AVLNode newRight = node_right.leftChild;// 可能null
// 开始旋转
node_right.leftChild = node;
node.rightChild = newRight;
// 重新计算高度
node.height = getMaxHeight(node, getNodeHeight(node.leftChild), getNodeHeight(node.rightChild)) + 1;
node_right.height = getMaxHeight(node_right, getNodeHeight(node_right.leftChild),
getNodeHeight(node_right.rightChild)) + 1;
return node_right;
}
/*
* LR:先左旋转(RR),再右旋转(LL)
*/
public static AVLNode LR(AVLNode node) {
// RR
node.leftChild = RR(node.leftChild);
// LL
AVLNode ll = LL(node);
return ll;
}
/*
* RL:先右旋转(LL),再左旋转(RR)
*/
public static AVLNode RL(AVLNode node) {
// LL
node.rightChild = LL(node.rightChild);
// RR
AVLNode rr = RR(node);
return rr;
}
/*
* 插入操作:向树root中插入结点data。在写代码时可以用如下实例思考代码:
* 以(1、2、3)的插入顺序观察RR型---->root = RR(root);
* 以(3、5、4)的插入顺序观察RL型---->root = RL(root);
* 以(3、2、1)的插入顺序观察LL型---->root = LL(root);
* 以(5、3、4)的插入顺序观察LR型---->root = LR(root);
*/
public static AVLNode insert(AVLNode root, int data) {
AVLNode newNode = new AVLNode(data, null, null);
if (root == null) {// 空树,新插入结点即为根
root = newNode;
return root;
}
if (data == root.value) {
System.out.println("AVL树中不存在值相等的节点");
return null;
}
if (data > root.value) {// 走根root的右子树
root.rightChild = insert(root.rightChild, data);
// 判断插入后是否会引起失衡
if (getNodeHeight(root.rightChild) - getNodeHeight(root.leftChild) == 2) {
if (data > root.rightChild.value) {// RR型
root = RR(root);
} else {// RL型
root = RL(root);
}
}
} else {
root.leftChild = insert(root.leftChild, data);
// 判断插入后是否会引起失衡
if (getNodeHeight(root.leftChild) - getNodeHeight(root.rightChild) == 2) {
if (data > root.leftChild.value) {// LR型
root = LR(root);
} else {// LL型
root = LL(root);
}
}
}
root.height = getMaxHeight(root, getNodeHeight(root.leftChild), getNodeHeight(root.rightChild)) + 1;
return root;
}
// 中序遍历AVL树【有序结果】
public static void search(AVLNode root) {
if (root == null) {
return;
}
search(root.leftChild);
System.out.print(root.value + " ");
search(root.rightChild);
}
public static void main(String[] args) {
AVLNode root = new AVLNode(3);// 树根
insert(root, 2);
insert(root, 1);
insert(root, 4);
insert(root, 5);
// 特别注意:因为在插入、遍历时都使用了递归,小心栈溢出问题,可以手动调大栈大小!
search(root);
}
标签:11,25,结点,node,rightChild,leftChild,2020,二叉树,root 来源: https://blog.csdn.net/Prestigious/article/details/110111162