如何在網路中 傳輸二叉樹森林 (C++原始碼實現),二叉樹森林

來源:互聯網
上載者:User

如何在網路中 傳輸二叉樹森林 (C++原始碼實現),二叉樹森林

 

前些日子有朋友遇到這個問題來問我,我覺得有點意思,便實現了代碼,寫篇文章做個總結,與網友分享。

 

需求:

實現兩個API,在用戶端:傳入一個二叉樹的根結點指標,輸出可以在網路中傳輸的ASCII串。在伺服器端:根據傳入的ASCII串來解析產生一個二叉樹,返回二叉樹的根結點指標。

 

思路:

看到這個問題,首先想到的是二叉樹補全法,將這課二叉樹補全,變成一顆完全二叉樹,再使用數組進行儲存,寫入檔案中。這樣做需要在節點中增加一個屬性,標記是否為補全的節點。這種方法不太合理,因為使用了補全操作,對於一顆很不規則的二叉樹,將會佔用非常大的儲存空間,並且修改了二叉樹的屬性。

 

我們從而二叉樹的表示下手,如何表示一顆二叉樹?除了把各結點用指標連起來,直接表示出二叉樹的結構,還有其他的方式能表示出二叉樹的方式嗎?

回顧二叉樹的定義,二叉樹是一個嵌套結構,我們可以用集合來表示嵌套結構。

 

例如:


可用三元組來表示:[a b c] 我們用先序遍曆的方式來表示。

 

我們規定保證[]之內至少有三個元素,即空缺的孩子結點用特殊符號表示。

例如:[a b $] 表示一顆根結點元素為a,左孩子結點元素為b,無右孩子結點的二叉樹。

 

單個結點的二叉樹,直接用空格與其他二叉樹隔開即可,不用[]來包含

 

例如:


 [ a b [ c [ d f g ] [ e h i ] ] ]

 

這樣,只要把二叉樹轉換成如此規則的字串,就可以很方便的在網路中傳輸了。

在伺服器端再把獲得的格式字串解析成二叉樹即可。

 

一個二叉樹森林:

例如:

A  [A [ B C [ D E $ ] ] $ ]  [ A $ B ]

樹與樹之間用空格隔開。 單結點的樹不用括弧。

 

思路很簡單,但實現起來不是那麼容易的。下面我們就來編碼實現。

 

第一部分:


從二叉樹森林轉換成格式字串

為了寫一個更加通用的函數,我們實現二叉樹森林到字串的轉化。

函數介面為:

int  BinaryTreesToFormateString(const vector<TreeNode*>&binaryTreesVec, string& formateStrRet)

參數binaryTreesVec為二叉樹森林中各樹的根結點指標。

參數formateStrRet為返回的格式字串。

 

我們對二叉樹進行先序遍曆即可,由於二叉樹的深度可能很大,用遞迴遍曆效率較低,故我們用迭代的方式進行先序遍曆。

 

先序遍曆的過程

1、 從根結點開始,沿著左連結方向遍曆,直至遇到左連結為空白的結點(把沿途遇到的父結點地址壓入棧中)。

2、 改變遍曆方向,重複1過程,直到遇到左右連結都為空白的葉結點。

3、 彈棧,取出彈出棧的父結點的右連結,從1過程開始重複。

 

 

但是我們如何在先序遍曆中把二叉樹的邊界括弧[ ]添加進去呢?

我們觀察發現,每個父結點之前都加括弧[,因為是先序遍曆,父結點是這顆子樹第一個被訪問到的。但什麼時候加右括弧]呢?

在訪問到右葉結點時。但訪問到右結點加的這個右括弧,只是其與其父結點組成的最小的二叉樹的右邊界,還有其上層二叉樹的右邊界,上上層二叉樹的右邊界……

 



