關於森林的建立和相關操作(待完善)
森林的建立一般來說有三種結構:雙親,孩子連結串列,孩子兄弟,其中孩子連結串列是使用最廣泛的,雙親表示主要反映的是一種鄰接關係,孩子連結串列也是如此,因此,這兩種結構主要是應用在圖的儲存中,表示鄰接矩陣和鄰接表,而孩子兄弟表示法則是樹和森林的最佳儲存。
首先是對於他的實現:我在這裡介紹一種比較麻煩的方法,也就是通過建立雙親表示的森林來間接建立二叉連結串列。
1 bool CreateTreeFromFile ( Ptree *PT,char fileName[] ) 2 { 3 FILE *pFile; 4 char str[1000]; //存放讀出的一行字串5 char strTmp[10] = ""; 6 pFile = fopen(fileName,"r"); 7 if ( pFile == NULL ) 8 { 9 cout<<"錯誤,檔案開啟失敗!\n"; 10 return false; 11 } 12 13 while ( fgets(str,1000,pFile ) != NULL ) 14 { 15 TrimLeft(str); 16 if ( str[0] == '\n' )17 { 18 continue; 19 } 20 strncpy(strTmp,str,2); 21 22 if ( strcmp( strTmp,"//" ) == 0 ) 23 { 24 continue; 25 } 26 else 27 { 28 break; 29 } 30 } 31 if ( strcmp( str,"Tree or Forest\n" ) != 0 ) 32 { 33 cout<<"開啟檔案錯誤!"<<endl; 34 fclose(pFile); 35 return false; 36 } 37 while ( fgets(str,1000,pFile ) != 0 ) 38 { 39 TrimLeft(str); 40 if ( str[0] == '\n' ) 41 { 42 continue; 43 } 44 strncpy(strTmp,str,2); 45 46 if ( strcmp( strTmp,"//" ) == 0 ) 47 { 48 continue; 49 } 50 else 51 { 52 break; 53 } 54 } 55 //將各個節點的值放入陣列中 56 char *token = strtok(str," "); 57 int nNum = 0; 58 while ( token != NULL ) 59 { 60 PT->node[nNum].data = *token; 61 PT->node[nNum].parent = -1; //這個很重要,奠定了森林的基礎 62 token = strtok(NULL," "); 63 nNum++; 64 PT->n++; 65 } 66 //為陣列中各個節點的parent指標賦值 67 68 int Np; //儲存父節點下標 69 int Nc; //儲存子節點下標 70 elementType datap; //儲存父節點的值 71 elementType datac; //儲存子節點的值 72 //迴圈取檔案中的父子節點資訊 73 while ( fgets(str,1000,pFile ) != NULL ) 74 { 75 TrimLeft(str); 76 if ( str[0] == '\n' ) 77 { 78 continue; 79 } 80 strncpy(strTmp,str,2); 81 82 if ( strcmp( strTmp,"//" ) == 0 ) 83 { 84 continue; 85 } 86 char *token = strtok(str," "); 87 datap = *token; 88 token = strtok(NULL," "); 89 datac = *token; 90 91 int j = 0; 92 while ( j < PT->n ) 93 { 94 if ( datap == PT->node[j].data ) 95 { 96 Np = j; 97 break; 98 } 99 j++; 100 } 101 j = 0; 102 while ( j < PT->n ) 103 { 104 if ( datac == PT->node[j].data ) 105 { 106 Nc = j; 107 break; 108 } 109 j++; 110 } 111 PT->node[Nc].parent = Np; 112 } 113 fclose(pFile); 114 } 115 bool CreateFNtree( Ptree *PT,FNnode *&FT,int w ) 116 { 117 int v1,v2; 118 FT = new FNnode; 119 FT->data = PT->node[w].data; 120 FT->firstson = NULL; 121 FT->nextbrother = NULL; 122 v1 = firstChild(PT,w); 123 if ( v1 != -1 ) 124 { 125 CreateFNtree(PT,FT->firstson,v1); 126 } 127 v2 = nextSibling(PT,w); 128 if ( v2 != -1 ) 129 { 130 CreateFNtree(PT,FT->nextbrother,v2); 131 } 132 }
當然,為了實現這鐘麻煩的操作,我們還自定了許許多多的函式,這裡就不再一一列舉了,但是呢,博主是為了完成作業才這麼寫的,個人建議呢自己互動式輸入。
下面就是針對二叉連結串列儲存的樹和森林的一些相關操作:
先序和後序遍歷
1 void PreOrder ( FNnode *FT ) 2 { 3 if (FT) 4 { 5 cout<<FT->data<<","; 6 PreOrder(FT->firstson); 7 PreOrder(FT->nextbrother); 8 } 9 } 10 void PostOrder( FNnode *FT ) 11 { 12 if (FT) 13 { 14 PostOrder(FT->firstson); 15 cout<<FT->data<<","; 16 PostOrder(FT->nextbrother); 17 } 18 }
層次遍歷
一般來說層次遍歷都是需要佇列參與的,這個也不例外:
1 void LevelOrder ( FNnode *FT ) 2 { 3 seqQueue Q; 4 initialQueue(&Q); 5 FNnode *temp = FT->nextbrother; 6 inQueue(&Q,FT); 7 while ( !queueEmpty(Q) || temp != NULL ) 8 { 9 while( temp != NULL ) 10 { 11 inQueue(&Q,temp); 12 temp = temp->nextbrother; 13 } 14 queueFront(Q,&temp); 15 cout<<temp->data<<","; 16 outQueue(&Q); 17 if ( temp->firstson != NULL ) 18 { 19 temp = temp->firstson; 20 } 21 else 22 { 23 temp = NULL; 24 } 25 } 26 }
我們通過簡單的分析便可以發現,在原森林中同一層次的節點,在二叉連結串列的實現中都變成了他的nextbrother,而當前節點的firstson便變成了他的下一層次的節點的第一個,因此我們就會發現,我們只要每次將當前節點的所有的nextbrother全部入隊,然後出隊,指標移向firstson然後繼續將nextbrother全部入隊,依次類推,便可以簡單的實現。
求森林的高度
1 int ForestHeight ( FNnode *FT ) 2 { 3 int h1; 4 int h2; 5 if ( FT == NULL ) 6 { 7 return 0; 8 } 9 if ( FT->firstson == NULL ) 10 { 11 return 1; 12 } 13 h1 = ForestHeight(FT->firstson) + 1; 14 h2 = ForestHeight(FT->nextbrother); 15 16 if ( h1 > h2 ) 17 { 18 return h1; 19 } 20 else 21 { 22 return h2; 23 } 24 25 }
這個求森林的高度的函式是用遞迴來實現的,其實他的實現過程與二叉樹的實現比較類似,只是要注意,在實現時nextbrother指向的是和他同一層次的節點而firstson指向的是下一層節點,最後要注意出口。
下面是求森林節點數和葉子節點數的函式,森林節點的數目不再多說,葉子結點要注意,在森林中是葉子結點的不一定在二叉連結串列中是葉子結點,而在二叉連結串列中是葉子結點在森林中一定是葉子結點,其中的區別在於,在森林轉化為二叉連結串列的過程中,一些葉子結點有兄弟節點,因此就變成了連著兄弟節點的分支,但是注意,它是葉子節點,就保證了它是沒有左子樹的,而在二叉連結串列中,左子樹也就是firstson就表示著有子節點,因此,只要是左子樹為空,那麼他就是一個葉子節點。
1 int ForestNode ( FNnode *FT ) 2 { 3 if ( FT == NULL ) 4 { 5 return 0; 6 } 7 else 8 { 9 return ForestNode(FT->firstson) + ForestNode(FT->nextbrother) + 1; 10 } 11 } 12 int LeafNode ( FNnode *FT ) 13 { 14 if ( FT == NULL ) 15 { 16 return 0; 17 } 18 if ( FT->firstson == NULL ) 19 { 20 return 1 + LeafNode(FT->nextbrother); 21 } 22 return LeafNode(FT->firstson) + LeafNode(FT->nextbrother);
求森林的度數:
這裡森林的度數,其實就是求每個節點的度數,然後進行比較,而每個節點的度數,其實就是子節點的個數,而子節點的個數就是firstson的brother的個數,所以程式碼顯而易見
1 int NodeDegree ( FNnode *FT ) 2 { 3 FNnode *temp = FT->firstson; 4 int i = 0; 5 6 while ( temp != NULL ) 7 { 8 temp = temp->nextbrother; 9 i++; 10 } 11 return i; 12 } 13 int ForestDegree ( FNnode *FT,int *h ) 14 { 15 if (FT) 16 { 17 int temp; 18 temp = NodeDegree(FT); 19 if ( temp > *h ) 20 { 21 *h = temp; 22 } 23 ForestDegree(FT->firstson,h); 24 ForestDegree(FT->nextbrother,h); 25 } 26 }
求節點的層次
這裡求節點的層次,我借鑑了二叉樹的經驗,分步來求,傳入H表示層次數,先在firstson中尋找,遞迴的過程H+1,然後再在nextbrother中尋找,遞迴過程不+1,在找到時,就返回當前的h
1 int NodeLevel ( FNnode *FT,elementType x ,int h ) 2 { 3 int l; 4 if ( FT == NULL ) 5 { 6 return 0; 7 } 8 if ( FT->data == x ) 9 { 10 return h; 11 } 12 else 13 { 14 l = NodeLevel(FT->firstson,x,h + 1); 15 if ( l != 0) 16 { 17 return l; 18 } 19 else 20 { 21 l = NodeLevel(FT->nextbrother,x,h); 22 } 23 } 24 }
廣義表輸出:
我們可以簡單的發現,廣義表的輸出順序是和先序輸出相同的,因此,我們在先序輸出的基礎上,對括號進行控制,便可以很簡單的實現演算法
1 void Lists ( FNnode *FT ) 2 { 3 if (FT) 4 { 5 cout<<FT->data; 6 if ( FT->firstson != NULL ) 7 { 8 cout<<"("; 9 Lists(FT->firstson); 10 } 11 if ( FT->nextbrother != NULL ) 12 { 13 Lists(FT->nextbrother); 14 } 15 else 16 { 17 cout<<")"; 18 } 19 } 20 }