樹形數據結構總結一(堆,Trie,並查集)

樹形結構是很是重要的一種數據結構。咱們能夠經過平衡二叉樹來實現排序問題,用樹結構來表示源程序的語法結構,樹也能夠表示數據庫或文件系統。而且不少容器的底層都是樹結構。算法

下面先來了解關於樹結構的名詞有哪些: 數據庫

image

  • 結點:表示樹中的數據元素,A,B...H就是節點。
  • 結點的度:結點所擁有的子樹的個數,B的度爲2。
  • 樹的度:樹中各結點度的最大值,這裏樹的度爲2。
  • 葉子結點(Leaf Node):度爲0的結點。D,H,F,G結點都是葉子結點。
  • 孩子(Child):結點左右節點。在圖中,結點D,E是結點B的孩子。
  • 雙親(Parent):結點的上層結點叫該結點的雙親。結點B是D,E的雙親。
  • 祖先(Ancestor):從根到該結點所經分支上的全部結點。A是其餘全部節點的祖先。
  • 子孫(Descendant):以某結點爲根的子樹中的任一結點。A以外的全部結點都是A的子孫。
  • 兄弟(Brother):同一雙親的孩子。結點B,C互爲兄弟,D,F互爲兄弟,F,G互爲兄弟。
  • 堂兄弟(Sibling):同一層的雙親不一樣的結點。E和F互爲堂兄弟。
  • 結點的層次(Level of Node):從根結點到樹中某結點所經路徑上的分支數稱爲該結點的層次。A的層次爲4。
  • 樹的深度(Depth of Tree):樹中結點的最大層次數。這裏樹的深度爲4。

這裏只解釋了二叉樹(一個節點只有左右兩個孩子的狀況),固然樹不必定只有兩個孩子,好比下文會出現的並查集和Trie,但咱們能夠把大多數樹轉化爲二叉樹,這樣更便於讓咱們理解概念。數組

在開始二叉樹以前再來看關於二叉樹中的概念:bash

滿二叉樹:一個深度爲k且有2^k - 1個結點的二叉樹稱爲滿二叉樹網絡

image

徹底二叉樹:徹底二叉樹從根結點到倒數第二層知足完美二叉樹,最後一層能夠不徹底填充,其葉子結點都靠左對齊。數據結構

image

平衡二叉樹:每一個節點的左右子樹深度差不超過1ui

image

絕對平衡樹:只有最後一層是葉子節點,而且知足二分搜索樹的特色spa

image

二分搜索樹

若是如今有一個數組,給出一個數(這個數在數組中),要求找到這個數在這個數組的下標位置,咱們只能一個一個去遍歷查找,這個時候的時間複雜度是O(n),爲了加快查找速度,若是咱們獲得的數組是排好序的,咱們可使用折半查找法,這樣時間複雜度就爲O(logn)了,速度會很快。而折半查找的思路就是利用了二分搜索的思想。設計

先來看二分搜索樹的節點構造:3d

class Node{
    int data;//節點值
    Node left;//左孩子
    Node right;//右孩子
}
複製代碼

二分搜索樹定義

節點的左子樹的全部節點小於這個節點,節點的右子樹的全部節點大於這個節點。

二分搜索樹的操做

插入

二分搜索樹的插入操做十分簡單:

  • 判斷是否有根節點,沒有當前節點做爲根節點
  • 有就作比較,比當前節點大就和它的右節點比較,比當前節點小就和它的左節點比較
  • 若是比較到葉子節點就插入
  • 重複2步驟,直到3步驟執行結束

插入的平均時間複雜度爲O(logn)

刪除

刪除的操做稍微麻煩一些,將要刪除的節點分爲三種狀況

  • 沒有孩子:直接遍歷到節點刪除便可
  • 有一個孩子:刪除該節點,讓這個節點的孩子節點移到這個節點的位置
  • 有兩個孩子:刪除該節點,讓這個節點的後孩子的最左節點X(左孩子的最右節點也可)移動到這個位置,這時X只有可能有右孩子,再對原來X的位置進行只有一個孩子時刪除操做便可。

