串
在數(shù)據(jù)結(jié)構(gòu)中,串是由零個(gè)或多個(gè)字符組成的有限序列。它是一種線性數(shù)據(jù)結(jié)構(gòu),常用于表示和處理文本、字符串等信息。
串的特點(diǎn)包括:
- 順序性:串中的字符按照一定的先后順序排列,每個(gè)字符都有一個(gè)唯一的位置。
- 有限性:串的長度是有限的,它由字符的個(gè)數(shù)決定。
- 可變性:串可以根據(jù)需要進(jìn)行插入、刪除和修改操作。
串的實(shí)現(xiàn)方式有多種,常見的有兩種主要方式:
-
數(shù)組實(shí)現(xiàn):使用字符數(shù)組來存儲(chǔ)串的字符序列。通過定義一個(gè)固定大小的數(shù)組,將串中的字符依次存放在數(shù)組中,可以通過數(shù)組下標(biāo)來訪問和操作串中的字符。這種實(shí)現(xiàn)方式簡單直觀,但需要預(yù)先分配足夠的內(nèi)存空間,并且對(duì)于插入、刪除等操作需要移動(dòng)大量的字符,效率較低。
-
鏈表實(shí)現(xiàn):使用鏈表結(jié)構(gòu)來存儲(chǔ)串的字符序列。每個(gè)節(jié)點(diǎn)包含一個(gè)字符和一個(gè)指向下一個(gè)節(jié)點(diǎn)的指針,通過將節(jié)點(diǎn)按順序連接起來形成鏈表來表示串。這種實(shí)現(xiàn)方式不需要預(yù)先分配固定大小的內(nèi)存空間,可以根據(jù)實(shí)際需要?jiǎng)討B(tài)調(diào)整串的長度,插入、刪除操作只需修改指針的指向,效率較高。
串的常見操作包括:
- 獲取長度:獲取串的字符個(gè)數(shù)。
- 判空:判斷串是否為空。
- 比較:比較兩個(gè)串是否相等或大小關(guān)系。
- 連接:將兩個(gè)串連接成一個(gè)新的串。
- 子串:提取原串中的一部分作為子串。
- 查找:在串中查找指定字符或子串的位置。
- 插入、刪除:在指定位置插入字符或刪除字符。
串在實(shí)際應(yīng)用中非常重要,常被用于文本處理、搜索引擎、編譯器等領(lǐng)域。了解和熟練掌握串的相關(guān)知識(shí)對(duì)于編程和算法設(shè)計(jì)都非常有幫助。
串的基本操作
好的,我來詳細(xì)介紹一下每一個(gè)字符串操作。
-
字符串初始化(String Initialization)
這個(gè)操作是將一個(gè)字符串常量或字符數(shù)組賦值給一個(gè)字符數(shù)組??梢杂靡韵聝煞N方式進(jìn)行字符串初始化:char str[] = "Hello";
或者
char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
在第一種方式中,字符串常量被自動(dòng)轉(zhuǎn)換為字符數(shù)組并復(fù)制到字符數(shù)組
str
中。在第二種方式中,字符數(shù)組用字符常量的形式初始化,最后一個(gè)字符必須是空字符'\0'
,表示字符串的結(jié)尾。 -
字符串賦值(String Copy)
這個(gè)操作是將一個(gè)字符串復(fù)制到另一個(gè)字符串中??梢允褂?strcpy()
函數(shù)進(jìn)行字符串賦值:strcpy(dest, src);
這里的
src
是源字符串,dest
是目標(biāo)字符串。strcpy()
函數(shù)會(huì)將src
中的字符復(fù)制到dest
中,直到遇到空字符'\0'
為止。 -
字符串連接(String Concatenation)
這個(gè)操作是將一個(gè)字符串連接到另一個(gè)字符串的末尾??梢允褂?strcat()
函數(shù)進(jìn)行字符串連接:strcat(dest, src);
這里的
src
是源字符串,dest
是目標(biāo)字符串。strcat()
函數(shù)會(huì)將src
中的字符連接到dest
的末尾,直到遇到空字符'\0'
。 -
字符串長度(String Length)
這個(gè)操作是求一個(gè)字符串的長度??梢允褂?strlen()
函數(shù)進(jìn)行字符串長度計(jì)算:strlen(str);
這里的
str
是要計(jì)算長度的字符串。strlen()
函數(shù)會(huì)返回str
中的字符數(shù),不包括空字符'\0'
。 -
字符串比較(String Comparison)
這個(gè)操作是比較兩個(gè)字符串是否相等??梢允褂?strcmp()
函數(shù)進(jìn)行字符串比較:strcmp(str1, str2);
這里的
str1
和str2
是要比較的字符串。strcmp()
函數(shù)會(huì)返回一個(gè)整數(shù)值,表示兩個(gè)字符串的大小關(guān)系。如果str1
大于str2
,則返回一個(gè)正整數(shù);如果str1
小于str2
,則返回一個(gè)負(fù)整數(shù);如果兩個(gè)字符串相等,則返回0。 -
字符串復(fù)制(String Copy with Length)
這個(gè)操作是將一個(gè)字符串的前n個(gè)字符復(fù)制到另一個(gè)字符串中??梢允褂?strncpy()
函數(shù)進(jìn)行字符串復(fù)制:strncpy(dest, src, n);
這里的
src
是源字符串,dest
是目標(biāo)字符串,n
是要復(fù)制的字符數(shù)。strncpy()
函數(shù)會(huì)將src
中的前n
個(gè)字符復(fù)制到dest
中,如果src
的字符數(shù)不足n
,則在dest
中填充空字符'\0'
。 -
字符串查找(String Searching)
這個(gè)操作是在一個(gè)字符串中查找特定字符或子串??梢允褂?strchr()
函數(shù)查找一個(gè)特定字符:strchr(str, ch);
這里的
str
是要查找的字符串,ch
是要查找的字符。strchr()
函數(shù)會(huì)返回一個(gè)指向第一個(gè)匹配字符的指針,如果未找到該字符,則返回NULL
。
另外,如果要查找一個(gè)特定子串,可以使用strstr()
函數(shù):strstr(str, substr);
這里的
str
是要查找的字符串,substr
是要查找的子串。strstr()
函數(shù)會(huì)返回一個(gè)指向第一個(gè)匹配子串的指針,如果未找到該子串,則返回NULL
。 -
字符串分割(String Tokenization)
這個(gè)操作是將一個(gè)字符串分割成多個(gè)子串??梢允褂?strtok()
函數(shù)進(jìn)行字符串分割:strtok(str, delimiters);
這里的
str
是要分割的字符串,delimiters
是分割符字符串。strtok()
函數(shù)會(huì)返回一個(gè)指向當(dāng)前子串的指針,每次調(diào)用strtok()
函數(shù)時(shí),它都會(huì)返回下一個(gè)子串??梢允褂?NULL
作為第一個(gè)參數(shù)來繼續(xù)返回下一個(gè)子串,直到所有的子串都被遍歷過。
2.串的簡單模式匹配算法
串的簡單模式匹配算法(也叫樸素模式匹配算法)是一種用來在一個(gè)文本串中查找一個(gè)模式串的算法。它的思想是從文本串的第一個(gè)字符開始,依次和模式串的每個(gè)字符進(jìn)行比較,如果匹配成功,則繼續(xù)比較下一個(gè)字符,否則將文本串向右移動(dòng)一位,重新從文本串的下一個(gè)字符開始與模式串進(jìn)行比較。
具體實(shí)現(xiàn)步驟如下:
- **從文本串的第一個(gè)字符開始,依次與模式串的第一個(gè)字符、第二個(gè)字符、第三個(gè)字符……進(jìn)行比較。**替換
- 如果當(dāng)前字符匹配成功,則繼續(xù)比較下一個(gè)字符。
- 如果當(dāng)前字符匹配失敗,則將文本串向右移動(dòng)一位,并從新的位置開始重新與模式串進(jìn)行比較。
- 如果文本串已經(jīng)被移動(dòng)到了末尾,但是模式串還沒有被完全匹配上,則說明匹配失敗。
代碼實(shí)現(xiàn)如下(以 C 語言為例):
int simpleMatch(char* text, char* pattern) {
int i,j;
int textLen = strlen(text);
int patternLen = strlen(pattern);
for (i = 0; i <= textLen - patternLen; i++) {
for (j = 0; j < patternLen; j++) {
if (text[i + j] != pattern[j]) break;
}
if (j == patternLen) return i;
}
return -1;
}
其中,text
表示文本串,pattern
表示模式串。函數(shù)返回第一次匹配成功的位置,如果沒有匹配成功則返回 -1
。
該算法的時(shí)間復(fù)雜度為 O ( n m ) O(nm) O(nm),其中 n n n 和 m m m 分別為文本串和模式串的長度。在最壞情況下,需要進(jìn)行 n ? m + 1 n-m+1 n?m+1 次比較,因此該算法的效率并不高,但是在實(shí)際應(yīng)用中,該算法可以作為其他更高效的字符串匹配算法的基礎(chǔ)。
矩陣的壓縮存儲(chǔ)(》?)
矩陣的壓縮存儲(chǔ)是一種有效地存儲(chǔ)稀疏矩陣(大部分元素為0)的方法,可以節(jié)省存儲(chǔ)空間。常見的矩陣壓縮存儲(chǔ)方法有三種:行壓縮存儲(chǔ)、列壓縮存儲(chǔ)和坐標(biāo)壓縮存儲(chǔ)。
-
行壓縮存儲(chǔ)(Compressed Row Storage, CRS):
在行壓縮存儲(chǔ)中,矩陣的非零元素按行存儲(chǔ),并且每一行的非零元素按照列索引的順序排列。此外,還需要一個(gè)額外的數(shù)組記錄每一行的起始位置和結(jié)束位置。這樣,對(duì)于一個(gè) m × n 的稀疏矩陣,需要三個(gè)數(shù)組來存儲(chǔ)數(shù)據(jù):-
val
數(shù)組:保存非零元素的值。 -
col_ind
數(shù)組:保存非零元素所在的列索引。 -
row_ptr
數(shù)組:保存每一行的起始位置和結(jié)束位置的索引。
-
-
列壓縮存儲(chǔ)(Compressed Column Storage, CCS):
在列壓縮存儲(chǔ)中,矩陣的非零元素按列存儲(chǔ),并且每一列的非零元素按照行索引的順序排列。與行壓縮存儲(chǔ)類似,需要三個(gè)數(shù)組來存儲(chǔ)數(shù)據(jù):-
val
數(shù)組:保存非零元素的值。 -
row_ind
數(shù)組:保存非零元素所在的行索引。 -
col_ptr
數(shù)組:保存每一列的起始位置和結(jié)束位置的索引。
-
-
坐標(biāo)壓縮存儲(chǔ)(Coordinate Storage, COO):
在坐標(biāo)壓縮存儲(chǔ)中,可以簡單地將每個(gè)非零元素的行號(hào)、列號(hào)和值存儲(chǔ)在一個(gè)三元組中。因此,需要三個(gè)數(shù)組來存儲(chǔ)數(shù)據(jù):-
row_ind
數(shù)組:保存非零元素的行號(hào)。 -
col_ind
數(shù)組:保存非零元素的列號(hào)。 -
val
數(shù)組:保存非零元素的值。
-
這些壓縮存儲(chǔ)方法適用于大部分元素為0的稀疏矩陣,可以顯著減少存儲(chǔ)空間的使用,并且在某些計(jì)算中可以提高計(jì)算效率。選擇哪種壓縮存儲(chǔ)方法取決于具體應(yīng)用場(chǎng)景和對(duì)存儲(chǔ)和計(jì)算效率的需求。
當(dāng)矩陣是稀疏矩陣(大部分元素為0)時(shí),傳統(tǒng)的二維數(shù)組存儲(chǔ)方式會(huì)造成大量的存儲(chǔ)空間浪費(fèi),而壓縮存儲(chǔ)方法可以有效地減少存儲(chǔ)空間的使用。
-
行壓縮存儲(chǔ)(CRS):
在行壓縮存儲(chǔ)中,矩陣的非零元素按行存儲(chǔ),并且每一行的非零元素按照列索引的順序排列。此外,還需要一個(gè)額外的數(shù)組記錄每一行的起始位置和結(jié)束位置。例如,對(duì)于以下稀疏矩陣:1 0 0 0 0 0 2 0 3 4 0 5
對(duì)應(yīng)的行壓縮存儲(chǔ)如下:
-
val
數(shù)組:[1, 2, 3, 4, 5],保存非零元素的值。 -
col_ind
數(shù)組:[0, 2, 0, 1, 3],保存非零元素所在的列索引。 -
row_ptr
數(shù)組:[0, 1, 3, 5],保存每一行的起始位置和結(jié)束位置的索引。
在行壓縮存儲(chǔ)中,對(duì)于第 i 行的元素,其非零元素的位置范圍是
row_ptr[i]
到row_ptr[i+1]-1
。 -
-
列壓縮存儲(chǔ)(CCS):
在列壓縮存儲(chǔ)中,矩陣的非零元素按列存儲(chǔ),并且每一列的非零元素按照行索引的順序排列。與行壓縮存儲(chǔ)類似,需要三個(gè)數(shù)組來存儲(chǔ)數(shù)據(jù)。以同樣的稀疏矩陣為例:-
val
數(shù)組:[1, 3, 2, 4, 5],保存非零元素的值。 -
row_ind
數(shù)組:[0, 2, 0, 2, 2],保存非零元素所在的行索引。 -
col_ptr
數(shù)組:[0, 1, 2, 4, 5],保存每一列的起始位置和結(jié)束位置的索引。
在列壓縮存儲(chǔ)中,對(duì)于第 j 列的元素,其非零元素的位置范圍是
col_ptr[j]
到col_ptr[j+1]-1
。 -
-
坐標(biāo)壓縮存儲(chǔ)(COO):
在坐標(biāo)壓縮存儲(chǔ)中,可以簡單地將每個(gè)非零元素的行號(hào)、列號(hào)和值存儲(chǔ)在一個(gè)三元組中。因此,需要三個(gè)數(shù)組來存儲(chǔ)數(shù)據(jù)。以同樣的稀疏矩陣為例:-
row_ind
數(shù)組:[0, 2, 2, 0, 2],保存非零元素的行號(hào)。 -
col_ind
數(shù)組:[0, 2, 3, 0, 3],保存非零元素的列號(hào)。 -
val
數(shù)組:[1, 3, 5, 2, 4],保存非零元素的值。
在坐標(biāo)壓縮存儲(chǔ)中,每個(gè)三元組表示一個(gè)非零元素的位置和值。
-
這些壓縮存儲(chǔ)方法對(duì)于稀疏矩陣的存儲(chǔ)可以大大節(jié)省存儲(chǔ)空間。同時(shí),在進(jìn)行稀疏矩陣的運(yùn)算時(shí),這些壓縮存儲(chǔ)方法也能夠提高計(jì)算效率。選擇哪種壓縮存儲(chǔ)方法取決于具體應(yīng)用場(chǎng)景和對(duì)存儲(chǔ)和計(jì)算效率的需求。
當(dāng)處理稀疏矩陣時(shí),選擇適合的壓縮存儲(chǔ)方法可以提高存儲(chǔ)效率和計(jì)算效率。下面是一些選擇壓縮存儲(chǔ)方法的考慮因素:
-
稀疏度:稀疏度是指矩陣中非零元素占總元素?cái)?shù)量的比例。如果矩陣非常稀疏,即非零元素很少,那么壓縮存儲(chǔ)方法可以更好地節(jié)省存儲(chǔ)空間。行壓縮存儲(chǔ)和列壓縮存儲(chǔ)在稀疏度較高時(shí)表現(xiàn)較好。
-
存儲(chǔ)需求:根據(jù)實(shí)際存儲(chǔ)需求選擇壓縮存儲(chǔ)方法。如果需要快速訪問某一行或某一列的元素,行壓縮存儲(chǔ)和列壓縮存儲(chǔ)可能更合適。如果需要隨機(jī)訪問元素,坐標(biāo)壓縮存儲(chǔ)可能更適用。
-
計(jì)算需求:根據(jù)對(duì)矩陣的操作和計(jì)算需求選擇壓縮存儲(chǔ)方法。不同的壓縮存儲(chǔ)方法對(duì)于不同的矩陣運(yùn)算操作(如矩陣乘法、矩陣加法等)具有不同的計(jì)算效率。通常情況下,行壓縮存儲(chǔ)和列壓縮存儲(chǔ)在矩陣乘法等操作中表現(xiàn)較好。
-
內(nèi)存限制:考慮可用內(nèi)存大小,選擇適當(dāng)?shù)膲嚎s存儲(chǔ)方法。行壓縮存儲(chǔ)和列壓縮存儲(chǔ)通常需要額外的數(shù)組來存儲(chǔ)索引信息,因此可能需要更多的內(nèi)存空間。
需要注意的是,選擇壓縮存儲(chǔ)方法時(shí)需要綜合考慮上述因素,并根據(jù)具體應(yīng)用場(chǎng)景進(jìn)行權(quán)衡。不同的壓縮存儲(chǔ)方法在不同的情況下可能會(huì)有不同的性能表現(xiàn)。
樹和二叉樹
前驅(qū) 后繼
樹和二叉樹都是常見的數(shù)據(jù)結(jié)構(gòu),用于組織和表示具有分層結(jié)構(gòu)的數(shù)據(jù)。下面是它們的介紹:
-
樹(Tree):
- 樹是由節(jié)點(diǎn)(Node)組成的集合,每個(gè)節(jié)點(diǎn)可以有零個(gè)或多個(gè)子節(jié)點(diǎn)。
- 樹的一個(gè)節(jié)點(diǎn)被稱為根節(jié)點(diǎn)(Root),它沒有父節(jié)點(diǎn)。
- 其他節(jié)點(diǎn)都有且只有一個(gè)父節(jié)點(diǎn),形成父子關(guān)系。
- 除了根節(jié)點(diǎn)外,其他節(jié)點(diǎn)可以分為內(nèi)部節(jié)點(diǎn)和葉節(jié)點(diǎn)(Leaf)。內(nèi)部節(jié)點(diǎn)有至少一個(gè)子節(jié)點(diǎn),而葉節(jié)點(diǎn)沒有子節(jié)點(diǎn)。
- 節(jié)點(diǎn)之間的連接稱為邊(Edge)。
- 樹的常見應(yīng)用包括文件系統(tǒng)、組織結(jié)構(gòu)、圖算法等。
-
二叉樹(Binary Tree):
- 二叉樹是一種特殊的樹結(jié)構(gòu),其中每個(gè)節(jié)點(diǎn)最多有兩個(gè)子節(jié)點(diǎn),分別稱為左子節(jié)點(diǎn)和右子節(jié)點(diǎn)。
- 二叉樹的子節(jié)點(diǎn)位置固定,不能超過兩個(gè)。
- 二叉樹可以為空,即不包含任何節(jié)點(diǎn),或者是由根節(jié)點(diǎn)和左子樹、右子樹組成的。
- 二叉樹的遍歷方式包括前序遍歷(先訪問根節(jié)點(diǎn),然后遞歸地訪問左子樹和右子樹)、中序遍歷(先遞歸地訪問左子樹,然后訪問根節(jié)點(diǎn),最后遞歸地訪問右子樹)和后序遍歷(先遞歸地訪問左子樹和右子樹,最后訪問根節(jié)點(diǎn))等。
二叉樹是樹的一種特殊情況,在很多應(yīng)用中具有重要作用。它可以用于實(shí)現(xiàn)搜索和排序算法,例如二叉搜索樹(Binary Search Tree);也可以用于構(gòu)建表達(dá)式樹、哈夫曼樹等。樹和二叉樹在計(jì)算機(jī)科學(xué)中有廣泛的應(yīng)用,對(duì)于理解和解決各種問題都具有重要意義。
樹的基本概念介紹
樹是一種具有分層結(jié)構(gòu)的數(shù)據(jù)結(jié)構(gòu),它由節(jié)點(diǎn)和邊組成,每個(gè)節(jié)點(diǎn)可以有零個(gè)或多個(gè)子節(jié)點(diǎn),除了根節(jié)點(diǎn)外,每個(gè)節(jié)點(diǎn)都有一個(gè)父節(jié)點(diǎn)。下面是樹的一些基本概念:
-
根節(jié)點(diǎn)(Root):樹的頂部節(jié)點(diǎn),沒有父節(jié)點(diǎn)。
-
內(nèi)部節(jié)點(diǎn)(Internal Node):除了根節(jié)點(diǎn)和葉節(jié)點(diǎn)外,其他節(jié)點(diǎn)都是內(nèi)部節(jié)點(diǎn),即有至少一個(gè)子節(jié)點(diǎn)的節(jié)點(diǎn)。
-
葉節(jié)點(diǎn)(Leaf):沒有子節(jié)點(diǎn)的節(jié)點(diǎn),也稱為終端節(jié)點(diǎn)。
-
子節(jié)點(diǎn)(Child):一個(gè)節(jié)點(diǎn)的直接下屬節(jié)點(diǎn)。
-
父節(jié)點(diǎn)(Parent):一個(gè)節(jié)點(diǎn)的直接上級(jí)節(jié)點(diǎn)。
-
兄弟節(jié)點(diǎn)(Sibling):具有相同父節(jié)點(diǎn)的節(jié)點(diǎn),即同級(jí)節(jié)點(diǎn)。
-
子樹(Subtree):以一個(gè)節(jié)點(diǎn)為根節(jié)點(diǎn),它的所有后代節(jié)點(diǎn)組成的樹。
-
深度(Depth):根節(jié)點(diǎn)到某個(gè)節(jié)點(diǎn)的路徑長度,根節(jié)點(diǎn)的深度為 0,其余節(jié)點(diǎn)深度為其父節(jié)點(diǎn)深度加 1。
-
高度(Height):從某個(gè)節(jié)點(diǎn)到它的最遠(yuǎn)葉節(jié)點(diǎn)的路徑長度,葉節(jié)點(diǎn)的高度為 0,樹的高度為根節(jié)點(diǎn)的高度。
-
層次(Level):根節(jié)點(diǎn)為第一層,其子節(jié)點(diǎn)為第二層,以此類推。
-
森林(Forest):由多棵樹組成的集合。
樹在計(jì)算機(jī)科學(xué)中有許多應(yīng)用,例如解析表達(dá)式、查找和排序數(shù)據(jù)、構(gòu)建文件系統(tǒng)和數(shù)據(jù)庫等。了解樹的基本概念可以幫助我們更好地理解它們的實(shí)現(xiàn)和應(yīng)用。
二叉樹
二叉樹是一種特殊的樹結(jié)構(gòu),其中每個(gè)節(jié)點(diǎn)最多有兩個(gè)子節(jié)點(diǎn),分別稱為左子節(jié)點(diǎn)和右子節(jié)點(diǎn)。以下是二叉樹的一些基本概念:
-
根節(jié)點(diǎn)(Root):二叉樹的頂部節(jié)點(diǎn),沒有父節(jié)點(diǎn)。
-
內(nèi)部節(jié)點(diǎn)(Internal Node):除了根節(jié)點(diǎn)外,其他節(jié)點(diǎn)都是內(nèi)部節(jié)點(diǎn),即有至少一個(gè)子節(jié)點(diǎn)的節(jié)點(diǎn)。
-
葉節(jié)點(diǎn)(Leaf):沒有子節(jié)點(diǎn)的節(jié)點(diǎn),也稱為終端節(jié)點(diǎn)。
-
子節(jié)點(diǎn)(Child):一個(gè)節(jié)點(diǎn)的直接下屬節(jié)點(diǎn)。
-
父節(jié)點(diǎn)(Parent):一個(gè)節(jié)點(diǎn)的直接上級(jí)節(jié)點(diǎn)。
-
兄弟節(jié)點(diǎn)(Sibling):具有相同父節(jié)點(diǎn)的節(jié)點(diǎn),即同級(jí)節(jié)點(diǎn)。
-
左子節(jié)點(diǎn)和右子節(jié)點(diǎn):一個(gè)節(jié)點(diǎn)的直接下屬節(jié)點(diǎn),分別稱為左子節(jié)點(diǎn)和右子節(jié)點(diǎn)。
-
子樹(Subtree):以一個(gè)節(jié)點(diǎn)為根節(jié)點(diǎn),它的所有后代節(jié)點(diǎn)組成的二叉樹。
-
二叉樹的屬性:
- 對(duì)于每個(gè)節(jié)點(diǎn),最多有兩個(gè)子節(jié)點(diǎn)。
- 左子節(jié)點(diǎn)和右子節(jié)點(diǎn)的順序是固定的,左邊的子節(jié)點(diǎn)先于右邊的子節(jié)點(diǎn)。
- 二叉樹可以為空,即不包含任何節(jié)點(diǎn)。
-
二叉樹的遍歷方式:
- 前序遍歷(Preorder Traversal):先訪問根節(jié)點(diǎn),然后遞歸地訪問左子樹和右子樹。
- 中序遍歷(Inorder Traversal):先遞歸地訪問左子樹,然后訪問根節(jié)點(diǎn),最后遞歸地訪問右子樹。
- 后序遍歷(Postorder Traversal):先遞歸地訪問左子樹和右子樹,最后訪問根節(jié)點(diǎn)。
二叉樹在計(jì)算機(jī)科學(xué)中具有廣泛的應(yīng)用,例如二叉搜索樹、哈夫曼樹、表達(dá)式樹等。它們對(duì)于表示和操作數(shù)據(jù)以及解決各種問題都非常重要。
幾個(gè)特殊的二叉樹
二叉樹的性質(zhì)
二叉樹是一種常見的數(shù)據(jù)結(jié)構(gòu),它由節(jié)點(diǎn)組成,每個(gè)節(jié)點(diǎn)最多有兩個(gè)子節(jié)點(diǎn)(左子節(jié)點(diǎn)和右子節(jié)點(diǎn))。以下是二叉樹的一些性質(zhì):
-
根節(jié)點(diǎn):二叉樹的頂部節(jié)點(diǎn)稱為根節(jié)點(diǎn)。它是樹的起始點(diǎn),沒有父節(jié)點(diǎn)。
-
子節(jié)點(diǎn):一個(gè)節(jié)點(diǎn)的直接下級(jí)節(jié)點(diǎn)稱為其子節(jié)點(diǎn)。一個(gè)節(jié)點(diǎn)最多可以有兩個(gè)子節(jié)點(diǎn),分別為左子節(jié)點(diǎn)和右子節(jié)點(diǎn)。
-
葉子節(jié)點(diǎn):沒有子節(jié)點(diǎn)的節(jié)點(diǎn)稱為葉子節(jié)點(diǎn),也稱為終端節(jié)點(diǎn)。
-
父節(jié)點(diǎn):一個(gè)節(jié)點(diǎn)的直接上級(jí)節(jié)點(diǎn)稱為其父節(jié)點(diǎn)。
-
兄弟節(jié)點(diǎn):具有相同父節(jié)點(diǎn)的節(jié)點(diǎn)稱為兄弟節(jié)點(diǎn)。
-
深度:節(jié)點(diǎn)到根節(jié)點(diǎn)的層數(shù)稱為深度。根節(jié)點(diǎn)的深度為0,其子節(jié)點(diǎn)的深度為1,依次遞增。
-
高度:節(jié)點(diǎn)到葉子節(jié)點(diǎn)的最長路徑的邊數(shù)稱為高度。葉子節(jié)點(diǎn)的高度為0,根節(jié)點(diǎn)的高度為樹的高度。
-
路徑:從一個(gè)節(jié)點(diǎn)到另一個(gè)節(jié)點(diǎn)的通路稱為路徑。
-
層次遍歷:按照從上到下、從左到右的順序遍歷二叉樹的節(jié)點(diǎn)。
-
前序遍歷:先訪問根節(jié)點(diǎn),然后遞歸地遍歷左子樹和右子樹。
-
中序遍歷:先遞歸地遍歷左子樹,然后訪問根節(jié)點(diǎn),最后遞歸地遍歷右子樹。
-
后序遍歷:先遞歸地遍歷左子樹和右子樹,最后訪問根節(jié)點(diǎn)。
這些性質(zhì)是二叉樹的一些基本概念和遍歷方式,可以幫助我們理解和操作二叉樹數(shù)據(jù)結(jié)構(gòu)。
二叉樹的存儲(chǔ)結(jié)構(gòu)
二叉樹的存儲(chǔ)結(jié)構(gòu)有兩種常見的方式:順序存儲(chǔ)和鏈?zhǔn)酱鎯?chǔ)。
-
順序存儲(chǔ):
在順序存儲(chǔ)結(jié)構(gòu)中,可以使用數(shù)組來表示二叉樹。通常按照完全二叉樹的形式進(jìn)行存儲(chǔ),即從上到下、從左到右依次存儲(chǔ)節(jié)點(diǎn)。具體描述如下:// 定義二叉樹節(jié)點(diǎn) struct TreeNode { int data; struct TreeNode* left; struct TreeNode* right; }; // 定義順序存儲(chǔ)結(jié)構(gòu) struct SeqBinaryTree { struct TreeNode** nodes; int capacity; int size; };
在初始化二叉樹時(shí),需要為順序存儲(chǔ)結(jié)構(gòu)分配足夠的空間,并將二叉樹的節(jié)點(diǎn)指針存儲(chǔ)在對(duì)應(yīng)的位置上。
注意:由于順序存儲(chǔ)結(jié)構(gòu)要求按照完全二叉樹的形式存儲(chǔ)節(jié)點(diǎn),因此在實(shí)際使用中可能會(huì)浪費(fèi)一部分空間。
-
鏈?zhǔn)酱鎯?chǔ):
在鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)中,通過定義節(jié)點(diǎn)結(jié)構(gòu)體,利用指針來表示二叉樹節(jié)點(diǎn)之間的關(guān)系。具體描述如下:// 定義二叉樹節(jié)點(diǎn) struct TreeNode { int data; struct TreeNode* left; struct TreeNode* right; };
在鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)中,每個(gè)節(jié)點(diǎn)包含了數(shù)據(jù)以及指向左子樹和右子樹的指針。通過指針的鏈接,可以形成完整的二叉樹結(jié)構(gòu)。
-
雙親孩子表示法:
在雙親孩子表示法中,每個(gè)節(jié)點(diǎn)除了包含數(shù)據(jù)和指向左右子節(jié)點(diǎn)的指針外,還包含一個(gè)指向父節(jié)點(diǎn)的指針。具體描述如下:// 定義二叉樹節(jié)點(diǎn) struct TreeNode { int data; struct TreeNode* parent; struct TreeNode* left; struct TreeNode* right; };
這種表示法可以方便地通過父節(jié)點(diǎn)指針訪問節(jié)點(diǎn)的父節(jié)點(diǎn),但相應(yīng)地增加了空間的開銷。
-
孩子兄弟表示法:
在孩子兄弟表示法中,每個(gè)節(jié)點(diǎn)包含了數(shù)據(jù)以及指向第一個(gè)孩子節(jié)點(diǎn)和右兄弟節(jié)點(diǎn)的指針。具體描述如下:// 定義二叉樹節(jié)點(diǎn) struct TreeNode { int data; struct TreeNode* firstChild; struct TreeNode* nextSibling; };
這種表示法適用于任意多叉樹,通過孩子節(jié)點(diǎn)和兄弟節(jié)點(diǎn)的鏈接,可以形成復(fù)雜的樹結(jié)構(gòu)。
除了以上介紹的存儲(chǔ)結(jié)構(gòu),還有其他一些衍生的存儲(chǔ)結(jié)構(gòu),如線索二叉樹、哈夫曼樹等。具體選擇哪種存儲(chǔ)結(jié)構(gòu),取決于對(duì)二叉樹操作的需求和空間效率的考量。
二叉樹的遍歷
在二叉樹中,遍歷是指按照一定的順序訪問所有節(jié)點(diǎn),包括遍歷所有根節(jié)點(diǎn)、遍歷所有左子樹和遍歷所有右子樹。常見的二叉樹遍歷方式有三種:前序遍歷、中序遍歷和后序遍歷。
-
前序遍歷:
在前序遍歷中,先訪問當(dāng)前節(jié)點(diǎn),然后遍歷左子樹,最后遍歷右子樹。具體實(shí)現(xiàn)如下:void preOrderTraversal(struct TreeNode* root) { if (root == NULL) { return; } printf("%d ", root->data); // 訪問當(dāng)前節(jié)點(diǎn) preOrderTraversal(root->left); // 遍歷左子樹 preOrderTraversal(root->right); // 遍歷右子樹 }
-
中序遍歷:
在中序遍歷中,先遍歷左子樹,然后訪問當(dāng)前節(jié)點(diǎn),最后遍歷右子樹。具體實(shí)現(xiàn)如下:void inOrderTraversal(struct TreeNode* root) { if (root == NULL) { return; } inOrderTraversal(root->left); // 遍歷左子樹 printf("%d ", root->data); // 訪問當(dāng)前節(jié)點(diǎn) inOrderTraversal(root->right); // 遍歷右子樹 }
-
后序遍歷:
在后序遍歷中,先遍歷左子樹,然后遍歷右子樹,最后訪問當(dāng)前節(jié)點(diǎn)。具體實(shí)現(xiàn)如下:void postOrderTraversal(struct TreeNode* root) { if (root == NULL) { return; } postOrderTraversal(root->left); // 遍歷左子樹 postOrderTraversal(root->right); // 遍歷右子樹 printf("%d ", root->data); // 訪問當(dāng)前節(jié)點(diǎn) }
以上是常見的三種二叉樹遍歷方式。在實(shí)際使用中,可以根據(jù)需要進(jìn)行適當(dāng)?shù)男薷暮蛿U(kuò)展,例如增加層序遍歷等其他遍歷方式。
二叉樹的先序序列和后序序列,無法唯一確定一棵二叉樹
二叉樹的先序序列和后序序列無法唯一確定一棵二叉樹。這是因?yàn)樵谙刃蛐蛄泻秃笮蛐蛄兄校挥泄?jié)點(diǎn)的相對(duì)順序,而沒有直接的線索可以確定每個(gè)節(jié)點(diǎn)的父節(jié)點(diǎn)和子節(jié)點(diǎn)之間的關(guān)系。
例如,考慮以下兩棵二叉樹:
1 1
/ \ / \
2 3 2 3
/ \
4 4
這兩棵二叉樹的先序序列和后序序列都是不同的。第一棵二叉樹的先序序列是 [1, 2, 3, 4],后序序列是 [2, 4, 3, 1];而第二棵二叉樹的先序序列是 [1, 2, 3, 4],后序序列是 [2, 4, 3, 1]??梢钥吹剑鼈兊南刃蛐蛄泻秃笮蛐蛄惺窍嗤?,但它們的結(jié)構(gòu)是不同的。
因此,要唯一確定一棵二叉樹,需要額外的信息,例如中序序列或者節(jié)點(diǎn)之間的連接方式。通常情況下,我們會(huì)使用先序序列和中序序列或者中序序列和后序序列來唯一確定一棵二叉樹。
使用中序序列可以唯一確定一棵二叉樹的原因是,中序遍歷是一種按照節(jié)點(diǎn)值從小到大的順序進(jìn)行遍歷的方式,每個(gè)節(jié)點(diǎn)的左子樹都包含比它小的節(jié)點(diǎn),右子樹都包含比它大的節(jié)點(diǎn)。因此,在中序序列中,當(dāng)前節(jié)點(diǎn)的左側(cè)所有節(jié)點(diǎn)都應(yīng)該是其左子樹中的節(jié)點(diǎn),右側(cè)所有節(jié)點(diǎn)都應(yīng)該是其右子樹中的節(jié)點(diǎn)。這樣,我們就可以通過先序序列和中序序列確定根節(jié)點(diǎn),然后通過遞歸的方式確定左子樹和右子樹。
以一個(gè)例子來說明。
假設(shè)有如下一棵二叉樹:
5
/ \
3 7
/ \ \
1 4 9
它的先序序列為 [5, 3, 1, 4, 7, 9],中序序列為 [1, 3, 4, 5, 7, 9]。
根據(jù)先序序列,我們知道根節(jié)點(diǎn)是 5。然后,在中序序列中找到 5 的位置,可以知道左子樹的中序序列為 [1, 3, 4],右子樹的中序序列為 [7, 9]。接下來,我們遞歸的求解左子樹和右子樹。對(duì)于左子樹,它的先序序列為 [3, 1, 4],中序序列為 [1, 3, 4];對(duì)于右子樹,它的先序序列為 [7, 9],中序序列為 [7, 9]。我們可以通過遞歸的方式構(gòu)建這棵二叉樹。
使用后序序列來唯一確定一棵二叉樹的原理類似。后序序列是指在遍歷二叉樹時(shí),先遍歷左子樹和右子樹,最后遍歷根節(jié)點(diǎn)。因此,在后序序列中,每個(gè)節(jié)點(diǎn)的左右子樹都已經(jīng)被訪問完畢,我們可以通過這個(gè)性質(zhì)從后往前遞推出每個(gè)節(jié)點(diǎn)的左右子樹的范圍,然后遞歸的方式構(gòu)造二叉樹。
將樹轉(zhuǎn)換成二叉樹
將一棵樹轉(zhuǎn)換成二叉樹可以有多種方法,這里介紹一種常見的方法,即將每個(gè)節(jié)點(diǎn)的孩子節(jié)點(diǎn)按照從左到右的順序連接起來。
假設(shè)我們有以下一棵樹:
A
/ \
B C
/ \ \
D E F
我們可以將它轉(zhuǎn)換成以下的二叉樹:
A
/
B
/ \
D E
\
C
\
F
轉(zhuǎn)換的步驟如下:
- 對(duì)于每個(gè)節(jié)點(diǎn),將其第一個(gè)孩子作為其左孩子,并將其兄弟節(jié)點(diǎn)作為其右孩子。
- 對(duì)于每個(gè)節(jié)點(diǎn)的右孩子,將其右孩子的兄弟節(jié)點(diǎn)作為它的右孩子的右孩子,以此類推。
具體操作如下:
- 對(duì)于節(jié)點(diǎn) A,將 B 作為其左孩子,C 作為其右孩子。
- 對(duì)于節(jié)點(diǎn) B,將 D 作為其左孩子,E 作為其右孩子。
- 對(duì)于節(jié)點(diǎn) C,將 F 作為其右孩子。
- 對(duì)于節(jié)點(diǎn) E,將 C 作為其右孩子。
- 對(duì)于節(jié)點(diǎn) F,沒有孩子節(jié)點(diǎn)。
按照以上操作,我們就成功地將一棵樹轉(zhuǎn)換成了二叉樹。
需要注意的是,轉(zhuǎn)換后的二叉樹可能不是二叉搜索樹或平衡二叉樹等特殊類型的二叉樹,它只是將樹的結(jié)構(gòu)改造成了二叉樹的形式。轉(zhuǎn)換后的二叉樹的遍歷順序可能與原樹不同,所以在具體應(yīng)用中可能需要根據(jù)情況進(jìn)行相應(yīng)的調(diào)整。
將森林轉(zhuǎn)換成二叉樹
將一棵森林轉(zhuǎn)換成二叉樹的方法類似于將一棵樹轉(zhuǎn)換成二叉樹,我們可以按照以下步驟進(jìn)行轉(zhuǎn)換:
- 對(duì)于每個(gè)有左兄弟的節(jié)點(diǎn),將其左兄弟作為其左孩子,并將其右兄弟作為其右孩子。
- 對(duì)于每個(gè)沒有左兄弟的節(jié)點(diǎn),將其右兄弟作為其右孩子。
具體操作如下:
- 對(duì)于節(jié)點(diǎn) A,將 B 作為其左孩子,C 作為其右孩子。
- 對(duì)于節(jié)點(diǎn) B,將 D 作為其左孩子,E 作為其右孩子。
- 對(duì)于節(jié)點(diǎn) F,將 G 作為其右孩子。
轉(zhuǎn)換后的二叉樹如下:
A
/ \
B C
/ \ \
D E F
/
G
需要注意的是,轉(zhuǎn)換后的二叉樹可能不是二叉搜索樹或平衡二叉樹等特殊類型的二叉樹,它只是將森林的結(jié)構(gòu)改造成了二叉樹的形式。轉(zhuǎn)換后的二叉樹的遍歷順序可能與原森林不同,所以在具體應(yīng)用中可能需要根據(jù)情況進(jìn)行相應(yīng)的調(diào)整。
二叉樹轉(zhuǎn)換成對(duì)應(yīng)的森林
將一棵二叉樹轉(zhuǎn)換成對(duì)應(yīng)的森林可以按照以下步驟進(jìn)行:
- 對(duì)于每個(gè)節(jié)點(diǎn),如果它有左孩子,則將其左孩子作為一個(gè)新的樹。
- 對(duì)于每個(gè)節(jié)點(diǎn),如果它有右孩子,則將其右孩子作為一個(gè)新的樹,并將其父節(jié)點(diǎn)與右孩子之間的連接斷開。
具體操作如下:
假設(shè)我們有以下的二叉樹:
A
/ \
B C
/ \ \
D E F
/
G
我們可以將它轉(zhuǎn)換成以下的森林:
A C F
/ \ \
B E G
/
D
轉(zhuǎn)換的步驟如下:
- 對(duì)于節(jié)點(diǎn) A,將其左孩子 B 作為一個(gè)新的樹。
- 對(duì)于節(jié)點(diǎn) B,將其左孩子 D 作為一個(gè)新的樹。
- 對(duì)于節(jié)點(diǎn) C,將其右孩子 F 作為一個(gè)新的樹。
- 對(duì)于節(jié)點(diǎn) F,將其左孩子 G 作為一個(gè)新的樹。
按照以上操作,我們成功地將一棵二叉樹轉(zhuǎn)換成了對(duì)應(yīng)的森林。
需要注意的是,轉(zhuǎn)換后的森林中每個(gè)樹的根節(jié)點(diǎn)可能不是原二叉樹的根節(jié)點(diǎn),而是二叉樹中的某個(gè)節(jié)點(diǎn)。轉(zhuǎn)換后的森林的遍歷順序可能與原二叉樹不同,所以在具體應(yīng)用中可能需要根據(jù)情況進(jìn)行相應(yīng)的調(diào)整。
樹的遍歷
樹的遍歷是指按照一定規(guī)則訪問樹中的所有節(jié)點(diǎn)。常見的樹的遍歷方式有三種:前序遍歷、中序遍歷和后序遍歷。下面我將逐一介紹這三種遍歷方式的操作步驟:
-
前序遍歷(Preorder Traversal):
- 訪問根節(jié)點(diǎn)。
- 遞歸地前序遍歷左子樹。
- 遞歸地前序遍歷右子樹。
-
中序遍歷(Inorder Traversal):
- 遞歸地中序遍歷左子樹。
- 訪問根節(jié)點(diǎn)。
- 遞歸地中序遍歷右子樹。
-
后序遍歷(Postorder Traversal):
- 遞歸地后序遍歷左子樹。
- 遞歸地后序遍歷右子樹。
- 訪問根節(jié)點(diǎn)。
需要注意的是,以上是針對(duì)二叉樹的遍歷方式,對(duì)于多叉樹或森林,你可以將其看作一組獨(dú)立的子樹,分別按照相應(yīng)的遍歷方式進(jìn)行遍歷。
以下是一個(gè)示例樹的結(jié)構(gòu),我們以此來演示三種遍歷方式:
A
/ \
B C
/ \ \
D E F
對(duì)應(yīng)的遍歷結(jié)果為:
- 前序遍歷:A -> B -> D -> E -> C -> F
- 中序遍歷:D -> B -> E -> A -> C -> F
- 后序遍歷:D -> E -> B -> F -> C -> A
樹的遍歷方式可以根據(jù)實(shí)際需求進(jìn)行選擇,每種方式都有其特定的應(yīng)用場(chǎng)景。
森林的遍歷
森林是由多個(gè)獨(dú)立的樹組成的集合。對(duì)于森林的遍歷,我們可以將其看作對(duì)每個(gè)樹分別進(jìn)行遍歷操作。
常見的森林遍歷方式有三種:前序遍歷、中序遍歷和后序遍歷。下面我將逐一介紹這三種遍歷方式在森林中的操作步驟:
-
前序遍歷(Preorder Traversal):
- 對(duì)于森林中的每棵樹,先訪問根節(jié)點(diǎn)。
- 遞歸地前序遍歷樹的所有子樹。
-
中序遍歷(Inorder Traversal):
- 對(duì)于森林中的每棵樹,遞歸地中序遍歷樹的所有左子樹。
- 訪問根節(jié)點(diǎn)。
- 遞歸地中序遍歷樹的所有右子樹。
-
后序遍歷(Postorder Traversal):
- 對(duì)于森林中的每棵樹,遞歸地后序遍歷樹的所有子樹。
- 訪問根節(jié)點(diǎn)。
需要注意的是,在森林遍歷中,每棵樹都是獨(dú)立的,互不影響。因此,遍歷順序是先處理完一棵樹,再處理下一棵樹。
以下是一個(gè)示例森林的結(jié)構(gòu),由兩棵樹組成:
A D
/ \ / \
B C E F
對(duì)應(yīng)的遍歷結(jié)果為:
- 前序遍歷:A -> B -> C -> D -> E -> F
- 中序遍歷:B -> A -> C -> E -> D -> F
- 后序遍歷:B -> C -> A -> E -> F -> D
通過對(duì)每棵樹進(jìn)行遍歷操作,我們可以依次訪問到森林中的所有節(jié)點(diǎn)。森林的遍歷方式可以根據(jù)實(shí)際需求進(jìn)行選擇,每種方式都有其特定的應(yīng)用場(chǎng)景。
二叉排序樹
二叉排序樹(Binary Search Tree,BST),也稱為二叉搜索樹或二叉查找樹,是一種特殊的二叉樹結(jié)構(gòu)。它滿足以下性質(zhì):
- 對(duì)于樹中的每個(gè)節(jié)點(diǎn)n,其左子樹的所有節(jié)點(diǎn)的值都小于n的值。
- 對(duì)于樹中的每個(gè)節(jié)點(diǎn)n,其右子樹的所有節(jié)點(diǎn)的值都大于n的值。
- 左子樹和右子樹都是二叉排序樹。
這個(gè)性質(zhì)保證了在二叉排序樹中,每個(gè)節(jié)點(diǎn)的值都大于其左子樹的所有節(jié)點(diǎn)的值,并且小于其右子樹的所有節(jié)點(diǎn)的值。這使得在二叉排序樹中進(jìn)行查找、插入和刪除操作都可以在平均情況下以O(shè)(log n)的時(shí)間復(fù)雜度完成。
以下是一個(gè)示例的二叉排序樹:
6
/ \
2 8
/ \ / \
1 4 7 9
/ \
3 5
在這個(gè)二叉排序樹中,每個(gè)節(jié)點(diǎn)的值都滿足左小右大的關(guān)系。例如,節(jié)點(diǎn)2的左子樹所有節(jié)點(diǎn)的值都小于2,節(jié)點(diǎn)4的左子樹所有節(jié)點(diǎn)的值都小于4,節(jié)點(diǎn)8的右子樹所有節(jié)點(diǎn)的值都大于8。
通過二叉排序樹,我們可以很方便地進(jìn)行查找、插入和刪除等操作。例如,要查找值為3的節(jié)點(diǎn),我們可以從根節(jié)點(diǎn)開始比較,由于3小于6,在根節(jié)點(diǎn)的左子樹中繼續(xù)查找;再由于3大于2,在2的右子樹中繼續(xù)查找;最終找到了值為3的節(jié)點(diǎn)。
需要注意的是,當(dāng)二叉排序樹中存在重復(fù)的值時(shí),通常有多種可能的構(gòu)建方式,因?yàn)橄嗤闹悼梢苑旁谧笞訕浠蛴易訕渲?。此外,如果二叉排序樹不平衡(即左右子樹的高度差過大),可能會(huì)導(dǎo)致查找效率下降,因此有一些平衡二叉排序樹的變種,如紅黑樹和AVL樹,用于提高性能。
當(dāng)我們對(duì)二叉排序樹進(jìn)行操作時(shí),可以執(zhí)行以下幾種常見的操作:
-
查找(Search):在二叉排序樹中查找某個(gè)特定值。從根節(jié)點(diǎn)開始比較,根據(jù)比較結(jié)果決定是向左子樹還是右子樹搜索,直到找到目標(biāo)值或搜索到空節(jié)點(diǎn)。
-
插入(Insert):向二叉排序樹中插入一個(gè)新的節(jié)點(diǎn)。首先進(jìn)行查找操作,找到插入位置的父節(jié)點(diǎn),然后根據(jù)插入值與父節(jié)點(diǎn)值的大小關(guān)系,確定新節(jié)點(diǎn)應(yīng)該插入到父節(jié)點(diǎn)的左側(cè)還是右側(cè)。
-
刪除(Delete):刪除二叉排序樹中的某個(gè)節(jié)點(diǎn)。首先進(jìn)行查找操作,找到待刪除節(jié)點(diǎn)。刪除節(jié)點(diǎn)時(shí)需要考慮不同情況:
- 若待刪除節(jié)點(diǎn)為葉子節(jié)點(diǎn)(沒有子節(jié)點(diǎn)),直接刪除即可。
- 若待刪除節(jié)點(diǎn)只有一個(gè)子節(jié)點(diǎn),將其子節(jié)點(diǎn)替代待刪除節(jié)點(diǎn)的位置。
- 若待刪除節(jié)點(diǎn)有兩個(gè)子節(jié)點(diǎn),可以選擇將其左子樹的最大節(jié)點(diǎn)或右子樹的最小節(jié)點(diǎn)替代待刪除節(jié)點(diǎn),保持二叉排序樹的性質(zhì)。
-
遍歷(Traversal):按照特定的順序訪問二叉排序樹中的所有節(jié)點(diǎn)。常見的遍歷方式包括前序遍歷、中序遍歷和后序遍歷。這些遍歷方式在前面的回答中已經(jīng)詳細(xì)介紹過了。
對(duì)于二叉排序樹的操作,需要注意保持樹的性質(zhì)不變。在插入和刪除操作中,需要進(jìn)行相應(yīng)的調(diào)整,以確保新樹仍然是一個(gè)有效的二叉排序樹。
需要指出的是,二叉排序樹的性能取決于樹的結(jié)構(gòu)是否平衡。如果樹的不平衡程度過高,可能會(huì)導(dǎo)致操作的時(shí)間復(fù)雜度退化為線性復(fù)雜度,從而降低效率。因此,為了提高性能,可以使用一些平衡二叉排序樹的數(shù)據(jù)結(jié)構(gòu),如紅黑樹、AVL樹等。這些樹結(jié)構(gòu)可以自動(dòng)調(diào)整節(jié)點(diǎn)的位置,以保持樹的平衡性。
插入:比較插入值和根節(jié)點(diǎn)大小
二叉排序樹的構(gòu)造
構(gòu)造二叉排序樹的基本思路是將元素逐個(gè)插入到樹中。下面是一種常用的構(gòu)造方法:
-
創(chuàng)建一個(gè)空的二叉排序樹。
-
依次將元素插入到二叉排序樹中:
- 如果樹為空,則將第一個(gè)元素作為根節(jié)點(diǎn)。
- 如果樹不為空,則從根節(jié)點(diǎn)開始比較待插入元素與當(dāng)前節(jié)點(diǎn)的大小關(guān)系:
- 如果待插入元素小于當(dāng)前節(jié)點(diǎn)的值,繼續(xù)在左子樹中插入。
- 如果待插入元素大于當(dāng)前節(jié)點(diǎn)的值,繼續(xù)在右子樹中插入。
- 如果待插入元素等于當(dāng)前節(jié)點(diǎn)的值,根據(jù)具體需求,可以將重復(fù)元素放在左子樹或右子樹中。
-
重復(fù)步驟2,直到所有元素都插入到二叉排序樹中。
以下是一個(gè)示例的二叉排序樹的構(gòu)造過程:
假設(shè)有以下元素需要插入到二叉排序樹中:7, 3, 9, 2, 1, 4, 8, 6, 5。
-
創(chuàng)建一個(gè)空的二叉排序樹。
-
將第一個(gè)元素7作為根節(jié)點(diǎn)。
-
插入元素3:
- 3小于7,插入到7的左子樹中。
-
插入元素9:
- 9大于7,插入到7的右子樹中。
-
插入元素2:
- 2小于3,插入到3的左子樹中。
-
插入元素1:
- 1小于2,插入到2的左子樹中。
-
插入元素4:
- 4大于3,插入到3的右子樹中。
-
插入元素8:
- 8大于7,插入到7的右子樹中。
-
插入元素6:
- 6大于3,小于7,插入到7的左子樹的右子樹中。
-
插入元素5:
- 5大于3,小于6,插入到6的左子樹中。
最終的二叉排序樹如下所示:
7
/ \
3 9
/ \ /
2 4 8
/ \
1 6
/
5
通過以上步驟,我們成功地構(gòu)造了一個(gè)二叉排序樹。需要注意的是,由于二叉排序樹的構(gòu)造過程是依次插入元素,如果元素的插入順序不同,最終構(gòu)造出的二叉排序樹可能會(huì)有所不同,但它們都滿足二叉排序樹的性質(zhì)。
二叉排序樹的刪除
二叉排序樹的刪除操作需要考慮到三種情況:
-
待刪除節(jié)點(diǎn)為葉子節(jié)點(diǎn):直接刪除即可。
-
待刪除節(jié)點(diǎn)只有一個(gè)子節(jié)點(diǎn):用其子節(jié)點(diǎn)代替待刪除節(jié)點(diǎn)。
-
待刪除節(jié)點(diǎn)有兩個(gè)子節(jié)點(diǎn):找到待刪除節(jié)點(diǎn)的前驅(qū)或后繼節(jié)點(diǎn),用它來替代待刪除節(jié)點(diǎn)。
下面分別介紹這三種情況的具體操作步驟。
1. 待刪除節(jié)點(diǎn)為葉子節(jié)點(diǎn)
如果待刪除節(jié)點(diǎn)為葉子節(jié)點(diǎn),即沒有左右子節(jié)點(diǎn),那么直接將該節(jié)點(diǎn)從樹中刪除即可。
假設(shè)我們要?jiǎng)h除節(jié)點(diǎn)5,如下圖所示:
7
/ \
3 9
/ \ /
2 4 8
/ \
1 5
/
6
首先找到要?jiǎng)h除的節(jié)點(diǎn)5,因?yàn)樗侨~子節(jié)點(diǎn),所以直接將它從樹中刪除。刪除后,需要將節(jié)點(diǎn)5的父節(jié)點(diǎn)4的右子樹指針置為空。
刪除后的樹結(jié)構(gòu)如下圖所示:
7
/ \
3 9
/ \ /
2 4 8
/
1
/
6
2. 待刪除節(jié)點(diǎn)只有一個(gè)子節(jié)點(diǎn)
如果待刪除節(jié)點(diǎn)只有一個(gè)子節(jié)點(diǎn),那么可以直接用其子節(jié)點(diǎn)代替它。
以刪除節(jié)點(diǎn)4為例,如下圖所示:
7
/ \
3 9
/ \ /
2 4 8
/ \
1 5
/
6
首先找到要?jiǎng)h除的節(jié)點(diǎn)4,因?yàn)樗挥幸粋€(gè)子節(jié)點(diǎn)5,所以將節(jié)點(diǎn)5代替節(jié)點(diǎn)4。注意,此時(shí)需要將節(jié)點(diǎn)5的左子樹指針指向節(jié)點(diǎn)4的左子樹,同時(shí)將節(jié)點(diǎn)5的父節(jié)點(diǎn)指針指向節(jié)點(diǎn)4的父節(jié)點(diǎn)。
刪除后的樹結(jié)構(gòu)如下圖所示:
7
/ \
3 9
/ \ /
2 5 8
/ \
1 6
3. 待刪除節(jié)點(diǎn)有兩個(gè)子節(jié)點(diǎn)
如果待刪除節(jié)點(diǎn)有兩個(gè)子節(jié)點(diǎn),那么需要找到它的前驅(qū)或后繼節(jié)點(diǎn),用它來替代待刪除節(jié)點(diǎn)。這里以使用前驅(qū)節(jié)點(diǎn)作為例子。
前驅(qū)節(jié)點(diǎn)是指比待刪除節(jié)點(diǎn)小的最大節(jié)點(diǎn)。在二叉排序樹中,前驅(qū)節(jié)點(diǎn)一定在待刪除節(jié)點(diǎn)的左子樹中。具體操作步驟如下:
-
在待刪除節(jié)點(diǎn)的左子樹中,找到最右側(cè)的節(jié)點(diǎn),即為前驅(qū)節(jié)點(diǎn)。
-
將前驅(qū)節(jié)點(diǎn)的值賦給待刪除節(jié)點(diǎn),并刪除前驅(qū)節(jié)點(diǎn)。
以刪除節(jié)點(diǎn)3為例,如下圖所示:
7
/ \
3 9
/ \ /
2 4 8
/ \
1 5
/
6
首先找到要?jiǎng)h除的節(jié)點(diǎn)3,因?yàn)樗袃蓚€(gè)子節(jié)點(diǎn),需要找到它的前驅(qū)節(jié)點(diǎn)2。在節(jié)點(diǎn)3的左子樹中,最右側(cè)的節(jié)點(diǎn)就是節(jié)點(diǎn)2。將節(jié)點(diǎn)2的值賦給節(jié)點(diǎn)3,然后刪除節(jié)點(diǎn)2,即可完成刪除操作。
刪除后的樹結(jié)構(gòu)如下圖所示:
7
/ \
2 9
/ \ /
1 4 8
/ \
5 6
通過以上三種情況的操作,我們可以實(shí)現(xiàn)二叉排序樹節(jié)點(diǎn)的刪除。需要注意的是,在刪除節(jié)點(diǎn)時(shí),需要保證刪除后的樹仍然滿足二叉排序樹的性質(zhì)。
哈夫曼樹
哈夫曼樹(Huffman Tree),又稱最優(yōu)樹,是一種帶權(quán)路徑長度最短的樹。在哈夫曼樹中,每個(gè)葉子節(jié)點(diǎn)都有一個(gè)權(quán)值,每個(gè)非葉子節(jié)點(diǎn)的權(quán)值等于其左右子樹權(quán)值之和,因此哈夫曼樹是一棵帶權(quán)樹。
哈夫曼樹應(yīng)用廣泛,特別是在數(shù)據(jù)壓縮領(lǐng)域中。數(shù)據(jù)壓縮就是將一個(gè)數(shù)據(jù)集合轉(zhuǎn)換為另一個(gè)更小的數(shù)據(jù)集合,以便通過更少的存儲(chǔ)空間或網(wǎng)絡(luò)帶寬來傳輸數(shù)據(jù)。哈夫曼樹可以用來實(shí)現(xiàn)無損壓縮,即將數(shù)據(jù)壓縮為更小的數(shù)據(jù)集合,并且在解壓縮時(shí)不會(huì)丟失原始數(shù)據(jù)。
構(gòu)建哈夫曼樹的過程如下:
-
將所有權(quán)值作為單獨(dú)的節(jié)點(diǎn),構(gòu)造n棵只有根節(jié)點(diǎn)的二叉樹。
-
在這n棵二叉樹中,選取兩棵根節(jié)點(diǎn)權(quán)值最小的樹作為左右子樹合并成一棵新樹。新樹的根節(jié)點(diǎn)權(quán)值為左右子樹根節(jié)點(diǎn)權(quán)值之和。
-
將這個(gè)新樹插入到原來的二叉樹集合中。
-
重復(fù)步驟2和3,直到只剩下一棵二叉樹,這棵二叉樹即為哈夫曼樹。
例如,給定下面的權(quán)值數(shù)組:
[5, 6, 7, 8, 9]
首先,將這些權(quán)值構(gòu)造成5棵只有根節(jié)點(diǎn)的二叉樹,如下所示:
5 6 7 8 9
| | | |
| | | |
+ + + +
| | | |
| | | |
null null null null
選取兩個(gè)根節(jié)點(diǎn)權(quán)值最小的二叉樹進(jìn)行合并,得到一棵新的二叉樹,其根節(jié)點(diǎn)權(quán)值為5+6=11,如下所示:
11
/ \
5 6
再將這棵新樹插入到原來的二叉樹集合中,得到4棵二叉樹,如下所示:
7 8 9 11
/ \ / \ / \ / \
5 6 5 6 5 6 7 8
| | |
| | |
null null 9
重復(fù)以上步驟,直到只剩下一棵二叉樹,即可得到哈夫曼樹,如下所示:
36
/ \
16 20
/ \
7 9
/ \ / \
3 4 5 4
在哈夫曼樹中,葉子節(jié)點(diǎn)代表原始數(shù)據(jù)中的符號(hào),非葉子節(jié)點(diǎn)代表兩個(gè)或更多符號(hào)的組合。哈夫曼編碼就是將每個(gè)符號(hào)映射到其所對(duì)應(yīng)的葉子節(jié)點(diǎn)路徑上的二進(jìn)制編碼。哈夫曼編碼的特點(diǎn)是:任何一個(gè)符號(hào)的編碼都不是另一個(gè)符號(hào)編碼的前綴,因此可以通過哈夫曼編碼實(shí)現(xiàn)無損壓縮。
當(dāng)我們有一個(gè)已經(jīng)構(gòu)建好的哈夫曼樹后,可以利用它進(jìn)行數(shù)據(jù)的壓縮和解壓縮。
首先,我們需要通過遍歷哈夫曼樹來構(gòu)建每個(gè)字符對(duì)應(yīng)的哈夫曼編碼。在遍歷過程中,左子樹路徑上的編碼為0,右子樹路徑上的編碼為1。當(dāng)遍歷到葉子節(jié)點(diǎn)時(shí),就可以得到該字符對(duì)應(yīng)的哈夫曼編碼。
例如,在上面給出的哈夫曼樹中,假設(shè)字符’A’對(duì)應(yīng)的葉子節(jié)點(diǎn)路徑為左-左-左(編碼為000),字符’B’對(duì)應(yīng)的葉子節(jié)點(diǎn)路徑為左-左-右(編碼為001),以此類推。
在進(jìn)行數(shù)據(jù)壓縮時(shí),我們可以將原始數(shù)據(jù)中的字符替換為其對(duì)應(yīng)的哈夫曼編碼。這樣,相同的字符在壓縮后會(huì)占用更少的空間,從而實(shí)現(xiàn)了數(shù)據(jù)的壓縮。
例如,如果原始數(shù)據(jù)是"ABACD",對(duì)應(yīng)的哈夫曼編碼為"0000010001010100",壓縮后的數(shù)據(jù)只需占用16位空間。
在進(jìn)行數(shù)據(jù)解壓縮時(shí),我們需要借助哈夫曼樹來根據(jù)壓縮后的編碼逐步還原出原始數(shù)據(jù)。從根節(jié)點(diǎn)開始,根據(jù)壓縮數(shù)據(jù)的每一位編碼,依次遍歷哈夫曼樹。當(dāng)遇到葉子節(jié)點(diǎn)時(shí),就找到了對(duì)應(yīng)的字符,可以將其輸出,并回到根節(jié)點(diǎn)繼續(xù)下一位編碼的解析。
例如,對(duì)于壓縮后的數(shù)據(jù)"0000010001010100",我們可以根據(jù)哈夫曼樹還原出原始數(shù)據(jù)"ABACD"。文章來源:http://www.zghlxwxcb.cn/news/detail-796623.html
總結(jié)一下,哈夫曼樹是一種帶權(quán)路徑長度最短的樹,常用于數(shù)據(jù)壓縮中。通過構(gòu)建哈夫曼樹和生成哈夫曼編碼,我們可以實(shí)現(xiàn)數(shù)據(jù)的無損壓縮和解壓縮。文章來源地址http://www.zghlxwxcb.cn/news/detail-796623.html
到了這里,關(guān)于數(shù)據(jù)結(jié)構(gòu)期末復(fù)習(xí)(4)串 樹和二叉樹的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!