如何在網路中 傳輸二叉樹森林 (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);
}...餘下全文>>