Redis研究-3.3數據結構之樹與查找、排序等

1.樹相關的內容
  1.1 Tree概念
      樹是n(n>=0)個節點的有限集。n=0的時候,咱們把它叫作空樹。在任何一非空樹中知足兩個特色:(1) 有且只有一個叫作根的節點。(2)n>1時,其他節點可分爲m(m>0)個 互不相交的有限集T1,T2,...其中每個結合自己也是一棵樹。
     上面的這概念用到了遞歸的定義。

    樹的相關概念:
    節點的度:是指這個節點的子樹的個數。
    樹的度:是指樹的節點中擁有最大多數量的節點的度。
   節點的關係:節點的子樹的根叫作該節點的 孩子,相應的,該節點成爲孩子的 雙親(父母同體)。同一個雙親的孩子之間叫作 兄弟,節點的祖先是從根節點到該節點所經歷的分支上的全部的節點。以某節點爲根的子樹中的任一節點都是該節點的 子孫
    節點的層次:節點的層次從根開始定義,根爲第一層,根的孩子爲第二層,以此類推。雙親在同一層的互爲堂兄弟。
    樹的深度:樹中節點的最大層次叫作樹的深度或者高度。
咱們用一顆樹來講清楚上面的概念。
 
其中,節點D的度是3,由於他有3顆子樹,整棵樹的度是4,由於整棵樹中全部的節點的最大的度是G,他有4顆子樹。節點D、E叫作兄弟。
D、E、F是堂兄弟,並且樹的深度爲4.
1.2 樹的結構和存儲
   經過上面的描述,咱們知道,一棵樹是由節點來構成的,所以樹節點是最基本的組成單位,那麼,一棵樹的節點有怎麼表示或者說是怎麼構成的呢?咱們知道,一棵樹的節點他不多是從石頭裏面蹦出來的(除了跟元素),所以,他必須存在雙親,同時,他有可能存在子樹(這裏指子節點)。那麼,咱們就能夠獲得基本的節點結構以下:
typedef struct TreeNode{
    struct TreeNode *parent;//雙親
    struct TreeNode **childs;//孩子數組
    int childCount;//孩子的數量
    void *data;//節點的數據
}TreeNode;



可是,咱們單從節點自己來講,上述的表示方法,存在了不少的冗餘信息,所以,咱們從最平凡的節點信息來看,也就是說,對於節點,咱們只考慮他的雙親和自身的數據,所以,能夠表示爲下面的結構:
typedef struct SNode{
    struct SNode *parent;//雙親
    void *data;//節點的數據
}SNode;



上述的代碼的雙親,咱們是用指針來表示的,爲了簡化咱們的表述,咱們能夠把上述的雙親的指針表述方式換成一個整數型的位置數據來表示(根節點的位置數據爲-1)。
 
typedef struct NNode{
   int parentPosition;//雙親
    void *data;//節點的數據
    int32_t curPosition;//當前節點的座標
}NNode;
 那麼,針對咱們這章節最開始提到的那顆樹,咱們能夠表示爲下面的表格:
curpositon data parent
0 A -1
1 B 0
2 C 0
3 D 1
4 E 1
5 F 2
6 G 2
7 H 3
8 I 3
9 J 3
10 K 6
11 L 6
12 M 6
13 N 6
 
 這種表示方法對咱們找到某節點的雙親是很方便的,可是你想想,要想找到某節點的孩子,是否是很費勁,所以,咱們須要作一下改進,也就是說,咱們要考慮節點的度的問題,改進後,能夠獲得下面的節點結構定義:
typedef struct CPNode{
    int32_t parentPosition;//雙親
    void *data;//節點的數據
    int32_t curPosition;//當前節點的座標
    int32_t *childs;//節點的孩子位置座標數組
    int32_t degree;//節點的度
}CPNode;



經過此次的改進,咱們在某節點上,能很快的找到雙親和子樹。爲此,咱們可以獲得最終的樹的結構定義:
typedef struct tree{
  int32_t tail;//樹的根節點的標號
  int32_t nodeCount;//樹的節點數量
  struct CPNode *nodes;//節點數組
}tree;



好啦,咱們把這種拓展到通常的狀況,獲得常規的樹節點表示方式和樹的表示方式:



