樹結構與Java實現html
目錄java
提到『樹』這種數據結構,相信不少人首先想到的就是『二叉樹』。node
的確,二叉樹做爲一種重要的數據結構,它結合了數組和鏈表的優勢,有不少重要的應用。mysql
咱們都知道,數組的特色是查詢迅速,根據index能夠快速定位到一個元素。可是,若是要插入一個元素,就須要將這個元素位置以後的全部元素後移。平均來說,一個長度爲N的有序數組,插入元素要移動的元素個數爲N/2。有序數組的插入的時間複雜度爲O(N),刪除操做的時間複雜度也爲O(N)。git
對於插入和刪除操做頻繁的數據,不建議採用有序數組。github
鏈表的插入和刪除效率都很高,只要改變一些值的引用就好了,時間複雜度爲O(1)。可是鏈表的查詢效率很低,每次都要從頭開始找,依次訪問鏈表的每一個數據項。平均來講,要從一個有N個元素的鏈表查詢一個元素,要遍歷N/2個元素,時間複雜度爲O(N)redis
對於查找頻繁的數據,不建議使用鏈表。算法
本節先不介紹二叉樹,而是先講一下樹這種數據結構。相信有了本節的知識做爲基礎,再瞭解二叉樹就會輕鬆不少。spring
在計算機科學中,樹(英語:tree)是一種抽象數據類型(ADT)或是實現這種抽象數據類型的數據結構,用來模擬具備樹狀結構性質的數據集合。sql
它是由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實現樹結構的基本操做,並演示建立樹、添加子節點、遍歷樹和搜索指定節點等操做。
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]";
}
}
複製代碼
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
的源碼,都會有所幫助。