版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/danielzhou888/article/details/78143607
敬请关注博客,后期不断更新优质博文,谢谢
源码:
------------------------------------------------------------------------------------
Node.java:
package
cn.com.tree;
/**
* 二叉树节点
*
@author
daniel
zhou
*
*/
public
class
Node {
// 数据项
public
long
data
;
// 数据项
public
String
sData
;
// 左子节点
public
Node
leftChild
;
// 右子节点
public
Node
rightChild
;
/**
* 构造方法
*
@param
data
*
@param
sData
*/
public
Node(
long
data, String sData){
this
.
data
= data;
this
.
sData
= sData;
}
}
--------------------------------------------------------------------------------
Tree.java:
package
cn.com.tree;
/**
* 二叉树类
*
@author
daniel
zhou
*
*/
public
class
Tree {
// 根节点
public
Node
root
;
/**
* 原理:在一颗二叉树上插入节点,插入是建立在小于父节点,
* 则插入到父节点左边,如果大于父节点,则插入到父节点右边。
* 在插入树节点,需要判断树根是否为空,如果不为空,则需要
* 当前节点指向树根和父节点指向当前节点。直到当前节点等于null,
* 那么可以在父节点左边或者右边插入新节点,并且返回树根跳出循环。
* 如果树根为空,直接把树根指向新创建的节点,实现过程如下所示:
*
* 插入节点
*
@param
value
*/
public
void
insert(
long
value, String sValue){
// 封装节点
Node newNode =
new
Node(value, sValue);
// 引用当前节点
Node current =
root
;
// 引用父节点
Node parent;
// 如果root为空,也就是第一次插入的时候
if
(
root
==
null
){
root
= newNode;
return
;
}
else
{
while
(
true
){
// 父节点指向当前节点
parent = current;
// 如果当前指向的节点数据比插入的要大,则向左走
if
(current.
data
> value){
current = current.
leftChild
;
if
(current ==
null
){
parent.
leftChild
= newNode;
return
;
}
}
else
{
current = current.
rightChild
;
if
(current ==
null
){
parent.
rightChild
= newNode;
return
;
}
}
}
}
}
/**
* 查找节点
*
@param
value
*
@return
*/
public
Node find(
long
value){
// 引用当前节点
Node current =
root
;
// 循环,只要查找值不等于当前节点的数据项
while
(current.
data
!= value){
// 进行比较,比较查找值和当前节点的大小
if
(current.
data
> value){
current = current.
leftChild
;
}
else
{
current = current.
rightChild
;
}
// 如果查找不到
if
(current ==
null
){
return
null
;
}
}
return
current;
}
二叉树删除节点原理图:
由于过程比较复杂,这里用图来表示
/**
* 删除节点:
* 工作原理:
从二叉查找树上删除节点的操作复杂程度取决于删除哪个节点。如果删除没有子节点的节点就非常简单,
如果节点只有一个子节点,不管是左子节点还是右子节点,就变得稍微有点复杂,如果节点包含两个子节点就最复杂。
如果待删除节点是叶子节点,那么只需要将从父节点指向它的链接指向null。
如果待删除节点只包含一个子节点,那么原本指向它的节点就得使其指向它的子节点。
如果待删除节点包含两个子节点,那么我们可以采用两种方式:
一种是查找待删除节点左子树上的最大值,
一种是查找待删除节点右节点上的最小值。
我们采取后者,找到最小值后,将临时节点上的值复制到待删除节点,然后再删除临时节点。
*
@param
value
*/
public
boolean
delete(
long
value){
// 引用当前节点,从父节点开始
Node current =
root
;
// 引用当前节点父节点
Node parent =
root
;
// 是否为左节点
boolean
isLeftChild =
true
;
while
(current.
data
!= value){
parent = current;
// 进行比较
if
(current.
data
> value){
// 向左走
current = current.
leftChild
;
isLeftChild =
true
;
}
else
{
// 向左走
current = current.
rightChild
;
isLeftChild =
false
;
}
// 如果查找不到
if
(current ==
null
){
return
false
;
}
}
// 找到对应的节点
if
(current.
leftChild
==
null
&& current.
rightChild
==
null
){
// 1,该节点没有子节点,直接删除叶子节点
// 如果是根节点
if
(current ==
root
){
root
=
null
;
}
else
if
(isLeftChild){
// 如果该节点是其父节点的左子节点,将其父节点的左子节点置为null
parent.
leftChild
=
null
;
}
else
{
// 如果该节点是其父节点的右子节点,将其父节点的右子节点置为null
parent.
rightChild
=
null
;
}
}
else
if
(current.
rightChild
==
null
){
// 2,该节点没有右子节点,直接将该节点的左子节点挂在其父节点的左/右节点上
if
(current ==
root
){
root
= current.
leftChild
;
}
else
if
(isLeftChild){
// 如果该节点是其父节点的左子节点,将该节点的左子节点挂在其父节点的左子节点上
parent.
leftChild
= current.
leftChild
;
}
else
{
// 如果该节点是其父节点的右子节点,将该节点的左子节点挂在其父节点的右子节点上
parent.
rightChild
= current.
leftChild
;
}
}
else
if
(current.
leftChild
==
null
){
// 3,该节点没有左子节点,直接将该节点的右子节点挂在其父节点的左/右节点上
if
(current ==
root
){
root
= current.
rightChild
;
}
else
if
(isLeftChild){
// 如果该节点是其父节点的左子节点,将该节点的左子节点挂在其父节点的左子节点上
parent.
leftChild
= current.
rightChild
;
}
else
{
// 如果该节点是其父节点的右子节点,将该节点的右子节点挂在其父节点的右子节点上
parent.
rightChild
= current.
rightChild
;
}
}
else
{
// 4,该节点有左右子节点
// 获取替代被删除节点的节点(即:找到该节点的中序后继节点,并将该节点替代被删除节点)
Node successor = getSuccessor(current);
if
(current ==
root
){
root
= successor;
}
else
if
(isLeftChild){
// 如果该节点是其父节点的左子节点
parent.
leftChild
= successor;
}
else
{
// 如果该节点是其父节点的右子节点
parent.
rightChild
= successor;
}
// 固定替代节点的位置(固定其在被删除节点的位置,右边位置已经固定,现在固定左边)
successor.
leftChild
= current.
leftChild
;
}
return
true
;
}
/** 返回替代被删除节点的节点对象
* 工作原理:
* 被删除的有两个孩子节点,这种情况最复杂,因为要考虑到删除之后顺序不能乱。
* 所以这种类型的节点要删除,如果直接删,真个树的大小顺序就乱了,所以需要考虑,
* 在树中找到一个合适的节点来把这个节点给替换掉,用这种方法来保持整个数的稳定。
* 所以又一个问题又来了了,该找哪个节点来替换它?结论是,需要在树中找出所有比
* 被删除节点的值大的所有数,并在这些数中找出一个最小的数来。听起来很拗,如果
* 把它用图形来描述的话,就是,从被删除的节点出发经过它的右节点,然后右节点最
* 左边的叶子节点就是我们要找的,它有一个专业名词叫中序后继节点。下面专门来写一个方法来找它:
*/
public
Node getSuccessor(Node delNode) {
// 替代节点
Node successor = delNode;
// 替代节点的父节点
Node successorParent = delNode;
// 从该节点的出发经过它的右节点
Node current = delNode.
rightChild
;
//找到该节点右节点最左边的叶子节点(就是我们要找的替代该节点的节点)
while
(current !=
null
){
successorParent = successor;
successor = current;
current = current.
leftChild
;
}
// 如果替代节点不为该节点的右节点
if
(successor != delNode.
rightChild
){
// 替代节点的父节点的左节点=替代节点的右节点
successorParent.
leftChild
= successor.
rightChild
;
// 替代节点(已经换到被删除节点的位置上)的右节点=原来被删除节点的右节点
successor.
rightChild
= delNode.
rightChild
;
}
// 如果替代节点为该节点的右节点,直接返回
return
successor
;
}
//*************N--root, L--left, R--right****************//
/** 遍历顺序:
- 前序遍历:NLR
1.访问根节点
2.前序遍历左子树
3.前序遍历右子树
中序遍历: LNR
1.中序遍历左子树
2.访问根节点
3.中序遍历右子树
后序遍历: LRN
1.后序遍历左子树
2.后序遍历右子树
3.访问根节点
*/
1
前序遍历
遍历的顺序为:ABDGHCEIF
/**
* 基本思想:
1.访问根节点
2.前序遍历左子树
3.前序遍历右子树
* 顺序:NLR
* 前序遍历(递归遍历)
*
@param
localNode
*/
public
void
frontOrder(Node localNode){
if
(localNode !=
null
){
// 访问根节点
System.
out
.println(localNode.
data
+
", "
+ localNode.
sData
);
// 前序遍历左子树
frontOrder(localNode.
leftChild
);
// 前序遍历右子树
frontOrder(localNode.
rightChild
);
}
}
/**
* 前序非递归遍历:
对于任一结点p:
a. 访问结点p,并将结点p入栈;
b. 判断结点p的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,
并将栈顶结点的右孩子置为当前的结点p,循环置a;若不为空,则将p的左孩子置为当前结点p;
c. 直到p为空,并且栈为空,则遍历结束。
*
@param
localNode
*/
public
void
frontOrder2(Node localNode){
}
2
中序遍历
遍历的顺序为:GDHBAEICF
/**
* 基本思想:
1.中序遍历左子树
2.访问根节点
3.中序遍历右子树
* 中序遍历
* 顺序:LNR
*
@param
localNode
*/
public
void
inOrder(Node localNode){
if
(localNode !=
null
){
// 中序遍历左子树
inOrder(localNode.
leftChild
);
// 访问根节点
System.
out
.println(localNode.
data
+
", "
+localNode.
sData
);
// 中序遍历右子树
inOrder(localNode.
rightChild
);
}
}
3
后序遍历
遍历的顺序为:GHDBIEFCA
/**
* 基本思想:
1.后序遍历左子树
2.后序遍历右子树
3.访问根节点
* 后序遍历
* 顺序:LRN
*
@param
localNode
*/
public
void
afterOrder(Node localNode){
if
(localNode !=
null
){
// 后序遍历左子树
afterOrder(localNode.
leftChild
);
// 后序遍历右子树
afterOrder(localNode.
rightChild
);
// 访问根节点
System.
out
.println(localNode.
data
+
", "
+localNode.
sData
);
}
}
4
层序遍历
规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,
从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。
遍历的顺序为:ABCDEFGHI
}
--------------------------------------------------------------------------------