參考自《Java數據結構與算法》
- 定義一個節點類,使節點與二叉樹操作分離
-
class Node {
-
int value;
-
Node leftChild;
-
Node rightChild;
-
-
Node(
int value) {
-
this.value = value;
-
}
-
-
public void display() {
-
System.out.print(
this.value +
"\t");
-
}
-
-
@Override
-
public String toString() {
-
// TODO Auto-generated method stub
-
return String.valueOf(value);
-
}
-
}
- 需要實現的二叉樹操作
-
class BinaryTree {
-
private Node root =
null;
-
-
BinaryTree(
int value) {
-
root =
new Node(value);
-
root.leftChild =
null;
-
root.rightChild =
null;
-
}
-
public Node findKey(int value) {}
//查找
-
public String insert(int value) {}
//插入
-
public void inOrderTraverse() {}
//中序遍歷遞歸操作
-
public void inOrderByStack() {}
//中序遍歷非遞歸操作
-
public void preOrderTraverse() {}
//前序遍歷
-
public void preOrderByStack() {}
//前序遍歷非遞歸操作
-
public void postOrderTraverse() {}
//后序遍歷
-
public void postOrderByStack() {}
//后序遍歷非遞歸操作
-
public int getMinValue() {}
//得到最小(大)值
-
public boolean delete(int value) {}
//刪除
-
}
- 查找數據:
-
public Node findKey(int value) {
-
Node current = root;
-
while (
true) {
-
if (value == current.value) {
-
return current;
-
}
else
if (value < current.value) {
-
current = current.leftChild;
-
}
else
if (value > current.value) {
-
current = current.rightChild;
-
}
-
-
if (current ==
null) {
-
return
null;
-
}
-
}
-
}
- 插入數據:與查找數據類似,不同點在於當節點為空時,不是返回而是插入
-
public String insert(int value) {
-
String error =
null;
-
-
Node node =
new Node(value);
-
if (root ==
null) {
-
root = node;
-
root.leftChild =
null;
-
root.rightChild =
null;
-
}
else {
-
Node current = root;
-
Node parent =
null;
-
while (
true) {
-
if (value < current.value) {
-
parent = current;
-
current = current.leftChild;
-
if (current ==
null) {
-
parent.leftChild = node;
-
break;
-
}
-
}
else
if (value > current.value) {
-
parent = current;
-
current = current.rightChild;
-
if (current ==
null) {
-
parent.rightChild = node;
-
break;
-
}
-
}
else {
-
error =
"having same value in binary tree";
-
}
-
}
// end of while
-
}
-
return error;
-
}
- 遍歷數據:
1)中序遍歷:最常用的一種遍歷方法
-
/**
-
* //中序遍歷(遞歸):
-
* 1、調用自身來遍歷節點的左子樹
-
* 2、訪問這個節點
-
* 3、調用自身來遍歷節點的右子樹
-
*/
-
public void inOrderTraverse() {
-
System.out.print(
"中序遍歷:");
-
inOrderTraverse(root);
-
System.out.println();
-
}
-
-
private void inOrderTraverse(Node node) {
-
if (node ==
null)
-
return ;
-
-
inOrderTraverse(node.leftChild);
-
node.display();
-
inOrderTraverse(node.rightChild);
-
}
-
/**
-
* 中序非遞歸遍歷:
-
* 1)對於任意節點current,若該節點不為空則將該節點壓棧,並將左子樹節點置為current,重復此操作,直到current為空。
-
* 2)若左子樹為空,棧頂節點出棧,訪問節點后將該節點的右子樹置為current
-
* 3) 重復1、2步操作,直到current為空且棧內節點為空。
-
*/
-
public void inOrderByStack() {
-
System.out.print(
"中序非遞歸遍歷:");
-
Stack<Node> stack =
new Stack<Node>();
-
Node current = root;
-
while (current !=
null || !stack.isEmpty()) {
-
while (current !=
null) {
-
stack.push(current);
-
current = current.leftChild;
-
}
-
-
if (!stack.isEmpty()) {
-
current = stack.pop();
-
current.display();
-
current = current.rightChild;
-
}
-
}
-
System.out.println();
-
}
2)前序遍歷:
-
/**
-
* //前序遍歷(遞歸):
-
* 1、訪問這個節點
-
* 2、調用自身來遍歷節點的左子樹
-
* 3、調用自身來遍歷節點的右子樹
-
*/
-
public void preOrderTraverse() {
-
System.out.print(
"前序遍歷:");
-
preOrderTraverse(root);
-
System.out.println();
-
}
-
-
private void preOrderTraverse(Node node) {
-
if (node ==
null)
-
return ;
-
-
node.display();
-
preOrderTraverse(node.leftChild);
-
preOrderTraverse(node.rightChild);
-
}
-
/**
-
* 前序非遞歸遍歷:
-
* 1)對於任意節點current,若該節點不為空則訪問該節點后再將節點壓棧,並將左子樹節點置為current,重復此操作,直到current為空。
-
* 2)若左子樹為空,棧頂節點出棧,將該節點的右子樹置為current
-
* 3) 重復1、2步操作,直到current為空且棧內節點為空。
-
*/
-
public void preOrderByStack() {
-
System.out.print(
"前序非遞歸遍歷:");
-
Stack<Node> stack =
new Stack<Node>();
-
Node current = root;
-
while (current !=
null || !stack.isEmpty()) {
-
while (current !=
null) {
-
stack.push(current);
-
current.display();
-
current = current.leftChild;
-
}
-
-
if (!stack.isEmpty()) {
-
current = stack.pop();
-
current = current.rightChild;
-
}
-
}
-
System.out.println();
-
}
3)后序遍歷:
-
/**
-
* //后序遍歷(遞歸):
-
* 1、調用自身來遍歷節點的左子樹
-
* 2、調用自身來遍歷節點的右子樹
-
* 3、訪問這個節點
-
*/
-
public void postOrderTraverse() {
-
System.out.print(
"后序遍歷:");
-
postOrderTraverse(root);
-
System.out.println();
-
}
-
-
private void postOrderTraverse(Node node) {
-
if (node ==
null)
-
return ;
-
-
postOrderTraverse(node.leftChild);
-
postOrderTraverse(node.rightChild);
-
node.display();
-
}
-
/**
-
* 后序非遞歸遍歷:
-
* 1)對於任意節點current,若該節點不為空則訪問該節點后再將節點壓棧,並將左子樹節點置為current,重復此操作,直到current為空。
-
* 2)若左子樹為空,取棧頂節點的右子樹,如果右子樹為空或右子樹剛訪問過,則訪問該節點,並將preNode置為該節點
-
* 3) 重復1、2步操作,直到current為空且棧內節點為空。
-
*/
-
public void postOrderByStack() {
-
System.out.print(
"后序非遞歸遍歷:");
-
Stack<Node> stack =
new Stack<Node>();
-
Node current = root;
-
Node preNode =
null;
-
while (current !=
null || !stack.isEmpty()) {
-
while (current !=
null) {
-
stack.push(current);
-
current = current.leftChild;
-
}
-
-
if (!stack.isEmpty()) {
-
current = stack.peek().rightChild;
-
if (current ==
null || current == preNode) {
-
current = stack.pop();
-
current.display();
-
preNode = current;
-
current =
null;
-
}
-
}
-
}
-
System.out.println();
-
}
- 得到最小(大)值:依次向左(右)直到空為之
-
public int getMinValue() {
-
Node current = root;
-
while (
true) {
-
if (current.leftChild ==
null)
-
return current.value;
-
-
current = current.leftChild;
-
}
-
}
- 刪除:刪除操作很復雜,刪除節點大致分為三種情況:
2)刪除節點只有一個子節點:只有一個左子節點和只有一個右子節點
3)刪除節點有兩個子節點:這種情況比較復雜,需要尋找后繼節點,即比要刪除的節點的關鍵值次高的節點是它的后繼節點。
說得簡單一些,后繼節點就是比要刪除的節點的關鍵值要大的節點集合中的最小值。
得到后繼節點的代碼如下:
-
/**
-
*
-
* 得到后繼節點,即刪除節點的左后代
-
*/
-
private Node getSuccessor(Node delNode) {
-
Node successor = delNode;
-
Node successorParent =
null;
-
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;
-
}
-
//任何情況下,都需要將刪除節點的左子節點指向后繼節點的左子節點
-
successor.leftChild = delNode.leftChild;
-
-
return successor;
-
}
a)如果后繼節點是剛好是要刪除節點的右子節點(此時可以知道,這個右子節點沒有左子點,如果有,就不該這個右子節點為后繼節點)
-
//刪除的節點為父節點的左子節點時:
-
parent.leftChild = successor;
-
successor.leftChild = delNode.leftChild;
-
-
//刪除的節點為父節點的右子節點時:
-
parent.rightChild = successor;
-
successor.leftChild = delNode.leftChild
b)如果后繼節點為要刪除節點的右子節點的左后代:
-
//刪除的節點為父節點的左子節點時:
-
successorParent.leftChild = successor.rightChild;
-
successor.rightChild = delNode.rightChild;
-
parent.leftChild = successor;
-
successor.leftChild = delNode.leftChild;
-
-
//刪除的節點為父節點的右子節點時:
-
successorParent.leftChild = successor.rightChild;
-
successor.rightChild = delNode.rightChild;
-
parent.rightChild = successor;
-
successor.leftChild = delNode.leftChild;
綜合以上各種情況,刪除代碼如下:
-
public boolean delete(int value) {
-
Node current = root;
//需要刪除的節點
-
Node parent =
null;
//需要刪除的節點的父節點
-
boolean isLeftChild =
true;
//需要刪除的節點是否父節點的左子樹
-
-
while (
true) {
-
if (value == current.value) {
-
break;
-
}
else
if (value < current.value) {
-
isLeftChild =
true;
-
parent = current;
-
current = current.leftChild;
-
}
else {
-
isLeftChild =
false;
-
parent = current;
-
current = current.rightChild;
-
}
-
-
//找不到需要刪除的節點,直接返回
-
if (current ==
null)
-
return
false;
-
}
-
-
//分情況考慮
-
//1、需要刪除的節點為葉子節點
-
if (current.leftChild ==
null && current.rightChild ==
null) {
-
//如果該葉節點為根節點,將根節點置為null
-
if (current == root) {
-
root =
null;
-
}
else {
-
//如果該葉節點是父節點的左子節點,將父節點的左子節點置為null
-
if (isLeftChild) {
-
parent.leftChild =
null;
-
}
else {
//如果該葉節點是父節點的右子節點,將父節點的右子節點置為null
-
parent.rightChild =
null;
-
}
-
}
-
}
-
//2、需要刪除的節點有一個子節點,且該子節點為左子節點
-
else
if (current.rightChild ==
null) {
-
//如果該節點為根節點,將根節點的左子節點變為根節點
-
if (current == root) {
-
root = current.leftChild;
-
}
else {
-
//如果該節點是父節點的左子節點,將該節點的左子節點變為父節點的左子節點
-
if (isLeftChild) {
-
parent.leftChild = current.leftChild;
-
}
else {
//如果該節點是父節點的右子節點,將該節點的左子節點變為父節點的右子節點
-
parent.rightChild = current.leftChild;
-
}
-
}
-
}
-
//2、需要刪除的節點有一個子節點,且該子節點為右子節點
-
else
if (current.leftChild ==
null) {
-
//如果該節點為根節點,將根節點的右子節點變為根節點
-
if (current == root) {
-
root = current.rightChild;
-
}
else {
-
//如果該節點是父節點的左子節點,將該節點的右子節點變為父節點的左子節點
-
if (isLeftChild) {
-
parent.leftChild = current.rightChild;
-
}
else {
//如果該節點是父節點的右子節點,將該節點的右子節點變為父節點的右子節點
-
parent.rightChild = current.rightChild;
-
}
-
}
-
}
-
//3、需要刪除的節點有兩個子節點,需要尋找該節點的后續節點替代刪除節點
-
else {
-
Node successor = getSuccessor(current);
-
//如果該節點為根節點,將后繼節點變為根節點,並將根節點的左子節點變為后繼節點的左子節點
-
if (current == root) {
-
root = successor;
-
}
else {
-
//如果該節點是父節點的左子節點,將該節點的后繼節點變為父節點的左子節點
-
if (isLeftChild) {
-
parent.leftChild = successor;
-
}
else {
//如果該節點是父節點的右子節點,將該節點的后繼節點變為父節點的右子節點
-
parent.rightChild = successor;
-
}
-
}
-
}
-
current =
null;
-
return
true;
-
}
- 測試代碼
-
public
class BinaryTreeDemo {
-
public static void main(String[] args) {
-
BinaryTree bt =
new BinaryTree(
52);
-
bt.insert(
580);
-
bt.insert(
12);
-
bt.insert(
50);
-
bt.insert(
58);
-
bt.insert(
9);
-
bt.insert(
888);
-
bt.insert(
248);
-
bt.insert(
32);
-
bt.insert(
666);
-
bt.insert(
455);
-
bt.insert(
777);
-
bt.insert(
999);
-
bt.inOrderTraverse();
-
bt.preOrderTraverse();
-
bt.postOrderTraverse();
-
System.out.println(bt.findKey(
32));
-
System.out.println(bt.findKey(
81));
-
System.out.println(
"最小值:" + bt.getMinValue());
-
// bt.delete(32); //刪除葉子節點
-
// bt.delete(50); //刪除只有一個左子節點的節點
-
// bt.delete(248); //刪除只有一個右子節點的節點
-
// bt.delete(248); //刪除只有一個右子節點的節點
-
// bt.delete(580); //刪除有兩個子節點的節點,且后繼節點為刪除節點的右子節點的左后代
-
// bt.delete(888); //刪除有兩個子節點的節點,且后繼節點為刪除節點的右子節點
-
bt.delete(
52);
//刪除有兩個子節點的節點,且刪除節點為根節點
-
-
bt.inOrderTraverse();
-
}
-
}
測試結果:
中序遍歷:91232 50 52 58248455580 666777 888999
中序非遞歸遍歷:9 12 32 50 5258248 455580 666777 888999
前序遍歷:52 12 9 50 3258058 248 455 888 666 777 999
前序非遞歸遍歷:52 12 9 50 3258058 248 455 888 666 777 999
后序遍歷:9 32 50 12 45524858 777 666 999 888 580 52
后序非遞歸遍歷:9 32 50 12 45524858 777 666 999 888 580 52
32
null
最小值:9
中序遍歷:9 12 32 50 58248455580 666777 888999
</article>