所謂“打包操作”,就是把初步產生的格式字串,加上右括弧],比如,剛才我們初步產生了 [A [ B C [ D E $  打包操作後變為:[ A [ B C [ D E $ ] ]

(從後往前掃,發現有三個元素,且之前是左括弧[,故正好這三個元素可以組成一個子二叉樹,在其後添加右括弧]。 加完右括弧之後,我們再檢查是否還有應該添加的右括弧。

我們把配對的子二叉樹看作是一個結點,即把[ D E $ ]看作是一個結點,這樣B C [ D E $ ] 為三個元素,可以組成一個子樹,在其後添加右括弧,[ B C [ D E $ ] ]。 再把這樣一顆子樹看作一個結點,其前(包括它自己)共有2個結點,無法組成一顆二叉樹,故無需再添加右括弧了。 打包操作結束。)


不知道我有沒有說清楚整個過程,還是不太明白的朋友,可在下面留言。^_^

 

若遍曆指標遍曆到某結點的右連結為NULL,那麼就開始打包操作。(因為可以確定起碼一顆小子樹遍曆完畢。)

 

【原始碼】

//把二叉樹森林轉換為格式字串 ( 注意我們的格式是前序走訪。 )int  BinaryTreesToFormateString(const vector<TreeNode*>& binaryTreesVec, string& formateStrRet){TreeNode* workPtr = NULL ;stack<TreeNode*> nodeNodeStack ;int nullLinkType = 0 ; //空連結類型,0表示workPtr=NULL造成的空連結,1表示是左空連結,2表示是右空連結//二叉子樹格式串容器(儲存各步驟逐漸產生的二叉樹,最後會整合成一棵整二叉樹)vector<string> biSubTreeStrVec ; //因為要打包操作,所以不能直接把值放到formateStrRet中for (vector<TreeNode*>::const_iterator i = binaryTreesVec.begin();          i != binaryTreesVec.end(); ++i)    {workPtr = *i ;nullLinkType = 0 ;//workPtr是單結點的二叉樹if (workPtr->lchild == NULL && workPtr->rchild == NULL){formateStrRet += workPtr->value ;formateStrRet += " " ;continue ;}while(workPtr != NULL || !nodeNodeStack.empty()){if(workPtr != NULL){//workPtr是左孩子葉結點(在左連結上的葉結點)if (nullLinkType == 1 && workPtr->lchild == NULL && workPtr->rchild == NULL){//把結點值列印到vector<string>中biSubTreeStrVec.push_back(workPtr->value) ;workPtr = NULL ;nullLinkType = 0 ;}//workPtr是右孩子葉結點(在右連結上的葉結點)else if (nullLinkType == 2 && workPtr->lchild == NULL && workPtr->rchild == NULL){biSubTreeStrVec.push_back(workPtr->value) ;Pack(biSubTreeStrVec) ; //打包操作workPtr = NULL ;nullLinkType = 0 ;}//其它情況--即workPtr為父結點else {biSubTreeStrVec.push_back("[") ;biSubTreeStrVec.push_back(workPtr->value) ;nodeNodeStack.push(workPtr) ;workPtr = workPtr->lchild ;nullLinkType = 1 ; //標記workPtr指向的為左連結}}else //workPtr == NULL{//workPtr是左空連結if (nullLinkType == 1){biSubTreeStrVec.push_back(NULLNODEVAL) ;////列印空標記}//workPtr是右空連結else if (nullLinkType == 2){biSubTreeStrVec.push_back(NULLNODEVAL) ;Pack(biSubTreeStrVec) ; //打包操作}workPtr = nodeNodeStack.top() ;nodeNodeStack.pop() ;//彈出棧頂元素 workPtr = workPtr->rchild ; //改變處理結點的方向 nullLinkType = 2 ; //標記workPtr指向的為右連結}}//while//把本次產生的二叉樹字串列印到結果中for (vector<string>::const_iterator j = biSubTreeStrVec.begin();              j != biSubTreeStrVec.end(); ++j){formateStrRet += *j ;formateStrRet += " " ;}biSubTreeStrVec.clear() ;}//forreturn 0 ;}//打包操作--把biSubTreeStrVec中存的結點打包成一棵子二叉樹就盡量打包成一棵子樹int Pack(vector<string>& biSubTreeStrVec){do{int cnt = 0 ;string subBiTreeFormateStr ;for (vector<string>::const_iterator iVec = biSubTreeStrVec.end()-1;     *iVec != "[" ; iVec--){cnt++ ;}if (cnt != 3)break ;for ( ; iVec != biSubTreeStrVec.end(); iVec++){subBiTreeFormateStr += *iVec ;subBiTreeFormateStr += " " ;}subBiTreeFormateStr += "] " ;for (int i = 0 ; i < 4; i++){biSubTreeStrVec.pop_back() ;}biSubTreeStrVec.push_back(subBiTreeFormateStr) ;if (biSubTreeStrVec.size() == 1)break ;} while (1) ;return 0 ;}


 

第二部分:


解析格式字串,產生二叉樹森林。

函數介面為

int  FormateStringToBinaryTrees(constvector<string>& wordsVec, vector<TreeNode*>& treesVecRet)

參數wordsVec:把格式字串分割成一個個單詞的集合。(此處最好還是用string原字串做參數,當時寫代碼時考慮不周......)

參數treesVecRet:為返回的二叉樹森林的根結點集合。

 

解析字串的過程:

我們設定兩個棧,一個存括弧,稱為markStack。一個存二叉樹的結點地址,稱為nodePtrStack。

 

若當前掃描到的字元為左括弧[,則把左括弧[壓入markStack中。

若當前掃描到的字元為資料,則構造一個結點,把結點地址壓入nodePtrStack中。

若當前掃描到的字元為右括弧],則表示一顆子樹的終結,故彈nodePtrStac棧來構造這顆子樹。把nodePtrStac棧彈三次,構造成一顆子樹。然後把新構造的子樹根結點壓棧。(把這個子樹看作是一顆大子樹的一個結點) 然後再彈markStack棧把這顆子樹的左括弧[彈出。

若nodePtrStack為空白時,則一顆完整的二叉樹產生完畢。

 



【原始碼】

//把格式字串轉換成二叉樹森林//此程式只能檢測到一般的格式錯誤,對於其他的一些錯誤格式不能識別,健壯性較差,以後若用到此代碼,需完善。int FormateStringToBinaryTrees(const vector<string>& wordsVec, vector<TreeNode*>& treesVecRet){    stack<string> markStack ;    stack<TreeNode*> nodePtrStack ;    string markStackPopTmp ;    //標記棧彈棧操作的廢棄值    TreeNode* rootNodePtr = NULL ;   //根結點指標    TreeNode* newNodePtr = NULL ;    //新產生結點指標    TreeNode* lchildPtr = NULL ;     //左孩子指標    TreeNode* fatherPtr = NULL ;     //父指標    TreeNode* rchildPtr = NULL ;     //右孩子指標    //我們遵循的原則是先壓markStack棧,再壓nodePtrStack,若發現不同步,則填充空值。    for (vector<string>::const_iterator i = wordsVec.begin();          i != wordsVec.end(); ++i)    {        if (*i == "[")        {            markStack.push(*i) ;        }        else if (*i == "]") //遇到"]"表示一顆子樹的終結,故彈棧來構造這顆子樹        {            //構造子樹,並把構造好的子樹根結點壓棧            if (nodePtrStack.size() >= 3)            {                rchildPtr = nodePtrStack.top() ;                nodePtrStack.pop() ;                lchildPtr = nodePtrStack.top() ;                nodePtrStack.pop() ;                fatherPtr = nodePtrStack.top() ;                nodePtrStack.pop() ;            }            else            {   //報告出錯                cout << " # " << fatherPtr->value << " # " ;                cout << "[] Formate Error!\n" << endl ;                return -1 ;            }            fatherPtr->lchild = lchildPtr ;            fatherPtr->rchild = rchildPtr ;             if (markStack.top() == "[")            {                markStack.pop() ;                if (!nodePtrStack.empty())                {                    nodePtrStack.push(fatherPtr) ;  //把新構造的子樹根結點壓棧                }                else //若結點指標棧為空白,則表示此顆二叉樹產生完畢,把根結點fatherPtr儲存在treesVecRet                {                    treesVecRet.push_back(fatherPtr) ;                }            }            else             {    //報錯("["與"]"不配對)                cout << " # " << fatherPtr->value << " # " ;                cout << "[] Pair Error!\n" << endl ;                return -1 ;            }            //彈棧到最後發現,符號棧markStack為空白,但結點棧中還有值,格式錯誤            if (markStack.empty() && !nodePtrStack.empty())            {                cout << " # " << fatherPtr->value << " # " ;                cout << "[] Formate Error!\n" << endl ;                return -1 ;            }        }//if(*i == "]")        else //當前值為結點中的索引值        {            //構造新結點,並把其結點指標壓棧            if (*i == NULLNODEVAL) //若是空標記,則此為空白結點NULL                newNodePtr = NULL ;            else                newNodePtr = new TreeNode(*i) ;            nodePtrStack.push(newNodePtr) ;            if (markStack.empty()) //若向結點指標棧壓棧時,mark棧為空白,說明此為單節點樹。            {                treesVecRet.push_back(nodePtrStack.top()) ;                nodePtrStack.pop() ;            }        }    }//for    //若到最後兩個棧中還有不為空白的,則說明輸入格式有誤    if (!markStack.empty() || !nodePtrStack.empty())    {        cout << "Formate Error!\n" << endl ;        return -1 ;    }    return 0 ;}

第三部分:測試

#include <iostream>#include <fstream>#include <algorithm>#include <stack>#include <string>#include <sstream>#include <vector>#define NULLNODEVAL  "$"  //定義空結點值為$(不要和正常結點衝突)using namespace std ;//二叉樹的結點class TreeNode{public:    string value ;    TreeNode* lchild ;    TreeNode* rchild ;    TreeNode(string f_value, TreeNode* f_lchild = NULL, TreeNode* f_rchild = NULL)     {        value = f_value;        lchild = f_lchild;        rchild = f_rchild ;    }} ;int ExtractWordsFromFile(ifstream& inputFile, vector<string>& wordsVecRet) ;int FormateStringToBinaryTrees(const vector<string>& wordsVec, vector<TreeNode*>& treesVecRet) ;int BinaryTreesToFormateString(const vector<TreeNode*>& binaryTreesVec, string& formateStrRet) ;int Pack(vector<string>& biSubTreeStrVec) ;void InOrderTraverse(TreeNode* T) ;void PreOrderTraverse(TreeNode* T) ;void TestPack(void) ;

int main(void){    ifstream dataFile(".\\test.txt") ;    if (!dataFile)    {        cout << "Can't open the data file!" << "\n";        exit(0) ;    }        //分割字串    vector<string> wordsVec ;    ExtractWordsFromFile(dataFile, wordsVec) ;    //產生二叉樹森林    vector<TreeNode*> binaryTreesVec;    FormateStringToBinaryTrees(wordsVec, binaryTreesVec) ;    cout << "格式字串產生的二叉樹森林:" << endl ;    //檢測結果--列印出此二叉樹森林    for (vector<TreeNode*>::const_iterator i = binaryTreesVec.begin();          i != binaryTreesVec.end(); ++i)    {cout << "先序遍曆:" ;        PreOrderTraverse(*i) ;        cout << endl ;cout << "中序遍曆:" ;        InOrderTraverse(*i) ;        cout << endl ;cout << "--------" << endl ;    }    //由二叉樹森林產生格式字串    string biTreeForestStr ;    BinaryTreesToFormateString(binaryTreesVec, biTreeForestStr) ;    cout << "二叉樹森林產生的格式字串:" << endl ;    cout << biTreeForestStr << endl ;    return 0 ;}//從檔案中提取字串int ExtractWordsFromFile(ifstream& inputFile, vector<string>& wordsVecRet){    string aLineString ;    string wordTmp ;    while(getline(inputFile, aLineString))     {        stringstream strin(aLineString);        while (strin >> wordTmp)        {            wordsVecRet.push_back(wordTmp) ;        }    }    return 0 ;}//中序遍曆整個二叉樹void InOrderTraverse(TreeNode* T){    if(T != NULL)    {        InOrderTraverse(T->lchild) ;        cout << T->value << "   ";        InOrderTraverse(T->rchild) ;    } }//前序走訪整個二叉樹void PreOrderTraverse(TreeNode* T){    if(T == NULL)//樹為空白     {        return ;    }    else    {        cout << T->value << "   ";        PreOrderTraverse(T->lchild) ;        PreOrderTraverse(T->rchild) ;    } }

 

test.txt中資料為:

A  [A [ B C [ D E $ ] ] $ ]  [ A $ B ]

 



//以上程式碼群組合在一起可以編譯運行。

//寫的有些複雜,若大家有更好的做法,請不吝賜教。







二叉樹轉森林的的代碼(C/C++)

這個其實我有,就是在書上,不想敲到電腦上!
 
用c語言寫二叉樹,原始碼

先序輸入 如 abc##d##e## (#表示空)
輸出 cbdae

#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define OVERFLOW -2
#include "cstdlib"
#include "malloc.h"
typedef int Status;
typedef char TElemType;
#include <stdio.h>
#include <iostream>
using namespace std;

typedef struct BiTNode { // 結點結構
TElemType data;
struct BiTNode *lchild, *rchild; // 左右孩子指標
} BiTNode, *BiTree;

Status CreateBiTree(BiTree &T){//構造二叉樹
TElemType ch;
scanf("%c",&ch);
if(ch=='#') T=NULL;
else{
if(!(T=(BiTree)malloc(sizeof(BiTNode)))) exit(OVERFLOW);
T->data=ch;
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
}
return OK;
}//CreateBiTree

Status Visit(char Data)
{
printf("%c",Data);
return OK;
}

Status InOrderTraval(BiTree pTree)
{
if(pTree)
{
if(InOrderTraval(pTree->lchild))
{
if(Visit(pTree->data))
{
if(InOrderTraval(pTree->rchild))
{
return OK;
}
}
return ERROR;
}
return ERROR;
}
else
{
return OK;
}
}

Status v(char a)
{
printf("%c",a);
return OK;
}
void main()
{
BiTree A,b;
printf("先序輸入,空用 # 表示:\n");
CreateBiTree(A);
b=A;
printf("中序輸出:\n");
InOrderTraval(b);
}...餘下全文>>
 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.