我們可以把由“0”和“1”組成的字符串分爲三類:全“0”串稱爲B串,全“1”串稱爲I串,既含“0”又含“1”的串則稱爲F串。
FBI樹是一種二叉樹1,它的結點類型也包括F結點,B結點和I結點三種。由一個長度爲2^N的“01”串S可以構造出一棵FBI樹T,遞歸的構造方法如下:
1) T的根結點爲R,其類型與串S的類型相同;
2) 若串S的長度大於1,將串S從中間分開,分爲等長的左右子串S1和S2;由左子串S1構造R的左子樹T1,由右子串S2構造R的右子樹T2。
現在給定一個長度爲2^N的“01”串,請用上述構造方法構造出一棵FBI樹,並輸出它的後序遍歷2序列。
解題思路:使用函數求出每個節點,然後通過該節點構建樹,最後遍歷樹即可。
難點:建樹函數void buildtree(int start, int end, treeNode* &root) 當start與end相等時,如果遞歸參數不當,如構建左子樹使用函數buildtree(start, (start+end)/2, root->lc),會陷入無限遞歸的深淵。
處理方法:
1:求串的左半邊時用(start+end+1)/2-1;求右半邊用(start+end)/2+1, 構建函數最開始用判斷條件start>end時return;
2: 構建函數在遞歸調用前判斷start是否等於end如果等於不遞歸調用函數;
代碼:
#include <cstdio>
#include <cstring>
#include <cmath>
const int MAXN = 2000;
char str[MAXN];
int nodenum;
class treeNode
{
public:
treeNode *lc;
treeNode *rc;
char c;
};
char findRoot(int start, int en)
{
if(start > en)
return 'N';
int i;
char first = str[start];
for(i=start; i <= en; i++)
{
if(str[i] != first)
return 'F';
}
if(first == '0')
return 'B';
return 'I';
}
void buildtree(int start, int end, treeNode* &root)
{
if(start > end)
{
root = NULL;
return;
}
char rootc;
rootc = findRoot(start, end);
root = new treeNode;
root->c = rootc;
buildtree(start,(start + end + 1) / 2-1, root->lc);
buildtree((start + end) / 2+1, end, root->rc);
}
void lvisit(treeNode *root)
{
if(root == NULL) return;
lvisit(root->lc);
lvisit(root->rc);
printf("%c", root->c);
}
int main()
{
scanf("%d", &nodenum);
scanf(" %s", str);
nodenum = pow(2, nodenum);
treeNode *root = NULL;
buildtree(0, nodenum-1, root);
lvisit(root);
printf("\n");
}
優化:
上面的方法過於麻煩:
其實由於樹是先序建立的,所以如果建立根結點後,遞歸建立左子樹和右子樹前就把節點的值輸出,那麼樹建立結束的同時就輸出樹的先序遍歷序列。如果在建立左右子樹後再輸出節點的值,那麼最後輸出了樹後序遍歷序列。如果在建立左子樹後,建立右子樹前輸出節點的值,那麼最後輸出了樹的後序遍歷序列。
只要符合先序建樹的條件就可以用這種方法。
這樣以來就不用保存樹的節點,也不用在遍歷樹了。減少了時間複雜度和空間複雜度。
代碼如下:
使用處理方法1:
#include <cstdio>
#include <cstring>
#include <cmath>
const int MAXN = 1050;
char str[MAXN];
int nodenum;
char findRoot(int start, int en)
{
while(start<en)
{
if(str[start] != str[en])
return 'F';
start++;
}
if(str[en] == '0')
return 'B';
return 'I';
}
void buildtree(int start, int end)
{
if(start > end)
return;
buildtree(start,(start + end + 1) / 2-1);
buildtree((start + end) / 2+1, end);
printf("%c", findRoot(start, end));
}
int main()
{
scanf("%d", &nodenum);
scanf(" %s", str);
buildtree(0, pow(2, nodenum)-1);
printf("\n");
}
進一步優化:
在求每個節點時,其實不用重新遍歷串了,只需根據該節點左右孩子就可以確定該節點是F還是B還是I。如果左右節點不同,該節點爲F,否則該節點和其左右節點相同。這樣時間複雜度會進一步降低,代碼如下:
#include <cstdio>
#include <cstring>
#include <cmath>
const int MAXN = 1025;
char str[MAXN];
int nodenum;
char buildtree(int start, int end)
{
char a;
if(start != end)
{
a = buildtree(start,(start + end)/2);
if(a!= buildtree((start + end)/2+1, end)) a = 'F';
printf("%c", a);
return a;
}
if(str[start] == '0') a = 'B';
else a = 'I';
printf("%c", a);
return a;
}
int main()
{
scanf("%d", &nodenum);
scanf(" %s", str);
buildtree(0, pow(2, nodenum)-1);
printf("\n");
}