1. 程式人生 > >【昊昊帶你學】基本資料結構(下)

【昊昊帶你學】基本資料結構(下)

連結串列

前面大家用隊、棧的時候用的都是陣列。陣列挺好用的,不過蛋疼的是,數組裡面要增、刪資料就蛋疼了。不過咱們有連結串列,下面我來醜陋地給大家畫個連結串列~

哈哈,說了畫給大家,就果斷要親手畫,果然很醜陋的說 O_o

我來簡單給大家先說一下連結串列的每個節點都是怎麼個結構~看中間那一坨,這就是一個節點,prev部分指向它的前驅,而next則指向它的後繼。這樣,該節點就成功得與前後向連結了。而中間那個data(不是dota喲~)就是這個節點的資料部分,當然不一定只有一個,畫成一個比較清晰而已。大家看到最右邊節點的後繼中是個“/”,這代表著指向null,即空。碰到這種情況,就是說,這已經是連結串列的一端了。當然頭和尾都有可能。最左邊那個head()就是這個連結串列的頭指標,頭指標可以讓你能夠找到連結串列中的第一個元素,大家應該明白,連結串列不像陣列,可以通過下標直接找到節點(優化除外~),這時候,你要用連結串列中某一個節點的時候就得從頭指標往後遍歷。我們可以看到,連結串列比陣列好在它能夠很方便的在某處增加節點或刪除節點,但同時,陣列對於每個元素的定位又更加快速~兩個資料結構都各有千秋,大家要結合實際問題靈活選用。下面來將一些具體的函式:

LIST-SEARCH(L,k)

         X ← head[L]

While x ≠ NIL and key[x] ≠ k

                   Do  x ← next[x]

Return x

PS: 這是在連結串列中搜索某結點的一個方法。L是一個連結串列,k是我們要搜尋的KEY,我們先把L的頭指標給x,然後當x還指向連結串列中節點並且沒有發現KEY值的時候做 x ← next[x]這件事,那這件事是什麼意思呢?有的孩紙應該看明白了,就是將x 指向當前節點的後繼,從而達到連結串列遍歷的目的。

LIST-INSERT(L,x)

next[x] ← head[L]

       If  head[L] ≠ NIL

                   Then prev[head[L]] ← x

       Head[L] ← x

       Prev[x] ← NIL

PS:這是在連結串列頭插入節點的方法。L是一個連結串列,x是一個節點(這個時候x還是孤零零的一個~)。我們先讓x的後繼指向本來的頭結點。如果頭結點不為空,就將它的前驅指向x(之前前驅為空)。此時,頭指標指向x。最後將x的前驅賦為空。大家可以自己在草稿紙上畫一下上述過程。如果是在連結串列中插入結點,類似,希望童鞋們自行考慮該如何操作,只要注意四個地方的指向(可能會有點繞,不過結構是很對稱的):前驅,後繼,前驅的後繼,後繼的前驅。搞清楚這四個的關係,其實也是連結串列最核心的東西~

LIST-DELETE(L,x)

       If  prev[x] ≠ NIL

                   Then next[prev[x]] ← next[x]

                   Else head[L] ← next[x]

       If  next[x] ≠ NIL

                   Then prev[next[x]] ← prev[x]

PS: 這是在連結串列中刪除一個節點。L是一個連結串列,x是一個節點。首先如果x的前驅不為空,那麼就將前驅的後繼指向x本來的後繼,否則x是頭結點,那麼直接將頭指標指向x的後繼。接下來,如果後繼不為空則將後繼的前驅指向x本來的前驅,當然如果是空的話,你不用理會。刪除操作的核心,還是我們剛剛提到的四點:前驅,後繼,前驅的後繼,後繼的前驅。大家看一眼下圖,這是在連結串列中刪除節點的情況。①所對應的就是第一個if的操作,它將前驅(prev[x])的後繼(next[prev[x]] == next[x] )指向了原本的後繼(next[x])。第二步類似。

 

對於連結串列還有一種哨兵的優化,如果大家有興趣可以自行百度谷歌,也可以聯絡我來討論哈。

樹(二叉樹)

連結串列整完了,咱們來說說樹,這裡我們重點講二叉樹。樹的每個節點結構與連結串列挺類似的,只是原來的前驅、後繼變為了父親、左右孩子。

 

