樹結構與Java實現
目錄
前言
提到『樹』這種資料結構,相信很多人首先想到的就是『二叉樹』。
的確,二叉樹作為一種重要的資料結構,它結合了陣列和連結串列的優點,有很多重要的應用。
我們都知道,陣列的特點是查詢迅速,根據index可以快速定位到一個元素。但是,如果要插入一個元素,就需要將這個元素位置之後的所有元素後移。平均來講,一個長度為N的有序陣列,插入元素要移動的元素個數為N/2。有序陣列的插入的時間複雜度為O(N),刪除操作的時間複雜度也為O(N)。
對於插入和刪除操作頻繁的資料,不建議採用有序陣列。
連結串列的插入和刪除效率都很高,只要改變一些值的引用就行了,時間複雜度為O(1)。但是連結串列的查詢效率很低,每次都要從頭開始找,依次訪問連結串列的每個資料項。平均來說,要從一個有N個元素的連結串列查詢一個元素,要遍歷N/2個元素,時間複雜度為O(N)
對於查詢頻繁的資料,不建議使用連結串列。
本節先不介紹二叉樹,而是先講一下樹這種資料結構。相信有了本節的知識作為基礎,再瞭解二叉樹就會輕鬆很多。
樹的概念
概述
在電腦科學中,樹(英語:tree)是一種抽象資料型別(ADT)或是實現這種抽象資料型別的資料結構,用來模擬具有樹狀結構性質的資料集合。
它是由n(n>0)個有限節點組成一個具有層次關係的集合。
把它叫做“樹”是因為它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。
它具有以下的特點:
每個節點都只有有限個子節點或無子節點; 沒有父節點的節點稱為根節點; 每一個非根節點有且只有一個父節點; 除了根節點外,每個子節點可以分為多個不相交的子樹; 樹裡面沒有環路(cycle) —— 維基百科
根據樹的定義,下面的結構就不是『樹』:
術語
- 路徑
從某個節點依次到達另外一個節點所經過的所有節點,就是這兩個節點之間的路徑。
- 根
樹頂端的節點被稱為根。從根出發到達任意一個節點只有一條路徑。
- 父節點
除了根節點之外,每個節點都可以向上找到一個唯一的節點,這個節點就是當前節點的父節點。相應的,父節點下方的就是子節點。
- 葉子節點
沒有子節點的“光桿司令”就被稱為葉子節點。
- 子樹
每個子節點作為根節點的樹都是一個子樹。
- 層
一個樹結構的代數就是這個樹的層。
- 度
一棵樹中,最大的節點的度稱為樹的度。
- 兄弟節點
具有相同父節點的節點互稱為兄弟節點;
實際應用
樹結構有非常廣泛的應用,比如我們常用的檔案目錄系統,就是一個樹結構。
例如在Windows10作業系統的CMD命令列輸入tree
命令,就可以輸出目錄樹:
tree
卷 Windows 的資料夾 PATH 列表
卷序列號為 1CEB-7ABE
C:.
├─blog
│ ├─cache
│ │ └─JavaCacheGuidance
│ ├─datastructure
│ ├─editor
│ │ └─notepad++
│ ├─framework
│ │ └─guava
│ │ └─retry
│ ├─git
│ └─java
│ └─package-info
├─category
│ ├─food
│ │ ├─fruit
│ │ └─self
│ ├─job
│ │ └─bz
│ │ └─project
│ │ └─ad
│ │ └─exch
│ ├─people
│ ├─practical
│ │ └─work
│ │ └─ecommerce
│ │ └─inventory
│ ├─tech
│ │ ├─algorithm
│ │ │ └─tree
│ │ └─java
│ │ ├─concurrent
│ │ │ └─thread
│ │ ├─design
│ │ ├─i18n
│ │ ├─jcf
│ │ └─spring
│ │ └─springboot
│ └─tool
│ ├─data
│ │ └─db
│ │ ├─mysql
│ │ └─redis
│ └─site
│ └─stackoverflow
└─me
└─phonephoto
複製程式碼
實現樹
講解了樹結構的特點和相關概念以後,下面用Java實現樹結構的基本操作,並演示建立樹、新增子節點、遍歷樹和搜尋指定節點等操作。
TreeNode
package net.ijiangtao.tech.algorithms.algorithmall.datastructure.tree;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* 實現樹結構
*
* @author ijiangtao
* @create 2019-04-18 15:13
**/
public class TreeNode<T> implements Iterable<TreeNode<T>> {
/**
* 樹節點
*/
public T data;
/**
* 父節點,根沒有父節點
*/
public TreeNode<T> parent;
/**
* 子節點,葉子節點沒有子節點
*/
public List<TreeNode<T>> children;
/**
* 儲存了當前節點及其所有子節點,方便查詢
*/
private List<TreeNode<T>> elementsIndex;
/**
* 建構函式
*
* @param data
*/
public TreeNode(T data) {
this.data = data;
this.children = new LinkedList<TreeNode<T>>();
this.elementsIndex = new LinkedList<TreeNode<T>>();
this.elementsIndex.add(this);
}
/**
* 判斷是否為根:根沒有父節點
*
* @return
*/
public boolean isRoot() {
return parent == null;
}
/**
* 判斷是否為葉子節點:子節點沒有子節點
*
* @return
*/
public boolean isLeaf() {
return children.size() == 0;
}
/**
* 新增一個子節點
*
* @param child
* @return
*/
public TreeNode<T> addChild(T child) {
TreeNode<T> childNode = new TreeNode<T>(child);
childNode.parent = this;
this.children.add(childNode);
this.registerChildForSearch(childNode);
return childNode;
}
/**
* 獲取當前節點的層
*
* @return
*/
public int getLevel() {
if (this.isRoot()) {
return 0;
} else {
return parent.getLevel() + 1;
}
}
/**
* 遞迴為當前節點以及當前節點的所有父節點增加新的節點
*
* @param node
*/
private void registerChildForSearch(TreeNode<T> node) {
elementsIndex.add(node);
if (parent != null) {
parent.registerChildForSearch(node);
}
}
/**
* 從當前節點及其所有子節點中搜尋某節點
*
* @param cmp
* @return
*/
public TreeNode<T> findTreeNode(Comparable<T> cmp) {
for (TreeNode<T> element : this.elementsIndex) {
T elData = element.data;
if (cmp.compareTo(elData) == 0)
return element;
}
return null;
}
/**
* 獲取當前節點的迭代器
*
* @return
*/
@Override
public Iterator<TreeNode<T>> iterator() {
TreeNodeIterator<T> iterator = new TreeNodeIterator<T>(this);
return iterator;
}
@Override
public String toString() {
return data != null ? data.toString() : "[tree data null]";
}
}
複製程式碼
TreeNodeIterator
package net.ijiangtao.tech.algorithms.algorithmall.datastructure.tree;
import java.util.Iterator;
/**
*
* 迭代器
*
* @author ijiangtao
* @create 2019-04-18 15:24
**/
public class TreeNodeIterator<T> implements Iterator<TreeNode<T>> {
enum ProcessStages {
ProcessParent, ProcessChildCurNode, ProcessChildSubNode
}
private ProcessStages doNext;
private TreeNode<T> next;
private Iterator<TreeNode<T>> childrenCurNodeIter;
private Iterator<TreeNode<T>> childrenSubNodeIter;
private TreeNode<T> treeNode;
public TreeNodeIterator(TreeNode<T> treeNode) {
this.treeNode = treeNode;
this.doNext = ProcessStages.ProcessParent;
this.childrenCurNodeIter = treeNode.children.iterator();
}
@Override
public boolean hasNext() {
if (this.doNext == ProcessStages.ProcessParent) {
this.next = this.treeNode;
this.doNext = ProcessStages.ProcessChildCurNode;
return true;
}
if (this.doNext == ProcessStages.ProcessChildCurNode) {
if (childrenCurNodeIter.hasNext()) {
TreeNode<T> childDirect = childrenCurNodeIter.next();
childrenSubNodeIter = childDirect.iterator();
this.doNext = ProcessStages.ProcessChildSubNode;
return hasNext();
} else {
this.doNext = null;
return false;
}
}
if (this.doNext == ProcessStages.ProcessChildSubNode) {
if (childrenSubNodeIter.hasNext()) {
this.next = childrenSubNodeIter.next();
return true;
} else {
this.next = null;
this.doNext = ProcessStages.ProcessChildCurNode;
return hasNext();
}
}
return false;
}
@Override
public TreeNode<T> next() {
return this.next;
}
/**
* 目前不支援刪除節點
*/
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
複製程式碼
測試
下面實現的樹結構,與前面圖中的樹結構完全相同。
package net.ijiangtao.tech.algorithms.algorithmall.datastructure.tree;
/**
* tree
*
* @author ijiangtao
* @create 2019-04-18 15:03
**/
public class TreeDemo1 {
public static void main(String[] args) {
System.out.println("********************測試遍歷*************************");
TreeNode<String> treeRoot = getSetA();
for (TreeNode<String> node : treeRoot) {
String indent = createIndent(node.getLevel());
System.out.println(indent + node.data);
}
System.out.println("********************測試搜尋*************************");
Comparable<String> searchFCriteria = new Comparable<String>() {
@Override
public int compareTo(String treeData) {
if (treeData == null)
return 1;
boolean nodeOk = treeData.contains("F");
return nodeOk ? 0 : 1;
}
};
TreeNode<String> foundF = treeRoot.findTreeNode(searchFCriteria);
System.out.println("F: parent=" + foundF.parent + ",children=" + foundF.children);
}
private static String createIndent(int depth) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < depth; i++) {
sb.append(' ');
}
return sb.toString();
}
public static TreeNode<String> getSetA() {
TreeNode<String> A = new TreeNode<String>("A");
{
TreeNode<String> B = A.addChild("B");
TreeNode<String> C = A.addChild("C");
TreeNode<String> D = A.addChild("D");
{
TreeNode<String> E = B.addChild("E");
TreeNode<String> F = C.addChild("F");
TreeNode<String> G = C.addChild("G");
{
TreeNode<String> H = F.addChild("H");
TreeNode<String> I = F.addChild("I");
TreeNode<String> J = F.addChild("J");
}
}
}
return A;
}
}
複製程式碼
- 輸出
********************測試遍歷*************************
A
B
E
C
F
H
I
J
G
D
********************測試搜尋*************************
F: parent=C,children=[H, I, J]
複製程式碼
總結
本節我帶領大家一起了解了樹這種重要的資料結構,並且講解了樹相關的概念和術語,最後為大家實現了基本的樹操作。
學習完本節內容,對我們下面要介紹的二叉樹,以及Java中TreeSet
和TreeMap
的原始碼,都會有所幫助。