二叉樹鏈式存儲和遍歷

来源:http://www.cnblogs.com/cyhe/archive/2016/05/27/5533336.html
-Advertisement-
Play Games

1 二叉樹的鏈式存儲 1.1 鏈式存儲 順序存儲對空間利用率較低,所以,二叉樹一般採用鏈式存儲結構,用一個鏈表來存儲一顆二叉樹。二叉鏈表至少包含3個域:數據域data,左指針域lchild和右指針域rchild,如果再加上一個指向雙親結點的指針就變成了三叉鏈表。 二叉樹的鏈式存儲結構如下: 根據完全 ...


1 二叉樹的鏈式存儲

1.1 鏈式存儲

      順序存儲對空間利用率較低,所以,二叉樹一般採用鏈式存儲結構,用一個鏈表來存儲一顆二叉樹。二叉鏈表至少包含3個域:數據域data左指針域lchild右指針域rchild,如果再加上一個指向雙親結點的指針就變成了三叉鏈表

 

二叉樹的鏈式存儲結構如下:

/**
 * 二叉鏈表結點
 * @author cyhe
 */
private class Node{
    Integer data;
    Node lchild, rchild;
}

      根據完全二叉樹的序列遞歸創建二叉樹,輸入序列時不存在的結點用0代替,以下是創建的代碼和一些有用的方法。

/**
 * 存儲先序輸入的二叉樹,預設大小為10,當超過10自動調用resize方法擴容
 */
private Integer[] nodes = new Integer[10];
public LinkBiTree(){
    init();
}
/**
 * 獲取根節點
 * @return
 */
public Node getRoot(){
    return root;
}
/**
 * 滿了自動擴容
 * @param max
 */
private void resize(int max){
    Integer[] temp = new Integer[max];
    for(int i=0; i<nodes.length; i++){
        temp[i] = nodes[i];
    }
    nodes = temp;
}
/**
 * 先序輸入二叉樹,不存在的結點使用0
 */
public void init(){
    System.out.println("先序列輸入一個二叉樹,不存在的結點用0代替,使用逗號隔開:");
//        String[] ins = StdIn.readString().split(",");
    String[] ins = "1,2,3,4,0,5,7,8".split(",");
    n = ins.length;
    for (int i = 0; i < ins.length; i++) {
        if(i>=nodes.length){
            resize(2 * nodes.length); // 擴大兩倍
        }
        nodes[i] = Integer.valueOf(ins[i]);
    }
    System.out.println("LinkBiTree [nodes=" + Arrays.toString(nodes) + "]");
    root = build(1); // 遞歸創建樹
    System.out.println("輸入的樹高度為:"+depth(root));
    print();
}
/**
 * 遞歸創建一顆樹, 使用完全二叉樹序列
 * @param node
 * @param data
 */
public Node build(int index){
    if (index > n) {
        return null;
    }
    Integer tmp = nodes[index - 1]; // 獲取結點的值
    if (tmp == 0) { // 若為 0 表示結點不存在
        return null;
    } else {
        Node node = new Node();
        node.data = tmp;
        node.lchild = build(2 * index); // 創建左子樹
        node.rchild = build(2 * index + 1); // 創建右子樹
        return node;
    }
}
/**
 * 遞歸獲取二叉樹的高度
 * @return
 */
public int depth(Node node){
    if(node != null){
        int l = depth(node.lchild); // 左子樹高度
        int r = depth(node.rchild); // 右子樹高度
        return l > r ? l + 1 : r + 1; // 樹的高度為子樹最大高度加上根節點
    }
    return 0; // 空樹高度為0
}

1.2 層次遍歷

/**
 * 層次遍歷,利用隊列是實現
 */
public void levelOrder(Node root){
    RingBuffer<Node> queue = new RingBuffer<Node>(n+1);
    queue.put(root); // 根節點先進隊列
    
    while(queue.size()>0){
        Node tmp = queue.get();
        System.out.print(tmp.data + " "); //

        if (tmp.lchild != null) { // 如果根節點的左子樹存在,把左子樹編號入棧
            queue.put(tmp.lchild);
        }
        
        if (tmp.rchild != null) { // 如果根節點的右子樹存在,把右子樹編號入棧
            queue.put(tmp.rchild);
        }
    }
}

1.3 先序遍歷

1.3.1 遞歸實現

/**
 * 遞歸先序遍歷
 */
public void preOrderRecur(Node node){
    if(node != null){
        System.out.print(node.data+" "); //
        preOrderRecur(node.lchild); //
        preOrderRecur(node.rchild); //
    }
}

1.3.2 非遞歸實現

實現方法1:

/**
 * 非遞歸先序遍歷
 */
public void preOrder(Node node){
    ArrayStack<Node> stack = new ArrayStack<Node>(n + 1);
    stack.push(node);
    while (!stack.isEmpty()) {
        Node tmp = stack.pop();
        System.out.print(tmp.data + " "); //

        if (tmp.rchild != null) { // 如果根節點的右子樹存在,把右子樹編號入棧
            stack.push(tmp.rchild);
        }
        if (tmp.lchild != null) { // 如果根節點的左子樹存在,把左子樹編號入棧
            stack.push(tmp.lchild);
        }
    }
}

實現方法2:

/**
 * 非遞歸先序遍歷
 */
public void preOrderOne(Node node){
    ArrayStack<Node> stack = new ArrayStack<Node>(n + 1);
    while (node != null || !stack.isEmpty()) {
        while(node != null){ // 把最左側的全部入棧
            System.out.print(node.data + " "); //
            stack.push(node);
            node = node.lchild;
        }
        Node tmp = stack.pop(); // 彈出最後入棧的左子樹
        node = tmp.rchild; // 看它有沒有右孩子
    }
}

