1. 程式人生 > >資料結構與演算法:Python語言描述 6~9章課後習題

資料結構與演算法:Python語言描述 6~9章課後習題

MarkDown語法寫的,不知道為啥上傳到CSDN不生效,算了就這樣將就著看吧......還有,轉載請註明出處,謝謝!

## 第六章、二叉樹和樹
***
### 1.複習下面概念
* 樹形結構:也是由結點(結點中的邏輯單元,可用於儲存資料)和結點之間的連線關係(一種後繼關係)構成,但其結構和線性結構不同,最重要的特徵包括:結構不為空時,都有且僅有一個起始結點,叫做樹根;按結點間的連線關係,樹根外的結點都有且僅有一個前驅,但可以有0個或者多個後繼(不同點)。
* 樹根:樹形結構中,除結構為空外,都存在著的有且僅有一個的起始結點,叫做樹根。
* 前驅:存在前一個結點。
* 後繼: 存在後一個結點。
* 二叉樹: 最簡單的樹形結構,特點是:樹中每個結點最多關聯到兩個後繼結點;關聯的後繼結點分左右,或為左關聯結點,或為右關聯結點。
* 左子樹、右子樹:左關聯結點就是左子樹;右~就是右~。
* 空樹:樹中結點個數為零。
* 單點樹:只有一個結點—根。
* 父節點、子節點:一棵二叉樹的根結點稱為該樹的子樹根結點的父結點;子樹的根結點稱為二叉樹樹根結點的子結點。
* 左/右子結點:左/右後繼結點
* 父子關係:父結點到子結點的邊形成的一種單方向的父/子結點關係。
* 祖先/子孫結點:基於父子關係可以定義其傳遞關係,稱為~。
* 樹葉:兩棵子樹都空,沒有子結點的結點叫做~。
* 分支節點:樹中除樹葉外的其餘結點叫做~。
* 結點的度數:一個結點的子結點的個數就是它的度數。
* 路徑:根據祖先/子孫關係,從一個祖先結點到它的任何子孫結點都存在一系列的邊,形成了兩者的聯絡。這一系列首尾相連的邊稱為樹中的一條路徑。每個結點到它的子結點的有一條長度為1路徑;到它自身路徑長度為0.
* 路徑長度:一條路徑包含的多少條數。
*  結點的層數:從根開始,根的層數為0,往下子結點為下一層,層數+1.
* 樹的高度(深度):樹中結點的最大層數就是高度。
* 滿二叉樹:所有分直結點的度數都是2.
* 擴充二叉樹:加入足夠多的新葉結點,使得原有結點度數都變為2.
* 內部結點、外部結點:擴充二叉樹中,原有結點就是內部結點,新增的新葉結點就是外部結點。
* 內部路徑長度、外部路徑長度:從樹根到樹中各內部結點的路徑長度的和;~到樹中的外部結點的~和。
* 完全二叉樹:對於一棵高度為h的二叉樹,從0~h-1層的結點度數都為2(結點都滿),如果最下的那一層結點不滿,則所有結點都在最左邊連續排列,空位在右邊,這樣的二叉樹就叫做~。
* 二叉樹的遍歷:按照某種系統化的方式,訪問二叉樹的每個結點一次(可能會操作結點的資料)。
* 深度優先遍歷:順著二叉樹的一條路徑走,直到檢查完一個葉結點,往下沒有結點了,才回溯繼續。
* 先根序(先序)遍歷:按照DLR順序,先訪問根結點,再訪問左子結點,最後右子結點。
* 中根序(對稱序/中序)遍歷:LDR
* 後根序(後序)遍歷:LRD
* 先根序列:按照先根序遍歷得到的結點訪問序列。
* 對稱(中根)序列:按照中根序~。
* 後根序列:按照後根序~。
* 寬度優先遍歷(按層次順序遍歷):在所有路徑上齊頭並進。
* 層次序列:按照寬度優先遍歷得到的結點序列。
* 表示式樹:二叉樹中結點與子樹的關係可用於表示運算子和運算物件的作用關係。
* 二元表示式:只包含二元運算子的表示式。
* 算術表示式:二元表示式中的運算物件都是數。
* 表示式求值:對錶達式化簡,把能計算的都計算出來。
* 優先佇列:基於佇列,特點是存入其中的資料都附有一個數值,叫做優先順序,用來表示這個項的優先程度。優先佇列要保證任何時候,訪問或者彈出的都是這個優先佇列中元素的優先順序最高的元素。
* 優先關係:集合中元素之間的關係為有序的,靠前的就是更優先的。
* 優先順序:項的優先程度。
* 堆:結點裡儲存資料的完全二叉樹就是堆,堆中的資料的儲存要滿足一種關係,任何一個結點裡所存的資料,它按規定的優先關係都要先用或者等於其子結點裡的資料。
* 堆序:任何一個結點裡所存的資料按規定的優先關係都要先用或者等於其子結點裡的資料。
* 小頂堆:資料小的優先構造出來的堆就是小頂堆。
* 大頂堆:資料大的優先構造出來的堆就是大頂堆。
* 篩選(向下篩選、向上篩選)(以小頂堆為例):把元素和它的父結點的資料相比較,小的話交換兩個元素的位置,不斷進行這樣的操作,直到不小於父結點的資料,或者是到達根結點,才停止。
* 堆排序:如果一個連續表裡儲存的資料是一個小頂堆,按照優先佇列的操作方式反覆彈出元素,就能得到一個遞增序列。
* 離散事件模擬:前面講過。被模擬系統的行為可以抽象為一些離散事件的發生,所發生事件可以引發新的時間等。人們希望通過計算機模擬理解系統行為,評價和涉及真實世界中實際的或者所需的系統。
* 模擬框架:模擬按照事件的發生時間為順序來處理,在模擬系統裡用一個優先佇列儲存,已知在將來某些特定時刻發生的事件,系統的執行就是不斷從優先佇列裡取出等待事件,一個個的處理,直到模擬結束(或者沒有事件了,佇列為空)。要注意的是,一些時間在處理時,可能會引發新的事件。
* 事件佇列:一個儲存模擬中快取的事件的優先佇列。
* 宿主系統:事件有一個host引數,用於表示該事件的發生所在的模擬系統。事件執行時,可能會訪問宿主系統。
* 二叉樹的連結實現:用一個數據單元表示一個二叉樹結點,通過子結點的連結(指標)建立結點間的聯絡。(與連續表的連結實現類似)。
* 二叉樹結點類:結點類的建構函式接受三個引數,分別為它的結點資料、左子結點連結、右子結點連結(預設值為None,使用預設值時構造的就是葉結點)。
* 非遞迴的二叉樹遍歷演算法:迴圈、生成器
* 非遞迴後序遍歷:這種遍歷方法比較麻煩,因為一個根結點要經過三次,第一次經過去處理左子結點,第二次經過是從左子結點返回來去處理右子結點,最後返回來才去處理根結點。對非遞迴先根序遍歷演算法進行修改就能得到。
* 下行迴圈:迴圈體中的內層迴圈,目標是找到下一個應該訪問的結點。
* 二叉樹類:(為什麼已經有了二叉樹結點類,還要定義一個二叉樹類呢?直接基於結點類構造的二叉樹具有遞迴的結構,可以很方便地遞迴處理,但這樣的"二叉樹結構"有不統一之處:用None表示空樹,但是None的型別並不是BinTNode。此外還有一個原因是,基於二叉樹結點類構造的結構不是一種封裝良好的抽象資料型別。)
* 帶權擴充二叉樹:給擴充二叉樹的每個外部結點標一個數值,用來表示與該葉有關的某種性質,稱為這個結點的權。進而得到的就是~,其外部路徑長度是WPL=i從0到m,wili之和。
* 哈夫曼樹(最優二叉樹):設有實數集W={w0,w1,...,wm-1},T是一顆擴充二叉樹,它的m個外部結點分別以wi(i=0,1,2...n-1)為權,而且T的帶權外部路徑長度WPL在所有的這樣的擴充二叉樹中達到最小,那麼,T就被稱為資料集W的最優二叉樹或者哈夫曼樹。
* 哈夫曼演算法:就是構造哈夫曼樹的演算法 。演算法的輸入為實數集W={w0,w1,...,wm-1},F是一個包含k棵二叉樹的集合,開始時k=m,F={T0,T1...Tm-1},其中的每個Ti都是一棵只包含權威Wi的根結點的單點二叉樹。重複以下步驟,直到集合F中只剩下一棵樹,這棵二叉樹就是集合W上的哈夫曼樹(注意最後得到的哈夫曼樹不唯一):1.構造一個新二叉樹,它的左右子二叉樹是從集合F中選取的兩棵權值最小的二叉樹,構造出的這棵新二叉樹的權值設定為那兩棵二叉樹的權值之和。2.將那兩棵二叉樹從集合F中刪掉,把新構造出的這棵二叉樹加入到集合F中。
* 哈夫曼編碼:把哈夫曼演算法中的集合F定義為需要編碼的字元集合,實數集W定義為各個字元在實際資訊傳輸或儲存中出現的頻率,構造出一棵哈夫曼樹,樹中的每個分支結點到它的左子結點的邊上標0,到它的右子結點的邊上標1,以從根節點到一個外部結點的路徑上的0/1序列,作為這個外部結點的標記字元的編碼,這樣得到的就是哈夫曼編碼。
* 最優編碼:哈夫曼編碼是給定字符集(出現頻率確定)的最優編碼。儲存傳輸時平均開銷最小;對任意一對字元Ci和Cj,字元Ci的編碼不是Cj編碼的字首。
* 有序樹和無序樹:根據子樹排列順序有無意義,可把樹分為有序樹和無序樹。由於計算機表示時存在自然的順序,所以資料結構中主要考慮有序樹,下面預設樹是有序樹。
* 樹和樹林:樹和二叉樹差不多,不同的地方有:樹的分支結點的度數沒有限制,可以有任意的子結點;二叉樹的子結點有左右之分,而在樹中沒有這個概念,但是因為樹的子結點是有序的,所以可以說第一個子樹,下一個子樹...;樹林就是樹的集合。
* 樹根:非空樹中,有且僅有一個的起始結點就是樹根。
* 有序樹林和無序樹林:根據樹林中的樹排列順序有無意義,可分為~~,有序樹林可以說第一棵樹,下一棵樹...
* 搜尋樹:前面提過。在一般的狀態搜尋問題裡,搜尋過程中經歷的狀態(結點)和狀態之間的轉移關係(邊)形成了一棵樹,稱為‘搜尋樹’。
* 子結點引用表示法:子指標表示法。用一個數據單元表示結點,通過結點間的連結表示樹結構,可以用list表示每個結點。(由於樹的結點的度數不定,所以要給結點引用域設定一個值,度數超過這個值的樹不支援)(缺點:會出現大量空閒的節點引用域,造成浪費)
* 父結點引用表示法:為克服子結點引用表示法空間浪費的缺點,又提出了父結點引用表示法。在樹中,除了根結點外,每個分支結點都有且僅有一個父結點。所以,每個結點(根結點除外)可以只用一個引用域來儲存父結點的引用。方法:用一個順序表表示一棵樹,每個表元素對應於一個結點,記錄結點資料和父結點引用。(優點:空間開銷小。缺點:要訪問一個結點的子結點,需要通過查詢,複雜度是O(n))
* 長子-兄弟表示法:樹的二叉樹表示。二叉樹的左子結點表示樹結點的第一個子結點,右子結點表示結點的下一個子結點。(這樣只適用於結點度數小於等於2的樹。)
***
### 2.
* 5*6=30  (預設構造出的樹是一棵。) 
### 3.
*  1+1+2+1=5              (預設有序樹。)
### 4.