typedef struct gTreeNode{
void *data;//節點數據
int32_t degree;//節點的度
struct gTreeNode *parent;//雙親,向前指針
struct gTreeNode *next;//使用鏈表來存儲兄弟節點,向後指針
}gTreeNode;



//樹結構定義
typedef struct g{
struct gTreeNode *tail;
int32_t nodes;//樹節點數量
}gTreeNode;



 
OK,樹的結構已經基本完成了,下面說說一些常規的樹的定義(不管他有多麼特殊,均可以使用上述的樹的相關表示)
 
2.二叉樹
二叉樹就是在通常樹的定義上加了一個限制條件,這個限制條件就是一個節點的度不能超過2.這就是二叉樹。
 
在二叉樹裏面也存在集中特殊的二叉樹:
  2.1 斜樹:是指二叉樹的節點要麼只有左子樹,要麼就只有右子樹的狀況,就叫作斜樹;
  
  2.2滿二叉樹:是指在二叉樹中,若是全部的分支節點都存在左子樹和右子樹,而且,全部的葉子節點都在同一層上。
 
  2.3 徹底二叉樹:這個特列是相較於滿二叉樹來定義的,意思是說,對一個具備n個節點的二叉樹,若是編號爲i的節點與一樣深度的滿二叉樹中編號爲i的節點的二叉樹中位置徹底相同,那麼,這就是二叉樹啦,換句話說,徹底二叉樹是滿二叉樹的一部分,且全部葉子節點都是在最下面兩層,並且最下層的葉子節點,必定是從最左邊連續開始的,若是倒數第二層有葉子節點,那麼,這些節點就應該是在右部的連續位置,若是節點的度爲1,那麼這個節點只能有一個左孩子。
那麼,針對二叉樹,到底有哪些性質呢?
性質1:二叉樹的第i層上最多有個節點。
性質2:深度爲K的二叉樹,最多有-1個節點。
        一層:2^0=1=2^0=2^1-1
        二層:1+2^(2-1)=1+2^1=2^2-1
        三層:2^2-1+2^(3-1)=2^3-1
性質3:若是一顆二叉樹的終端節點爲m,度爲2的節點數量爲n,那麼,m=n+1;
    這個性質是怎麼獲得的呢?咱們知道,對一顆二叉樹來講,樹的節點分爲三種狀況:度爲1的節點n0、葉子節點m、度爲2的節點n,那麼樹的節點數量T=n0+m+n.
  同時,咱們知道,全部二叉樹節點間的連線的數量時等於總節點數減1的。同時,咱們也知道,對於一個度爲2的節點,他的出線應該是2條,所以,度爲2的節點對應的連線應該=2n.而對於終端節點是沒有出線的,對於度爲1的節點,他的出線只有1,所以,從連線的角度來看節點的數量T=2n+n0+1,所以,能夠獲得n0+m+n=2n+n0+1,從而獲得m=n+1;
      性質4:具備n個節點的徹底二叉樹的深度爲 +1.
       怎麼算出來的?咱們知道,一個滿二叉樹的節點數量n= -1,其中k爲這個二叉樹的度,那麼,這裏的k= .
      根據徹底二叉樹的定義,他的節點數量最可能是能夠等於一樣深度的滿二叉樹節點數量 -1的,可是必定是大於 .也就是說 ,由於n必須是整數,能夠取對數,就能夠獲得上面的結論。
 
 
2.4 二叉樹的結構和存儲
   經過上面的學習,咱們知道,二叉樹中的節點有應該具有四個屬性:數據域、左孩子、右孩子、雙親,所以,對於二叉樹來講,咱們能夠從新定義上面提到的樹的節點結構的定義:
typedef struct BiTNode{
    void *data;//數據域
    struct BiTNode *left;//左孩子
    struct BiTNode *right;//右孩子
    struct BiTNode *parent;//雙親
}BiTNode;



2.5 遍歷二叉樹
   學習到這裏,樹以及二叉樹相關的概念和定義已經搞定,那麼,針對一個樹來講,咱們要遍歷其中的節點,怎麼辦呢?我記得我在讀本科的時候,有次考試就是考了這種題目,NND,當時不是很清楚,結果可想而知。
 在遍歷二叉樹的時候,咱們採用的方法取決於咱們選擇的方向。
咱們先看一顆二叉樹,而後再一個方法一個方法的來看是怎麼實現的
 
