1. 程式人生 > 其它 >樹の講解-----二叉樹入門(例題)

樹の講解-----二叉樹入門(例題)

上篇部落格寫的太爛了,我自己都不想看

題目

P1827 [USACO3.4]美國血統 American Heritage

簡單來說就是已知中序遍歷和前序遍歷,求後序遍歷

輸入第一行為中序遍歷,第二行為前序遍歷

輸入輸出樣例 


輸入   	 輸出 

ABEDFCHG  AEFDBHGC
CBADEFGH 

解題思路

我們先來一步步分析,已知中序遍歷和前序遍歷,根據這三種順序的定義我們可以知道,我們只需要先遍歷一遍這棵樹的左子樹和右子樹最後輸出所在子樹的根節點就可以得出後序遍歷的答案

  • 這題不建議看著深基來做......因為深基上面是先輸入前序再輸出後序,如果那樣程式核心得顛倒一下,這個我們之後再說

先打出程式的主體框架

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
string s1,s2;//s1前序,s2中序
inline void work(int l1,int r1,int l2,int r2)    

//l1:前序中目前所在子樹根節點位置 r1:前序中目前所在子樹範圍終點的位置 
l2:中序中目前所在子樹在前序中開始的地方 r2:中序中目前所在子樹在s2中結束的地方
{
    /*若干Code*/
}
int main()
{
    cin>>s1>>s2;
    swap(s1,s2);//這題我是用深基上的方法做的,所以交換一下比較方便qwq
    work(0,s1.size()-1,0,s2.size()-1);
    return 0;
}
            

然後我們根據上述的思路打出核心遞迴程式碼的大體

for(int i=l2;i<=r2;i++)
    {
        if(s1[l1]==s2[i])//i:目前所在子樹根節點的位置(中序遍歷中)
        {
            work(l1+1,l1+1+(i-1-l2),l2,i-1);//左子樹,實際上等同於work(l1+1,l1+i-l2,l2,i-1)
            work(l1+i-l2+1,r1,i+1,r2);//右子樹,這裡的第一個值為什麼是l1+i-l2+1稍微一想就會非常簡單
            printf("%c",s1[l1]);
            return;
        }
    }

總程式

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
string s1,s2;
inline void work(int l1,int r1,int l2,int r2)               
{
    for(int i=l2;i<=r2;i++)
    {
        if(s1[l1]==s2[i])
        {
            work(l1+1,l1+1+(i-1-l2),l2,i-1);
            work(l1+i-l2+1,r1,i+1,r2);
            printf("%c",s1[l1]);
            return;
        }
    }
}
int main()
{
    cin>>s1>>s2;
    swap(s1,s2);
    work(0,s1.size()-1,0,s2.size()-1);
    return 0;
}

好看點的第二遍打的Code

#include<cstdio>
#include<iostream>
using namespace std;
string a,b;//a中序
inline void work(int l1,int r1,int l2,int r2)
{
    for(int i=l1;i<=r1;i++)
    {
        if(a[i]==b[l2])
        {
            work(l1,i-1,l2+1,l2+i-l1);//左子樹
            work(i+1,r1,l2+i-l1+1,r2);//右子樹
            cout<<b[l2];
            return;
        }
    }
}
int main()
{
    cin>>a>>b;
    work(0,a.size()-1,0,b.size()-1);//l1,r1中序,l2,r2前序
    return 0;
}

P1030 [NOIP2001 普及組] 求先序排列

還是先來上程式碼一步步講解

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
string a,b;//a中序,b後序
inline void work(int l1,int r1,int l2,int r2)//l1r1中序,l2r2後序
{
    for(int i=l1;i<=r1;i++)
    {
        if(a[i]==b[r2])
        {
            printf("%c",b[r2]);
            work(l1,i-1,l2,l2+i-1-l1);//左子樹
            work(i+1,r1,l2+i-1-l1+1,r2-1);//右子樹
            return;
        }
    }

}
int main()
{
    cin>>a>>b;
    work(0,a.size()-1,0,b.size()-1);
    return 0;
}

程式碼主體啥意思咱也都知道,我們直接講遞迴程式碼

for(int i=l1;i<=r1;i++)
    {
        if(a[i]==b[r2])
        {
            printf("%c",b[r2]);
            work(l1,i-1,l2,l2+i-1-l1);//左子樹
            work(i+1,r1,l2+i-1-l1+1,r2-1);//右子樹
            return;
        }
    }

首先因為是求先序排列,所以我們把輸出(即根節點)放在最前面,再進行左右子樹的遍歷,因為這次知道的是後序遍歷,後序遍歷的根節點放在後面,所以我們輸出的是 b[r2] (不明白的寫個前中後遍歷就知道了)

然後我們看左子樹的遍歷

work(l1,i-1,l2,l2+i-1-l1);

前兩個變數好解釋,是中序遍歷裡左子樹的範圍,我們舉個例子

中序遍歷:4352617
後序遍歷: 4536271

後序遍歷根節點再後面,所以我們做個Sign,標記一下根節點的位置,也就是列舉的 \(i\) 的值

左子樹的範圍
         l1               i-1
中序遍歷:4    3   5   2   6   |1|  7

         l2            l2+(i-1-l1)
         |                | 
後序遍歷: 4    5   3   6   2    7  |1|

再看右子樹的範圍

 work(i+1,r1,l2+i-1-l1+1,r2-1);

右子樹的範圍
                                  i+1/r1(實際上是重合了)
                                   |
中序遍歷:4    3   5   2   6   |1|  7

                     		l2+i-1-l1+1/r2-1(同上)
                                | 	
後序遍歷: 4    5   3   6   2    7  |1|

然後就沒有然後了

Ending