在網上給大家粘了個圖下來,L就是左孩子,R就是右孩子,這個圖沒有把父親的域畫出來,大家應該可以明白是什麼意思。我們以後再畫二叉樹就不會再帶著左右兩個域,請大家注意。最頂端的節點叫做根,當然也有單獨把根拿出來的情況,個人感覺看情況吧。而最底層的節點(沒有孩子節點)的叫做葉子節點。

有了連結串列的基礎,我相信這裡的增、刪、查詢大家應該可以自行完成,就算不會我估計看程式碼也可以看得懂了,我就不再這矯情地一點一點分析了。直接來給大家講一講二叉樹的一種建樹方法,以及依靠遞迴來實現的三序遍歷。

首先來提一下三序遍歷:

先序遍歷:首先訪問根結點然後遍歷左子樹,最後遍歷右子樹。什麼意思呢?遍歷一棵樹我們可以把問題分解開。遍歷一整棵樹,我們可以先訪問根節點(這顯然可以做到),然後遍歷左子樹,最後遍歷右子樹。可以看到我們把原來的問題分劃成了三個子問題:第一個訪問根節點,我們已經解決。後兩個問題是,分別遍歷左右子樹。 (場外觀眾小賢:咦?這不是原來的問題?) bingo!我們把原來的問題分成了三個規模更小的問題,一個已經解決,另兩個跟原來的問題一樣。這就是傳說中的分治演算法,分而治之!對於遍歷左子樹的問題,我們同樣可以先訪問左子樹的根,再遍歷下面的左右子樹………………分劃啊分劃~直到分化到某一個葉節點的時候,訪問該節點,無左右孩子,退回上一層。建議童鞋們自己畫一個三層左右的二叉樹,自己按這個過程體會一下分治的思想,分治的思想搞清楚之後,進一步去理解遞迴就會輕鬆的多了。下面我給大家粘一段我暑假作業的程式碼。是java的,我就不用虛擬碼了。T T《 導論》上沒有,不敢瞎寫。

static public void preOrder(Node node)

       {

              if (node != null)

              {

                     node.print();

                     preOrder(node.leftChild);

                     preOrder(node.rightChild);

              }

              else

              {

                     System.out.print('#');

              }

       }

PS:我給大家解讀一下這段程式碼,不是很難。preOrder就是先序遍歷。Node node說的是傳進來一個Node型別的node,它是一個節點,(Node類有leftChild 和 rightChild兩個域,還有data。)這就是我們要遍歷子樹的根節點。如果節點不為空的話我們先打印出node本身,然後我們分別先序遍歷左右孩子,如果為空看具體情況操作,我編的這段程式碼是用#來表示NIL。這裡遞迴的理解的確是個難點,大家靜下心來手動走幾遍過程就會改善許多。(艾瑪累死了,蘇海~~~~~~過來給爺揉揉肩)

相信有了先序的鋪墊,中序和後序就會輕鬆很多。

中序遍歷:

    中序遍歷首先遍歷左子樹,然後訪問根結點,最後遍歷右子樹。

後序遍歷:

後序遍歷首先遍歷左子樹,然後遍歷右子樹,最後遍歷訪問根結點。

在遞迴實現的情況下,這三個幾乎沒有差別,就是在上面if語句中顛倒一下幾個語句的位置。而在用棧來實現非遞迴的三序遍歷時就會有較大的差別了~至少本菜自己編的時候這三種順序差別很大。

講如何去建立一棵樹:建樹的方法有很多,我只講一下我自己最常用的一種方法。輸入的資料是按照先序給出的二叉樹,‘#’代表葉節點。比如ABC##DE#G##F###就會建立出如下的一棵二叉樹:

 

下面給出java的一段程式碼:

static Node createTree()

         {

                   Node node = null;

                   if ( ch[i] != '#')

                   {

                            node = new Node();

                            node.data = ch[i];

                            i++;

                            node.leftChild = createTree();

                            node.rightChild = createTree();

                   }

                   else

                   {

                            i++;

                   }

                   return node;

         }

PS: i是Node類的一個靜態變數,記錄建樹的進度。首先建立一個空結點,如果表示式中當前值不是#,那麼給node賦值,再分別給左右孩子建樹。這是按照先序來的。跟剛剛先序遍歷幾乎一樣,大家可以對照看看,不過多敖述。

下面給出的這個連結中,是一棵二叉排序樹,它的建樹規則就是左子樹中的數<=根中的數<=右子樹中的數