//二叉樹、節點定義
typedef struct BiTNode{
   void *data;//數據域
    struct BiTNode *left;//左孩子
    struct BiTNode *right;//右孩子
    struct BiTNode *parent;//雙親
}BiTNode,*BiTree;
2.5.1 前序遍歷
    前序遍歷的規則就是從根節點開始,若是二叉樹爲空,則直接返回,不然 先訪問根節點,而後前序遍歷左子樹,再前序遍歷右子樹。
  
獲得的結果是:ABDHIEJCFG
//前序遍歷二叉樹
void preOrderTraverse(BiTree t){
    if(NULL==t||NULL==t->data) return ;
    //操做每一個節點,好比說是打印節點
    printf("%s",t->data);
    preOrderTraverse(t->left);
    preOrderTraverse(t->right);
}


2.5.2 中序遍歷 node

      中序遍歷的規則就是從根節點開始,若是二叉樹爲空,則直接返回,不然從 根節點開始(並非先訪問根節點),中序遍歷根節點的左子樹,而後訪問根節點,最後訪問根節點的右子樹。
最終的結果是HDIBJEAFCG
 
//中序遍歷二叉樹
void inOrderTraverse(BiTree t){
    if(NULL==t||NULL==t->data) return ;
    inOrderTraverse(t->left);
    //操做每一個節點,好比說是打印節點
    printf("%s",t->data);
    inOrderTraverse(t->right);
}



2.5.3 後序遍歷
    後序遍歷的規則是,若是樹爲空,則直接返回,不然從左到右先葉子後節點的方式來訪問左右子樹,最後訪問根節點。
最終的訪問結果是:HIDJEBFGCA
//後序遍歷二叉樹
void inOrderTraverse(BiTree t){
    if(NULL==t||NULL==t->data) return ;
    inOrderTraverse(t->left);
    inOrderTraverse(t->right);
        //操做每一個節點,好比說是打印節點
    printf("%s",t->data);
}



 
2.5.4 層序遍歷
  層序遍歷的規則是,若果樹爲空,則直接返回,不然先從根的第一層開始,在訪問同一層的時候,先左後右。
 
 
 
咱們回到先前定義的二叉樹節點,你會發現會存在一個很嚴重的問題,是什麼問題呢?
 咱們定義的節點有三個域,一個是左孩子指針域,一個是右孩子指針域,另外是數據域。可是,咱們看一個二叉樹的例子,就能清楚的發現這種作法,有一個很大的弊端。
 
(注意:上圖我是忽略了每一個節點的雙親指針域的狀況了的展示狀況)咱們看到上面的不少節點的指針域是沒有充分利用起來的,怎麼辦呢?咱們知道,對於一個有n個節點的二叉樹,一共有2n個指針域,同時又n-1條連線,也就是說存在2n-(n-1)=n+1個空指針域,上面的樹節點的空指針域有11個空指針域。
  咱們用中序遍從來遍歷上面的二叉樹,獲得的結果是HDIBJEAFCG,問題來了,你能很容易的知道任意節點的前驅和後繼麼?若是想要知道,咱們必須得先遍歷一次才知道,所以,咱們能夠想象一下,在創建節點的時候,是否是能夠明確的標註呢?咱們所以把這種具備前驅和後繼的樹稱做線索二叉樹。
改進後,咱們獲得的節點的結構定義就變成了下面的定義:
/**
* Link=0 表示左右孩子指針標示
*Thread=1 表示前驅或後繼指針標示
*/
typedef enum {Link,Thread} Tag;
typedef struct tagBiTreeNode{
    struct tagBiTreeNode *left;
    struct tagBiTreeNode *right;
    void *data;
    Tag LTag;
    Tag RTag;
}tagBiTreeNode,*tagBiTree;



 
好啦,二叉樹的內容基本搞定了,可是你發現沒有,咱們針對的實現基本都是基於二叉樹的,可是,咱們平時生活中的又有多少是知足二叉樹結構的呢?咱們可否把常規的樹轉化爲 二叉樹呢?
 
3.樹、森林、二叉樹的轉換
   我把後面的內容放在這個章節的後續章節來講吧,貌似這章節的內容太多了
 
 
不少東西可能會沒有說清楚,歡迎發表意見,同時能夠加QQ:359311095探討
相關文章
相關標籤/搜索