天天看點

樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

一 樹基礎

1 樹的定義

1 兩種定義方式

樹: 非線性結構

1 樹是n(n>=0)個元素的集合

n=0時,稱為空樹

樹隻有一個特殊的節點是沒有前驅元素的,稱為樹的根,及Root

樹中除了根節點外,其餘的元素隻能有一個前驅,可以有0個或多個後繼,根沒有前驅,隻有後繼

2 遞歸定義

樹T 是n(n>=0)個元素的集合,n=0時,稱為空樹

其有且隻有一個特殊元素及根,剩餘的元素都可以被劃分為m個互不相交的集合,T1,T2,T3...Tm,而每個集合都是樹,稱為T的子樹subtree,其子樹也有自己的根

2 數的叢集術語

前驅: 目前節點前面的元素

後驅: 目前節點後面的元素

結點: 樹中的資料元素

節點的度degree:節點擁有的子樹的數目稱為度,記做d(v)

葉子結點: 結點的度為0,稱為葉子結點,終端結點,末端結點

分支結點:結點的度不為0,稱為非終端結點或分支結點

分支:結點之間的關系,及連線

内部結點:除根節點以外的分支結點,當然不包括葉子節點,及掐頭,去尾,留中間

樹的度: 樹的度是各個節點的度的最大值。

孩子(兒子child)結點:結點的子樹的根節點稱為該結點的孩子

雙親(父Parent)結點:一個結點是它各子樹的根結點的雙親。

兄弟(sibling)結點:具有相同雙親結點的結點

祖先結點:從根結點到該結點所經分支上的所有結點

子孫結點:結點所有子樹上的結點都稱為子孫

結點的層次:根結點為第一層,根結點的孩子為第二層,依次類推,記做L(v)

樹的深度(高度Depth):樹的層次的最大值

堂兄弟:雙親在同一層的結點。

有序樹:結點的子樹是有序的(兄弟有大小,有先後次序),不能交換

無序樹:結點的子樹是無序的,可以交換

路徑:樹中的K個節點n1,n2...nk,滿足ni是n(i+1)的雙親,稱為n1到nk的一條路徑,就是一條線串下來的,前一個都是後一個的父(前驅)節點

路徑長度=路徑上結點長度-1,及分支數

森林:m(m>=0)棵不相交的樹的集合

對于結點而言,其子樹的集合就是森林,

3 樹特點:

1 唯一的根

2 子樹不相交

3 除了根以外,每個元素隻能有一個前驅,可以有0個或多個後繼

4 根結點沒有雙親節點(前驅),葉子節點沒有孩子結點(後繼)

5 vi是vj的雙親,則L(vi)=L(vj)-1,也就是說雙親比孩子節點的層次小1

2 二叉樹概念

1 特點

1 每個結點最多2個子樹

二叉樹不存在度數大于2的節點

2 它是有序樹,左子樹,右子樹是順序的,不能交換次序

3 即使某一個結點隻有一顆子樹,也要确定其是左子樹還是右子樹

2 二叉樹的五種形态

1 空二叉樹

2 隻有一個根結點

3 根結點隻有左子樹

4 根結點隻有右子樹

5 根結點有左子樹和右子樹

3 斜樹

樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

左斜樹:所有結點都隻有左子樹

右斜樹:所有結點都隻有右子樹

4 滿二叉樹

樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

一顆二叉樹的所有分支結點都存在左子樹和右子樹,且所有葉子節點都隻存在在最下面一層。

同樣深度的二叉樹,滿二叉樹節點最多

k為深度(1<=k<=n),則節點總數為 2**(k)-1

5 完全二叉樹

樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

若二叉樹的深度為k,二叉樹的層數從1到k-1層的結點都打到了最大個數,在第k層的所有結點都集中在最左邊,這就是完全二叉樹

完全二叉樹由滿二叉樹引出

滿二叉樹一定是完全二叉樹,但完全二叉樹不一定是滿二叉樹

k為深度(1<=k<=n),則結點總數最大值為2**k-1,當達到最大值的時候就是滿二叉樹

3 二叉樹的性質

1 在二叉樹中的第i層上最多有2**i-1 個節點(i>=1)

2 深度為k的二叉樹,至多有2**k -1個節點(k>=1)

3 對于任何一顆二叉樹T,如果其終點節點數為n0,度數為2的節點為n2,則有n0=n2+1,及葉子結點數-1=度數為2的節點數。

證明:

