1. 程式人生 > >二叉樹按層遍歷 基於圖的寬度優先搜尋的應用 二叉樹的序列化和反序列化

二叉樹按層遍歷 基於圖的寬度優先搜尋的應用 二叉樹的序列化和反序列化

:這其實是圖的寬度優先搜尋的應用。

比如這棵樹按層遍歷的結果為:1 2 3 4 5 6 7 8  也就是一層一層按從左到右的順序列印,這種遍歷的方式是用我們熟悉的佇列來實現的,但是在面試中,往往要求面試者在按層列印的時候連同行號相關的資訊也打印出來。

案例二:

這道題的難點是如何該換行?

其實只需要2個變數,last和nlast,就可以解決這個問題。

假設每一層都做從左到右的寬度優先遍歷,如果發現遍歷到last節點,說明該換行了,換行之後要令last=nlast就可以繼續下一行的列印過程,一直重複這個動作,知道所有的節點都列印完了,那麼整個過程就可以結束了。那麼問題就變成了如何去正確的更新last和nlast,其實只需要讓nlast一直跟蹤記錄寬度優先佇列中最新加入的節點即可,這是因為最新加入的佇列的節點一定是目前發現的下一行的最右節點,所有在當前行打完時,nlast一定下一行所有節點中最右的節點。

接下來還是用例子來說明整個過程,先準備好一個佇列,即為queue,再準備2個變數last和nlast:

開始時先令last等於節點1

然後把節點1放入queue:

從queue中彈出節點1並列印:

然後把節點1的孩子,依次放入到queue中,然後放入節點2時,

令nlast等於節點2,放入queue中:

放入節點3時,令nlast=3,

此時發現,彈出的節點1與last相等,所以該換行了,

並令last等於nlast,也就是節點3,從queue彈出節點2並列印,

然後把節點2的孩子也就是節點4放入queue

同時令nlast等於節點4:

從queue中彈出節點3並列印:

然後把節點3 的孩子放入queue中,放入節點5時:

nlast更新為節點5,放入節點6時nlast更新為節點6:

此時發現彈出的節點3已經是last節點了,所以換行:

並再令last等於nlast,也就是last變成了節點6,last又成功的更新成了下一行最右的節點,

也就是說,nlast始終記錄著剛進入佇列的節點,也就是一直記錄著目前出現的下一層的最右的節點,當某一節點走到last的時候,說明這一層列印完了,需要列印換行,接下來讓last等於nlast,就是正確的找到了下一層的最右的節點,從而每次都能正確的完成換行這個動作。

 

 

接下來講:

我們知道平時在程式裡面構造的二叉樹是存在在記憶體中,邏輯結構可以使用紙和筆畫出來,但是很多時候我們還是希望有用檔案的方式把二叉樹記錄下來,這樣一來,下次想要重構二叉樹時,就可以憑藉在檔案中的記錄將整顆二叉樹還原出來。把二叉樹記錄成檔案的過程叫做二叉樹的序列化過程。也叫二叉樹的持久化過程。把檔案中的記錄還原成二叉樹的過程叫做二叉樹的反序列過程。

二叉樹的序列化方法有很多,常見的有先序,中序,後序遍歷來序列化。還有根據二叉樹按層遍歷的方法來進行序列化:

 

案例三:

二叉樹的序列化可以通過先序,中序,後序的方法來序列化。

這裡介紹先序的方式(中序和後序的方式與先序是同理的):

#表示這個節點為空,節點值不存在。

當然也可以使用其他的特殊字元來表示空的這個節點。

!表示一個值的結束。

也是一個特殊字元,當然也可以使用其他的特殊字元來表示一個值的結束。

比如:現在看到的,根據先序遍歷的過程,首先遇到的就是頭節點12,

所以str變為:

然後遇到節點3,則在末尾加上

變為:

然後遇到3的左孩子和右孩子都是空節點,所以繼續在str的後面加上 ,變成:

,然後遍歷12的右孩子,也是空節點,所以str最終為:

.

為什麼在一個節點值的後面要加上一個!或者其他的特殊字元來表示一個值的結束呢?

