数据结构2(二叉树)
目录
1:什么是二叉树
二叉树是树形结构的数据,之所以叫二叉树是因为每个节点只能分两个叉,如所示:
该结构每一层的数据是二的n次方.规律如下
第一层:2*0=1
第二层:2*1=2
第二层:2*2=4
总数为:2*3-1=7
二叉树的意思就是说:
1:每个节点只能有两个儿子,下面的图就是一颗二叉树。
2:一棵树至少会有一个节点(根节点)
3:而节点的定义就是:一个数据、两个指针
一棵树至少会有一个节点(根节点)
树由节点组成,每个节点的数据结构是这样的:
因此,我们定义树的时候往往是**->定义节点->节点连接起来就成了树**,而节点的定义就是:一个数据、两个指针(如果有节点就指向节点、没有节点就指向null)
2:代码简单实现
创建树结构:
package com.thit.tree;
public class TreeNode {
//树的左节点
private TreeNode leftTreeNode;
//树的右节点
private TreeNode rightTreeNode;
//树上的值
private int value;
public TreeNode() {
}
public TreeNode(int value) {
super();
this.value = value;
}
public TreeNode getLeftTreeNode() {
return leftTreeNode;
}
public void setLeftTreeNode(TreeNode leftTreeNode) {
this.leftTreeNode = leftTreeNode;
}
public TreeNode getRightTreeNode() {
return rightTreeNode;
}
public void setRightTreeNode(TreeNode rightTreeNode) {
this.rightTreeNode = rightTreeNode;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
代码创建树结构:
package com.thit.tree;
public class Test1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
//根节点-->10
TreeNode treeNode1 = new TreeNode(10);
//子节点-->5
TreeNode treeNode2 = new TreeNode(5);
//5的左孩子
TreeNode treeNode5Z = new TreeNode(3);
treeNode5Z.setLeftTreeNode(new TreeNode(2));
treeNode5Z.setRightTreeNode(new TreeNode(4));
//5的右孩子
TreeNode treeNode5Y = new TreeNode(7);
treeNode5Y.setLeftTreeNode(new TreeNode(6));
treeNode5Y.setRightTreeNode(new TreeNode(8));
//右孩子-->20
TreeNode treeNode3 = new TreeNode(20);
//20的左孩子-->15
TreeNode treeNode4 = new TreeNode(15);
//20的右孩子-->35
TreeNode treeNode5 = new TreeNode(35);
//组装二叉树,组装子节点5
treeNode1.setLeftTreeNode(treeNode2);
treeNode1.setRightTreeNode(treeNode3);
//创建子节点5的左右节点分别是:3和7
treeNode2.setLeftTreeNode(treeNode5Z);
treeNode2.setRightTreeNode(treeNode5Y);
//创建子节点20的左右节点分别是:15和35
treeNode3.setLeftTreeNode(treeNode4);
treeNode3.setRightTreeNode(treeNode5);
}
}
树结构如下:
解析是递归方法:
输出结果是:
3:二叉树结构分析(二分法查询思想)
二叉树特点:
1:查询修改快,只需要按照层级查找;最大次数为层高。
2:插入情况下,容易造成层级剧增
3.1:查询
在如下的二叉树中,由于二叉树的数据量是在按照2的N次次方的形式在增加。但是层级是n增加的很慢
如果我们要查找10的值,我们只需要比较四次即可,
第一次:和根节点比较10>9,在右边
第二次:和13比较,比13小,在左边
第三次:和11比较,比11小,在左边
第三次:和10比较,相等,找到目标数据
查询的时候只需要按照层级查询,所以很快,修改也很快
3.1:插入
目标二叉树如下:
如果我们接下来我们依次插入如下五个节点:7,6,5,4,3。依照二叉查找树的特性,结果会变成什么样呢?
会变成这个结构:
二叉树在插入的情况下会出现这种层级剧增的情况, 查找速度变得很慢,时间复杂度剧增,有可能会出现只有一条腿有数据的二叉树,类似链表。查询时间跟链表似的,变得很慢,这就是二叉树失去了平衡,变成了非平衡二叉树。
非平衡二叉树:任意节点的左右子节点节点层级过深,层级叉超过了1
平衡二叉树:任意节点的左右子节点层级差不超过1
4:红黑树
4.1什么是红黑树
红黑树定义和图例如下:
(1)每个结点要么是红的要么是黑的。
(2)根结点是黑的。
(3)每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。
(4)如果一个结点是红的,那么它的两个儿子都是黑的。
(5)从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
红黑树也是二叉树的一种,但是红黑树怎么实现二叉树平衡,而不会造成层级差超过1呢?
红黑树,主要通过旋转和变色,维持二叉树的层级结构稳定,不出现退化
旋转:红黑树的旋转是一种能保持二叉搜索树性质的搜索树局部操作。有左旋和右旋两种旋转,通过改变树中某些结点的颜色以及指针结构来保持对红黑树进行插入和删除操作后的红黑性质。
左旋:对某个结点x做左旋操作时,假设其右孩子为y而不是T.nil:以x到y的链为“支轴”进行。使y成为该子树新的根结点,x成为y的左孩子,y的左孩子成为x的右孩子。如下左旋伪代码所示:
伪代码解析:
(1)第2行:定位结点x,y的位置关系,即y为x的右孩子;
(2)第3~5行:修改指针使结点y的左子树成为结点x的右子树;
(3)第6~11行:修改指针使结点y成为结点x的父节点的新的子结点,即替换结点x的位置;
(4)第12~13行:修改指针使结点x成为结点y的左孩子结点。
右旋:
对某个结点x做右旋操作时,假设其左孩子为y而不是T.nil:以x到y的链为“支轴”进行。使y成为该子树新的根结点,x成为y的右孩子,y的右孩子成为x的左孩子。
4.2红黑树旋转,变色演示:
变色:
为了重新符合红黑树的规则,尝试把红色节点变为黑色,或者把黑色节点变为红色。
下图所表示的是红黑树的一部分,需要注意节点25并非根节点。因为节点21和节点22连续出现了红色,不符合规则4,所以把节点22从红色变成黑色:
但这样并不算完,因为凭空多出的黑色节点打破了规则5,所以发生连锁反应,需要继续把节点25从黑色变成红色:
此时仍然没有结束,因为节点25和节点27又形成了两个连续的红色节点,需要继续把节点27从红色变成黑色:
变色之后如下:这个变色满足了红色节点不能连续,但是出现了不满足任意节点到其根节店都有相同的黑色节点的情况,所以需要进行旋转。
此时节点17和节点25是连续的两个红色节点,那么把节点17变成黑色节点?恐怕不合适。这样一来不但打破了规则4,而且根据规则2(根节点是黑色),也不可能把节点13变成红色节点。
变色已无法解决问题,我们把节点13看做X,把节点17看做Y,像刚才的示意图那样进行左旋转:
由于根节点必须是黑色节点,所以需要变色,变色结果如下:
这样就结束了吗?并没有。因为其中两条路径(17 -> 8 -> 6 -> NIL)的黑色节点个数是4,其他路径的黑色节点个数是3,不符合规则5。
这时候我们需要把节点13看做X,节点8看做Y,像刚才的示意图那样进行右旋转:
最后根据规则来进行变色:
如此一来,我们的红黑树变得重新符合规则。这一个例子的调整过程比较复杂,经历了如下步骤:
变色 -> 左旋转 -> 变色 -> 右旋转 -> 变色
5:红黑树总结
红黑树是一种平衡的二叉树,弥补了二叉树退化的在极端情况下退化为链表的情况,保证了树高可以控制.
查询情况:按照层次遍历即可,速度很快,速度需要查log2(n)的时间单位
插入:插入数据之后需要调色,还有可能引起旋转,但旋转最多两次,依然可控,但操作时间仍可以保持为 O(log n) 次 。
删除:删除数据之后,需要调色,还有可能引起旋转,但旋转最多三次,依然可控,但操作时间仍可以保持为 O(log n) 次 。