總結點數為n=n0+n1+n2,其中n0為度數為0的節點,及葉子節點的數量,n1為度數為1 的節點的數量,n2為節點為2度數的數量。

一棵樹的分支數為n-1,因為除了根節點外,其餘結點都有一個分支,及n0+n1+n2-1

分支數還等于n0*0+n1*1+n2*2及 n1+2n2=n-1

可知 2*n2+n1=n0+n1+n2-1 及就是 n2=n0-1

4 高度為k的二叉樹,至少有k個節點

5 具有n個節點的完全二叉樹的深度為int(log2n)+1 或者 math.ceil(log2(n+1))

6 有一個n個節點的完全二叉樹, 結點按照層序編号,如圖

樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

如果i=1,則節點i是二叉樹的根,無雙親,如果i>1,則其雙親為int(i/2),向下取整。就是葉子節點的編号整除2得到的就是父節點的編号,如果父節點是i,則左孩子是2i,若有右孩子,則右孩子是2i+1,。

如果2i>n,則結點i無左孩子,及結點為葉子結點,否則其左孩子節點存在編号為2i。

如果2i+1>n,則節點i無右孩子,此處未說明是否不存在左孩子,否則右孩子的節點存在編号為2i+1。

二 二叉樹周遊

1 周遊方式

周遊: 及對樹中的元素不重複的通路一遍,又稱為掃描

1 廣度優先周遊

一次将一層全部拿完,層序周遊
樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

及 1 2 3 4 5一層一層的從左向右拿取

2 深度優先周遊

設樹的根結點為D,左子樹為L,右子樹為R。且要求L一定要在R之前,則有下面幾種周遊方式

1 前序周遊,也叫先序周遊,也叫先跟周遊,DLR

樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

1-2-4-5-3-6

2 中序周遊,也叫中跟周遊, LDR

樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

4-2-5-1-6-3

3 後序周遊,也叫後跟周遊,LRD

樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

4-5-2-6-3-1

三 堆排序

1 堆定義

1 堆heap 是一個完全二叉樹

2 每個非葉子結點都要大于或等于其左右孩子結點的值稱為大頂堆

樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼
3 每個非葉子結點都要小于或者等于其左右孩子結點的值稱為小頂堆
樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼
4 根節點一定是大頂堆的最大值,小頂堆中的最小值

2 堆排序

1 建構完全二叉樹

1 待排序數字為 49 38 65 97 76 13 27 49

2 建構一個完全二叉樹存放資料,并根據性質5對元素進行編号,放入順序的資料結構中

樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼
3 構造一個清單為 [0,49,38,65,97,76,13,27,49]的清單

2 建構大頂堆

1 度數為2的結點,如果他的左右孩子最大值比它大,則将這個值和該節點交換

2 度數為1 的結點,如果它的左孩子的值大于它,則交換

3 如果節點被置換到新的位置,則還需要和其孩子節點重複上述過程

3 建構大頂堆--起點選擇

樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

1 從完全二叉樹的最後一個結點的雙親開始,及最後一層的最右邊的葉子結點的父結點開始

2 結點數為n,則起始節點的編号為n//2(及最後一個結點的父節點)

4 建構大頂堆--下一個節點的選擇

從起始節點開始向左尋找其同層節點,到頭後再從上一層的最右邊節點開始繼續向左逐漸查找,直到根節點
樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

5 大頂堆目标

確定每個結點的都比其左右結點的值大
樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

第一步,調換97和38,保證跟大

樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

第二步,調換49和97

樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

第三步,調換76和49

樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

第四步,調換38和49

此時大頂堆的建構已經完成

3 排序

将大頂堆根節點這個最大值和最後一個葉子節點進行交換,則最後一個葉子節點成為了最大值,将這個葉子節點排除在待排序節點之外,并從根節點開始,重新調整為大頂堆,重複上述步驟。
樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼
樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼
樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼
樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼
樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼
樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼
樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

四 實戰和代碼

1 列印樹

l1=[1,2,3,4,5,6,7,8,9]

列印結果應當如下

樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼
思路:可通過将其數字映射到下方的一條線上的方式進行處理及就是 8 4 9 2 0 5 0 1 0 6 0 3 0 7 0 的數字,其之間的空格可以設定為一個空格的長度,其數字的長度也可設定為固定的2,則
樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

則第一層第一個數字據前面的空格個數為7個,據後面的空格也是7個,

第二層第一個數字據前面的空格個數為3個,第二個數字距離後面的空格也是3個,

