二叉樹的基本概念以及應用(遍歷、堆、哈夫曼樹、二叉判定樹、二叉搜索樹、二叉平衡樹)


完全二叉樹

  在完全二叉樹中,只有最下面兩層的結點的度可以小於2,最下面一層的葉子結點編號連續集中在靠左的位置上。

滿二叉樹

  一棵深度為𝑘,並且有2^𝑘−1個節點的二叉樹,為滿二叉樹。

二叉樹的性質

  • 在非空二叉樹的第i層上最多有個2^(𝑖−1)節點
  • 深度為𝑘的二叉樹最多有2^𝑘−1個節點
  • 具有n個節點的完全二叉樹的深度k=⌊log_2 (n) ⌋+1=⌈log_2 (n+1) ⌉

二叉樹的遍歷

  • 先序遍歷:若二叉樹為空,則空操作返回;否則①先訪問根結點;②然后先序遍歷左子樹;③先序遍歷右子樹。
  • 中序遍歷:若二叉樹為空,則空操作返回;否則①中序遍歷根結點的左子樹;②接着訪問根結點;③中序遍歷根結點的右子樹。
  • 后序遍歷:若二叉樹為空,則空操作返回;否則①后序遍歷根結點的左子樹;②后序遍歷根結點的右子樹;③最后訪問根結點。
  • 層次遍歷:先訪問第一層的結點,再對第二層的所有結點從左往右依次訪問,直到訪問完最后一層的所有結點。

  (注意:先序、中序、后序都是遞歸實現即可,層次遍歷類似於廣度優先搜索,需要使用隊列結構 

  由“先序+中序”或者“后序+中序”的遍歷結果可以得到唯一的一顆二叉樹。 

  • 先/后序確定當前根節點:先序序列的第一個元素為根節點,后序序列的最后一個元素為根節點;
  • 中序遍歷結果確定左右子樹的節點范圍:在中序序列中找到根節點所在位置,前面的是左子樹,后面的是右子樹
struct Node///結點
{
int data;
int lc, rc; 
}node[maxn*2]; ///還要算上內部節點的開銷,所以開兩倍maxn

int tot;///統計已經建立的頂點數量
int create(int len, int pre_st, int in_st) ///遞歸建樹
{ 
    if (len == 0)
        return -1;
    int me = tot++;
    node[me].data = pre[pre_st];///先序序列第一個元素為根結點
    for (int left = 0; left < len; ++left) 
    {
        if (in[in_st+left] == pre[pre_st]) 
        {
            node[me].lc = create(left, pre_st + 1, in_st);///建左子樹
            node[me].rc = create(len - left - 1, pre_st + left + 1, in_st + left + 1);
            return me;
        }
    }
}

vector<int>post;
void postorder(int root) ///后序遍歷
{
    if(root!=-1)
    {
        postorder(node[root].lc);
        postorder(node[root].rc);
        post.push_back(node[root].data);
    }
}                              

 堆

  二叉堆是完全二叉樹。二叉堆滿足二個特性:1.父結點的鍵值總是大於或等於(小於或等於)任何一個子節點的鍵值。2.每個結點的左子樹和右子樹都是一個二叉堆(都是最大堆或最小堆)。一般都用數組來表示堆,i結點的父結點下標就為(i-1)/2。它的左右子結點下標分別為2i+1和2i+2。

  • 插入元素

目前數組所存元素0->n-1,新插入的元素都將插入到n位置上,然后再從(n-1)/2父結點開始從下至上調整最大堆。 

void Insert_heap(int a[], int i)
{
    for (int j = (i - 1) / 2; (j >= 0 && i != 0)&& a[i] > a[j]; i = j, j = (i - 1) / 2)
        Swap(a[i], a[j]);
}///(i-1)/2表示父結點
  •  刪除元素

 對堆的刪除都是取出堆頂元素(最大、最小元素),此時先將n-1位置上的元素放到堆頂0號位置,再對0->n-2這樣的堆進行從上至下的調整。

///從i節點開始調整,n為節點總數。從0開始計算i節點的子節點為 2*i+1, 2*i+2
void AdjustDown(int a[], int i, int n)
{
        int j, temp;
 
        temp = a[i];
    j = 2 * i + 1;///左孩子
    while (j < n)
    {
        if (j + 1 < n && a[j + 1] > a[j]) //在左右孩子中找最大的
            j++;
 
        if (a[j] <= temp)///最大的孩子也比父節點小
            break;
 
        a[i] = a[j];     //把較大的子結點往上移動,替換它的父結點
        i = j;
        j = 2 * i + 1;
    }
    a[i] = temp;
}
//在最大堆中刪除數
void Delete_heap(int a[], int n)
{
    Swap(a[0], a[n - 1]);
    AdjustDown(a, 0, n - 1);
}

哈夫曼樹

概念:

  • 擴充二叉樹:只存在度為0和2的結點組成的二叉樹;
  • 結點間的路徑長度:樹中一個結點到另一個結點所包括的邊的數目;
  • 樹的內路徑長度:從根到所有非葉子結點的路徑長度之和;
  • 樹的外路徑長度:從根到所有葉子結點的路徑長度之和;
  • 樹的路徑長度之和:從根到樹 的所有結點的路徑長度之和;
  • 結點的帶權路徑長度:從根到該結點的路徑長度與該結點的權值乘積;
  • 樹的帶權路徑長度WPL:樹中所有葉子結點的帶權路徑長度之和
  • 定理:設I和E分別是一棵擴充二叉樹的內路徑長度和外路徑長度,n是樹中非葉子結點的數目,則E=I+2n

哈夫曼樹的特點:

  • 哈夫曼樹的結點個數不能是偶數(因為擴充二叉樹只有度為0和2的結點);
  • 一棵哈夫曼樹的加權路徑長度等於其中所有分支結點(不包括葉子結點)的權值之和;
  • 哈夫曼樹是帶權路徑長度最短的擴充二叉樹,權值越大的結點離根結點越近

構建哈夫曼樹:

  用給定的權值構建n個左右子樹都為空的二叉樹,所有結點都包含在F集合中;從F中選擇權值最小和次最小的兩棵樹作為左右子樹(規定左子樹比右子樹小),然后構建新的二叉樹,新二叉樹的權值為左右子樹權值之和,在F中刪除左右子樹權值,把新生成的結點權值加入其中;然后重復上述操作,直到F中只有一個結點即為根結點。

  假設哈夫曼樹中有n個葉子結點,則哈夫曼樹中總共有2n-1個結點。實際存儲時采用一維數組存儲,0號存儲單元不使用,1->n號單元分別存儲葉子結點,n+1->2n-1單元存儲哈夫曼樹形成過程中的非葉子結點。

typedef struct
{
    ElementType Data;///結點的數據域
    int w;                  ///結點的權值
    int parent,lchild,rchild;
}HFMTNode;

void CreateHFMTNode(HFMTNode Ht[],int N)
{
    int i,j,k,lmin,rmin;///lmin和rmin為最小權值的兩個結點位置
    int min1,min2;///min1和min2為最小兩個結點的權值
    for(i=1;i<2N;i++)///初始化數組
        Ht[i].parent=Ht[i].lchild=Ht[i].rchild[i]=-1;
    for(i=N+1;i<2N;i++)///n+1->2n-1的位置存儲新生成的非葉子結點
    {
        min1=min2=MAX;
        lmin=rmin=-1;
        for(k=1;k<=i-1;k++)///尋找當前最小的兩個結點
        {
            if(Ht[k].parent==-1)///該結點還沒被構造二叉樹
            {
                if(Ht[k].w<min1)
                {
                    min2=min1;rmin=lmin;
                    min1=Ht[k].w;lmin=k;
                }
                else if(Ht[k].w<min2)
                {
                    min2=Ht[k].w;rmin=k;
                 }
            }
        }
        Ht[lmin].parent=i;Ht[rmin].parent=i;
        Ht[i].w=Ht[lmin].w+Ht[rmin].w;
        Ht[i].lchild=lmin;Ht[i].rchild=rmin;
    }
}

 哈夫曼編碼:

  給使用頻率較高的字符進行較短的編碼,是一種可變長編碼,同時哈夫曼編碼也是一種前綴編碼(任何一個字符編碼都不是其它字符編碼的前綴),可以保證解碼不會產生二義性。一邊規定左分支為0,右分支為1

二叉判定樹

  二叉判定樹是用於描述解決問題的思路。二叉判定樹的根結點是有序表中序號為mid=(n+1)/2的記錄,左子樹是與有序表1->mid-1的范圍存儲,根結點的右子樹是mid+1]-> n相對應的位置。左子樹都比根結點小,右子樹都比根結點大。(注意:二叉判定樹的形態只與表中元素個數n有關,與表中元素的關鍵字無關)

  平均搜索長度

  • 搜索成功:As(n)=(每層結點數 × 比較次數(層數) / n
  • 搜索失敗:Af(n)=(每層外節點數×比較次數(上一層層數))/(n+1)

二叉搜索樹

  概念:所有結點的關鍵字的值都不一樣,左子樹都比根結點小,右子樹都比根結點大,而且根結點的左右子樹也都是二叉搜索樹。

  • 若中序遍歷二叉搜索樹,會得到一個關鍵字遞增排列的有序序列。
  • 已知一棵二叉搜索樹的先序遍歷序列,可以唯一確定一棵二叉樹。

  刪除結點:

  • 待刪除的結點沒有子樹:直接刪除
  • 待刪除的結點僅有一顆子樹:用唯一子樹覆蓋父結點,原本指向被刪除結點的指針改為指向被刪除結點的子樹
  • 待刪除的結點有兩顆子樹:使待刪除結點的左子樹代替被刪除的結點,將被刪除結點的右子樹放置於被刪除結點的左子樹的最右邊

二叉平衡樹

  1. 可以是空樹
  2. 假如不是空樹,任何一個結點的左子樹與右子樹都是平衡二叉樹,並且高度之差的絕對值不超過1
  3. 二叉平衡樹是建立在二叉搜索樹的基礎上,為了維護二叉搜索樹的高度,設置了平衡因子

調整二叉平衡樹:

  • 從新插入結點回溯,找到第一個非平衡結點標為s,s向下的子樹為r
  • s-r為LL型:以r為結點向上提;
  • s-r為LR型:再將r的孩子標為u,以u為根結點,r和s分別為u的孩子(根據左小右大的原則分布)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM