1. 程式人生 > >資料結構 2 字串 陣列、二叉樹以及二叉樹的遍歷

資料結構 2 字串 陣列、二叉樹以及二叉樹的遍歷

上一節的學習中,我們已經結合JAVA 本身,將線性表所包含的順序表、連結串列、棧、佇列等資料結構通通學習了一番,並且將這些資料結構的一些基本操作。比如 - add() - remove() - pop() 等等方法都進行了列舉,通過這些,我們將對線性表有了一個直接的認識。這節將學習有關字串、廣義表等內容。 ## 字串 怎麼理解字串呢,想必大家都知道。我們在學習JAVA 8大基本型別的時候就有學習到字串,但是JAVA 裡面的字串是一個`物件` 它是一個引用型別,並且初始化後,是一個`不可變物件` ```java String str = "hello"; ``` 很簡單,我們通過一句程式碼,就可以將一個字串常量化出來,為什麼說常量化呢,這裡我們沒有采用new 的方式,所以這個變數其實是儲存在了常量池裡面。而不是堆裡面。 從嚴格意義來說,字串其實也是符合 `線性結構` 的,因為我們都知道,字串都是由基本型別的字元 `char` 連線起來組成的,他們存在`一對一`的關係。只不過這個線性表裡面存的全部都是字元型別罷了。 ```java private final char value[]; public String() { this.value = "".value; } ``` 通過直接new 的方式,我們可以發現,建構函式裡面其實是用一個字元陣列來進行儲存字串的,我們這裡的陣列其實是一個靜態陣列,它不像List 一樣,可以自由的變化長度,初始化了。它就確定死了,多長就多長。 ### 字串的儲存方式 - 普通陣列定長儲存(String) - 堆分配記憶體:動態陣列儲存(StringBuffer) - 連結串列儲存 ```java public StringBuffer(String str) { super(str.length() + 16); append(str); } ------------------ char[] value; AbstractStringBuilder(int capacity) { value = new char[capacity]; } public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; } ``` `ensureCapacityInternal` 就是進行擴容的操作。這裡的Stringbuffer 就是在堆上面進行的操作。可以像線性表一樣。進行擴容操作。 ### BF 演算法 BF演算法是一種最普通的演算法,在指定的字串中匹配子串,匹配到則返回子串開始的下標。這個演算法在String 當中最具有代表性的就是`indexOf(String str)` 這個方法了。 ```java static int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex) { if (fromIndex >= sourceCount) { return (targetCount == 0 ? sourceCount : -1); } if (fromIndex < 0) { fromIndex = 0; } if (targetCount == 0) { return fromIndex; } char first = target[targetOffset]; int max = sourceOffset + (sourceCount - targetCount); for (int i = sourceOffset + fromIndex; i <= max; i++) { /* Look for first character. */ if (source[i] != first) { while (++i <= max && source[i] != first); } /* Found first character, now look at the rest of v2 */ if (i <= max) { int j = i + 1; int end = j + targetCount - 1; for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++); if (j == end) { /* Found whole string. */ return i - sourceOffset; } } } return -1; } ``` IndexOf(str) 相比於BF(暴力)還是有很多優化的地方,比如首先會判斷首字元是否相同,若首字元都不相同,那直接不用說了。跳過了一些重複的地方。 ![image.png](https://file.chaobei.xyz/blogs/image_1583549300896.png_imagess) ![image.png](https://file.chaobei.xyz/blogs/image_1583549324305.png_imagess) n 表示子串的長度 m表示源字串的長度 這個演算法最優的時候,若直接在首位碰到,則時間複雜度 `O(n)` 最不幸的要是最後一位才可以碰到,則必須要迴圈 m*n 次 則時間複雜度 `O(M*N)` ## 陣列 陣列我在之前就介紹過,一種最基本的儲存結構。線上性表的幾種分類中。順序表底層就是使用陣列來實現的。 從本質上說,陣列作為一種儲存結構,而不是一種基本型別,可以理解為容納基本型別的一個容器。 常見的一維陣列,其實是很簡單的, ![image](https://file.chaobei.xyz/blogs/image_1583475589373.png_imagess) 指定一個一維陣列,指定大小後,它的大小是不可再變的。並且陣列元素的型別全部是統一的。 ## 結構樹 資料結構的樹儲存結構中。常用語儲存`一對多`的資料,樹結構當中,常用的還是二叉樹、以及紅黑樹等,我這裡一一介紹。 ### 二叉樹 二叉樹是結構樹裡面最有名也是使用最多的樹結構資料結構,檢查一個樹是否滿足二叉樹,從兩個方面下手 1. 每個節點下最多掛兩個子節點 只能是0、1、2 2. 樹的本身是有序的 如何來確定這個樹是否是一個有序樹還是無序樹呢? - 按照一定的規則將節點左右分佈,那就是有序樹,反之則是無序樹 ![image](https://file.chaobei.xyz/blogs/image_1583569768788.png_imagess) ### 二叉樹規律 按照前人的總結,我們可以得出以下結論。 - 一個深度為K 的二叉樹,最多包含節點數 `2的k次方-1` - 二叉樹指定n 層級所包含的節點數為 `2的n-1次方` 如果將二叉樹在細分的話,又可以分為滿二叉樹與完全二叉樹。 ### 滿二叉樹 一聽這個名字,就可以理解到。它是滿(飽和)的,所以,來用畫圖表示一下 ![image.png](https://file.chaobei.xyz/blogs/image_1583571530046.png_imagess) 如果二叉樹除了葉子節點以外,所有的節點的度都是2(有兩個子元素),那麼它就是滿二叉樹 ### 完全二叉樹 如果二叉樹除去葉子節點是一個滿二叉樹,並且葉子節點從左向右依次排列。那麼這就是一個完全二叉樹。 ![image.png](https://file.chaobei.xyz/blogs/image_1583571439323.png_imagess) 但是,這樣排列的卻不能成為是完全二叉樹。 ![image.png](https://file.chaobei.xyz/blogs/image_1583571749912.png_imagess) ### 完全二叉樹性質 完全二叉樹不但具有普通二叉樹的性質,並且還有自己的一些獨特性質。例如: - 具有n個節點的完全二叉樹的深度是 `⌊log2n⌋+1。` ## 二叉樹的儲存方式 對於滿二叉樹以及完全二叉樹,完全可以採用陣列的方式來儲存,因為陣列就是用來儲存對於一對一線性規律的。所以完全合適 ![image.png](https://file.chaobei.xyz/blogs/image_1583571439323.png_imagess) ![image.png](https://file.chaobei.xyz/blogs/image_1583573501679.png_imagess) 對於沒有元素的位置,使用`^` 空處位置。要是遇到刁鑽一些的二叉樹,例如下面這個,那直接很浪費空間。 ![image.png](https://file.chaobei.xyz/blogs/image_1583574764389.png_imagess) 這裡面的灰色代表元素缺失,這樣儲存到數組裡面的話,是很浪費記憶體空間的。 ![image.png](https://file.chaobei.xyz/blogs/image_1583574780432.png_imagess) ## 鏈式二叉樹 當然,我們既然不能用陣列儲存,那就使用指標儲存。我們知道二叉樹的定義裡已經規定好,兒子節點不能超過兩個,並且本身還需要是一個有序樹 ![image.png](https://file.chaobei.xyz/blogs/image_1583575304469.png_imagess) 每一個節點需要包含兩個指標,用於指向左節點和右節點即可。中間則包含本節點的資料即可。這樣相比於陣列,我們之前也學習過連結串列、雖然資料不再是連續的,但是可以隨意的增加節點、不會造成記憶體空間的浪費。 ## 二叉樹遍歷 一個二叉樹的遍歷,是為了訪問其所有的節點。有這樣幾種方式 - 前序遍歷 - 中序遍歷 - 後序遍歷 - 層序遍歷 ### 前序遍歷 通俗的說就是從二叉樹的根節點出發,先訪問當前節點的左節點,一直向下。直到當前節點沒有左節點,再遍歷右節點。 ![image.png](https://file.chaobei.xyz/blogs/image_1583575304469.png_imagess) 我們按照當前圖所示,簡單講解一下: 1. 從A根節點入手,列印A 2. 訪問A節點的左節點 列印B 3. 訪問當前節點的左節點,列印D 4. D沒有左右節點,本次遍歷完成,訪問B節點右分支 列印E 5. E節點沒有左右分支,遍歷本節點結束。 6. 遍歷A節點右分支,列印C 7. C沒有左右節點,A節點遍歷完成。退出迴圈。 以上的列印順序應該是:`ABDEC` ### 中序遍歷 中序遍歷,最大的特點就是:第二次到達節點輸出內容,也是從左到右的順序,從根節點出發。 還是按照上面那張圖: 1. 從A出發,找到B 2. 在B找到D 3. D節點沒有左右節點,列印D 4. 第二次回到B 列印B 5. 訪問B節點右節點E 6. E沒有左右節點,第二次回到E 列印E 7. B完成,回到A,列印A 8. 訪問A右節點C,沒有左右節點直接列印C 經過以上列印訪問,順序為:`DBEAC` ### 後序遍歷 後序遍歷,在原有中序遍歷上稍微有些不同,當第`三次到達`節點時,才會輸出,還是按照根節點入手,從左到右的方式。 1. A節點入手,到達B 2. B節點左節點,到達D 3. D節點沒有左右節點,直接列印D 4. 回到B節點,第二次到達,到E節點 5. E節點沒有左右節點,直接列印E 6. 回到B,第三次到達,列印B 7. 回到A 第二次到達,到達C 8. 直接列印C,回到A 第三次到達,列印A 最後的順序應該是:`DEBCA` ### 層序遍歷 之前已經說過了有關層次的相關概念。層序遍歷,主要思路是:通過藉助佇列,從樹的根節點開始,左右節點節點入隊,每次佇列中一個節點出隊,直到所有的節點都出隊。出隊的先後順序就是遍歷的結果。 ![image.png](https://file.chaobei.xyz/blogs/image_1583571530046.png_imagess) 按照這幅圖來說明一下: 1. 首先根節點1入隊。 2. 根節點1出隊,同時將1的兩個子節點`2,3`放入佇列 3. 佇列頭2出隊,將2節點的兩個子節點`4,5`放入佇列 4. 佇列頭3出隊,同時將3的兩個子節點`6,7`放入佇列 5. 不斷迴圈,直到佇列為空。則遍歷完成。 ## 小結 通過本節,應該瞭解到樹這個重要的概念。以及樹當中最常見的二叉樹的遍歷以及使用。並且瞭解字串、陣列等基本概念 ## 參考 http://c.biancheng.net/view/3392.html https://www.jianshu.com/p/a47d6