刪除的平均時間複雜度爲O(logn)

查找

查找和插入的方法相似,這裏就不贅述了

查找的平均時間複雜度爲O(logn)

DFS,BFS

若是想遍歷一棵二叉樹有兩種方法,深度優先遍歷和廣度優先遍歷,而深度優先遍歷又能夠分爲前序,中序,後序遍歷。

  • 深度優先遍歷(DFS):深度優先遍歷的思想就是將節點先從每條分支先遍歷完
    • 前序遍歷:節點-左孩子-右孩子
    • 中序遍歷:左孩子-節點-右孩子
    • 後序遍歷:左孩子-右孩子-節點
  • 廣度優先遍歷(BFS):從每一層開始遍歷

下面來看個圖來看具體的遍歷時如何的:

image

  • 前序遍歷:ABCDEF
  • 中序遍歷:CBDAEF
  • 後序遍歷:CDBFEA
  • 廣度優先遍歷:ABECDF

二分搜索樹的做用

  • 對數組來講它的增長,刪除的時間要O(n),查找的時間也要O(n)。
  • 對二分搜索樹來講它的增長,刪除,查找的時間都爲O(logn)。

堆的定義

堆是一種徹底二叉樹,而且一個節點要大於(小於)它的左右孩子

來看堆的節點構造

class Node{
    int data;//節點值
    Node left;//左孩子
    Node right;//右孩子
}
複製代碼

堆的操做

對於堆來講他更注重將最大的放在首位和堆性質的維護。這裏由於堆是一個徹底二叉樹,因此能夠用數組來保存堆中的元素。

如圖(如下使用最小堆,即每一個節點小於它的孩子節點):

image

那麼對於一個節點,咱們能夠很容易的找到這個節點的雙親結點和左右節點。

//得到雙親結點的下標
int getParent(int index){
    return (index + 1) / 2 - 1;
}
//得到左孩子
int getLeftChild(int index){
    return index * 2 + 1;
}
//得到右孩子
int getRightChild(int index){
    return index * 2 + 2;
}
複製代碼
  • 插入,這一步要作的叫堆的sift up
    • 先將元素放到數組最後的位置
    • 將這個元素與它的雙親節點進行大小比較,若是這個節點比它的雙親結點小,那麼和這個節點交換位置
    • 重複步驟2一直比較到頭節點或是大於雙親結點的時候結束
  • 刪除(這裏的刪除指的是刪除頭元素、頭節點的意思),這一步要作的叫堆的sift down
    • 將數組中下標爲0的元素移除,並將數組中最後一個元素放到下標爲0的位置
    • 將當前位置爲0的節點記爲x,找出x的左右孩子中較小的那個元素的座標記爲y,將x與y比較,若是x小於y,那麼x與y交換位置
    • 重複步驟2一直到葉子節點或是大於y的時候結束

堆的做用

堆可以實現優先隊列,在現實生活中也有優先隊列的應用,例如排隊的時候會先讓老人和小孩到隊列前面。

拓展

d叉堆

徹底d叉樹,根最小。能夠想成原來咱們的堆是二叉堆,這個d能夠爲2,3,4

索引堆

以上咱們討論的堆是比較每一個節點的data,若是這個data是一種很龐大的數據結構,那麼會很耗時。這時咱們能夠用索引堆,在原來堆的基礎上用一個索引數組來存儲數據元素的位置,即索引堆裏面包含兩個數組

二項堆

二項堆是二項樹的集合。二項樹也是一種樹結構,二項樹的第K棵樹有2k個結點,高度是k;深度爲d的結點共有個結點。二項堆就由一組二項樹所構成。

還有斐波那契堆,Pairing堆等等。

線段樹

