1. 程式人生 > >基於順序儲存實現的多叉樹(1):深度優先儲存

基於順序儲存實現的多叉樹(1):深度優先儲存

需求分析
   在資料結構中,樹有兩種儲存方式,一種是鏈式儲存,另一種是順序儲存。前者就是使用指標來記錄樹結點間的關係,在新增結點或刪除結點時,只需改變與父結點或兄弟結點的指標值即可,實現較為簡單;後者就是使用陣列來儲存,可以用相對偏移量來記錄樹結點間的關係,在新增結點或刪除結點時,則不僅是改變與父結點或兄弟結點的相對偏移量,還需要改變其它結點的相對偏移量,實現較為複雜。近來在專案中,對一個普通文字檔案進行分析提取資料,而這個檔案內的資料從內容看,具有層次巢狀關係,要將這樣的資料傳送到伺服器去處理,我考慮了兩種如下方法:
 (1)自定義XML格式,在本地使用XML庫,如libxml2、tinyxml等,將資料寫到XML臨時檔案或記憶體中,再將這個XML臨時檔案或記憶體發過去,在伺服器那邊使用XML庫來解析。這種方法比較通用而且跨平臺,如果XML庫不支援將其所儲存的資料轉儲到一塊連續記憶體,那麼就只能先存到XML臨時檔案,再將這個檔案發過去,這樣一來,就存在磁碟IO操作,效率較低。否則,就可以先將資料轉儲到一塊連續記憶體,再將這塊記憶體發過去,這樣一來,這塊連續記憶體就需要另外開闢,因此多了一套記憶體管理操作,但是比用臨時檔案方式,沒有磁碟IO,效率要高些。

 (2)實現基於順序儲存的樹,而且還是多叉樹,因為實際資料具有多層次巢狀關係,將資料放進這顆樹中,再直接將這顆樹發過去,在伺服器那邊直接解析這顆樹,這樣一來,不用臨時檔案,沒有磁碟IO,無須另外開闢記憶體,充分利有現有空間,效率較高。

設計開發
   從伺服器效率至上的觀點考慮,我選擇了第2種方法,並實現了基於順序儲存的多叉樹,關於順序儲存,又有兩種如下方式:
  (1)深度優先儲存,按照自上而下從左到右儲存樹的所有結點,先儲存結點及它的孩子,再儲存它的兄弟。因此結點的孩子和兄弟都不一定是連續的,當一個結點的所有孩子都是葉子結點時,則所有孩子是連續存放的。結點和它的第一個孩子(若有)是連續的,如下圖所示
 
  (2)廣度優先儲存,
按照從左到右自上而下儲存樹的所有結點,先儲存結點及它的兄弟,再儲存它的孩子,因此結點的孩子和兄弟都是連續存放的,孩子與其父親之間不一定是連續的,如下圖所示 
                      
   本文描述第1種儲存方式實現的多叉樹,介紹三種主要操作:設定根結點、增加結點和刪除結點,為簡單起見,使用vector作為內部動態陣列,使用索引而非迭代器作為外部介面,來訪問結點,索引0表示空索引,有效索引從1開始。關於迭代器的設計,有諸多考慮,如前序、後序、廣度優先、指定深度、葉子結點等各種遍歷方法,因時間和篇幅原因,不能一一講述,待後面有時間會陸續補充完善。
   1)樹結點定義,由5個偏移量域和1個數據域組成,C++程式碼描述如下     1template<typename T> 2struct order_tree_node
 3{
 4    size_t parent_;      
 5    size_t first_child_;  
 6    size_t last_child_;  
 7    size_t prev_sibling_; 
 8    size_t next_sibling_; 
 9    T      data_;         
10
11    order_tree_node();        
12    order_tree_node(const T& data);    
13}
;   為了方便,定義了order_tree_node的兩個建構函式,其實現如下
 1    template<typename T> 2    order_tree_node<T>::order_tree_node()
 3        :parent_(0)
 4        ,first_child_(0)
 5        ,last_child_(0)
 6        ,prev_sibling_(0)
 7        ,next_sibling_(0)
 8    {
 9    }
10    template<typename T>11    order_tree_node<T>::order_tree_node(const T& data)
12        :parent_(0)
13        ,first_child_(0)
14        ,last_child_(0)
15        ,prev_sibling_(0)
16        ,next_sibling_(0)
17        ,data_(data)
18    {
19    }
   2)設定根結點,為方便實現,根結點固定存放在陣列中第1個位置,對應下標為0,C++程式碼描述如下  
 1    template<typename T> 2    inline typename mtree<T,false>::iterator_base mtree<T,false>::set_root(const T& val)
 3    {
 4        if (!base_type::empty())
 5        {
 6            *(get_root()) = val;
 7        }
 8        else 9        {
10            tree_node node(val);
11            push_back(node);
12        }
13        return iterator_base(this,0);
14    }
   這裡用到了get_root函式來獲取根結點,其實現如下  1    template<typename T> 2    inline typename mtree<T,false>::iterator_base mtree<T,false>::get_root() 
 3    {
 4        return iterator_base(this,0);
 5    }
 6    template<typename T> 7    inline typename mtree<T,false>::const_iterator_base mtree<T,false>::get_root() const 8    {
 9        return const_iterator_base(this,0);
10    }
   3)增加結點,這裡要分為三步,第一步要找到插入位置,第二步插入結點,第三步改變相關結點的相對偏移量,這裡相關結點包括當前所插結點、所插結點兄弟結點、父結點、祖先結點及其右兄弟結點;注意,這裡可以作一些異常安全考慮,即如果第二步操作失敗了,則可直接返回,這樣就可保證整顆樹不受影響。為了簡單起見,以下C++程式碼對異常安全沒有作處理,描述如下  1    template<typename T> 2    template<typename tree_iterator> 3    inline tree_iterator mtree<T,false>::append_child(tree_iterator iter,const T& val)
 4    {
 5        assert(!iter.is_null());
 6        size_t off = append_child(iter.off_,val);    
 7        tree_iterator it(iter);
 8        it.off_ = off;
 9        return it;
10    }
11    template<typename T>12    inline typename mtree<T,false>::fd_iterator mtree<T,false>::append_child(fd_iterator iter,const T& val)
13    {
14        assert(!iter.is_null());
15        size_t off = append_child(iter.off_,val);    
16        fd_iterator it(iter);
17        it.off_ = off; ++it.depth_;
18        return it;
19    }
   以上模板成員函式及其深度迭代器的特化版本都呼叫了內部append_child(size_t)函式,該函式實現如下:  1    template<typename T> 2    inline size_t mtree<T,false>::append_child(size_t index,const T& val)
 3    {
 4        size_t parent = index, pos;
 5        tree_node *p_parent =&(*this)[parent],*p_node, *p_child;
 6
 7        //找到插入位置 8        pos = parent; p_node = p_parent;
 9        while (p_node->last_child_)
10        {
11            pos += p_node->last_child_;
12            p_node =&(*this)[pos];
13        }
14        size_t child =++pos; 
15        //插入結點16        tree_node node(val);
17        if (child >=this->size())
18            push_back(node);
19        else20            base_type::insert(begin()+child,node);
21
22        //更新當前結點的prev_sibling值和其左兄弟結點的next_sibling值23        p_parent =&(*this)[parent];
24        p_child =&(*this)[child]; 
25        if (p_parent->last_child_)
26        {
27            pos = parent+p_parent->last_child_;
28            (*this)[pos].next_sibling_ = p_child->prev_sibling_ = child-pos;
29        }
30        //從父結點開始,向上更新當前結點所有右邊結點的偏移量31        size_t next; 
32        tree_node* p_next;
33        pos = parent;
34        do35        {
36            p_node =&(*this)[pos];
37            if (p_node->next_sibling_)
38            {
39                if (p_node->parent_)
40                    ++(*this)[pos-p_node->parent_].last_child_;
41                //

相關推薦

基於順序儲存實現1深度優先儲存

需求分析    在資料結構中,樹有兩種儲存方式,一種是鏈式儲存,另一種是順序儲存。前者就是使用指標來記錄樹結點間的關係,在新增結點或刪除結點時,只需改變與父結點或兄弟結點的指標值即可,實現較為簡單;後者就是使用陣列來儲存,可以用相對偏移量來記錄樹結點間的關係,在新增結點或刪除結點時,則不僅是改變

基於順序儲存實現7深度遍歷

 1    template<typename T> 2    template<bool is_const,bool is_reverse> 3    inline typename mtree<T,false>::template fd_iterator_impl<

基於順序儲存實現6葉子遍歷

 1    template<typename T> 2    template<bool is_const,bool is_reverse> 3    inline typename mtree<T,false>::template leaf_iterator_impl&

LeetCode-105.從前序與中序遍歷序列構造二相關話題深度優先搜尋

根據一棵樹的前序遍歷與中序遍歷構造二叉樹。 注意: 你可以假設樹中沒有重複的元素。 例如,給出 前序遍歷 preorder = [3,9,20,15,7] 中序遍歷 inorder = [9,3,15,20,7] 返回如下的二叉樹: 3 / \ 9

LeetCode-106.從中序與後序遍歷序列構造二相關話題深度優先搜尋

根據一棵樹的中序遍歷與後序遍歷構造二叉樹。 注意: 你可以假設樹中沒有重複的元素。 例如,給出 中序遍歷 inorder = [9,3,15,20,7] 後序遍歷 postorder = [9,15,7,20,3] 返回如下的二叉樹: 3 / \

森林轉二

本來不怎麼想寫這個,但發現網上的都是“殘疾”部落格,講得不是很詳細,所以我還是要寫一下。 多叉轉二叉有“左兒子右兄弟”的說法,然而對於什麼都不知道的小白,這句話沒有任何用…… 思路 大體就兩步,很好理解,如圖是用來舉慄的多叉樹: 兄弟連 將

資料結構-二1以及前序、中序、後序遍歷python實現

上篇文章我們介紹了樹的概念,今天我們來介紹一種特殊的樹——二叉樹,二叉樹的應用很廣,有很多特性。今天我們一一來為大家介紹。 二叉樹 顧名思義,二叉樹就是隻有兩個節點的樹,兩個節點分別為左節點和右節點,特別強調,即使只有一個子節點也要區分它是左節點還是右節點。 常見的二叉樹有一般二叉樹、完全二叉樹、滿二叉樹、線

1-----遍歷

not nor tree tle pri 遞歸 分享 idt image 一、前序遍歷: 遞歸方式: def preorder(tree): if tree: print(tree.val) preorder(tree.getLef

執行緒1繼承Thread類和實現Runnable介面

多執行緒的兩種實現方法: 1.繼承Thread類     繼承Thread類,重寫run()方法。建立多執行緒的時候,需要建立物件例項,然後呼叫start()方法。類物件的屬性屬於執行緒私有,執行緒之間互不影響。 public class ClassExtendT

遞迴recursion演算法與二1

筆者按:曾經剛開始學習資料結構和演算法時,總會為簡潔雋永的遞迴程式碼而驚歎,也想寫出如此優雅的程式碼,但是思考過程真的實屬不易!!!那時候遞迴都會盡量用顯式棧來規避。 生活中的遞迴!        首先,對遞迴要有一個類似盜夢空間或者平行世界的認識,就

LeetCode-109.有序連結串列轉換二搜尋相關話題深度優先

給定一個單鏈表,其中的元素按升序排序,將其轉換為高度平衡的二叉搜尋樹。 本題中,一個高度平衡二叉樹是指一個二叉樹每個節點 的左右兩個子樹的高度差的絕對值不超過 1。 示例: 給定的有序連結串列: [-10, -3, 0, 5, 9], 一個可能的答案是:[0, -3,

基於雙端堆實現的優先順序佇列1原理

前言    眾所周知,stl中的優先順序佇列是基於最大堆實現的,能夠在對數時間內插入元素和獲取優先順序最高的元素,但如果要求在對數時間內還能獲取優先順序最低的元素,而不只是獲取優先順序最高的元素,該怎麼實現呢?可以用最大堆-最小堆或雙端堆資料結構來實現,最大堆-最小堆和雙端堆都是支援雙端優先佇列

自定義1

通過學習自定義樹,瞭解與樹相關的資料結構。   1)樹:n(n>=0)個結點的有限集。 結點的度:結點擁有的子樹的數目 葉子結點(終端結點):度為0的結點 分支結點(非終端結點):度不為0的結點 樹的度:樹中各結點的度的最大值 層次:根結點的層次

SSO 基於Cookie+fliter實現單點登入SSO工作原理

SSO的概念:        單點登入SSO(Single Sign-On)是身份管理中的一部分。SSO的一種較為通俗的定義是:SSO是指訪問同一伺服器不同應用中的受保護資源的同一使用者,只需要登入

十二Hibernate中的表操作1單向對一

art 保存 int gen round t對象 情況 映射文件 拋出異常 由“多”方可知“一”方的信息,比如多個員工使用同一棟公寓,員工可以知道公寓的信息,而公寓無法知道員工的信息。 案例一: pojo類 public class Department {

java執行緒1執行緒的建立和執行緒的安全問題

前言 java多執行緒多用於服務端的高併發程式設計,本文就java執行緒的建立和多執行緒安全問題進行討論。 正文 一,建立java執行緒 建立java執行緒有2種方式,一種是繼承自Thread類,另一種是實現Runnable介面。由於java只支援單

python簡易圖片處理1開啟\顯示\儲存圖片

一提到數字影象處理,可能大多數人會想到matlab,但是matlab有自身的一些缺點: 1.不開源,價格貴; 2.軟體容量大。一般3GB以上,高版本甚至達到5GB以上。 3.只易做研究,不易轉化成軟體。 因此,我傾向於學習python來進行影象的處理工作

0——二實現與二的遍歷

0.二叉樹的實現(C++) 未完,待補充 #include <iostream> #include<iostream> #include<queue> #include<stack> using namespace std; //二叉樹結點的

Multihedgehog找root

題意: K層樹定義:上面K層節點都有至少三個兒子,第K+1層全是葉子。給出一棵樹結構和K,問這棵樹是不是K叉(不知道root) 解析: 首先是dfs得出每個節點的度,度為1的是葉子。然後葉子開始一層一層往上剪,直到剩下一個節點便是root 找root的時候可

BZOJ 3684 大朋友和生成函式+FFT

Description 我們的大朋友很喜歡電腦科學,而且尤其喜歡多叉樹。對於一棵帶有正整數點權的有根多叉樹,如果它滿足這樣的性質,我們的大朋友就會將其稱作神犇的:點權為1的結點是葉子結點;對於任一點權大於1的結點u,u的孩子數目deg[u]屬於集合D,且u的點