* n0=n2+1=11
### 5.
* 全部結點至少=n2=n0-1
### 6.
* 高度至少為5
### 7.
* 歸納總結法  ???
### 8.
* ???
### 9.
* a) 先根序:ABDGI EHJK CF ; 中根序:DGI B E JHK A FC;後根序:IGD JKHE B FC A
* b)轉換成了一個包含五個樹的樹林,樹林怎麼便利???
* c)
### 10.
* n=15,I=n-1=14,E=I+2*n=44
### 11.
* + A * B C - / D E
### 12.
* 不能。證明:假設一個結點的度數為1,僅從先根序列和後根序列不能判斷出這個結點的子樹是左還是右。
### 13.
* a) 沒有左子結點 
* b)沒有右子結點
* c)沒有左子結點
* d)沒有子結點  (或者abcd都是空樹)
### 14.
* 沒有左子結點
### 15.
* ???
### 16.
* 字首編碼:在一個字符集中,任意一個字元都不和其他字元的字首相同。
* 哈夫曼編碼得到的二進位制編碼不會出現字首編碼。
* 論文查重?
### 17.
* a)是,10和10111
* b)是 ,10和101
* c)不是
* d)不是
### 18.
* a: 00000
* b: 1000
* c: 00001
* d: 010
* e: 101
* f: 1001
* g: 011
* h: 110
* i: 001 
* j: 111
* k: 0001 
### 19.
* 跟18類似,略
### 20.
* 結構歸納法
### 21.
* 葉結點個數=m+k-1
### 22.
* ???
### 23.
* ???
### 24.
* 寬度優先遍歷(樹,斷言一個結點的度數不為1)
* 用一個變數記錄當前層數,變數小於高度-2時,proc為斷言一個結點的度數為2;等於高度-1時,???
### 25.
~~~
def proc(t):
    x=t.left
    y=t.right
    t.left=y
    t.right=x