線段樹也是一種平衡二叉樹,可是它節點和平衡二叉樹的節點有些不一樣,它的每一個節點能夠看做是由一段數組組成,若是一個節點的數組是[l,r]而他的左孩子和它的右孩子值就是數組空間爲[l,mid]和[mid+1,r]的值(這裏值的意思是[left,right]按照本身的意願得到的樹,能夠輸left*right,也能夠是left+right),mid通常取作l+(r-l)/2

看一下線段樹的關鍵字段:

//tree表示線段樹中的全部節點,和層次遍歷的順序同樣,和堆的物理結構同樣
    //上圖中tree[0]就是0-7的值,tree[3]就是0-1的值
    private int[] tree;
複製代碼

image

線段樹的做用

線段樹可以解決一些算法問題,例如

  • 對一段區間進行染色(能夠覆蓋原來的顏色),m次對隨即區間染色,問在[i,j]區間能夠看見多少種顏色
  • 求某個太空區間中天體總量
  • 給出n個數,n<=1000000,和m個操做,每一個操做修改一段連續區間[a,b]的值

若是用線段樹這種結構就有一個方便的思路解決以上問題

這裏假設咱們要求求解[1,7]的值咱們直接獲取tree[8]+tree[4]+tree[2]+便可,即[1,1]+[2,3]+[4,7]就獲得[1,7]的值。若是將0-7採用二分搜索樹的方法,並從1加到7那個就是7*O(logn)的複雜度。

線段樹的操做

線段樹主要針對於查詢和修改,至於增長和刪除咱們不討論。

查詢

這裏直接看若是須要查詢一段區間該如何操做(懂了區間查詢以後單個元素查詢也會簡單一些)

這裏直接放一個例子來理解簡單一些,例如如今我要查詢[1,7]的值,讓查找的值爲x返回:

  • 先查找根節點[0,7]中是不是本身要查找的,[1,7]並不等於[0,7],要查找的左區間大於節點的左區間,先從左子樹[0,3]開始找,並把[1,7]分紅[1,3]和[4,7]
  • 從左子樹[0,3]開始查,[1,3]和[0,3]不相等,要查找的左區間大於節點的左區間,從節點的左區間[0,1]再找,並把[1,3]分爲[1,1]和[2,3]
  • 從左子樹爲[0,1]開始查,由於是左子樹,[1,1]和[0,1]不相等,和上面同樣分爲[1,1]和[2,3],因此[0,1]要再查找右子樹[1,1]
  • 這時[1,1]和[1,1]就相等了,返回[1,1]對應的值
  • 接下來將以前返回的值分別和上面步驟留下來的[2,3],[4,7]相加,獲得最終的值(由於[2,3],[4,7]都是在節點中直接能夠找到的,就不用再分了)

[2,3]的值就是tree[4],[4,7]的值就是tree[2],[1,1]的值就是tree[8]

大概流程就是上面所述,可能一開始看有點晦澀,但結合代碼來看使用遞歸仍是挺清晰的

修改

修改的操做和查詢的思路同樣的,不過要對遍歷過的節點進行修改,這裏就不贅述了

實際上,可以用線段樹解決的問題都能

Trie字典樹

字典樹不是二叉樹,和它的名字同樣,首先來看他的節點結構(根節點是不包含字符的,根節點的c是空)

class Node{
    char c;//當前節點的字符
    Node[] next;//當前節點的下一個節點
    boolean end;//判斷這個字母是否是一個結尾
}
複製代碼

咱們能夠發現字典樹的每一個節點由若干個指向下一節點的指針,總體字典樹的結構以下圖:

image

字典樹的做用

能夠看到上面的圖用深度優先中序遍歷的話他每遍歷到葉子節點都是一個單詞,如and,as,at,cn,com。

在之前的電話簿系統中,若是這時咱們用二分搜索樹去保存每一個聯繫人的信息,假設咱們的通信錄有一千萬個聯繫人信息,那麼咱們經過名字去查詢這個聯繫人的信息是很是費時的O(logn)。而若是咱們用字典樹,好比咱們要查詢一個名叫and的聯繫人,咱們經過根節點在next中找a,在a節點中找n,再在n節點中找b(若是這裏全是英文字母那麼next也只有26種狀況),這時咱們的時間複雜度只和這個單詞的長度有關O(K),K爲單詞長度。那麼可見這種狀況下字典樹的優點很是明顯。固然,字典樹的這種設計也就是典型的用空間換取時間的思路。