1.4 中序遍歷

1.4.1 遞歸實現

/**
 * 遞歸中序遍歷
 */
public void inOrderRecur(Node node){
    if(node != null){
        inOrderRecur(node.lchild); //
        System.out.print(node.data+" "); //
        inOrderRecur(node.rchild); //
    }
}

1.4.2 非遞歸實現

/**
 * 非遞歸中序遍歷
 */
public void inOrder(Node node){
    ArrayStack<Node> stack = new ArrayStack<Node>(n + 1);
    while (node != null || !stack.isEmpty()) {
        while(node != null){ // 把最左側的全部入棧
            stack.push(node);
            node = node.lchild;
        }
        Node tmp = stack.pop(); // 彈出最後入棧的左子樹
        System.out.print(tmp.data + " "); // 先訪問左子樹
        node = tmp.rchild; // 看它有沒有右孩子
    }
}

1.5 後序遍歷

1.5.1 遞歸實現

/**
 * 遞歸後序遍歷
 */
public void postOrderRecur(Node node){
    if(node != null){
        postOrderRecur(node.lchild); //
        postOrderRecur(node.rchild); //
        System.out.print(node.data+" "); //
    }
}

1.5.2 非遞歸實現

/**
 * 非遞歸後序遍歷
 */
public void postOrder(Node node){
    ArrayStack<Node> stack = new ArrayStack<Node>(n + 1);
    Node pre = null; // 前一個訪問的結點
    while (node != null || !stack.isEmpty()) {
        while(node != null){ // 把最左側的全部入棧
            stack.push(node);
            node = node.lchild;
        }
        Node tmp = stack.peek(); // 現在要判斷棧內結點有沒有右孩子,或者右孩子是否訪問過
        
        // 如果當前結點不存在右孩子或者右孩子已經訪問過,則訪問當前結點
        if(tmp.rchild == null || pre == tmp.rchild){
            Node n = stack.pop();
            System.out.print(n.data + " "); // 訪問結點
            pre = n;
        } else {
            node = tmp.rchild; // 否則訪問右孩子
        }
    }
}

2 測試

public static void main(String[] args) {
    LinkBiTree<Integer> biTree = new LinkBiTree<Integer>();
    
    System.out.print("先序遍歷(遞歸):");
    biTree.preOrderRecur(biTree.getRoot());
    System.out.print("\n中序遍歷(遞歸):");
    biTree.inOrderRecur(biTree.getRoot());
    System.out.print("\n後序遍歷(遞歸):");
    biTree.postOrderRecur(biTree.getRoot());
    System.out.print("\n層次遍歷:");
    biTree.levelOrder(biTree.getRoot());
    System.out.print("\n先序遍歷(非遞歸):");
//    biTree.preOrder(biTree.getRoot());
    biTree.preOrderOne(biTree.getRoot());
    System.out.print("\n中序遍歷(非遞歸):");
    biTree.inOrder(biTree.getRoot());
    System.out.print("\n後序遍歷(非遞歸):");
    biTree.postOrder(biTree.getRoot());
}

2.1 輸出結果

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • DateTime dt = DateTime.Now;Label1.Text = dt.ToString();//2009-07-5 13:21:25Label2.Text = dt.ToFileTime().ToString();//127756416859912816Label3.Text = ...
  • C# 導出CSV文件 由於工作需要,需要在Web端請求後,將查詢的數據寫入CSV文檔,返回給Web。 註意點: 1.長字元串的的數字,比如身份證號碼之類的,在csv中顯示的是用科學計數法顯示的,因此需要轉換一下,例如:字元串“12345678987654321”, 寫入時為"=\"123456789 ...
  • 前言 前幾天看一個朋友的博客時,看他用到了C#6的特性,而6出來這麼長時間還沒有正兒八經看過它,今兒專門看了下新特性,說白了也不過是語法糖而已。但是用起來確實能讓你的代碼更加乾凈些。Let's try it. 1、集合初始化器 public class Post { public DateTime ...
  • 1. JVM相關(包括了各個版本的特性) 對於剛剛接觸Java的人來說,JVM相關的知識不一定需要理解很深,對此裡面的概念有一些簡單的瞭解即可。不過對於一個有著3年以上Java經驗的資深開發者來說,不會JVM幾乎是不可接受的。 JVM作為java運行的基礎,很難相信對於JVM一點都不瞭解的人可以把j ...
  • 需要在程式中使用二維數組,網上找到一種這樣的用法: 1 2 3 4 5 6 #創建一個寬度為3,高度為4的數組 #[[0,0,0], # [0,0,0], # [0,0,0], # [0,0,0]] myList = [[0] * 3] * 4 1 2 3 4 5 6 #創建一個寬度為3,高度為4的 ...
  • 探完閉包[查看],再探命名空間。 對於命名空間,官方文檔已經說得很詳細[查看],我在這裡做了一下實踐和總結。 命名空間一個最明確的目的就是解決重名問題,PHP中不允許兩個函數或者類出現相同的名字,否則會產生一個致命的錯誤。這種情況下只要避免命名重覆就可以解決,最常見的一種做法是約定一個首碼。 例:項 ...
  • 還是從HelloWorld開始說吧... #include <stdio.h> int main(int argc, char* argv[]) { printf("Hello World!\n"); return 0; } 從源文件Hello.cpp編譯鏈接成Hello.exe,需要經歷如下步驟: ...
  • 給初學者之一:淺談java及應用學java 不知不覺也已經三年了 從不知java為何物到現在一個小小的j2ee項目經理雖說不上此道高手,大概也算有點斤兩了吧每次上網,泡bbs逛論壇,沒少去java相關的版面總體感覺初學者多,高手少,精通的更少由於我國高等教育制度教材陳舊,加上java自身發展不過十年 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...