def preorder(t,proc):
    if t is None:
        return
    proc(t)
    preorder(t.left)
    preorder(t.right)
~~~

***
***
## 第七章、圖
### 1.複習下面概念
* 圖:在計算機的資料結構領域,圖被看作是一類複雜資料結構,可用於表示各種複雜聯絡的資料集合。定義:一個圖是一個二元組,G=(V,E),V是圖的頂點集合,E是圖的邊的集合。
* 二元關係:兩個集合,他們的元素互相之間有某種聯絡,這兩個集合就是二元關係。
* 拓撲結構:把實體抽象成與其大小、形狀無關的“點”,而把連線實體的線路抽象成“線”,進而以圖的形式來表示這些點與線之間關係的方法,其目的在於研究這些點、線之間的相連關係。
* 圖論:數學領域裡的一個研究分支,專門研究圖這種拓撲結構。
* 圖演算法:研究圖的演算法。
* 頂點和邊:圖中的基本個體,用來表示任何討論中需要關心的實體。
* 有向圖、無向圖:有向圖的邊有方向,是頂點的有序對;無序圖的邊沒有方向,是頂點的無序對。
* 有向邊及其始點和終點:用尖括號形式表示,例如<v,v'>表示從頂點v到頂點v'的有向邊,v是邊的始點,v'是邊的終點。
* 無向邊:用圓括號表示,例如(v,v')。
* 鄰接(頂)點:對於圖G=(E,V),如果邊<vi,vj>屬於E,就稱vj是vi的鄰接頂點或者鄰接點,也稱這條邊是和頂點vi相關聯的邊,或者vi的鄰接邊。
* 鄰接邊:上面有
* 鄰接關係:邊集合E表示的是頂點之間的鄰接關係。
* 完全圖:任何兩個頂點之間都有邊的圖就是完全圖。
* 頂點的度:和這個頂點鄰接的邊的條數。
* 入度、出度:以這個頂點為終點邊的條數;以這個頂點為始點的邊的條數。
* 路徑:對於圖G=(E,V),如果存在頂點序列Vi0,Vi1,...,Vim,使得(Vi0,Vi1),(Vi1,Vi2)...(Vim-1,Vim)都屬於E,就說從頂點Vi0到Vim存在路徑,<Vi0,Vi1,...,Vim>就是從頂點Vi0到Vim的一條路徑。
* 路徑的長度:路徑上邊的條數。
* 迴路(環):起點、終點是同一個頂點的路徑。
* 簡單迴路:一個環路除了起點和終點相同外,其他頂點都不相同,就稱這個環路是~。
* 簡單路徑:'內部'沒有迴路的路徑。換句話說,就是起點終點可能相同,但其他頂點都不相同。(所以,簡單迴路屬於簡單路徑。)
* 有根圖:如果在有向圖G中存在一個頂點v,從v到圖中的其他頂點都有路徑,就說頂點v是圖G的一個根。(可能不唯一)。
* 連通:如果在無向圖G中,存在從vi到vj的路徑,就說vi,vj之間連通。
* 連通無向圖(連通圖):如果一個無向圖中任意兩個頂點之間都連通,就說這個無向圖是~。
* 強連通有向圖:如果一個有向圖中的任意兩個頂點vi,vj,從vi到vj連通,從vj到vi也連通,就說這個有向圖是~。
* 最小連通圖:一個連通圖,去掉任一個邊就不再是連通圖,就說這個連通圖是~。(包含n個頂點的~恰有n-1個邊)
* 最小有根圖:一個連通圖,去掉任一條邊就不再是有根圖,就說這個有根圖是~。(包含n個頂點的~恰有n-1個邊)
* 無向樹:滿足‘包含n個頂點的最小連通圖恰有n-1個邊’這個性質的圖G稱為無向樹。(從無向樹的任意一個頂點出發都能到達任何其他頂點。)
* 有向樹:滿足‘包含n個頂點的最小有根圖恰有n-1個邊’這個性質的圖G稱為有向圖。(這個有根圖的根可以看做樹根。)
* 子圖:對於圖G'(V',E'),G(V,E),如果V'包含於V,E'包含於E,就說G'是G的子圖。子圖就是原圖的一部分,而且滿足圖的基本定義。
* 無向圖的連通子圖:一個無向圖可能不是無向連通圖,但是它的子圖可能是連通的,這種子圖就稱為原圖的~。
* 有向圖的強連通子圖:一個有向圖可能不是強連通的,但是它的子圖可能是強連通的,這種子圖就稱為原圖的~。
* 極大連通子圖(連通分量):如果有向圖G'是G的一個連通子圖,並且,G'不‘真包含於’G,就說G'是G的極大連通子圖。(就是說G'的V'和E'都是極大的,不能擴充的。)
* 極大強連通子圖(強連通分量):與極大連通分量的定義類似。(這個要注意##看書的例子)
* 帶權圖:圖G的每條邊都被賦予一個權值,就稱G是帶權圖。
* 網路:就是帶權的連通無向圖。
* 鄰接矩陣:是圖的最基本表示方法。鄰接矩陣是表示圖中頂點間鄰接關係的方陣。對於有n個頂點的圖G(V,E),它的鄰接矩陣是一個n*n方陣,圖中的每個頂點按順序對應於矩陣裡的一行(或者一列),矩陣元素表示圖中的鄰接關係。
* 鄰接表表示法:為圖中每個頂點關聯一個表,用來記錄這個頂點的所有鄰接表。
* 頂點表:頂點是圖中最基本的成分,通常有標識,也可以順序編號,順序編號後就可以通過編號'隨機訪問'。圖中頂點通常不變,所以可以用一個順序表來實現。(每個頂點還要關聯一個表示它的鄰接邊表的連結串列。)
* 圖的遍歷(周遊):按照某種方式系統的訪問圖中每個頂點而且是僅訪問一次的過程。也叫做周遊。
* 可達頂點:從一個頂點v出發,如果到一個另外的頂點v'有路徑,v'就是v的可達頂點。
* 深度優先遍歷:1.假定從頂點v出發,先訪問頂點v並將其標為'已訪問',檢查v的鄰接頂點,從中選擇一個'未訪問'的,從它出發繼續進行~(這裡呼叫遞迴),直到這條路徑上的終點沒有鄰接頂點時進行回溯;2.反覆進行上述操作(這裡是呼叫遞迴),直到v的可達頂點都是'已訪問';3.檢查圖中是否還有'未訪問'的頂點,如果有,說明這個圖不是連通的,這時就要另選出一個'未訪問'頂點,從它出發重複前面的過程,直到它的可達頂點都已訪問,也直到圖的所有頂點都已訪問。
* 寬度優先遍歷: 1.假定從頂點v出發,首先訪問v並將其標為'已訪問',再檢查v的未訪問的鄰接頂點(假設為v0,v1,...,vm),依次全部訪問並標為已訪問;2.再檢查v0,v1,...,vm的鄰接頂點......如此的進行下去,知道v所有可達頂點都已訪問;3.檢查圖中是否還有'未訪問'的頂點,如果有,說明這個圖不是連通的,這時就要另選出一個'未訪問'頂點,從它出發重複前面的過程,直到它的可達頂點都已訪問,也直到圖的所有頂點都已訪問。
* 深度優先搜尋(DFS)序列:就是用深度優先遍歷得到的序列。
* 寬度優先搜尋(BFS)序列:就是用寬度優先遍歷得到的序列。
* 生成樹:性質:對於一個連通無向圖/強連通有向圖G(或者是有跟有向圖G'),從G的任一頂點v0出發(或者是從有根有向圖G'的根v0'出發),到圖中的其他頂點都存在路徑。如果圖G有n個頂點,必然可以找到G中的一個包含n-1條邊的集合且這個集合包含了從v0出發到其餘所有頂點的路徑(這裡是不是用包含不合適,邊集合能包含路徑嗎?組成??)。  對於無向圖,滿足這個性質的子圖就是它的一個最小連通子圖;對於強連通有向圖或者是有根有向圖,滿足這個性質的子圖T'就是它的一個最小有根子圖,也叫做G'的一棵生成樹。
* DFS生成樹:跟'搜尋樹'類似。按照深度優先遍歷方式,在遍歷中記錄訪問的所有頂點和邊,就得到原圖的DFS生成樹。
* BFS生成樹:按照寬度優先遍歷方式,在遍歷中記錄訪問的所有頂點和邊,就得到原圖的BFS生成樹。
* (網路的)最小生成樹:網路就是帶權連通無向圖。假定G是一個網路,G的一個生成樹的所有邊的權值之和就是這個生成樹的權。G可能有不止一個生成樹,這些生成樹中權值最小的一個叫做G的最小生成樹。
* Kruskal演算法:一種構建最小生成樹的簡單演算法。是基於簡單連通分量的最低代價互聯。簡單說就是不停從邊集E中找一條在T'=(V',{})中的兩個端點是兩個不同連通分量的邊e,把這條邊加入T'(這次操作使T'少了一個連通分量)。
* 連通分量的代表元:在Kruskal演算法實現中有一個問題,怎麼判斷兩個頂點在T'中是兩個不同連通分量呢?一種有效方法就是為每個連通分量確定一個代表元,代表元相等就是互相連通,就是同一個連通分量。
* Prim演算法:構建最小生成樹的另一種演算法,基於最小生成樹的一種重要性質-MST性質。
* MST性質:設G=(V,E)是一個網路,U是V任意一個真子集,設邊e=(u,v)屬於E,其中u屬於U,v屬於V-U,(也就是說,邊e的一個頂點u在U中,另一個不在),設滿足e的邊的集合是E1,邊e'是E1中權值最小的一個,那麼G必定有一棵包含邊e'的最小生成樹。
* 最短路徑問題:對於帶權有向圖和網路(帶權無向圖),從頂點v到v'的所有路徑中長度最短的那條就是最短路徑。找到最短路徑的問題就是~。
* 帶權圖上的路徑長度:邊上附有權,路徑長度就是路徑上的所有邊的權值之和。
* Dijkstra演算法:求單源點最短路徑,即從一個頂點出發到其餘頂點的最短路徑問題。(具體演算法書上有,太多不打了。)
* Floyd演算法:求任意頂點間的最短路徑,即計算出各對頂點間的最短路徑及其長度。(這個演算法操作機械繁瑣,缺乏直觀,不適合人工操作,我不甚理解)(具體演算法書上有,太多不打了。)
* AOV網:AOV網就是有向圖的頂點表示活動,邊表示活動先後順序。
* 拓撲排序和拓撲序列:對於給定的AOV網N,如果N中的'所有'頂點能排出一個滿足“若N中存在從頂端vi到vj的路徑,那麼在S裡vi就排在vj之前”的線性序列S=vi0,vi1...,vin-1,就說S是N的一個拓撲序列,這個操作就是拓撲排序。(具體的拓撲排序演算法書上有,太多了不打了。)
* 制約關係:一個活動發生了,另一個活動才能發生,這個關係就是~。
* AOE網(一類帶權有向圖):AOE網就是帶權有向圖的頂點表示事件,邊表示活動,邊的權表示活動持續時間。
* 關鍵路徑:完成整個工程的最短時間,就是從開始頂點到結束頂點的最長路徑的長度。(這個演算法我不甚理解)
***
### 2.

* 圖略
* 圖略
* 圖略
* 不是,頂點b,e和它們之間的兩個邊
### 3.
* 假設有n個頂點,n-1<=邊的個數<=n*(n-1)
* 可能有,可能沒有
* 出度範圍為[0,n-1]
* 入度範圍為[0,n-1]
### 4.
* (n^2)/2,n*(n-1)
### 5.
* 答案和4一樣
### 6.
* 環路是起點終點相同的無向圖,要求的問題資訊不足,最少是n個
### 7.
* 9
***
> 注意:在下面幾道題,頂點我都用下標來表示,比如頂點v2,就用2來表示。
***
### 8.
* 0149
* 023149
* 02349
* 025649
* 02375649
### 9.
* 1.
* 2.
* 3.
### 10.
* 圖略
* 圖略
* 014687523
* 0125 34678
* 用(一個頂點的下標,下一頂點的下標,兩個頂點之間邊的權)形式表示:(1,4,3)(3,6,4)(3,1,6)(2,5,6)(5,6,7)(7,8,8)(6,7,9)(2,0,9)
* (1,4,3)(1,3,6)(3,6,4)(6,5,7)(5,2,6)(6,7,9)(7,8,8)(2,0,9)
### 11.
* (0,2,5)(2,3,9)(0,5,9)(3,7,11)(3,1,12)(3,4,15)(4,9,19)(5,6,20)(9,8,32)

### 12.
* 0124 3765 89
* 0421 7368 59
### 13.
*
### 14.
*
*
### 15.
### 16.
### 17.

***
***
## 第八章、字典和集合
### 1.複習下面概念
* 資料儲存和訪問:~是計算機最基本的功能。
* 檢索(檢查):找到資料的儲存位置。知道了資料儲存在哪裡,只需要常量時間就可以得到它。
* 檢索碼(關鍵碼):檢索時提供的資訊。
* 關聯資料:資料項由兩部分組成,一是關鍵碼,二是與關鍵碼相關聯的實際資料。
* 字典(查詢表、對映、關聯表):支援基於關鍵碼的資料儲存與檢索的資料結構。
* 靜態字典:字典建立之後,內容和結構不在變化,主要操作只有檢索,實現時只需考慮檢索效率。(適合用最佳二叉排序樹實現)
* 動態字典:字典初始建立之後,內容和結構將一直處於動態變動之中,主要操作有檢索、插入、刪除,實現時不僅要考慮檢索效率,插入、刪除效率也要考慮。
* 平均檢索長度:在一次完整檢索過程中,比較關鍵碼的平均次數就稱為~。
* 索引:做基於關鍵碼的索引,就是要實現從關鍵碼到資料儲存位置的對映。這種對映就是索引。
* 關聯:一個數據項就是一個二元組(關鍵碼、值),稱之為關聯。
* 有序集合:元素之間存在某種順序儲存的集合。(比如整數的小於等於關係,字串的字典序)
* 二分法檢索:1.從元素範圍中取出位置居中的那個資料項,用檢索關鍵碼和它比較,如果想等檢索結束。如果檢索關鍵碼較大,則把檢索範圍修改為中間項之後的半區間;如果較小,則修改為中間項之前的半區間。2.重複步驟1,如果檢索範圍中已經沒有資料了還沒找到相等的關鍵碼,就是檢索失敗結束。
* 判定樹:用二叉樹來表示關鍵碼排序的順序表字典,樹中結點所標的數是資料項的關鍵碼。(樹中除葉結點外的每個分支結點都是一個關鍵碼範圍的居中位置的那個關鍵碼,它的左子樹是這個關鍵碼範圍的最小值,右子樹是最大值)
* 散列表(雜湊表):如果資料項連續儲存,而關鍵碼就是儲存資料的地址(或下標)。在這種情況下,基於關鍵碼能最快找到所需的資料(O(1)時間)。但是,關鍵碼不一定都是整數,可能是字串,那就不能作為地址(下標)了。況且,即便關鍵碼是整數,也可能因為取值太大而不適合作為地址(下標)。可以考慮通過一個變換(一個計算)把它們對映為一種下標。這樣做,就把基於關鍵碼的檢索變為了基於整數下標(儲存資料的地址)的直接元素訪問,有可能得到一種高效的字典實現技術。
* 雜湊:散列表的基本思想:如果一種關鍵碼不能或者不合適作為資料儲存的下標,可以通過一個變換或者一種計算把它們對映成一種下標。
* 雜湊函式(雜湊函式、雜湊函式):從可能的關鍵碼集合到一個整數區間(下標區間)的對映就是~。
* 衝突(碰撞):通常情況下,雜湊函式h是從一個大集合到一個小集合的對映。這樣就必不可免的出現多個不同的關鍵碼被h對應到同一個下標位置的情況,也就是說,必然會出現key1 != key2,但是h(key1) = h(key2)的情況,人們就說這裡出現了衝突(碰撞)。
* 同義詞:出現key1 != key2,但是h(key1) = h(key2)的情況,人們就說這裡出現了衝突(碰撞),也稱key1和key2是雜湊函式h下的同義詞。
* 負載因子:負載因子=(散列表中當時的實際資料項數)/(散列表的基本儲存區能容納的元素個數),~越大,越容易出現衝突。  
* 數字分析法(僅適用於關鍵碼集合已知):對於給定的關鍵碼集合,分析關鍵碼中各位數字的出現頻率,從中選出分佈情況較好的若干數字作為雜湊函式的值。例子:我們專業的學號統一是 201517040xx ,xx是班級內學號,比如我就是20151704078,那麼如果給定的關鍵碼集合是我們這屆測控專業的86名學生的學號,那麼用數學分析法選取個十位作為雜湊函式的值,得到的h(k)集合就是{01,02,...,78,...86}。
* 摺疊法:將較長的關鍵碼切分為幾段,再通過某種運算將他們合併。例如:關鍵碼都是十一位整數,可以先把它們每三位切分為一段,再把得到的三位整數相加並捨棄進位,以得到的結果作為雜湊函式的值。例項:20 / 151 /  704 /  078,三位整數相加並捨棄進位:20+151+704+078(捨棄進位)= 843,這樣就把11位整數對映到[0,999]的區間了。
 * 中平方法:先求出關鍵碼的平方,然後取出中間的幾位作為雜湊值。
> 數學分析法、摺疊法、中平方法都是適用於整數關鍵碼的雜湊方法。對於整數關鍵碼,雜湊函式的設計有兩個追求:1.把較長的整數關鍵碼對映到較小的區間;2.儘可能的消除關鍵碼和對映值之間明顯的規律性。通俗的說,雜湊函式的對映關係越亂越好,越不清晰越好。
* 除餘法:關鍵碼key是整數,用key除以某個不大於散列表長度m的整數p,以得到的餘數作為雜湊地址(或者是餘數+下標值)。為了儲存方便,人們經常將m取為2的冪值,此時p可以取小於m的最大素數(就是m-1,h=key mod p;如果餘數+下標值,並且連續表的下標從1開始,h=key mod p + i,i for [1,2,3,...])。注意p不要取偶數,如果用偶數作為除數,就會出現偶數關鍵碼對映到偶數雜湊值,奇數關鍵碼對映到基數雜湊值的明顯規律。除餘法的缺點:相近的關鍵碼將對映到相近的值。
* 基數轉換法:基數轉換法既適用於整數關鍵碼,還能用於字串關鍵碼。 1.對於整數關鍵碼:取一個正整數r,把關鍵碼看做r進位制的數,再把它轉換為十進位制或者二進位制數。通常r取素數以減少規律性。例如:取r=13,對於關鍵碼335647,計算得到:335647(13進位制) = 7*13^0+4*13^1+6*13^2+5*13^3+3*13^4+3*13^5=6758172 (十進位制),這時的對映值的取值範圍可能不合適,可以考慮用除餘法、摺疊法、或者刪除幾位數字的方法將其歸入所需的下標範圍。 2.對於字串關鍵碼:最常見的是把字串中的每個字元看做一個整數(怎麼看做呢?用字元的編碼值,即ASCII碼或者Unicode碼),再取一個正整數x,把得到的整數關鍵碼看做x進位制的整數(建議取素數29或31為基數)。這樣,就把字串轉換為了整數(x進位制),再用整數關鍵碼的雜湊方法把結果歸入散列表的下標範圍(比如除餘法)。
* 內消解技術:在用雜湊技術實現字典時,會出現衝突。內消解方法就是在基本的儲存區內部解決衝突問題。內消解的基本方法稱為開地址法。開地址法的基本想法是:當準備插入資料卻發現衝突時,設法在基本儲存區(順序表)裡為需要插入的資料項另行安排一個位置。為此,需要設計一種系統的易於計算的位置安排方式,稱為探查方式。探查方式又分為線性探查、雙雜湊探查。(書中用例項詳細講解了兩者和其各自優缺點。)(Python的dict、set就是基於散列表技術實現,採用的內消解技術解決衝突。)
* 外消解技術:在用雜湊技術實現字典時,會出現衝突。外消解方法就是在基本的儲存區外部解決衝突問題。1.溢位區方法:另行設定一個溢位儲存區,當插入資料項時沒發生衝突就直接插入基本儲存區,而當發生衝突時,就把相應資料和關鍵碼一起存入溢位儲存區。溢位儲存區的資料項也是順序排列。檢索、刪除操作就是先找到雜湊位置,如果那裡有資料項,但是檢索關鍵碼和資料項關鍵碼不匹配,就轉到溢位儲存區順序檢索,直到找到要找的關鍵碼,或者確定相應資料不存在。缺點:當溢位區的資料越來越多時,字典的效能將會趨於線性(因為溢位區是順序檢索)。 2. 桶雜湊:散列表的每個元素只是一個引用域,引用著一個儲存實際資料的儲存桶。設計想法:所有的資料項都不存放在基本儲存區中,而是另行存放,在散列表裡儲存對資料項的引用。桶雜湊中最簡單的是拉鍊法:在拉鍊法中一個儲存桶就是一個連結的結點表。字典中的資料項不儲存在散列表的主儲存區,而是存入相應結點表中的結點裡,具有同樣雜湊值的資料項都儲存在這個雜湊值對應的連結表中。插入:先找到關鍵碼的雜湊位置,把資料項插入到連結表的前端。檢索:通過雜湊函式找到相應的結點表,順序檢查相匹配的資料項。刪除就是先檢索,再刪除掉資料項。
* 溢位區方法:上面有
* 桶雜湊:上面有
* 拉鍊法:上面有
* 集合與元素:集合主要關注個體與個體的彙集之間的關係。集合是個體的彙集。元素就是個體。在電腦科學領域,具體的資料項就是個體,資料項的彙集就是集合。
* 屬於關係:一個個體是一個集合中的元素,就說這個個體屬於這個集合。
* 全集:當前的所有元素都包含的集合就是全集。
* 外延表示:明確列出集合中的所有元素。用一對花括號{},在花括號中列出集合所有的元素,就是~。
* 內涵表示(集合描述式):給出集合中所有元素應滿足的性質。稱為~或者描述式。
* 基數:一個集合中元素的個數稱為這個集合的~。
* 空集:集合中不包含元素。
* 並集:S並T的元素或者是S的元素,或者是T的元素。
* 交集:S交T包含且僅包含那些既屬於S又屬於T的元素。
* 補集:S-T包含且僅包含那些屬於S但是不屬於T的元素。
* 排序線性表:有序集合+二分法
* 位向量:位向量是集合的一種特殊實現技術。如果程式中需要實現、使用的集合都是某個集合U的子集,那麼:假設集合U元素個數是n,給集合U的每個元素確定一個編號作為這個元素的下標;對任何一個要考慮的集合S(肯定是U的子集),可以用一個n位的二進位制序列vs來表示,方法是:對於集合U的每個元素,如果屬於那麼在vs中就是取值1,不屬於就是取值0。二進位制序列s就是位向量。
* 二叉排序樹:是一種儲存資料(結點存放關鍵碼及關聯的資料)的二叉樹,把字典的資料儲存和查詢功能融合在二叉樹的結構裡。性質:一棵二叉排序樹或者是空樹,或者滿足以下性質:其根結點儲存這一個資料項(關鍵碼及關聯資料);如果結點的左子樹不空,那麼其左子樹的所有結點儲存的關鍵碼都小於它的關鍵碼的值;如果結點的右子樹不空,那麼其右子樹的所有結點儲存的關鍵碼都大於它的關鍵碼的值;非空的左子樹或者右子樹也是二叉排序樹。(遞迴結構)(~可以直接作為字典,把實際資料存放到結點中;也可以作為索引結構,實際資料另行儲存,結點中存放的關鍵碼所關聯的是實際資料的儲存位置資訊。)
* 最佳二叉排序樹:是使檢索的平均比較次數達到最小的二叉排序樹,即使關鍵碼在擴充二叉排序樹的平均檢索長度E(n)的值達到最小。又分為簡單情況(檢索概率相同)、一般情況(檢索概率不同)。
* 動態規劃:在整個計算過程中,逐步建立並維持好一大批子問題的解,在每一部基於這些小的子問題的解構造出更大的子問題的解,最終構造出整個問題的解(對於最佳二叉排序樹問題,就是維護較小的最佳二叉排序樹)。在設計複雜演算法是非常有用。
* 帶權路徑長度:
* 平衡二叉排序樹(AVL樹):實際使用中非常需要既能夠維持高效檢索,由能夠支援動態操作的排序樹結構。~是一類特殊的二叉排序樹。~或者是空樹,或者滿足以下性質:其左右子樹都是AVL樹(遞迴結構),其左右子樹的高度之差的絕對值不能超過1。
* 平衡因子(BF):平衡二叉樹的每個結點中會記錄這個結點的平衡因子。一棵樹的~就是其左子樹的高度減去其右子樹的高度,對平衡二叉樹來說,BF取值為1,0,-1。
* 失衡和調整:在插入、刪除等動態操作中,會改變樹的結構,如果結點的平衡因子大於1或者小於-1,就是失衡。調整就是在發現失衡後,只需要在最小不平衡子樹的根結點附近做區域性調整,就可以把該子樹的根的平衡因子的變為0(或者變為1或者-1就可以了),而且保證這棵子樹的高度與插入之前相同且資料排序正確。
* 多分支排序樹:一個結點裡可能存出多個關鍵碼,需要維持這些關鍵碼和相應子樹關鍵碼的排序關係。包括B樹、B+樹。
* B樹:一種動態的多分支排序樹,通過多種調整多分支排序樹結構的手段,來維護結點分支數,保證樹具有良好結構。
* B+樹:
***
### 2.
* 保持唯一性:O(n),O(n),O(n)
* 不~            :O(n),O(1),O(n)
### 3.
* s1 : 10010 11110 0010
* s2 : 01100 01110 1011 
* s3 : 11010 11100 0001
* 餘下的略
### 4.
* a) 給定一組12個關鍵碼的序列為[46,78,35,99,70,48,121,10,66,54,26,37],用 k % 17 作為雜湊函式且採用線性探查法消解衝突,得到關鍵碼相應儲存位置的序列為[12,10,1,14,2,15,3,11,16,4,9,5]
* b) 採用h2=k % 5 + 2 的雙雜湊技術消解衝突,得到關鍵碼相應儲存位置的序列為[12,10,1,14,2,6,4,3,15,5,9,?],關鍵碼37用該方法得不到儲存位置,因為計算結果是一個迴圈‘3,5,2,4,6, 3,5...’,迴圈中的儲存位置都已被佔用。
* 6種
### 5.
* 沒有第五題
### 6.
* 取中間位置的數4作為樹根,向下遞迴構建出樹。
### 7.
*  略
### 8.
* 二叉排序樹刪除很簡單,不用想最佳二叉排序樹一樣還要考慮負載因子。
### 9.
* (5+4+3+2+1)*2=30種
* 基本想法:序列要滿足:(#表示先於)65#7,65#96,7#43,43#36,43#57,96#74  ;序列一共7位,第一位必須是樹根65,而且96#74,所以96不能在序列的首位和末位,其他5位都可以;74必須在96之後,這樣就有5+4+3+2+1種序列;36和57的位置可以互換,所以總共有(5+4+3+2+1)*2=30種序列。
### 10.

* 修改: “採用字典序”我審題出錯了,以為是要考字串關鍵碼的基數轉換法...
* 先把字串關鍵碼轉換為整數關鍵碼,程式碼如下:
~~~
def str_hash(s):
    h1=0
    for c in s:
        h1=h1 * 29 + ord(c)
    return h1
L={'JAN' , 'FEB' , 'MAR' , 'APR' , 'MAY' , 'JUN' , 'JUL' , 'AUG' , 'SEP' , 'OCT' , 'NOV' , 'DEC'  }
print([str_hash(s.title()) for s in L])    
~~~
結果就是: [58161, 68935, 65737, 58027, 72844, 65735, 65157, 67684, 69426, 60216, 61897, 67691]
後面工作就很簡單了,略。
### 11.
* 略
### 12.
* 四階B樹:樹根(10 ); 樹根左子結點(3 5 7),樹根右子結點(13 15 18);樹根左子結點的子結點依次為(1 2 ),(4),(6),(8 9),樹根右子結點的子結點依次為:(11 12)(14) (16 17)(19 20)
* 四階B+樹:樹根(10 11); 樹根左子結點(4 8 10),樹根右子結點(14 18 20);樹根左子結點的子結點依次為(1 2 3 4),(5 6 7 8),(9 10),樹根右子結點的子結點依次為:(11 12 13 14)(15 16 17 18) (19 20)
### 13.
* 我的思路:不妨設兩個關鍵碼分別為x,y且x>y ;檢索關鍵碼x的位置,用變數a記錄x的結點位置,並用一個棧儲存從樹根到a的途徑結點;如果a有子結點,對a的兩個子樹進行對y的檢索,若y檢索成功,結果就是a的父結點,結束。若a沒有子結點或者a有子結點但y檢索失敗,就往下進行;取棧頂元素並用變數b表示,對b的另一個子樹進行對y的檢索,若y檢索成功,結果就是b,結束。若b只有一個子樹或者y檢索失敗,就繼續進行這一步,直到檢索成功,得到結果,結束。

***
## 第九章、排序
### 1.複習下面概念
* 排序問題:整理資料的序列,使其中元素按特定順序排列的操作。
* 全序關係:集合S上的一個全序關係,記為≤,是集合S上的一種自反、傳遞、反對稱的關係。對集合中的任意兩個元素x,y,都有x≤y,或者y≤x 成立。
* 反對稱關係:反對稱就是如果a和b滿足關係R,那麼反個順序,b和a肯定不滿足關係R。R稱為反對稱關係。舉個例子,真包含關係⊂是反對稱的,A⊂B則B一定不⊂A;包含關係⊆不是對稱也不是反對稱的,稱為非對稱關係,因為A⊆B 不能判斷B是否⊆A;相等關係=是對稱的,因為A=B可以推出B=A 。
* 等價類:在實際情況中很多時候,需要考慮的序把被排序資料歸結為一組(有序的)等價類,同屬一類的資料元素都被認為是相等的。
* 排序方法(排序演算法):完成排序工作的方法。
* 堆排序演算法:如果一個連續表裡儲存的資料是一個小頂堆,按照優先佇列的操作方式反覆彈出元素,就能得到一個遞增序列。不難想象,這就形成了一種可用於完成對連續表中元素的排序工作的可行方法。
* 基於關鍵碼比較的排序:就是根據記錄中所考慮的那個關鍵碼的序關係整理記錄序列,使之成為按關鍵碼排序的序列。
* 排序碼:在一次排序中考慮的關鍵碼。
* 內排序:在一次排序工作的執行過程中,如果待排序的記錄全部儲存在記憶體,這種工作就稱為~。
* 外排序:針對外存(磁碟等)資料的排序工作稱為~。
* 外排序演算法:有些排序演算法特別考慮到外存的特點,因此特別適合外存排序工作,這類演算法也被稱為外排序演算法。
* 歸併排序演算法:
* 排序中的基本操作(關鍵碼比較、資料記錄移動):確定資料的順序關係;調整資料的位置 和/或 順序。
* 最優排序演算法:最理想的排序演算法是O(n log n)時間,O(1)空間,穩定性,最好還具有適用性(不知道是否存在)。目前,蒂姆排序在實際應用中處於最優地位。
* 原地排序演算法:排序工作可以在原序列(表)中完成,只需要幾個簡單變數作為操作中的臨時儲存。具有這種性質的演算法也被稱為~。
* 穩定性:序列中可能會存在排序碼相同的記錄,如果一個排序演算法能夠維持序列中所有排序碼相同的記錄的相對位置,就說這個排序演算法是穩定的。
* 適應性:如果排序演算法對接近有序的序列工作的更快,就說這種排序演算法具有適應性。
* 插入排序:從序列的第二個元素開始,把它和之前的元素逐個比較,直到找到不大於之前的元素的位置,就把它插入到該位置。重複上個操作,直到未排序序列中的記錄個數為0。排序完畢。
* 選擇排序:每次從未排序序列中選出最小的紀錄,把它放到已排序序列的後面。重複上個操作,直到未排序序列中只剩下一個記錄。排序完畢。
* 交換排序(起泡排序):比較序列中相鄰記錄,發現相鄰的逆序對時,進行交換。通過反覆的比較、交換,最終完成整個序列的排序工作。詳細:起泡排序最多需要做n-1遍,每一遍都是一次完整的掃描,沒一次完整掃描的成果是:1)減少了序列中的逆序對。2)把當時最大的記錄放到已排序記錄前面。(這也是為什麼起泡排序能保證最多n-1遍)
* 分配排序(快速排序):選擇一個標準,把被排序序列中的記錄跟這個標準進行比較,劃分為大小兩組,小的組排在前面。採用上個操作,遞迴的分別去劃分得到的兩組記錄,直到每個記錄組中最多包含一個記錄時,整個序列的排序完成。
***
* 直接插入排序演算法:上面有。
* 二分法插入排序演算法:在已排序序列裡檢索元素的插入位置時,可以使用二分法。
* shell排序演算法:採用一種變動間隔的排序技術,其中用簡單插入排序作為基礎演算法,可以得到更高的操作效率。
* 直接選擇排序演算法:上面有。
* 樹形結構:選擇排序中,可以改變選擇方式以提高排序效率。可以考慮堆排序演算法。
* 交換和排序:通過不斷交換序列中的逆序記錄對,使序列漸漸的接近有序序列。
* 逆序:記錄對的序為逆。
* 氣泡排序演算法:上面有。
* 交錯排序演算法:快速排序的表實現。很抽象,結合影象去理解。
* 快速排序:上面有。
* 劃分:在快速排序中,把被排序序列中的記錄和某個標準比較,使劃分為兩組。
* 歸併排序演算法:基於歸併的思想也可以實現排序,稱為歸併排序。
* 歸併操作:是一種典型的序列操作。其工作是把兩個或更多有序序列合併為一個有序序列。
* 二路歸併:初始時,把待排序序列中的n個記錄看做n個有序的子序列,每個子序列長度為1;把當時序列組裡的有效子序列兩兩歸併,完成後,子序列個數減半,每個子序列長度為原來2倍;重複上個操作,最終得到一個長度為n的有序序列。(程式碼很複雜,仔細推導才能理解)
* 分配排序:重題,上面有。
* 多輪分配和收集:採用元素適合分配排序的‘元組’作為關鍵碼,通過多輪分配和收集,完成以這種元組為關鍵碼的記錄的排序工作。
* 高位優先演算法:從元組的最高位(最左位)開始考慮關鍵碼元素,稱為~。
* 低位優先演算法:從元組的最低位(最右位)開始考慮關鍵碼元素,稱為~。
* 基數排序:如果每位關鍵碼都是數字,那麼關鍵碼元組可以看做是以某種進製表示的一個整數,這樣排序過程就是從元組元素的低到高逐位進行收集和分配。這樣的處理過程就像是按照技術逐位處理,因此這種多輪分配排序也被稱為基數排序。
* 混成排序演算法:實際排序中可以採用多種演算法的組合,將其稱為混成方法。
* 蒂姆排序演算法:Python內建的排序函式sort,表list類的物件也有一個sort方法,這兩者同用一個排序演算法——蒂姆排序,是一種混成排序演算法。~ = 插入排序 + 歸併排序
* 主關鍵碼:實際應用中,資料記錄通常有一個主關鍵碼,例如各種唯一識別符號,比如學號、身份證號、手機唯一識別符號等。
* 其他關鍵碼:也經常需要把記錄中的其他成分作為排序碼,比如學生的姓名、性別、成績等排序。
***
## 2.

* 蒂姆排序(序列太長,且要求最快找到)
### 3.
* 簡單插入排序(和0比較),n次 。(只是用O(1)空間,O(n)時間)
### 4.
*  快速排序,劃分規則:-10,10 。(線性時間,儘量快速)
### 5.
* 歸併排序,O(n log n) 。(比較次數少於2n-3,也就是說不能用選擇排序的思想)
### 6.
* 快速排序,j=( len(lst)-1 ) / 2  。(找到序列的中位值,線性時間)