資料結構 2 字串 陣列、二叉樹以及二叉樹的遍歷
阿新 • • 發佈:2020-03-08
上一節的學習中,我們已經結合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