字典樹的操做

字典樹的操做主要爲增,刪,查

在開始以前,解釋一下end的做用,例如咱們有一個as和一個ass(壞笑.jpg),咱們如何判斷ass這個單詞是隻有ass仍是還有as這個單詞呢?咱們須要對每個節點進行一個標識,標識這個單詞是否在這裏是一個完整的單詞。例如ass,最後一個s中end就要爲true,由於ass最後的s是ass的結尾。而若是還有個as單詞,那麼第一個s就要爲true來表明as這個單詞以s結束。

增長

增長的邏輯就很簡單了,例如增長一個geek

  • 定位到頭節點,判斷next中是否有g,沒有的話先構造節點g(全部新構造的節點end爲false),再加入到當前節點的next之中,而後移動到節點g
  • 同理判斷e,構造並定位到下一個節點,而後e,而後k
  • 當移動到節點k時,表明單詞插入到結尾了,就要設置end爲true

刪除

例如上圖中咱們要刪除an這個單詞

  • 經過頭節點到達a,在達到n,若是n的next不爲空,那麼把n的end改成false便可
  • 若是n的next爲空,那麼刪掉n,回溯到上個節點刪掉next中n這個值,再判斷next之中是否爲空,不空的話結束。空的話再刪除a,結束。

查找

例如如今咱們要查找and

  • 經過根節點的next找a,存在,當前節點換位a節點
  • 判斷a節點的next找n,存在,當前節點換位n節點
  • 判斷n節點的next找d,存在,當前節點換位d節點,這時要查找的單詞到結尾了,再判斷d的end是否爲true,true的話表示找到,不然就是沒找到。

並查集

並查集是一種樹形結構,又叫「不相交集合」,保持了一組不相交的動態集合,每一個集合經過一個表明來識別,表明即集合中的某個成員,一般選擇根作這個表明。

並查集的做用

能夠解決鏈接問題,網絡中節點鏈接狀態,路徑問題。

舉個例子,例如咱們有若干個城鎮,不一樣城鎮之間可能會有相連的道路,將這些城鎮當作節點,而後判斷哪些城鎮之間是能夠通路的。

並查集的結構

image
如圖,咱們將每一個節點當作一個城鎮

先看左邊的圖,也就是合併前的圖,咱們能夠說d和c是連通的,g和e是連通的,c和f是不相通的。

若是這時咱們想在b城鎮和f城鎮之間連通,這時,咱們不能直接將b和f相連,應該讓f的根節點加到d的根節點下,那麼這裏f的根節點是e,b的根節點是a,也就是直接讓a成爲e的雙親節點,如圖。

咱們能夠看到並查集的邏輯結構是一種樹結構,但每一個節點有的只是本身的父親節點。

並查集的操做

由於並查集主要是來解決路徑查找問題的,因此相應的對並查集就須要一個操做isConnect來判斷兩個節點是否相連。若是兩個節點以後能夠連通,那麼就須要union操做將兩個節點連通

判斷節點是否相通

boolean isConnect(Node p, Node q)

經過上面的分析咱們知道了判斷節點是否相連主要比較的是根節點,因此只要獲得p的根節點和q的根節點再判斷兩個節點是否相同便可

節點合併

void unionElements(Node p, Node q)

和一開始分析的時候同樣,咱們能夠先求得p節點的根節點,再求得q節點的根節點,讓q節點的根節點的雙親結點指向p節點的根節點便可。

拓展

路徑壓縮

有可能出現全部的節點都在一條鏈上,而咱們能夠將整棵樹弄成只有兩層。如圖

image

下一章將會總結AVL,紅黑樹,B+樹

相關文章
相關標籤/搜索