因為如果不標記一個值的結束,最後產生的結果可能會有歧義。

比如:

這2棵樹雖然是不同的樹,但是如果一個值沒有結束符!號的話,

這2顆樹的先序序列化的結果都將是123###:

 

 

接下來介紹如何通過先序遍歷序列化的結果字串str來重構出原來的二叉樹的過程,也就是反序列化的過程。把結果字串str先變成字串型別的陣列,假設記為values,陣列代表一顆二叉樹先序遍歷的節點順序,比如:,生成的vlaues為:

,然後按照先序遍歷的順序建立出整棵樹:

 

首先遇到字串1,2,生成的節點值為的節點:

然後用剩下的字串建立節點12的左子樹,接下來遇到字串3,生成節點值為的節點,它是節點12的左孩子:

然後用剩下的字串建立節點3的左子樹,遇到#,生成空節點,它是節點3的左孩子:

該節點為空,所以這個節點沒有後續建立子樹的過程了,則回到節點3,用剩下的字串建立節點3的右子樹,接下來又遇到字串#,生成空節點,它是節點3的右孩子:

該節點為空,所以這個節點又沒有後續建立子樹的過程,那麼回到節點3,再回到節點1,用剩下的字串建立節點1 的右子樹,又遇到了字串#,生成空節點,

它是節點1 的右孩子,該節點為空,字串也用完了,那麼整個反序列化的過程結束。

那麼可以看到我們用什麼樣的遍歷方式序列化,就選擇用什麼樣的方式反序列化。

程式碼實現也比較容易.

一棵樹序列化的結果在下次反序列化的時候一定不會發生歧義。一定能夠重構出結構與之前完全一樣的樹。

中序,後序的反序列化過程同理,這裡不再詳述。

 

二叉樹還可以用按層的方式進行序列化,也就是按佇列依次按層遍歷所有的二叉樹的節點,遇到空,就在字串後面新增特殊字元表示空節點,並新增特殊字元表示序列化這個空節點的過程結束了,也就是剛才提到的!或者其他的特殊字元都好。遇到飛控節點就在後面新增節點的值表示該節點序列化的結果,並且也新增特殊字元,表示這個節點的序列化已經完畢了。反序列化過程同理。根據字串陣列並按照層遍歷的方式建立出整棵樹即可。

 

案例三:

旋轉詞是指一個字串左邊任意長度的子串,挪到右邊都叫做這個字串的旋轉詞,比如字串1234它的旋轉詞包括1234,2341,3412,4123.

如果str1的長度為n,本題最優解可以做到時間複雜度為O(N)

那麼它是怎麼做的呢?首先判斷str1與str2長度是否相等,如果不等,可以直接返回flase,

因為2個字串互為旋轉詞的話,它的長度應該是相等的,如果str1與str2是相等的話就繼續進行後續的判斷,首先生成str1+str1這樣大的字串,然後在大字串中尋找它是否包含str2這個字串,如果包含str2這個字串,說明str1和str2是互為旋轉詞的。如果這個大字串中不包含這個str2的話,說明str1和str2之間不是互為旋轉詞的。那麼這個尋找的過程實際上就是KMP演算法的主要內容。

用KMP演算法來做這個匹配即可。

那麼這麼做為什麼是對的?

舉例說明:

假設:,那麼生成的大字串就是

,我們需要注意的是在這個大字串中,

,1234是str1的旋轉詞,,2341是這個str1的旋轉詞,

3412是這個str1的旋轉詞,4123也是這個str1的旋轉詞。也就是說在str1+str1中:

str1+str1這個大字串窮舉了所有的旋轉詞,如果str2的瘡毒等於n,在str1+str1中能夠找到str2的話,說明str2一定是str1的旋轉詞。

 

案例四:

給定一個字串str,String代表一個句子,將句子中所有的單詞逆序。

這個實際上代表了字串問題在面試中常規的情況,不包含高深的演算法,僅僅是考察程式碼實現能力的題目。

它的做法是之前實現一個。。。

未完待續。。。。。。