樹結構與Java實現

西召發表於2019-04-18

樹結構與Java實現

樹結構與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中TreeSetTreeMap的原始碼,都會有所幫助。

相關連結

作者資源

參考資源

相關文章