二叉树图解网址:
Visualgo-二叉树
这里是二叉树的图示:
野生的二叉树![](http://www.linuxdiyf.com/bbs/attachments/forumid_45/12110114382e13cd71e9cd7814.jpg)
不好意思放错了
这一部分就是一棵树,而每一个圆圈则叫做一个节点,每个节点最终指向的内容都是NULL,这里就直接省略了
节点是最基本的单位,那么根据上图,我们应该这样设计节点:
第一部分:指针—— 一个节点会有两个指针,一个指向左边,一个指向右边
第二部分:数据—— 这里的数据是int整型,根据需要我们可以改为其他类,譬如说我在自己写的图书馆管理系统里就改成了Book这一个类
那么为什么要用二叉树作为数据结构呢?我们来看一下二叉树的特点:凡是左边的数都要比右边的小
这一特点可以缩短搜索的时间,每一个节点都只要选择一个方向进行搜索就行了。
下面,我们来具体实现一个二叉树:![](http://pic.qiantucdn.com/58pic/15/47/55/09858PICcmk_1024.png)
一、二叉树的构建与增加节点
首先是节点部分,一个节点应该包括:1.存储的数据2.指向比数据小的节点的指针3.指向比存储数据大的节点的指针
/**
* @author 起名比做卷子难
*/
public class Node {
private Node left = null;//指向较小的节点
private Node Right = null;//指向较大的节点
private int data;//存储的数据
public Node(int data) {
this.data = data;
}//创建新节点的时候把数据存储上
public Node getLeft() {
return left;
}
public void setLeft(Node left) {
this.left = left;
}
public Node getRight() {
return Right;
}
public void setRight(Node right) {
Right = right;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
}
然后开始构建,实质上就是向二叉树中增添数据,基本思路基于上面所说的二叉树的特点,左边的数据比右边的大,所以从树根开始,小于节点的,去左边,大于节点的,去右边,如果左边(右边)是空,那么就可以创建一个新节点了。
/**
* @author 起名比做卷子难
*/
public class BinaryTree {
//这是一棵树
private Node root = null;//树根
private Node node = null;//节点指针
}
//增加数据的方法
public void add(int num) {
//首先判断树根是否为空,若为空新建树根
if (root == null) {
root = new Node(num);
return;
}
node = root;//增加每一个数据都要从头开始搜索
while (true) {
if (num < node.getData()) {//如果增加的数据比节点数据小
if (node.getLeft() != null) {//进行判断,也是一个保护,考虑考虑如果节点左边空了呢?
node = node.getLeft();//节点指针指向左侧节点
continue;//继续循环
} else {//节点左边空了
node.setLeft(new Node(num));//新建节点,同时这个节点的左指针指向新建的节点
break;//跳出,增加完毕
}
} else {//以下的内容为上面的重复,尝试自己进行理解?
if (node.getRight() != null) {
node = node.getRight();
continue;
} else {
node.setRight(new Node(num));
break;
}
}
}
}
现在我们就创建好了一个二叉树了,但是我们看不见,为了进行检验,我们需要对二叉树进行遍历,这里使用DFS(深度优先搜索算法)进行遍历
DFS算法:(如果熟悉可忽略)
DFS其实很好理解,就是先遍历一支,再遍历另一支,就像图中的曲线跟箭头所示,一步步进行遍历,找到null就返回上一步
这里遍历的顺序是:7→4→2→4→6→4→7→11→9→11→15→11→7,看起来优点繁琐,但是实际上十分简单。
我们写一个方法来进行遍历,并在控制台打印以检验我们的构建是否成功
//DFS遍历二叉树
public void dfs(Node nodeBegin) {
Node node = nodeBegin;//从头开始遍历
//如果遍历到null了,证明这一支已经遍历完毕,直接返回
if (node == null) {
return;
}
dfs(node.getLeft());//遍历左支
dfs(node.getRight());//遍历右支
System.out.println(node.getData());//在遍历完左右支后再打印这个节点
return;
}
注意!!!这里有一个问题非常值得探讨,那就是NullPointerException
可能有人会问,既然我们在先前已经定义了一个node作为节点指针,为什么还要在这里多新建一个node呢?
我们不妨试一下,将“Node”删去,得到的运行结果是:
![](http://img.youxih.com/upload/file/news/baguaquwen/20141211/c35973964f58ba25cdf6344e695090a1.jpeg)
为什么会出现这种情况呢?很简单,在递归的过程中,变量node的值不断改变,直到到达数据为2的这个节点并dfs(2的左支)后,node = null,这时候再进行null.getRight(),当然会报错。这种细节问题最为令人恼火,也最为致命。
当然了,我这里写的很复杂,为的是引出这个问题,如果希望简洁,可以直接不新建Node,把凡是出现“node”的地方换成nodeBegin就可以了
我们再创建一个display()的方法,以后还有用
public void display() {
dfs(root);
}
/**
* @author 起名比做卷子难
* 强迫症新建一个类专门用于运行
*/
public class Store {
public static void main(String[] args) {
BinaryTree tree = new BinaryTree();//新建一个二叉树
/*
* 7
* / \
* 4 11
* / \ / \
* 2 6 9 15
* 数据结构图示
*/
int[] number = {7,6,11,2,6,9,15};//数组
for (int num : number){
tree.add(num);//for each方法向树中添加数据
}
tree.display();//运用display方法输出
}
}
运行结果是: 2 6 4 9 15 11 7(有换行,在这里没有打出来)
如果希望得到的是 2 4 6 7 9 11 15, 只需要把打印的语句放在递归左右支的中间就好。如果希望得到逆序输出的,也只需要调换语句顺序,这里就不解释了。
(这样一来有没有觉得很方便?)
二、二叉树的查找
数据都已经放好了,那么我们就来对二叉树进行查找。查找所基于的也是数据左大右小的特点,譬如说我要找“2”,那么我们先跟根节点7比较,2<7,向左边继续找,2<4,还是向左边找,最后就找到了2。这一步非常简单,原理跟二叉树的遍历相同,而且查找用到的只是while不需要用到递归,更为简单方便。下面是查找的代码。
public Node find(int num) {
Node node = root;//上面的情况太令人尴尬,索性就新建一个Node确保安全
while (node != null) {//只要节点不为空
if (node.getData() > num) {//要查找的小于当前节点
node = node.getLeft();//向左
continue;//继续
} else if (node.getData() < num) {//要查找的大于当前节点
node = node.getRight();//向右
continue;//继续
} else {//要查找的就是当前节点
return node;//返回当前节点
}
}
return null;//找不到就返回null
}
这里要实现的是找到二叉树的最小值,又或者是某个节点开始的最小值。二叉树找最大值、最小值都非常简单,只要一路向右(向左)就可以了。下面的代码写的是找最小值。
private Node findMin(Node node) {
while (node.getLeft() != null) {//判断左侧还有没有,起到保护作用
node = node.getLeft();//一路向左
}
return node;//返回的是一整个节点
}
三、二叉树节点的删除
前面做的都非常简单,小儿科而已,下面的删除节点操作就显得有点复杂了。我们先把删除的思路理清。
如图,二叉树的删除分为两部分(这里以4为被删除的节点)
(1)找到要删除的节点,然后在被删除的节点的右节点找一个最小支,然后将被删除节点的左支“嫁接”到最小支的左支,此时的最小支左支肯定为null,否则它就不是最小支。
(2)然后将被删除节点的父节点的左支(也有可能是右支,根据实际情况而定)连到被删除节点的右支(左支)就可以了。
思路其实很简单,难的是实现过程中对于各种情况的讨论,最终代码如下:
private Node get(Node node, int num) {
if (node == null || node.getData() == num)
return node;//若已到分支底部或已找到 退出递归
Node temp = null;//用于存放被删除节点的指针
Node min = null;//用于存放需要需要删除节点右枝最小节点的指针
if (node.getData() > num) {//被删除数据小于节点数据,所以下面只要讨论小于的情况
temp = get(node.getLeft(), num);// 寻找被删除的节点,先递归左边树枝,如果没有找到返回null
if (temp == null)//没有找到被删除节点
return null;//没有被删除的节点
else {//找到了要被删除的节点
if (temp.getRight() == null){//如果被删除节点没有右支,那么就直接把父节点的左支设为被删除节点的左支。
node.setLeft(temp.getLeft());//父节点的左支设为被删除节点的左支
return null;//删除以后就返回null
}
min = findMin(temp.getRight());//有右枝就找右枝最小节点
min.setLeft(temp.getLeft());//右枝的最小节点的左枝设为被删除节点的左枝
node.setLeft(min);//父节点的左支设为被删除节点有右侧最小节点
return null;
}
} else {
temp = get(node.getRight(), num);// 寻找被删除的节点,先递归右边树枝,如果没有找到返回null
if (temp == null)//下面的与上面的代码大同小异,尝试自己进行理解
return null;
else {
if (temp.getRight() == null){
node.setRight(temp.getLeft());
return null;
}//被删除的节点没有右枝
min = findMin(temp.getRight());
min.setLeft(temp.getLeft());
node.setRight(min);
return null;
}
}
}
到此为止,整个二叉树已经实现了。