基础算法-树,二叉树(四)
树
树状图是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
- 每个节点有零个或多个子节点;
- 没有父节点的节点称为根节点;
- 每一个非根节点有且只有一个父节点;
- 除了根节点外,每个子节点可以分为多个不相交的子树;
二叉树
二叉树(Binary Tree) 是另外一种树型结构,它的特点是每个节点至多只有两棵子树(即二叉树中不存在度大于2的结点),并且,二叉树的子树有左右之分,其次序不能任意颠倒。
与树的递归定义类似,二叉树的递归定义如下:二叉树或者是一棵空树,或者是一棵由一个根结点和两棵互不相交的分别称为根的左子树和右子树的子树所组成的非空树。
二叉树的特点:
- 每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点。
- 左子树和右子树是由顺序的,次序不能颠倒。
- 即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。
重点:二叉树中,左节点必须小于右节点。
二叉树的性质:
- 在二叉树的第i层上至多有2^(i-1)个结点(i>=1)。
- 深度为K的二叉树至多有2^k - 1个结点(k>=1)。
- 对任何一棵二叉树T,如果其终端结点数为n0, 度为2的结点数为n2,则n0 = n2 + 1。
- 具有n个结点的完全二叉树的深度为(logn)+1。
- 如果对一棵有n个结点的完全二叉树的结点按层序号遍历,对任意结点i有:
- 如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点i/2。
- 如果2i>n,则结点i无左孩子;否则其左孩子是结点2i。
- 如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1.
二叉树的复杂度:
二叉树的遍历
前序遍历: 若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。
我自己画的图:这就是所谓的前序遍历
中序遍历: 若二叉树为空,则空操作返回,否则从根结点开始,中序遍历根节点的左子树,然后访问根结点,再中序遍历根结点的右子树。
后序遍历: 若二叉树为空,则空操作返回,否则从左到右先叶子后节点的方式遍历访问左子树和右子树,最后是访问根结点。
层次遍历: 若二叉树为空,则空操作返回,否则从树的第一层开始,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。
层次遍历有:广度优先,深度优先。
广度优先:
深度优先:
1.先从根节点“靠左边”一直往最深的节点遍历;
2.再一层一层的上来把剩余节点以第一上面原则再次输出。
深度优先遍历出来的和前序遍历出来的结果一样。
注意:研究树的遍历的意义在于,计算机只会处理线性序列,而我们在现实程序中需要用到复杂的结构,树的遍历把树中的结点变成某种意义的线性序列,这给程序实现带来了好处。
代码实现
package cn.datastruts;
import cn.thread.first.syn.SyDome6;
import java.util.LinkedList;
import java.util.Stack;
/**
* 二叉树:是每个结点最多有两个子树的有序树,在使用二叉树的时候,数据并不是随便插入到节点中的,
* 一个节点的左子节点的关键值必须小于此节点,右子节点的关键值必须大于或者是等于此节点,
* 所以又称二叉查找树、二叉排序树、二叉搜索树。
*/
public class BinaryTree {
private TreeNode root = null;
public int put(int value) {
TreeNode newNode = new TreeNode(value);
if (root == null) {
root = newNode;
return value;
}
TreeNode currentNode = root;
while (true) {
if (newNode.value > currentNode.value || newNode.value == currentNode.value) {
if (currentNode.rightChild == null) {
currentNode.rightChild = newNode;
break;
}
currentNode = currentNode.rightChild;
} else {
if (currentNode.leftChild == null) {
currentNode.leftChild = newNode;
break;
}
currentNode = currentNode.leftChild;
}
}
return value;
}
public Integer find(int value) {
TreeNode currentNode = root;
if (currentNode == null) {
return null;
}
Integer temp;
while ((temp = currentNode.value) != value) {
System.out.println("pass=" + temp);
if (temp > value) {
currentNode = currentNode.rightChild;
} else {
currentNode = currentNode.leftChild;
}
if (currentNode == null) {
return null;
}
}
return temp;
}
//中序遍历,这样遍历出来的数据是递增的,从小到大
public void inOrder(TreeNode currentNode) {
if (currentNode != null) {
inOrder(currentNode.leftChild);
System.out.println("---" + currentNode.value);
inOrder(currentNode.rightChild);
}
}
int count = 15;
//前序遍历,这样遍历出来的数据是,乱序的
public void printlnOrder(TreeNode currentNode, boolean point) {
if (currentNode != null) {
if (point) {
count++;
} else {
count--;
}
String k = " ";
for (int i = 0; i < count; i++) {
k += " ";
}
System.out.println(k + currentNode.value);
printlnOrder(currentNode.leftChild, false);
printlnOrder(currentNode.rightChild, true);
}
}
//后序遍历,乱序的
public void postOrder(TreeNode currentNode, boolean point) {
if (currentNode != null) {
postOrder(currentNode.leftChild, false);
postOrder(currentNode.rightChild, true);
if (point) {
count++;
} else {
count--;
}
String k = " ";
for (int i = 0; i < count; i++) {
k += " ";
}
System.out.println(k + currentNode.value);
}
}
//无递归遍历,中序遍历,有序的哦
public void nonRecInOrder(TreeNode root) {
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode node = root;
while (node != null || stack.size() > 0) {
while (node != null) {
stack.push(node);
node = node.leftChild;
}
if (stack.size() > 0) {
node = stack.pop();//出栈,这里要记得,元素出栈后就会被抛弃掉哦!
System.out.println("stack--=" + stack.size() + "," + node.value);
node = node.rightChild;
}
}
}
//无递归遍历,前序遍历,有序的哦
public void nonRecPreOrder(TreeNode root) {
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode node = root;
while (node != null || stack.size() > 0) {
while (node != null) {
System.out.println("stack--=" + stack.size() + "," + node.value);
stack.push(node);
node = node.leftChild;
}
if (stack.size() > 0) {
node = stack.pop();//出栈,这里要记得,元素出栈后就会被抛弃掉哦!
node = node.rightChild;
}
}
}
//无递归遍历,后序遍历,有序的哦
public void nonRecPostOrder(TreeNode root) {
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode node = root, prev = root;
while (node != null || stack.size() > 0) {
while (node != null) {
stack.push(node);
node = node.leftChild;
}
if (stack.size() > 0) {
TreeNode t = stack.peek().rightChild;
if (t == null || t == prev) {
node = stack.pop();//出栈,这里要记得,元素出栈后就会被抛弃掉哦!
System.out.println("stack--=" + stack.size() + "," + node.value);
prev = node;
node = null;
} else {
node = t;
}
}
}
}
//广度优先,就是遍历树从上到下
public void spanFirst(TreeNode root) {
System.out.println("--广度优先---");
if (root == null) {
return;
}
LinkedList<TreeNode> linkedList = new LinkedList<TreeNode>();
linkedList.offer(root);
while (linkedList.size() > 0) {
TreeNode node = linkedList.poll();//出队列,队列先进先出,记住:出队列后元素就会被废弃掉哦。这就是出队列。
System.out.println(node.value);
if (node.leftChild != null) {
linkedList.offer(node.leftChild);
}
if (node.rightChild != null) {
linkedList.offer(node.rightChild);
}
}
}
//深度优先
public void depthFirst(TreeNode root) {
Stack<TreeNode> stack = new Stack<TreeNode>();
stack.push(root);
TreeNode node;
while (!stack.isEmpty()) {
node = stack.pop();
System.out.println("depthFirst==" + node.value);
if (node.rightChild != null) {
stack.push(node.rightChild);
}
if (node.leftChild != null) {
stack.push(node.leftChild);
}
}
}
private class TreeNode {
private int value = 0;
private TreeNode leftChild = null;
private TreeNode rightChild = null;
public TreeNode(int value) {
this.value = value;
this.leftChild = null;
this.rightChild = null;
}
}
public static void main(String[] args) {
BinaryTree tree = new BinaryTree();
tree.put(10);
tree.put(5);
tree.put(6);
tree.put(12);
tree.put(11);
tree.put(13);
tree.put(15);
tree.put(14);
tree.put(2);
tree.put(3);
System.out.println();
System.out.println(tree.find(13));
tree.inOrder(tree.root);
tree.printlnOrder(tree.root, false);
tree.postOrder(tree.root, false);
//无递归前中后
System.out.println("无递归前序遍历");
tree.nonRecPreOrder(tree.root);
System.out.println("无递归中序遍历");
tree.nonRecInOrder(tree.root);
System.out.println("无递归后序遍历");
tree.nonRecPostOrder(tree.root);
//广度优先
tree.spanFirst(tree.root);
//深度优先
tree.depthFirst(tree.root);
}
}
树节点的删除操作
二叉树节点的删除也是可以分为几种情况:
被删除节点为叶子节点;(如图:3,12,14,20,31)
被删除节点仅有一个子节点(子树);(如图:5:只有左子树,19:只有右子树)
被删除节点有两个子节点(子树);(如图:10,18,15,13,30)
被删除节点为叶子节点
思路:将该叶子节点的父节点指向的子节点的引用值设为空。
如:删除3节点,只需要将2的右节点指向null即可。
被删除节点仅有一个子树
思路:将该节点的父节点指向该节点的引用改成指向该节点的子节点。
如:删除5节点,只需要将10直接指向2即可。
被删除节点有两个子树
思路:处理这种情况有两种方法(二选一即可):
从待删除节点的左子树找节点值最大的节点A,替换待删除节点,并删除节点A;
从待删除节点的右子树找节点值最小的节点A,替换待删除节点,并删除节点A。
如:删除10节点,通过右子树找到右子树的最小节点。
删除10的时候右子树找到最小节点是12,那么把12的值覆盖10的值就可以了,而10节点的左右引用不变,这里只改value;然后把12这个节点删除就可以了,删除12节点不就又回到上面的删除叶子节点了吗。代码如下:
public TreeNode deleteNode(TreeNode node, int var) {
if (node == null) {
return null;
}
if (node.value == var) {
if (node.leftChild == null && node.rightChild == null) {
return null;
} else if (node.leftChild == null) {
return node.rightChild;
} else if (node.rightChild == null) {
return node.leftChild;
} else {
TreeNode tempNode = getSmallest(node.rightChild);
node.value = tempNode.value;
node.rightChild = deleteNode(node.rightChild, tempNode.value);
return node;
}
} else if (var < node.value) {
node.leftChild = deleteNode(node.leftChild, var);
return node;
} else {
node.rightChild = deleteNode(node.rightChild, var);
return node;
}
}
//找到getSmallest(node.rightChild);右子树最小的
public TreeNode getSmallest(TreeNode node) {
if (node.leftChild == null) {
return node;
} else {
return getSmallest(node.leftChild);
}
}
//找到getSmallest(node.leftChild);左子树最大的
public TreeNode getLeftMax(TreeNode node) {
if (node.leftChild == null) {
return node;
} else {
return getSmallest(node.rightChild);
}
}
代码分析:
1.删除3节点:
递归入栈步骤:
Node10.leftNode->Node2
Node2.rightNode->Node3
Node3=null;
递归出栈步骤:
Node3=null;
Node2.rightNode= Node3;
Node10.leftNode= Node2;
出栈完成。再来按照图走一遍。Node10.lenftNode->Node2; Node2.rightNode->null;
2.删除子树节点和上面的步骤一样哦,这里就不描述了。
3.删除双节点操作。
删除10节点;
由于10节点是根节点,所以刚来就找到了,不存在递归了。
直接进入getSmallest(Node10.rightNode);
getSmallest递归找到最小的值入栈步骤:
Node=getSmalleset1(node15.leftNode);
getSmalleset1()=getSmalleset2(node13.leftNode);
getSmalleset2()= return Node12;
出栈:
getSmallest2()=return Node12;
getSmallest1() = getSmallest2();
Node = getSmallest1();
最终找到了Node12这个节点。
TreeNode tempNode=Node12;
Node10.value=tempNode.value;//把10节点里面的值换成了12;
deleteNode(Node10.rightNode,12);删除12这个节点。这里就等于反复执行1,3步骤了。
参考地址
http://blog.youkuaiyun.com/jiangnan2014/article/details/38380513
http://blog.youkuaiyun.com/u010232305/article/details/50854683
https://noah_1992.gitbooks.io/-/content/bian_li_er_cha_shu.html 数据结构与算法
http://blog.youkuaiyun.com/luckyxiaoqiang/article/details/7518888 二叉树面试常问的相关算法
http://blog.youkuaiyun.com/gavin_john/article/details/72312276二叉树的定义
http://blog.youkuaiyun.com/javazejian/article/details/53727333树讲的不错,就是啰嗦了一点