資料結構與演算法(十一):二叉樹
阿新 • • 發佈:2020-07-12
一、什麼是二叉樹
1.概述
首先,需要了解樹這種資料結構的定義:
樹:是一類重要的非線性資料結構,是以分支關係定義的層次結構。每個結點有零個或多個子結點;沒有父結點的結點稱為根結點;每一個非根結點有且只有一個父結點;除了根結點外,每個子結點可以分為多個不相交的子樹
樹的結構類似現實中的樹,一個父節點有若干子節點,而一個子節點又有若干子節點,以此類推。
2.名詞解釋
名稱 | 含義 |
---|---|
根節點 | 樹的頂端結點 |
父節點 | 若一個節點含有子節點,則這個節點稱為其子節點的父節點 |
子節點 | 具有相同父節點的節點 |
兄弟節點 | 彼此都擁有同一個父節點的節點 |
葉子節點 | 即沒有子節點的節點 |
節點的權 | 即節點值 |
路節點的度 | 一個節點含有的子樹的個數 |
樹的度 | 一棵樹中,最大的節點的度稱為樹的度 |
深度 | 根結點到這個結點所經歷的邊的個數 |
層數 | 該節點的深度+1 |
高度 | 結點到葉子結點的最長路徑所經歷的邊的個數 |
樹高度 | 即根節點的高度 |
森林 | 由m(m>=0)棵互不相交的樹的集合稱為森林 |
3.二叉樹
二叉樹就是每個節點最多隻有兩顆子樹的樹:
對於二叉樹有:
-
滿二叉樹:所有的子節點都在最後一層,且節點總數與層數有
節點總數=2^n-1
-
完全二叉樹:從根節點到倒數第二層都符合滿二叉樹,但是最後一層節點不完全充填,葉子結點都靠左對齊
二、二叉樹的遍歷
二叉樹遍歷分為三種:
- 前序遍歷: 先輸出父節點,再遍歷左子樹和右子樹
- 中序遍歷: 先遍歷左子樹,再輸出父節點,再遍歷右子樹
- 後序遍歷: 先遍歷左子樹,再遍歷右子樹,最後輸出父節點
可見,根據父節點輸出順序即可以判斷是哪一種遍歷。
1.簡單程式碼實現
先建立節點類:
/** * @Author:黃成興 * @Date:2020-07-11 17:30 * @Description:二叉樹 */ public class BinaryTreeNode { private int nodeNum; /** * 右子節點 */ private BinaryTreeNode right; /** * 左子節點 */ private BinaryTreeNode left; public BinaryTreeNode(int nodeNum) { this.nodeNum = nodeNum; } @Override public String toString() { return "BinaryTreeNode{" + "nodeNum=" + nodeNum + '}'; } public int getNodeNum() { return nodeNum; } public void setNodeNum(int nodeNum) { this.nodeNum = nodeNum; } public BinaryTreeNode getRight() { return right; } public void setRight(BinaryTreeNode right) { this.right = right; } public BinaryTreeNode getLeft() { return left; } public void setLeft(BinaryTreeNode left) { this.left = left; } }
實現遍歷方法:
/**
* @Author:黃成興
* @Date:2020-07-11 17:44
* @Description:二叉樹
*/
public class BinaryTree {
private BinaryTreeNode root;
public BinaryTree(BinaryTreeNode root) {
if (root == null) {
throw new RuntimeException("根節點不允許為空!");
}
this.root = root;
}
public void preOrder(){
preOrder(root);
}
/**
* 前序遍歷
*/
public void preOrder(BinaryTreeNode node){
//列印節點
System.out.println(node);
//向左子樹前序遍歷
if (node.getLeft() != null) {
preOrder(node.getLeft());
}
//向右子樹前序遍歷
if (node.getRight() != null) {
preOrder(node.getRight());
}
}
public void inOrder(){
inOrder(root);
}
/**
* 中序遍歷
*/
public void inOrder(BinaryTreeNode node){
//向左子樹中序遍歷
if (node.getLeft() != null) {
inOrder(node.getLeft());
}
//列印節點
System.out.println(node);
//向右子樹中序遍歷
if (node.getRight() != null) {
inOrder(node.getRight());
}
}
public void postOrder(){
postOrder(root);
}
/**
* 後序遍歷
*/
public void postOrder(BinaryTreeNode node){
//向左子樹中序遍歷
if (node.getLeft() != null) {
postOrder(node.getLeft());
}
//向右子樹中序遍歷
if (node.getRight() != null) {
postOrder(node.getRight());
}
//列印節點
System.out.println(node);
}
}
2.測試
對含有7個簡單的滿二叉樹進行遍歷的結果:
前序遍歷:
BinaryTreeNode{nodeNum=1}
BinaryTreeNode{nodeNum=2}
BinaryTreeNode{nodeNum=4}
BinaryTreeNode{nodeNum=5}
BinaryTreeNode{nodeNum=3}
BinaryTreeNode{nodeNum=6}
BinaryTreeNode{nodeNum=7}
中序遍歷:
BinaryTreeNode{nodeNum=4}
BinaryTreeNode{nodeNum=2}
BinaryTreeNode{nodeNum=5}
BinaryTreeNode{nodeNum=1}
BinaryTreeNode{nodeNum=6}
BinaryTreeNode{nodeNum=3}
BinaryTreeNode{nodeNum=7}
後序遍歷:
BinaryTreeNode{nodeNum=4}
BinaryTreeNode{nodeNum=5}
BinaryTreeNode{nodeNum=2}
BinaryTreeNode{nodeNum=6}
BinaryTreeNode{nodeNum=7}
BinaryTreeNode{nodeNum=3}
BinaryTreeNode{nodeNum=1}
三、二叉樹的查詢
大體邏輯同遍歷,這裡就不在贅述了,直接放程式碼:
/**
* 前序查詢
* @param num
* @param node
* @return
*/
public BinaryTreeNode preSearch(int num,BinaryTreeNode node){
BinaryTreeNode result = null;
//判斷當前節點是否為查詢節點
if (node.getNodeNum() == num) {
result = node;
}
//判斷左節點是否為空,不為空就前序查詢節點
if (node.getLeft() != null) {
result = preSearch(num, node.getLeft());
}
//如果左樹找到就返回
if (result != null){
return result;
}
//否則就判斷並遞迴前序查詢右樹
if (node.getRight() != null) {
result = preSearch(num, node.getRight());
}
return result;
}
/**
* 中序查詢
* @param num
* @param node
* @return
*/
public BinaryTreeNode inSearch(int num,BinaryTreeNode node){
BinaryTreeNode result = null;
//判斷左節點是否為空,不為空就中序查詢節點
if (node.getLeft() != null) {
result = inSearch(num, node.getLeft());
}
//如果左樹找到就返回
if (result != null){
return result;
}
//如果左樹未找到就判斷當前節點是不是
if (node.getNodeNum() == num) {
result = node;
}
//否則就判斷並遞迴前序查詢右樹
if (node.getRight() != null) {
result = inSearch(num, node.getRight());
}
return result;
}
/**
* 後序查詢
* @param num
* @param node
* @return
*/
public BinaryTreeNode postSearch(int num,BinaryTreeNode node){
BinaryTreeNode result = null;
//判斷左節點是否為空,不為空就後序查詢節點
if (node.getLeft() != null) {
result = postSearch(num, node.getLeft());
}
//如果左樹找到就返回
if (result != null){
return result;
}
//否則就判斷並遞迴後序查詢右樹
if (node.getRight() != null) {
result = postSearch(num, node.getRight());
}
//判斷右樹是否找到
if (result != null){
return result;
}
//如果右樹仍未找到就判斷當前節點是不是
if (node.getNodeNum() == num) {
result = node;
}
return result;
}
四、二叉樹的刪除
對於二叉樹的刪除,有以下邏輯:
- 由於樹的節點和節點之間的聯絡是單向的,對於要刪除的節點,需要找到他的父節點進行刪除
- 從根節點開始遍歷節點,判斷節點的左右子節點是否為目標節點
- 如果是就刪除並返回
- 否則就持續向右或左遞迴,直到找到目標節點,或者將樹遍歷完為止
/**
* 刪除節點
* @param num
* @param node
* @return
*/
public void delete(int num, BinaryTreeNode node) {
//判斷刪除的是否為根節點
if (root.getNodeNum() == num) {
throw new RuntimeException("不允許刪除根節點!");
}
//如果子節點就是要刪除的節點
if (node.getLeft() != null && node.getLeft().getNodeNum() == num) {
node.setLeft(null);
return;
}
if (node.getRight() != null && node.getRight().getNodeNum() == num) {
node.setRight(null);
return;
}
//否則就往左樹或右樹遍歷直到找到或遍歷完為止
if (node.getLeft() != null) {
delete(num, node.getLeft());
}
if (node.getRight() != null) {
delete(num,node.getRight());
}
}