二叉树排序
二叉树排序主要包括,节点信息的设计、节点的插入和树的中序遍历
- 节点信息(包括左右孩子和节点value)
private int value;//节点参数
private Node left;//左孩子
private Node right;//右孩子
- 节点插入
左子树<当前value<右子树(针对相同的值,可以根据出现的先后顺序来确定大小,咱们的代码里不考虑相同值),也就是插入值小于当前节点值,就插入到左子树,大于当前节点值,就插入右子树,等于当前节点值抛弃插入值
//将节点插入树中 node表示新节点,root表示根节点
public Node insertNode(Node node,Node root){
if(root.getValue()>node.getValue()){//插入左子树
if(root.getLeft()!=null){
return insertNode(node,root.getLeft());
}else {
root.setLeft(node);
return node;
}
}else if (root.getValue() <node.getValue()){//插入右子树
if(root.getRight() !=null){
return insertNode(node,root.getRight());
}else {
root.setRight(node);
return node;
}
}else{//这个节点废弃掉
return null;
}
}
- 中序遍
//中序遍历
public void traverseTree(Node node){
if(node!=null){
traverseTree(node.getLeft());//遍历左子树
printNode(node);//输出节点信息
traverseTree(node.getRight());//遍历右子树
}
}
因为咱们的插入是按照的 左子树<当前value<右子树规则去插入数据,所以中序遍历的结果是有序数列。 4.二叉树排序的效率正常情况下还是不错的,时间复杂度可以说是nlogn,但是当遇见下面这几张图数据输入,效率就就有点不如意了,我们该如何处理?
- 可以通过打乱输入顺序,尽可能避免上面出现上面情况。这种方法比较简单,容易实现,但该方法依旧无法避免出现上面的情况
- 这几张图效率低,是因为不平衡,提高效率,那就想办法让树平衡。
接下来要说的红黑树就能完美解决该问题
红黑树的性质
- 每个节点或者是黑色,或者是红色。
- 根节点是黑色。
- 每个叶子节点是黑色。 (叶子节点指的是树尾端的空节点)
- 如果一个节点是红色的,则它的子节点必须是黑色的。
- 对于任意结点而言,其到叶结点的每条路径都包含相同数目的黑结点。
根据性质咱们可以将节点信息表示为这样
private int value;
private Node left;//左子树
private Node right;//右子树
private Node parent;//父节点
private boolean color;//true表示黑色,false表示红色
根据性质五咱们可以知道红黑树是根据到叶子节点的黑色节点个数来保证输的平衡的,来避免上图中咱们遇到不平衡的现象
旋转操作
旋转是红黑树的一个重要操作,红黑树是就是通过旋转和染色来维持树的平衡的 左旋和右旋
左旋转 得到右边的图,右旋转得到左边的图,左旋和右旋是相反的两个操作,旋转不会改变二叉查找树的性质
/**
* 左旋转代码实现
*
* @param node
*/
public void leftRoate(Node node){
if(node == null){
return;
}
Node pNode =node.getParent();
if(pNode == null){
return;
}
Node lChild =node.getLeft();
node.setParent(pNode.getParent());
if(pNode.getParent()!=null) {
if (pNode.getParent().getLeft() == pNode) {
pNode.getParent().setLeft(node);
}else {
pNode.getParent().setRight(node);
}
}
pNode.setParent(node);
node.setLeft(pNode);
pNode.setRight(lChild);
if(lChild!=null)
lChild.setParent(pNode);
}
/**
* 右旋转代码实现
*
* @param node
*/
public void rightRoate(Node node){
if(node ==null){
return;
}
Node pNode =node.getParent();
if(pNode ==null){
return;
}
Node rChild =node.getRight();
node.setParent(pNode.getParent());
if(pNode.getParent()!=null) {
if (pNode.getParent().getLeft() == pNode) {
pNode.getParent().setLeft(node);
}else {
pNode.getParent().setRight(node);
}
}
pNode.setParent(node);
node.setRight(pNode);
pNode.setLeft(rChild);
if(rChild!=null)
rChild.setParent(pNode);
}
节点插入
- 将红黑树当做一颗二叉查找树,进行插入
- 将插入节点染为红色(之所以插入红色是因为 若为黑色,一定违背性质5,违背了性质就意味着需要调整树,染为红色只有可能违背4,同时数中黑色节点较多,违背性质四的次数也少些。我们尽量少违背少调整。)
//将节点插入树中
public Node insertNode(Node node,Node root){
if(root.getValue()>node.getValue()){//插入左子树
if(root.getLeft()!=null){
return insertNode(node,root.getLeft());
}else {
root.setLeft(node);
node.setParent(root);//设置父节点
return node;
}
}else if (root.getValue() <node.getValue()){//插入右子树
if(root.getRight() !=null){
return insertNode(node,root.getRight());
}else {
root.setRight(node);
node.setParent(root);//设置父节点
return node;
}
}else{//这个节点废弃掉
return null;
}
}
插入代码和二叉树插入代码,除了设置下父节点,其他的都一样
平衡红黑树
新增节点就意味着有可能改变树的平衡,接下来就是分情况来调整树,调整树主要操作就是旋转和染色
父节点为黑色
若父节点为黑色,插入一个红色节点,不违背任何性质,直接插入,无需进行操作
父节点为红色
当父节点为红色时,插入节点后违背性质4,分情况进行修复红黑树
叔节点为红色
此时只需将父节点和叔节点置为黑色,然后将他的祖父节点,置为红色,然后将祖父节点当做当前节点,继续进行平衡,一直到根节点
叔节点为黑色
插入节点在父节点右支上
当父节点在祖父节点的左支上,以当前节点为轴进行左旋,将结构改为插入节点在左节点上,此时将问题转为插入节点在父节点的左支上,并且父节点在祖父节点的左支上
当父节点在祖父节点的右支上 ,以父节点为轴进行左旋,同时 父节点置为黑色,祖父节点置为红色
插入节点在父节点的左支上
当父节点在祖父节点的左支上,以父节点为轴进行右旋,同时 父节点置为黑色,祖父节点置为红色
当父节点在祖父节点的右支上,以当前节点为轴进行右旋,将结构改为插入节点在右节点上,此时将问题转为插入节点在父节点的右支上,并且父节点在祖父节点的右支上
//平衡树,
public void blanceTree(Node node){
if(node ==null){
return;
}
Node pNode = node.getParent();//父节点
if(pNode ==null){
node.setColor(true);
return;
}
if(pNode.isColor()){//父节点是黑色
// System.out.println("-----");
return;
}
Node ppNode = pNode.getParent();//祖父节点
if(ppNode.getLeft()!=null&&!ppNode.getLeft().isColor()&&ppNode.getRight()!=null&&!ppNode.getRight().isColor()){
ppNode.getRight().setColor(true);
ppNode.getLeft().setColor(true);
//节点染色
ppNode.setColor(false);
blanceTree(ppNode);
return;
}
if(pNode == ppNode.getLeft()){//父节点在祖父节点的左支上
if(node == pNode.getLeft()){//该节点在父节点左支上
rightRoate(pNode);
//节点染色
ppNode.setColor(false);
pNode.setColor(true);
}else {//该节点在父节点右支上
leftRoate(node);
rightRoate(node);
//节点染色
node.setColor(true);
ppNode.setColor(false);
}
}else {//父节点在祖父节点的右支上
if(node == pNode.getLeft()){//该节点在父节点左支上
rightRoate(node);
leftRoate(node);
//节点染色
node.setColor(true);
ppNode.setColor(false);
}else {//该节点在父节点右支上
leftRoate(pNode);
//节点染色
pNode.setColor(true);
ppNode.setColor(false);
}
}
}
最后一步
将插入和树平衡组合到一块,完成这一步就算完成红黑树的插入操作
//因为通过旋转有可能改变根节点,所以一定通过找父节点找到根节点
public Node getRoot() {
Node root =tree;
while (root.getParent()!=null){
root = root.getParent();
}
tree = root;
return root;
}
//插入一个数值
public void insert(int key){
Node node =createNode(key);
if(tree ==null){//如果树为空
node.setColor(true);//设置为黑色
tree =node;
return ;
}
Node insertNode = insertNode(node,getRoot());
// System.out.println(node.getValue());
blanceTree(insertNode);
}
总结
红黑树是一种通过 旋转和 染色达到平衡二叉查找树,它的复杂度是nlogn,是一种不错的数据结构。