第三層據前面的空格為1,第三層最後一個據後面的空格也是1,

第四層據前面的空格是0,第四層最後一個據後面的空格也是0,

現在在其标号前面從1開始,則層數和空格之間的關系是

1 7

2 3

3 1

4 0

轉換得到

4 7

3 3

2 1

1 0

及就是 2**(l-1) -1 l 表示層數

間隔關系如下

1 0

2 8

3 4

4 2

轉換得到

4 0

3 8

2 4

1 2

及就是 2**l其中l 表示層數。

代碼如下

import  math 
# 列印二叉樹
def  t(list):
    '''
    層數      層數取反(depth+1-i,depth表示總層數,此處為4,i表示層數)   據前面的空格數            間隔數        每層字數為
    1            4                                                              7                     0              1
    2            3                                                              3                     7              2
    3            2                                                              1                     3              4
    4            1                                                              0                     1              8
                                                                    (2**(depth-i)-1)      (2**(depth-i)-1)        (2**i)   

    層數和資料長度的關系是 n表示的是資料長度
    1     1  
    2-3   2   
    4-7   3
    8-15  4       
    2**(i-1)<num<(2**i)-1,及就是int(log2n) +1 及 math.ceil(log2n)
    '''
    index=1
    depth=math.ceil(math.log(len(list),2))    #此處擷取到的是此清單轉換成二叉樹的深度,此處前面插入了一個元素,保證清單和二叉樹一樣,其真實資料都是從1開始
    sep='  ' # 此處表示數字的寬度 
    for  i in range(depth):
        offset=2**i #每層的數字數量1  2  4  8  
        print (sep*(2**(depth-i-1)-1),end="")  # 此處取的是前面空格的長度
        line=list[index:index+offset] #提取每層的數量
        for  j,x  in enumerate(line):
            print ("{:>{}}".format(x,len(sep)),end="") # 此處通過format來擷取其偏移情況,第一個x表示其大括号中的内容,第二個表示偏移的程度
            interval= 0  if  i ==0  else  2**(depth-i)-1  # 此處擷取到的是間隔數,當值大于1時,滿足如此表達式
            if  j < len(line)-1: #選擇最後一個時不列印最後一個的後面的空格,及就是1,3,7後面的空格
                print (sep*interval,end="")
        index += offset
        print ()           

結果如下

樹,樹的周遊和堆排序一 樹基礎二 二叉樹周遊三 堆排序四 實戰和代碼

2 堆排序算法實作

# 建構一個子樹的大頂堆
def  heap_adjust(n,i,array):
    '''
    調整目前結點(核心算法)
    調整的結點的起點在n//2(二叉樹性質5結論),保證所有調整的結點都有孩子結點
    :param  n : 待比較數個數
    :param  i : 目前結點下标
    :param array : 待排序資料
    :return : None
    '''
    while  2*i<=n: # 孩子結點判斷,2i為左孩子,2i+1為右孩子 
        lchile_index= 2*i  #選擇結點起始并記錄 n=2*i-1,因為其是從0開始,是以隻有len()-1
        max_child_index=lchile_index  
        # 判斷左右孩子的大小,并将大的指派給max_child_index 
        if  n > lchile_index  and  array[lchile_index+1] > array[lchile_index]: #說明其有右孩子,且右孩子大于左孩子 
            max_child_index=lchile_index+1  #n=2i+1 # 此時指派的是下标
        # 判斷左右孩子的最大值和父結點比較,若左右結點的最大值大,則進行交換
        if  array[max_child_index] > array[i]:  #此處的i是父節點,通過和父節點進行比較來确認其最大,
            array[i],array[max_child_index]=array[max_child_index],array[i]
            i= max_child_index   #右子節點的下标會指派到父結點,并将其值指派到父結點,
        else:
            break 
# 建構所有的大頂堆傳入參數
def  max_heap(total,array):
    for i in range(total//2,0,-1):  #建構最後一個葉子結點的父結點
        heap_adjust(total,i,array)  #傳入總長和最後一個葉子結點的父結點和數列
        print  (t(array))
    return  array
#建構大頂堆,起點選擇    
def  sort(total,array):    # 此處起點是1,是以必須在前面添加一個元素,以保證其位置編号和樹相同
    while  total>1:
        max_heap(total,array)
        array[1],array[total]=array[total],array[1]  #将最後一個結點和第一個結點調換,
        total-=1
    return array           

3 總結