數據結構基礎總結

數據結構

一些概念

數據結構就是研究數據的邏輯結構物理結構以及它們之間相互關係,並對這種結構定義相應的運算,並且確保通過這些運算後所獲得的新結構仍然是原來的結構類型。html

  1. 數據:全部能被輸入到計算機中,且能被計算機處理的符號的集合。是計算機操做的對象的總稱。
  2. 數據元素:數據(集合)中的一個「個體」,數據及結構中討論的基本單位
  3. 數據項:數據的不可分割的最小單位。一個數據元素可由若干個數據項組成。
  4. 數據類型:在一種程序設計語言中,變量所具備的數據種類。整型、浮點型、字符型等等node

  5. 邏輯結構:數據之間的相互關係。linux

    • 集合 結構中的數據元素除了同屬於一種類型外,別無其它關係。
    • 線性結構 數據元素之間一對一的關係
    • 樹形結構 數據元素之間一對多的關係
    • 圖狀結構或網狀結構 結構中的數據元素之間存在多對多的關係
  6. 物理結構/存儲結構:數據在計算機中的表示。物理結構是描述數據具體在內存中的存儲(如:順序結構、鏈式結構、索引結構、哈希結構)等
  7. 在數據結構中,從邏輯上能夠將其分爲線性結構和非線性結構
  8. 數據結構的基本操做的設置的最重要的準則是,實現應用程序與存儲結構的獨立。實現應用程序是「邏輯結構」,存儲的是「物理結構」。邏輯結構主要是對該結構操做的設定,物理結構是描述數據具體在內存中的存儲(如:順序結構、鏈式結構、索引結構、希哈結構)等。
  9. 順序存儲結構中,線性表的邏輯順序和物理順序老是一致的。但在鏈式存儲結構中,線性表的邏輯順序和物理順序通常是不一樣的。git

  10. 算法五個特性: 有窮性、肯定性、可行性、輸入、輸出github

  11. 算法設計要求:正確性、可讀性、健壯性、高效率與低存儲量需求。(好的算法)
  12. 算法的描述有僞程序、流程圖、N-S結構圖等。E-R圖是實體聯繫模型,不是程序的描述方式。
  13. 設計算法在執行時間時須要考慮:算法選用的規模、問題的規模
  14. 時間複雜度:算法的執行時間與原操做執行次數之和成正比。時間複雜度有小到大:O(1)、O(logn)、O(n)、O(nlogn)、O(n2)、O(n3)。冪次時間複雜度有小到大O(2n)、O(n!)、O(nn)
  15. 空間複雜度:若輸入數據所佔空間只取決於問題自己,和算法無關,則只須要分析除輸入和程序以外的輔助變量所佔額外空間

線性表

線性表是一種典型的線性結構。頭結點無前驅有一個後繼,尾節點無後繼有一個前驅。鏈表只能順序查找,定位一個元素的時間爲O(N),刪除一個元素的時間爲O(1)算法

  1. 線性表的順序存儲結構:把線性表的結點按邏輯順序依次存放在一組地址連續的存儲單元裏。用這種方法存儲的線性表簡稱順序表。是一種隨機存取的存儲結構。順序存儲指內存地址是一塊的,隨機存取指訪問時能夠按下標隨機訪問,存儲和存取是不同的。若是是存儲,則是指按順序的,若是是存取,則是能夠隨機的,能夠利用元素下標進行。數組比線性錶速度更快的是:原地逆序、返回中間節點、選擇隨機節點。
    • 便於線性表的構造和任意元素的訪問
    • 插入:插入新結點,以後結點後移。平均時間複雜度:O(n)
    • 刪除:刪除節點,以後結點前移。平均時間複雜度:O(n)
  2. 線性鏈表:用一組任意的存儲單元來依次存放線性表的結點,這組存儲單元便可以是連續的,也能夠是不連續的,甚至是零散分佈在內存中的任意位置上的。所以,鏈表中結點的邏輯次序和物理次序不必定相同。爲了能正確表示結點間的邏輯關係,在存儲每一個結點值的同時,還必須存儲指示其後繼結點的地址。data域是數據域,用來存放結點的值。next是指針域(亦稱鏈域),用來存放結點的直接後繼的地址(或位置)。不須要事先估計存儲空間大小。
    • 單鏈表中每一個結點的存儲地址是存放在其前趨結點next域中,而開始結點無前趨,故應設頭指針head指向開始結點。同時,因爲最後一個結點無後繼,故結點的指針域爲空,即NULL。頭插法建表(逆序)、尾插法建表(順序)。增長頭結點的目的是算法實現上的方便,但增大了內存開銷。
      • 查找:只能從鏈表的頭指針出發,順鏈域next逐個結點往下搜索,直到搜索到第i個結點爲止。所以,鏈表不是隨機存取結構
      • 插入:先找到表的第i-1的存儲位置,而後插入。新結點先連後繼,再連前驅。
      • 刪除:首先找到ai-1的存儲位置p。而後令p–>next指向ai的直接後繼結點,即把ai從鏈上摘下。最後釋放結點ai的空間.r=p->next;p->next=r->next;delete r。
      • 判斷一個單向鏈表中是否存在環的最佳方法是快慢指針。
    • 靜態鏈表:用一維數組來實現線性鏈表,這種用一維數組表示的線性鏈表,稱爲靜態鏈表。靜態:體如今表的容量是必定的。(數組的大小);鏈表:插入與刪除同前面所述的動態鏈表方法相同。靜態鏈表中指針表示的是下一元素在數組中的位置。
    • 靜態鏈表是用數組實現的,是順序的存儲結構,在物理地址上是連續的,並且須要預先分配大小。動態鏈表是用申請內存函數(C是malloc,C++是new)動態申請內存的,因此在鏈表的長度上沒有限制。動態鏈表由於是動態申請內存的,因此每一個節點的物理地址不連續,要經過指針來順序訪問。靜態鏈表在插入、刪除時也是經過修改指針域來實現的,與動態鏈表沒有什麼分別
    • 循環鏈表:是一種頭尾相接的鏈表。其特色是無須增長存儲量,僅對錶的連接方式稍做改變,便可使得表處理更加方便靈活。
      • 在單鏈表中,將終端結點的指針域NULL改成指向表頭結點的或開始結點,就獲得了單鏈形式的循環鏈表,並簡單稱爲單循環鏈表。因爲循環鏈表中沒有NULL指針,故涉及遍歷操做時,其終止條件就再也不像非循環鏈表那樣判斷p或p—>next是否爲空,而是判斷它們是否等於某一指定指針,如頭指針或尾指針等。
    • 雙向鏈表:在單鏈表的每一個結點裏再增長一個指向其直接前趨的指針域prior。這樣就造成的鏈表中有兩個方向不一樣的鏈。雙鏈表通常由頭指針惟一肯定的,將頭結點和尾結點連接起來構成循環鏈表,並稱之爲雙向鏈表。設指針p指向某一結點,則雙向鏈表結構的對稱性可用下式描述:p—>prior—>next=p=p—>next—>prior。從兩個方向搜索雙鏈表,比從一個方向搜索雙鏈表的方差要小。
      • 插入:先搞定插入節點的前驅和後繼,再搞定後結點的前驅,最後搞定前結點的後繼。
      • 在有序雙向鏈表中定位刪除一個元素的平均時間複雜度爲O(n)
      • 能夠直接刪除當前指針所指向的節點。而不須要像單向鏈表中,刪除一個元素必須找到其前驅。所以在插入數據時,單向鏈表和雙向鏈表操做複雜度相同,而刪除數據時,雙向鏈表的性能優於單向鏈表

棧和隊列

棧(Stack)是限制在表的一端進行插入和刪除運算的線性表,一般稱插入、刪除的這一端爲棧頂(Top),另外一端爲棧底(Bottom)。先進後出。top= -1時爲空棧,top=0只能說明棧中只有一個元素,而且元素進棧時top應該自增數據庫

  1. 順序存儲棧:順序存儲結構
  2. 鏈棧:鏈式存儲結構。插入和刪除操做僅限制在鏈頭位置上進行。棧頂指針就是鏈表的頭指針。一般不會出現棧滿的狀況。 不須要判斷棧滿但須要判斷棧空。
  3. 兩個棧共用靜態存儲空間,對頭使用也存在空間溢出問題。棧1的底在v[1],棧2的底在V[m],則棧滿的條件是top[1]+1=top[2]。
  4. 基本操做:刪除棧頂元素、判斷棧是否爲空以及將棧置爲空棧等
  5. 對於n各元素的入棧問題,可能的出棧順序有C(2n,n)/(n+1)個。
  6. 堆棧溢出通常是循環的遞歸調用、大數據結構的局部變量致使的

應用,代碼json

  1. 進制轉換
  2. 括號匹配的檢驗
  3. 行編輯程序
  4. 迷宮求解:若當前位置「可通」,則歸入路徑,繼續前進;若當前位置「不可通」,則後退,換方向繼續探索;若四周「均無通路」,則將當前位置從路徑中刪除出去。
  5. 表達式求解:前綴、中綴、後綴。
    • 操做數之間的相對次序不變;
    • 運算符的相對次序不一樣;
    • 中綴式丟失了括弧信息,導致運算的次序不肯定
    • 前綴式的運算規則爲:連續出現的兩個操做數和在它們以前且緊靠它們的運算符構成一個最小表達式
    • 後綴式的運算規則爲:運算符在式中出現的順序恰爲表達式的運算順序;每一個運算符和在它以前出現且緊靠它的兩個操做數構成一個最小表達式。
  6. 實現遞歸:多個函數嵌套調用的規則是:後調用先返回。
  7. 瀏覽器歷史紀錄,Android中的最近任務,Activity的啓動模式,CPU中棧的實現,Word自動保存,解析計算式,解析xml/json。解析XML時,須要校驗節點是否閉合,節點閉合的話,有頭尾符號相對應,遇到頭符號將其放入棧中,遇到尾符號時,彈出棧的內容,看是否有與之對應的頭符號,棧的特性恰好符合符號匹配的就近原則。

不是全部的遞歸程序都須要棧來保護現場,比方說求階乘的,是單向遞歸,直接用循環去替代從1乘到n就是結果了,另一些須要棧保存的也能夠用隊列等來替代。不是全部的遞歸轉化爲非遞歸都要用到棧。轉化爲非遞歸主要有兩種方法:對於尾遞歸或單向遞歸,能夠用循環結構算法代替swift

隊列

隊列(Queue)也是一種運算受限的線性表。它只容許在表的一端進行插入,而在另外一端進行刪除。容許刪除的一端稱爲隊頭(front),容許插入的一端稱爲隊尾(rear)。先進先出。數組

  1. 順序隊列:順序存儲結構。當頭尾指針相等時隊列爲空。在非空隊列裏,頭指針始終指向隊頭前一個位置,而尾指針始終指向隊尾元素的實際位置
  2. 循環隊列。在循環隊列中進行出隊、入隊操做時,頭尾指針仍要加1,朝前移動。只不過當頭尾指針指向向量上界(MaxSize-1)時,其加1操做的結果是指向向量的下界0。除非向量空間真的被隊列元素所有佔用,不然不會上溢。所以,除一些簡單的應用外,真正實用的順序隊列是循環隊列。故隊空和隊滿時頭尾指針均相等。所以,咱們沒法經過front=rear來判斷隊列「空」仍是「滿」
  3. 鏈隊列:鏈式存儲結構。限制僅在表頭刪除和表尾插入的單鏈表。顯然僅有單鏈表的頭指針不便於在表尾作插入操做,爲此再增長一個尾指針,指向鏈表的最後一個結點。
  4. 設尾指針的循環鏈表表示隊列,則入隊和出隊算法的時間複雜度均爲O(1)。用循環鏈表表示隊列,一定有鏈表的頭結點,入隊操做在鏈表尾插入,直接插入在尾指針指向的節點後面,時間複雜度是常數級的;出隊操做在鏈表表頭進行,也就是刪除表頭指向的節點,時間複雜度也是常數級的。

  5. 隊空條件:rear==front,可是通常須要引入新的標記來講明棧滿仍是棧空,好比每一個位置布爾值

  6. 隊滿條件:(rear+1) % QueueSize==front,其中QueueSize爲循環隊列的最大長度
  7. 計算隊列長度:(rear-front+QueueSize)% QueueSize
  8. 入隊:(rear+1)% QueueSize
  9. 出隊:(front+1)% QueueSize
  10. 假設以數組A[N]爲容量存放循環隊列的元素,其頭指針是front,當前隊列有X個元素,則隊列的尾指針值爲(front+X mod N)

串(String)是零個或多個字符組成的有限序列。長度爲零的串稱爲空串(Empty String),它不包含任何字符。一般將僅由一個或多個空格組成的串稱爲空白串(Blank String) 注意:空串和空白串的不一樣,例如「 」和「」分別表示長度爲1的空白串和長度爲0的空串。

串的表示和實現:

  1. 定長順序存儲表示。靜態存儲分配的順序表。
  2. 堆分配存儲表示。存儲空間是在程序執行過程當中動態分配而得。因此也稱爲動態存儲分配的順序表
  3. 串的鏈式存儲結構。

串匹配:將主串稱爲目標串,子串稱之爲模式串。蠻力法匹配。KMP算法匹配。Boyer-Moore算法匹配。

數組和廣義表

數組和廣義表可當作是一種特殊的線性表,其特殊在於: 表中的元素自己也是一種線性表。內存連續。根據下標在O(1)時間讀/寫任何元素。

二維數組,多維數組,廣義表、樹、圖都屬於非線性結構

數組

數組的順序存儲:行優先順序;列優先順序。數組中的任一元素能夠在相同的時間內存取,即順序存儲的數組是一個隨機存取結構。

關聯數組(Associative Array),又稱映射(Map)、字典( Dictionary)是一個抽象的數據結構,它包含着相似於(鍵,值)的有序對。 不是線性表。

矩陣的壓縮:

  1. 對稱矩陣、三角矩陣:直接存儲矩陣的上三角或者下三角元素。注意區分i>=j和i

廣義表

廣義表(Lists,又稱列表)是線性表的推廣。廣義表是n(n≥0)個元素a1,a2,a3,…,an的有限序列,其中ai或者是原子項,或者是一個廣義表。若廣義表LS(n>=1)非空,則a1是LS的表頭,其他元素組成的表(a2,…an)稱爲LS的表尾。廣義表的元素能夠是廣義表,也能夠是原子,廣義表的元素也能夠爲空。表尾是指除去表頭後剩下的元素組成的表,表頭能夠爲表或單元素值。因此表尾不能夠是單個元素值。

例子:

  1. A=()——A是一個空表,其長度爲零。
  2. B=(e)——表B只有一個原子e,B的長度爲1。
  3. C=(a,(b,c,d))——表C的長度爲2,兩個元素分別爲原子a和子表(b,c,d)。
  4. D=(A,B,C)——表D的長度爲3,三個元素都是廣義 表。顯然,將子表的值代入後,則有D=(( ),(e),(a,(b,c,d)))。
  5. E=(a,E)——這是一個遞歸的表,它的長度爲2,E至關於一個無限的廣義表E=(a,(a,(a,(a,…)))).

三個結論:

  1. 廣義表的元素能夠是子表,而子表的元素還能夠是子表。由此,廣義表是一個多層次的結構,能夠用圖形象地表示
  2. 廣義表可爲其它表所共享。例如在上述例4中,廣義表A,B,C爲D的子表,則在D中能夠沒必要列出子表的值,而是經過子表的名稱來引用。
  3. 廣義表的遞歸性

考點:

  1. 廣義表是0個或多個單因素或子表組成的有限序列,廣義表能夠是自身的子表,廣義表的長度n>=0,因此能夠爲空表。廣義表的同級元素(直屬於同一個表中的各元素)具備線性關係
  2. 廣義表的表頭爲空,並不表明該廣義表爲空表。廣義表()和(())不一樣。前者是長度爲0的空表,對其不能作求表頭和表尾的運算;然後者是長度爲l的非空表(只不過該表中唯一的一個元素是空表),對其可進行分解,獲得的表頭和表尾均是空表()
  3. 已知廣義表LS=((a,b,c),(d,e,f)),運用head和tail函數取出LS中原子e的運算是head(tail(head(tail(LS)))。根據表頭、表尾的定義可知:任何一個非空廣義表的表頭是表中第一個元素,它能夠是原子,也能夠是子表,而其表尾一定是子表。也就是說,廣義表的head操做,取出的元素是什麼,那麼結果就是什麼。可是tail操做取出的元素外必須加一個表——「()「。tail(LS)=((d,e,f));head(tail(LS))=(d,e,f);tail(head(tail(LS)))=(e,f);head(tail(head(tail(LS))))=e。
  4. 二維以上的數組實際上是一種特殊的廣義表
  5. 在(非空)廣義表中:一、表頭head能夠是原子或者一個表 二、表尾tail必定是一個表 3.廣義表難以用順序存儲結構 4.廣義表能夠是一個多層次的結構

樹和二叉樹

一種非線性結構。樹是遞歸結構,在樹的定義中又用到了樹的概念。

基本術語:

  1. 樹結點:包含一個數據元素及若干指向子樹的分支;
  2. 孩子結點:結點的子樹的根稱爲該結點的孩子;
  3. 雙親結點:B結點是A結點的孩子,則A結點是B結點的雙親;
  4. 兄弟結點:同一雙親的孩子結點;
  5. 堂兄結點:同一層上結點;
  6. 結點層次:根結點的層定義爲1;根的孩子爲第二層結點,依此類推;
  7. 樹的高(深)度:樹中最大的結點層
  8. 結點的度:結點子樹的個數
  9. 樹的度: 樹中最大的結點度。
  10. 葉子結點:也叫終端結點,是度爲0的結點;
  11. 分枝結點:度不爲0的結點(非終端結點);
  12. 森林:互不相交的樹集合;
  13. 有序樹:子樹有序的樹,如:家族樹;
  14. 無序樹:不考慮子樹的順序;

二叉樹

二叉樹能夠爲空。二叉樹結點的子樹要區分左子樹和右子樹,即便只有一棵子樹也要進行區分,說明它是左子樹,仍是右子樹。這是二叉樹與樹的最主要的差異。注意區分:二叉樹、二叉查找樹/二叉排序樹/二叉搜索樹二叉平衡(查找)樹

二叉平衡樹確定是一顆二叉排序樹。堆不是一顆二叉平衡樹。

二叉樹與樹是不一樣的,二叉樹不等價於分支樹最多爲二的有序樹。當一個結點只包含一個子節點時,對於有序樹並沒有左右孩子之分,而對於二叉樹來講依然有左右孩子之分,因此二叉樹與樹是兩種不一樣的結構。

性質:

  1. 在二叉樹的第 i 層上至多有2i-1個結點。
  2. 深度爲 k 的二叉樹上至多含 2k-1 個結點(k≥1)
  3. 對任何一棵二叉樹,若它含有n0個葉子結點、n2個度爲 2 的結點,則必存在關係式:n0= n2+1。
  4. 具備 n 個結點的徹底二叉樹的深度爲⎣log2 n⎦+1 。
  5. n個結點的二叉樹中,徹底二叉樹具備最小的路徑長度。
  6. 若是對一棵有n個結點的徹底二叉樹的結點按層序編號,則對任一結點i(1<=i<=n),有:
    • 若是i=1,則結點i無雙親,是二叉樹的根;若是i>1,則其雙親的編號是 i/2(整除)。
    • 若是2i>n,無左孩子;不然,其左孩子是結點2i。
    • 若是2i+1>n,則結點i無右孩子;不然,其右孩子是結點2i+1。

二叉樹的存儲結構

  1. 順序存儲結構:僅僅適用於滿或徹底二叉樹,結點之間的層次關係由性質5肯定。
  2. 二叉鏈表法:每一個節點存儲左子樹和右子樹。三叉鏈表:左子樹、右子樹、父節點,總的指針是n+2
  3. 在有n個結點的二叉鏈表中,值爲非空的鏈域的個數爲n-1。在有N個結點的二叉鏈表中一定有2N個鏈域。除根結點外,其他N-1個結點都有一個父結點。因此,一共有N-1個非空鏈域,其他2N-(N-1)=N+1個爲空鏈域。
  4. 二叉鏈存儲法也叫孩子兄弟法,左指針指向左孩子,右指針指向右兄弟。而中序遍歷的順序是左孩子,根,右孩子。這種遍歷順序與存儲結構不一樣,所以須要堆棧保存中間結果。而中序遍歷檢索二叉樹時,因爲其存儲結構跟遍歷順序相符,所以不須要用堆棧。

遍歷二叉樹和線索二叉樹

遍歷二叉樹:使得每個結點均被訪問一次,並且僅被訪問一次。非遞歸的遍歷實現要利用棧。

  • 先序遍歷DLR:根節點->左子樹->右子樹
  • 中序遍歷LDR:左子樹->根節點->右子樹。必需要有中序遍歷才能獲得一棵二叉樹的正確順序
  • 後續遍歷LRD:左子樹->右子樹->根節點。須要棧的支持。
  • 層次遍歷:用一維數組存儲二叉樹時,老是以層次遍歷的順序存儲結點。層次遍歷應該藉助隊列。

線索二叉樹:對二叉樹全部結點作某種處理可在遍歷過程當中實現;檢索(查找)二叉樹某個結點,可經過遍歷實現;若是能將二叉樹線索化,就能夠簡化遍歷算法,提升遍歷速度,目的是加快查找結點的前驅或後繼的速度。

如何線索化?以中序遍歷爲例,若能將中序序列中每一個結點前趨、後繼信息保存起來,之後再遍歷二叉樹時就能夠根據所保存的結點前趨、後繼信息對二叉樹進行遍歷。對於二叉樹的線索化,實質上就是遍歷一次二叉樹,只是在遍歷的過程當中,檢查當前結點左,右指針域是否爲空,若爲空,將它們改成指向前驅結點或後繼結點的線索。前驅就是在這一點以前走過的點,不是下一將要去往的點

加上結點前趨後繼信息(結索)的二叉樹稱爲線索二叉樹。n個結點的線索二叉樹上每一個結點有2個指針域(指向左孩子和右孩子),總共有2n個指針域;一個n個結點的樹有n-1條邊,那麼空指針域= 2n - (n-1) = n + 1,即線索數爲n+1。指針域tag爲0,存放孩子指針,爲1,存放前驅/後繼節點指針。

線索樹下結點x的前驅與後繼查找:設結點x相應的左(右)標誌是線索標誌,則lchild(rchild)就是前驅(後繼),不然:

  • LDR–前驅:左子樹中最靠右邊的結點;後繼:右子樹中最靠左邊的結點
  • LRD–前驅:右子樹的根,若無右子樹,爲左子樹跟。後繼:x是根,後繼是空;x是雙親的右孩子、x是雙親的左孩子,但雙親無右孩子,雙親是後繼;x是雙親的左孩子,雙親有右孩子,雙親右子樹中最左的葉子是後繼
  • DLR–對稱於LRD線索樹—將LRD中全部左右互換,前驅與後繼互換,獲得DLR的方法。
  • 爲簡化線索鏈表的遍歷算法,仿照線性鏈表,爲線索鏈表加上一頭結點,約定:
    • 頭結點的lchild域:存放線索鏈表的根結點指針;
    • 頭結點的rchild域: 中序序列最後一個結點的指針;
    • 中序序列第一結點lchild域指向頭結點;
    • 中序序列最後一個結點的rchild域指向頭結點;

中序遍歷的線索二叉樹以及線索二叉樹鏈表示意圖
xiansuobinarytree

一棵左右子樹均不空的二叉樹在前序線索化後,其中空的鏈域的個數是1。前序和後續線索化後空鏈域個數都是1,中序是2。二叉樹在線索化後,仍不能有效求解的問題是前序求前序先驅,後序求後序後繼。

中序遍歷的順序爲:左、根、右,因此對於每一非空的線索,左子樹結點的後繼爲根結點,右子樹結點的前驅爲根結點,再遞歸的執行上面的過程,可得非空線索均指向其祖先結點。在中序線索二叉樹中,每一非空的線索均指向其祖先結點

在二叉樹上加上結點前趨、後繼線索後,可利用線索對二叉樹進行遍歷,此時,不需棧,也不需遞歸。基本步驟:

  1. p=T->lchild; p指向線索鏈表的根結點;
  2. 若線索鏈表非空,循環:
    • 循環,順着p左孩子指針找到最左下結點;訪問之;
    • 若p所指結點的右孩子域爲線索,p的右孩子結點即爲後繼結點循環: p=p->rchild; 並訪問p所指結點;(在此循環中,順着後繼線索訪問二叉樹中的結點)
    • 一旦線索「中斷」,p所指結點的右孩子域爲右孩子指針,p=p->rchild,使 p指向右孩子結點;

樹和森林

樹的存儲結構:

  1. 雙親表示法
  2. 孩子表示法
  3. 利用圖表示樹
  4. 孩子兄弟表示法(二叉樹表示法):鏈表中每一個結點的兩指針域分別指向其第一個孩子結點和下一個兄弟結點

將樹轉化成二叉樹:右子樹必定爲空

  1. 加線:在兄弟之間加一連線
  2. 抹線:對每一個結點,除了其左孩子外,去除其與其他孩子之間的關係
  3. 旋轉:以樹的根結點爲軸心,將整樹順時針轉45°

森林轉換成二叉樹:

  1. 將各棵樹分別轉換成二叉樹
  2. 將每棵樹的根結點用線相連
  3. 以第一棵樹根結點爲二叉樹的根

樹與轉換後的二叉樹的關係:轉換後的二叉樹的先序對應樹的先序遍歷;轉換後的二叉樹的中序對應樹的後序遍歷

哈弗曼樹/霍夫曼樹

一些概念

  1. 路徑:從一個祖先結點到子孫結點之間的分支構成這兩個結點間的路徑;
  2. 路徑長度:路徑上的分支數目稱爲路徑長度;
  3. 樹的路徑長度:從根到每一個結點的路徑長度之和。
  4. 結點的權:根據應用的須要能夠給樹的結點賦權值;
  5. 結點的帶權路徑長度:從根到該結點的路徑長度與該結點權的乘積;
  6. 樹的帶權路徑長度=樹中全部葉子結點的帶權路徑之和;一般記做 WPL=∑wi×li
  7. 哈夫曼樹:假設有n個權值(w1, w2, … , wn),構造有n個葉子結點的二叉樹,每一個葉子結點有一個 wi做爲它的權值。則帶權路徑長度最小的二叉樹稱爲哈夫曼樹。最優二叉樹。

前綴碼的定義:在一個字符集中,任何一個字符的編碼都不是另外一個字符編碼的前綴。霍夫曼編碼就是前綴碼,可用於快速判斷霍夫曼編碼是否正確。霍夫曼樹是滿二叉樹,如有n個節點,則共有(n+1)/2個碼子

給定n個權值做爲n的葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹爲最優二叉樹,也稱爲霍夫曼樹(Huffman Tree)。霍夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。

假設哈夫曼樹是二叉的話,則度爲0的結點個數爲N,度爲2的結點個數爲N-1,則結點總數爲2N-1。哈夫曼樹的結點個數必爲奇數。

哈夫曼樹不必定是徹底二叉樹,但必定是最優二叉樹。

若度爲m的哈夫曼樹中,其葉結點個數爲n,則非葉結點的個數爲[(n-1)/(m-1)]。邊的數目等於度。

圖遍歷與回溯

圖搜索->造成搜索樹

  1. 窮舉法。
  2. 貪心法。多步決策,每步選擇使得構成一個問題的可能解,同時知足目標函數。
  3. 回溯法。根據題意,選取度量標準,而後將可能的選擇方法按度量標準所要求順序排好,每次處理一個量,獲得該意義下的最優解的分解處理。

無向圖

  1. 迴路或環:第一個頂點和最後一個頂點相同的路徑。
  2. 簡單迴路或簡單環:除第一個頂點和最後一個頂點以外,其他頂點不重複出現的迴路
  3. 連通:頂點v至v’ 之間有路徑存在
  4. 連通圖:無向圖圖 G 的任意兩點之間都是連通的,則稱G是連通圖。
  5. 連通份量:極大連通子圖,子圖中包含的頂點個數極大
  6. 全部頂點度的和必須爲偶數

有向圖:

  1. 迴路或環:第一個頂點和最後一個頂點相同的路徑。
  2. 簡單迴路或簡單環:除第一個頂點和最後一個頂點以外,其他頂點不重複出現的迴路。
  3. 連通:頂點v至v’之間有路徑存在
  4. 強連通圖:有向圖G的任意兩點之間都是連通的,則稱G是強連通圖。各個頂點間都可達。
  5. 強連通份量:極大連通子圖
  6. 有向圖頂點的度是頂點的入度與出度之和。鄰接矩陣中第V行中的1的個數是V的出度

  7. 生成樹:極小連通子圖。包含圖的全部n個結點,但只含圖的n-1條邊。在生成樹中添加一條邊以後,一定會造成迴路或環。

  8. 徹底圖:有 n(n-1)/2 條邊的無向圖。其中n是結點個數。一定是連通圖。
  9. 有向徹底圖:有n(n-1)條邊的有向圖。其中n是結點個數。每兩個頂點之間都有兩條方向相反的邊鏈接的圖。
  10. 一個無向圖 G=(V,E) 是連通的,那麼邊的數目大於等於頂點的數目減一:|E|>=|V|-1,而反之不成立。若是 G=(V,E) 是有向圖,那麼它是強連通圖的必要條件是邊的數目大於等於頂點的數目:|E|>=|V|,而反之不成立。沒有迴路的無向圖是連通的當且僅當它是樹,即等價於:|E|=|V|-1。

圖的存儲形式

  1. 鄰接矩陣和加權鄰接矩陣
    • 無權有向圖:出度: i行之和;入度: j列之和。
    • 無權無向圖:i結點的度: i行或i列之和。
    • 加權鄰接矩陣:相連爲w,不相連爲∞
  2. 鄰接表
    • 用頂點數組表、邊(弧)表表示該有向圖或無向圖
    • 頂點數組表:用數組存放全部的頂點。數組大小爲圖頂點數n
    • 邊表(邊結點表):每條邊用一個結點進行表示。同一個結點的全部的邊造成它的邊結點單鏈表。
    • n個頂點的無向圖的鄰接表最多有n(n-1)個邊表結點。有n個頂點的無向圖最多有n*(n-1)/2條邊,此時爲徹底無向圖,而在鄰接表中每條邊存儲兩次,因此有n*(n-1)個結點

圖的遍歷

深度優先搜索利用棧,廣度優先搜索利用隊列

求一條從頂點i到頂點s的簡單路徑–深搜。求兩個頂點之間的一條長度最短的路徑–廣搜。當各邊上的權值均相等時,BFS算法可用來解決單源最短路徑問題。

生成樹和最小生成樹

每次遍歷一個連通圖將圖的邊分紅遍歷所通過的邊和沒有通過的邊兩部分,將遍歷通過的邊同圖的頂點構成一個子圖,該子圖稱爲生成樹。所以有DFS生成樹和BFS生成樹。

生成樹是連通圖的極小子圖,有n個頂點的連通圖的生成樹一定有n-1條邊,在生成樹中任意增長一條邊,一定產生迴路。若砍去它的一條邊,就會把生成樹變成非連通子圖

最小生成樹:生成樹中邊的權值(代價)之和最小的樹。最小生成樹問題是構造連通網的最小代價生成樹。

Kruskal算法:令最小生成樹集合T初始狀態爲空,在有n個頂點的圖中選取代價最小的邊並從圖中刪去。若該邊加到T中有迴路則丟棄,不然留在T中;依此類推,直至T中有n-1條邊爲止。

Prim算法、Kruskal算法和Dijkstra算法均屬於貪心算法。

  1. Dijkstra算法解決的是帶權重的有向圖上單源最短路徑問題,該算法要求全部邊的權重都爲非負值。
  2. Dijkstra算法解決了從某個原點到其他各頂點的最短路徑問題,由循環嵌套可知該算法的時間複雜度爲O(N*N)。若要求任一頂點到其他全部頂點的最短路徑,一個比較簡單的方法是對每一個頂點當作源點運行一次該算法,等於在原有算法的基礎上,再來一次循環,此時整個算法的複雜度就變成了O(N*N*N)。
  3. Bellman-Ford算法解決的是通常狀況下的單源最短路徑問題,在這裏,邊的權重能夠爲負值。該算法返回一個布爾值,以代表是否存在一個從源節點能夠到達的權重爲負值的環路。若是存在這樣一個環路,算法將告訴咱們不存在解決方案。若是沒有這種環路存在,算法將給出最短路徑和它們的權重。

雙連通圖和關節點

若從一個連通圖中刪去任何一個頂點及其相關聯的邊,它仍爲一個連通圖的話,則該連通圖被稱爲重(雙)連通圖

若連通圖中的某個頂點和其相關聯的邊被刪去以後,該連通圖被分割成兩個或兩個以上的連通份量,則稱此頂點爲關節點

沒有關節點的連通圖爲雙連通圖

  1. 若生成樹的根結點,有兩個或兩個以上的分支,則此頂點(生成樹的根)必爲關節點;
  2. 對生成樹上的任意一個非葉「頂點」,若其某棵子樹中的全部「頂點」沒有和其祖先相通的回邊,則該「頂點」必爲關節點。

有向無環圖及其應用

拓撲排序。在用鄰接表表示圖時,對有n個頂點和e條弧的有向圖而言時間複雜度爲O(n+e)。一個有向圖能被拓撲排序的充要條件就是它是一個有向無環圖。拓撲序列惟一不能惟一肯定有向圖。

AOV網(Activity On Vertex):用頂點表示活動,邊表示活動的優先關係的有向圖稱爲AOV網。AOV網中不容許有迴路,這意味着某項活動以本身爲先決條件。

拓撲有序序列:把AOV網絡中各頂點按照它們相互之間的優先關係排列一個線性序列的過程。若vi是vj前驅,則vi必定在vj以前;對於沒有優先關係的點,順序任意。

拓撲排序:對AOV網絡中頂點構造拓撲有序序列的過程。方法:

  1. 在有向圖中選一個沒有前驅的頂點且輸出之
  2. 從圖中刪除該頂點和全部以它爲尾的弧
  3. 重複上述兩步,直至所有頂點均已輸出;或者當圖中不存在無前驅的頂點爲止(此時說明圖中有環)

採用深度優先搜索拓撲排序算法能夠判斷出一個有向圖中是否有環(迴路).深度優先搜索只要在其中記錄下搜索的節點數n,當n大於圖中節點數時退出,並能夠得出有迴路。如有迴路,則拓撲排序訪問不到圖中全部的節點,因此也能夠得出迴路。廣度優先搜索過程當中若是訪問到一個已經訪問過的節點,多是多個節點指向這個節點,不必定是存在環。

算法描述:

  1. 把鄰接表中入度爲0的頂點依此進棧
  2. 若棧不空,則
    • 棧頂元素vj退棧並輸出;
    • 在鄰接表中查找vj的直接後繼vk,把vk的入度減1;若vk的入度爲0則進棧
  3. 若棧空時輸出的頂點個數不是n,則有向圖有環;不然,拓撲排序完畢。

AOE網:帶權的有向無環圖,其中頂點表示事件,弧表示活動,權表示活動持續時間。在工程上經常使用來表示工程進度計劃。

一些定義:

  1. 事件的最先發生時間(ve(j)):從源點到j結點的最長的路徑。意味着事件最先可以發生的時間。
  2. 事件的最遲發生時間(vl(j)):不影響工程的如期完工,事件j必須發生的時間。
  3. 活動ai由弧

查找

順序查找、折半查找、索引查找、分塊查找是靜態查找,動態查找有二叉排序樹查找,最優二叉樹查找,鍵樹查找,哈希表查找

靜態查找表

順序表的順序查找:應用範圍:順序表或線性鏈表表示的表,表內元素之間無序。查找過程:從表的一端開始逐個進行記錄的關鍵字和給定值的比較。

順序有序表的二分查找。平均查找時間(n+1)/n log2(n+1)

分塊查找:將表分紅幾塊,塊內無序,塊間有序,即前一塊中的最大值小於後一塊中的最小值。而且有一張索引表,每一項存放每一塊的最大值和指向該塊第一個元素的指針。索引表有序,塊內無序。因此,塊間查找用二分查找,塊內用順序查找,效率介於順序和二分之間;先肯定待查記錄所在塊,再在塊內查找。所以跟表中元素個數和塊中元素個數都有關。

  1. 用數組存放待查記錄,
  2. 創建索引表,由每塊中最大(小)的關鍵字及所屬塊位置的信息組成。
  3. 當索引表較大時,能夠採用二分查找
  4. 在數據量極大時,索引可能不少,可考慮創建索引表的索引,即二級索引,原則上索引不超過三級

分塊查找平均查找長度:ASLbs = Lb + Lw。其中,Lb是查找索引表肯定所在塊的平均查找長度, Lw是在塊中查找元素的平均查找長度。在n必定時,能夠經過選擇s使ASL儘量小。當s=sqrt(n)時,ASL最小。

  1. 時間:順序查找最差,二分最好,分塊介於二者之間
  2. 空間:分塊最大,須要增長索引數據的空間
  3. 順序查找對錶沒有特殊要求
  4. 分塊時數據塊之間在物理上可不連續。因此能夠達到插入、刪除數據只涉及對應的塊;另外,增長了索引的維護。
  5. 二分查找要求表有序,因此若表的元素的插入與刪除很頻繁,維持表有序的工做量極大。
  6. 在表不大時,通常直接使用順序查找。

動態查找

二叉排序樹的結點刪除:

  1. x爲葉子結點,則直接刪除
  2. x只有左子樹xL或只有右子樹xR ,則令xL或xR直接成爲雙親結點f的子樹;
  3. x即有左子樹xL也有右子樹xR,在xL中選值最大的代替x,該數據按二叉排序樹的性質應在最右邊。

平衡二叉樹:每一個結點的平衡因子都爲 一、-一、0 的二叉排序樹。或者說每一個結點的左右子樹的高度最多差1的二叉排序樹。

平衡二叉樹的平衡:

  1. 左調整(新結點插入在左子樹上的調整):
    • LL(插入在結點左子樹的左子樹上):旋轉先後高度都爲h+1
    • LR(新插入結點在左子樹的右子樹上):旋轉先後高度仍爲h+1
  2. 右調整(新結點插入在右子樹上進行的調整):
    • RR(插入在的右子樹的右子樹上):處理方法和 LL對稱
    • RL(插入在的右子樹的左子樹上):處理方法和 LR對稱

平衡樹創建方法:

  1. 按二叉排序樹插入結點
  2. 如引發結點平衡因子變爲|2|,則肯定旋轉點,該點是離根最遠(或最接近於葉子的點)
  3. 肯定平衡類型後進行平衡處理,平衡後以平衡點爲根的子樹高不變
  4. 最小二叉平衡樹的節點的公式以下 F(n)=F(n-1)+F(n-2)+1 這個相似於一個遞歸的數列,能夠參考Fibonacci數列,1是根節點,F(n-1)是左子樹的節點數量,F(n-2)是右子樹的節點數量。

常見的平衡二叉樹:

  1. 紅黑樹是平衡二叉樹,也就是左右子樹是平衡的,高度大概相等。這種狀況等價於一塊徹底二叉樹的高度,查找的時間複雜度是樹的高度,爲logn,插入操做的平均時間複雜度爲O(logn),最壞時間複雜度爲O(logn)
    紅黑樹
    • 節點是紅色或黑色。
    • 根是黑色。
    • 全部葉子都是黑色(葉子是NIL節點)。
    • 每一個紅色節點的兩個子節點都是黑色。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點)
    • 從任一節點到其每一個葉子的全部簡單路徑 都包含相同數目的黑色節點。
  2. avl樹也是自平衡二叉樹;紅黑樹和AVL樹查找、插入、刪除的時間複雜度相同;包含n個內部結點的紅黑樹的高度是o(logn); TreeMap 是一個紅黑樹的實現,能保證插入的值保證排序
  3. STL和linux多使用紅黑樹做爲平衡樹的實現:
    1. 若是插入一個node引發了樹的不平衡,AVL和RB-Tree都是最多隻須要2次旋轉操做,即二者都是O(1);可是在刪除node引發樹的不平衡時,最壞狀況下,AVL須要維護從被刪node到root這條路徑上全部node的平衡性,所以須要旋轉的量級O(logN),而RB-Tree最多隻需3次旋轉,只須要O(1)的複雜度。
    2. 其次,AVL的結構相較RB-Tree來講更爲平衡,在插入和刪除node更容易引發Tree的unbalance,所以在大量數據須要插入或者刪除時,AVL須要rebalance的頻率會更高。所以,RB-Tree在須要大量插入和刪除node的場景下,效率更高。天然,因爲AVL高度平衡,所以AVL的search效率更高。
    3. map的實現只是折衷了二者在search、insert以及delete下的效率。整體來講,RB-tree的統計性能是高於AVL的。

查找總結

  1. 既但願較快的查找又便於線性表動態變化的查找方法是哈希法查找。二叉排序樹查找,最優二叉樹查找,鍵樹查找,哈希法查找是動態查找。分塊、順序、折半、索引順序查找均爲靜態。分塊法應該是將整個線性表分紅若干塊進行保存,若動態變化則能夠添加在表的尾部(非順序結構),時間複雜度是O(1),查找複雜度爲O(n);若每一個表內部爲順序結構,則可用二分法將查找時間複雜度降至O(logn),但同時動態變化複雜度則變成O(n);順序法是挨個查找,這種方法最容易實現,不過查找時間複雜度都是O(n),動態變化時可將保存值放入線性表尾部,則時間複雜度爲O(1);二分法是基於順序表的一種查找方式,時間複雜度爲O(logn);經過哈希函數將值轉化成存放該值的目標地址,O(1)
  2. 二叉樹的平均查找長度爲O(log2n)——O(n).二叉排序樹的查找效率與二叉樹的高度有關,高度越低,查找效率越高。二叉樹的查找成功的平均查找長度ASL不超過二叉樹的高度。二叉樹的高度與二叉樹的形態有關,n個節點的徹底二叉樹高度最小,高度爲[log2n]+1,n個節點的單隻二叉樹的高度最大,高度爲n,此時查找成功的ASL爲最大(n+1)/2,所以二叉樹的高度範圍爲[log2n]+1——n.
  3. 鏈式存儲不能隨機訪問,必須是順序存儲

B_樹的B+樹

B_樹

B-樹就是B樹。m階B_樹知足或空,或爲知足下列性質的m叉樹:

B-樹

  1. 樹中每一個結點最多有m棵子樹
  2. 根結點在不是葉子時,至少有兩棵子樹
  3. 除根外,全部非終端結點至少有⎡m/2⎤棵子樹
  4. 有s個子樹的非葉結點具備 n = s-1個關鍵字,結點的信息組織爲:(n,A0,K1,A1,K2,A2 … Kn,An)。這裏:n爲關鍵字的個數,ki(i=1,2,…,n)爲關鍵字,且知足Ki小於Ki+1,,Ai(i=0,1,..n)爲指向子樹的指針。
  5. 全部的葉子結點都出如今同一層上,不帶信息(可認爲外部結點或失敗結點)。
  6. 關鍵字集合分佈在整顆樹中
  7. 任何一個關鍵字出現且只出如今一個結點中
  8. 搜索有可能在非葉子結點結束
  9. 其搜索性能等價於在關鍵字全集內作一次二分查找
  10. 只適用於隨機檢索,不適用於順序檢索。
  11. 有結點的平衡因子都爲零
  12. M階B-樹中含有N個關鍵字,最大深度爲log⎡m/2⎤(n+1)/2+2

B_樹中結點的插入

  1. m表明B_樹的階,插入總髮生在最低層
  2. 插入後關鍵字個數小於等於 m-1,完成。
  3. 插入後關鍵字個數等於m,結點分裂,以中點數據爲界一分爲二,中點數據放到雙親結點中。這樣就有可能使得雙親結點的數據個數爲m,引發雙親結點的分裂,最壞狀況下一直波及到根,引發根的分裂——B_樹長高。

3階B_樹的插入。每一個結點最多3棵子樹,2個數據;最少2棵子樹,1個數據。因此3階B_樹也稱爲2-3樹。

B_樹中結點的刪除

  1. 刪除發生在最底層
    • 被刪關鍵字所在結點中的關鍵字數目大於等於 m/2 ,直接刪除。
    • 刪除後結點中數據爲⎡m/2⎤-2,而相鄰的左(右)兄弟中數據大於⎡m/2⎤-1,此時左(右兄弟)中最大(小)的數據上移到雙親中,雙親中接(靠)在它後(前)面的數據移到被刪數據的結點中
    • 其左右兄弟結點中數據都是⎡m/2⎤-1,此時和左(右)兄弟合併,合併時連同雙親中相關的關鍵字。此時,雙親中少了一項,所以又可能引發雙親的合併,最壞一直到根,使B-樹下降一層。
  2. 刪除不在最底層
    • 在大於被刪數據中選最小的代替被刪數據,問題轉換成在最底層的刪除

B+樹

在實際的文件系統中,用的是B+樹或其變形。有關性質與操做相似與B_樹。

B+樹

差別:

  1. 有n棵子樹的結點中有n個關鍵字,每一個關鍵字不保存數據,只用來索引,全部數據都保存在葉子節點。
  2. 全部葉子結點中包含所有關鍵字信息,及對應記錄位置信息及指向含有這些關鍵字記錄的指針,且葉子結點自己依關鍵字的大小自小而大的順序連接。(而B樹的葉子節點並無包括所有須要查找的信息)
  3. 全部非葉子爲索引,結點中僅含有其子樹根結點中最大(或最小)關鍵字。 (而B樹的非終節點也包含須要查找的有效信息)
  4. 非葉最底層順序聯結,這樣能夠進行順序查找

B+特性

  1. 全部關鍵字都出如今葉子結點的鏈表中(稠密索引),且鏈表中的關鍵字剛好是有序的;
  2. 不可能在非葉子結點命中
  3. 非葉子結點至關因而葉子結點的索引(稀疏索引),葉子結點至關因而存儲(關鍵字)數據的數據層
  4. 更適合文件索引系統
  5. B+樹插入操做的平均時間複雜度爲O(logn),最壞時間複雜度爲O(logn)

查找過程

  • 在 B+ 樹上,既能夠進行縮小範圍的查找,也能夠進行順序查找;
  • 在進行縮小範圍的查找時,無論成功與否,都必須查到葉子結點才能結束;
  • 若在結點內查找時,給定值≤Ki, 則應繼續在 Ai 所指子樹中進行查找

插入和刪除的操做:相似於B_樹進行,即必要時,也須要進行結點的「分裂」或「合併」。

爲何說B+tree比B樹更適合實際應用中操做系統的文件索引和數據庫索引?

  1. B+tree的磁盤讀寫代價更低
    • B+tree的內部結點並無指向關鍵字具體信息的指針。所以其內部結點相對B 樹更小。若是把全部同一內部結點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多。一次性讀入內存中的須要查找的關鍵字也就越多。相對來講IO讀寫次數也就下降了。
    • 舉個例子,假設磁盤中的一個盤塊容納16bytes,而一個關鍵字2bytes,一個關鍵字具體信息指針2bytes。一棵9階B-tree(一個結點最多8個關鍵字)的內部結點須要2個盤快。而B+樹內部結點只須要1個盤快。當須要把內部結點讀入內存中的時候,B樹就比B+樹多一次盤塊查找時間(在磁盤中就是盤片旋轉的時間)。
  2. B+tree的查詢效率更加穩定
    • 因爲非終結點並非最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。因此任何關鍵字的查找必須走一條從根結點到葉子結點的路。全部關鍵字查詢的路徑長度相同,致使每個數據的查詢效率至關。

B樹和B+樹都是平衡的多叉樹。B樹和B+樹均可用於文件的索引結構。B樹和B+樹都能有效的支持隨機檢索。B+樹既能索引查找也能順序查找.

哈希表

  1. 在記錄的存儲地址和它的關鍵字之間創建一個肯定的對應關係;這樣不通過比較,一次存取就能獲得元素。
  2. 哈希函數——在記錄的關鍵字與記錄的存儲位置之間創建的一種對應關係。是從關鍵字空間到存儲位置空間的一種映象。
  3. 哈希表——應用哈希函數,由記錄的關鍵字肯定記錄在表中的位置信息,並將記錄根據此信息放入表中,這樣構成的表叫哈希表。
  4. Hash查找適合於關鍵字可能出現的值的集合遠遠大於實際關鍵字集合的情形。
  5. 更適合查找,不適合頻繁更新
  6. Hash表等查找複雜依賴於Hash值算法的有效性,在最好的狀況下,hash表查找複雜度爲O(1)。只有無衝突的hash_table複雜度纔是O(1)。通常是O(c),c爲哈希關鍵字衝突時查找的平均長度。插入,刪除,查找都是O(1)。平均查找長度不隨表中結點數目的增長而增長,而是隨負載因子的增大而增大
  7. 因爲衝突的產生,使得哈希表的查找過程仍然是一個給定值與關鍵字比較的過程。

根據抽屜原理,衝突是不可能徹底避免的,因此,選擇好的散列函數和衝突處理方法:

  1. 構造一個性能好,衝突少的Hash函數
  2. 如何解決衝突

經常使用的哈希函數

  1. 直接定址法。僅適合於:地址集合的大小 == 關鍵字集合的大小
  2. 數字分析法。對關鍵字進行分析,取關鍵字的若干位或其組合做哈希地址。僅適合於:能預先估計出全體關鍵字的每一位上各類數字出現的頻度。
  3. 平方取中法。以關鍵字的平方值的中間幾位做爲存儲地址。
  4. 摺疊法。將關鍵字分割成位數相同的幾部分,而後取這幾部分的疊加和(捨去進位)作哈希地址。移位疊加/間界疊加。適合於: 關鍵字的數字位數特別多,且每一位上數字分佈大體均勻狀況。
  5. 除留餘數法。取關鍵字被某個不大於哈希表表長m的數p除後所得餘數做哈希地址,即H(key)=key%p,p<=m。
  6. 隨機數法。取關鍵字的僞隨機函數值做哈希地址,即H(key)=random(key),適於關鍵字長度不等的狀況。

衝突解決

  1. 開放定址法。當衝突發生時,造成一個探查序列;沿此序列逐個地址探查,直到找到一個空位置(開放的地址),將發生衝突的記錄放到該地址中。即Hi=(H(key)+di) % m,i=1,2,……k(k<=m-1),H(key)哈希函數,m哈希表長,di增量序列。缺點:刪除:只能做標記,不能真正刪除;溢出;載因子過大、解決衝突的算法選擇很差會發生彙集問題。要求裝填因子α較小,故當結點規模較大時會浪費不少空間。
    • 線性探測再散列:di=1,2,3,…,m-1
    • 二次探測再散列:di=12,-12,22,-22,…,±k2(k<=m/2)
    • 僞隨機探測再散列: di爲僞隨機數序列
  2. 鏈地址法:將全部關鍵字爲同義詞的記錄存儲在一個單鏈表中,並用一維數組存放頭指針。拉鍊法中可取α≥1,且結點較大時,拉鍊法中增長的指針域可忽略不計,所以節省空間。一旦發生衝突,在當前位置給單鏈表增長結點就行。
  3. 其餘方法:再哈希法、創建公共溢出區
  4. 在用拉鍊法構造的散列表中,刪除結點的操做易於實現。拉鍊法的缺點是:指針須要額外的空間,故當結點規模較小時,開放定址法較爲節省空間。因爲拉鍊法中各鏈表上的結點空間是動態申請的,故它更適合於造表前沒法肯定表長的狀況。拉鍊法解決衝突時,須要使用指針,指示下一個元素的存儲位置
  5. 開哈希表–鏈式地址法;閉哈希表–開放地址法.開哈希和閉哈希主要的區別在於,隨着哈希表的密集度提升,使用閉哈希時,不只會與相同哈希值的元素髮生衝突,還容易與不一樣哈希值的元素髮生衝突;而開哈希則不受哈希表疏密與否的影響,始終只會與相同哈希值的元素衝突而已。因此在密集度變大的哈希表中查找時,顯然開哈希的平均搜索長度不會增加。
  6. 設有n個關鍵字具備相同的Hash函數值,則用線性探測法把這n個關鍵字映射到Hash表中須要作n*(n-1)/2次線性探測。若是使用二次探測再散列法將這n個關鍵字存入哈希表,至少要進行n*(n+1)/2次探測

Hash查找效率:裝填因子=表中記錄數/表容量

有B+Tree/Hash_Map/STL Map三種數據結構。對於內存中數據,查找性能較好的數據結構是Hash_Map,對於磁盤中數據,查找性能較好的數據結構是B+Tree。Hash操做能根據散列值直接定位數據的存儲地址,設計良好的hash表能在常數級時間下找到須要的數據,可是更適合於內存中的查找。B+樹是一種是一種樹狀的數據結構,適合作索引,對磁盤數據來講,索引查找是比較高效的。STL_Map的內部實現是一顆紅黑樹,可是隻是一顆在內存中創建二叉樹樹,不能用於磁盤操做,而其內存查找性能也比不上Hash查找。

內部排序

  1. 內部排序:所有數據可同時放入內存進行的排序。
  2. 外部排序:文件中數據太多,沒法所有調入內存進行的排序。

插入類:

  1. 直接插入排序。最壞狀況是數據遞減序,數據比較和移動量最大,達到O(n2),最好是數據是遞增序,比較和移動最少爲O(n)。趟數是固定的n-1,即便有序,也要依次從第二個元素開始。排序趟數不等於時間複雜度。
  2. 折半插入排序 。因爲插入第i個元素到r[1]到r[i-1]之間時,前i個數據是有序的,因此能夠用折半查找肯定插入位置,而後插入。
  3. 希爾排序。縮小增量排序。5-3-1。在實際應用中,步長的選取可簡化爲開始爲表長n的一半(n/2),之後每次減半,最後爲1。插入的改進,最後一趟已基本有序,比較次數和移動次數相比直接插入最後一趟更少

交換類:

  1. 冒泡排序。O(n2)一般認爲冒泡是比較差的,能夠加些改進,好比在一趟中無數據的交換,則結束等措施。
    • 在數據已基本有序時,冒泡是一個較好的方法
    • 在數據量較少時(15個左右)能夠用冒泡
  2. 快速排序。
    • 時間複雜度。最好狀況:每次支點總在中間,O(nlog2n),平均O(nlog2n)。最壞,數據已經是遞增或遞減,O(n2)。pivotkey的選擇越靠近中央,即左右兩個子序列長度越接近,排序速度越快。越無序越快。
    • 空間複雜度。需棧空間以實現遞歸,最壞狀況:S(n)=O(n);通常狀況:S(n)=O(log2n)
    • 在序列已經是有序的狀況下,時間複雜度最高。緣由:支點選擇不當。改進:隨機選取支點或最左、最右、中間三個元素中的值處於中間的做爲支點,一般能夠避免最壞狀況。因此,快速排序在表已基本有序的狀況下不合適。
    • 在序列長度已較短時,採用直接插入排序、起泡排序等排序方法。序列的個數一般取10左右。

選擇類排序:

  1. 簡單選擇排序。O(n2)。總比較次數n(n-1)/2。
  2. 堆排序。建堆 O(n),篩選排序O(nlogn)。找出若干個數中最大/最小的前K個數,用堆排序是最好。小根堆中最大的數必定是放在葉子節點上,堆自己是個徹底二叉樹,徹底二叉樹的葉子節點的位置大於[n/2]。時間複雜度不會由於待排序序列的有序程度而改變,可是待排序序列的有序程度會影響比較次數。
  3. 歸併排序。時間:與表長成正比,若一個表表長是m,另外一個是n,則時間是O(m+n)。單獨一個數組歸併,時間:O(nlogn),空間:O(n),比較次數介於(nlogn)/2和(nlogn)-n+1,賦值操做的次數是(2nlogn)。歸併排序算法比較佔用內存,但倒是效率高且穩定的排序算法。在外排序中使用。歸併的趟數是logn。
  4. 基數排序。在通常狀況下,每一個結點有 d 位關鍵字,必須執行 t = d次分配和收集操做。分配的代價:O(n);收集的代價:O(rd) (rd是基數);總的代價爲:O( d ×(n + rd))。適用於以數字和字符串爲關鍵字的狀況。
  5. 枚舉排序,一般也被叫作秩排序,比較計數排序。對每個要排序的元素,統計小於它的全部元素的個數,從而獲得該元素在整個序列中的位置,時間複雜度爲O(n2)

比較法分類的下界:O(nlogn)

排序算法的一些特色:

  1. 堆排序、冒泡排序、快速排序在每趟排序過程當中,都會有一個元素被放置在其最終的位置上。
  2. 有字符序列 {Q,H,C,Y,P,A,M,S,R,D,F,X} ,新序列{F,H,C,D,P,A,M,Q,R,S,Y,X},是快速排序算法一趟掃描的結果。(拿Q做爲分割點,快速排序一輪。二路歸併,第一趟排序,獲得 n / 2 個長度爲 2 的各自有序的子序列,第二趟排序,獲得 n / 4 個長度爲 4 的各自有序的子序列H Q C Y A P M S D R F X。若是是快速排序的話,第一個元素t將會被放到一個最準確的位置,t前的數均小於t,後面的數均大於t。希爾排序每一個小分組內將會是有序的。堆排序,把它構成一顆二叉樹的時候,該堆要麼就是大根堆,要麼就是小根堆,第一趟Y排在最後;冒泡,那麼確定會有數據下沉的動做,第一趟有A在第一位。)
  3. 在文件」局部有序」或文件長度較小的狀況下,最佳內部排序的方法是直接插入排序。(歸併排序要求待排序列已經部分有序,而部分有序的含義是待排序列由若干有序的子序列組成,即每一個子序列必須有序,而且其時間複雜度爲O(nlog2n);直接插入排序在待排序列基本有序時,每趟的比較次數大爲下降,即n-1趟比較的時間複雜度由O(n^2)降至O(n)。在待排序的元素序列基本有序或者每一個元素距其最終位置不遠也可用插入排序,效率最高的排序方法是插入排序
  4. 排序趟數與序列的原始狀態有關的排序方法是優化冒泡和快速排序法。(插入排序和選擇排序無論序列的原始狀態是什麼都要執行n-1趟,優化冒泡和快排不必定。仔細理解排序的次數比較次數的區別)
  5. 不穩定的排序方法:快排,堆排,希爾,選擇
  6. 要與關鍵字的初始排列次序無關,那麼就是最好、最壞、通常的狀況下排序時間複雜度不變, 總共有堆排序,歸併排序,選擇排序,基數排序
  7. 快速排序、Shell 排序、歸併排序、直接插入排序的關鍵碼比較次數與記錄的初始排列有關。折半插入排序、選擇排序無關。(直接插入排序在徹底有序的狀況下每一個元素只須要與他左邊的元素比較一次就能夠肯定他最終的位置;折半插入排序,比較次數是固定的,與初始排序無關;快速排序,初始排序不影響每次劃分時的比較次數,都要比較n次,可是初始排序會影響劃分次數,因此會影響總的比較次數,但快排平均比較次數最小;歸併排序在歸併的時候,若是右路最小值比左路最大值還大,那麼只須要比較n次,若是右路每一個元素分別比左路對應位置的元素大,那麼須要比較2*n-1次,因此與初始排序有關)
  8. 精儉排序,即一對數字不進行兩次和兩次以上的比較,插入和歸併是「精儉排序」。插入排序,前面是有序的,後面的每個元素與前面有序的元素比較,比較過的就是有序的了,不會再比較一次。歸併每次合併後,內部都是有序的,內部的元素之間不用再比較。選擇排序,每次在後面的元素中找到最小的,找最小元素的過程是在沒有排好序的那部分進行,全部確定會比較屢次。堆排序也需比較屢次。

外部排序

  1. 生成合並段(run):讀入文件的部分記錄到內存->在內存中進行內部排序->將排好序的這些記錄寫入外存,造成合並段->再讀入該文件的下面的記錄,往復進行,直至文件中的記錄所有造成合並段爲止。
  2. 外部合併:將上一階段生成的合併段調入內存,進行合併,直至最後造成一個有序的文件。
  3. 外部排序指的是大文件的排序,即待排序的記錄存儲在外存儲器上,待排序的文件沒法一次裝入內存,須要在內存和外部存儲器之間進行屢次數據交換,以達到排序整個文件的目的。外部排序最經常使用的算法是多路歸併排序,即將原文件分解成多個可以一次性裝入內存的部分,分別把每一部分調入內存完成排序。而後,對已經排序的子文件進行多路歸併排序
  4. 無論初始序列是否有序, 冒泡、選擇排序時間複雜度是O(n^2),歸併、堆排序時間複雜度是O(nlogn)
  5. 外部排序的總時間 = 內部排序(產出初始歸併段)所需時間 + 外存信息讀取時間 + 內部歸併所需的時間
  6. 外排中使用置換選擇排序的目的,是爲了增長初始歸併段的長度。減小外存讀寫次數須要減少歸併趟數

  7. 根據內存容量設若干個輸入緩衝區和一個輸出緩衝區。若採用二路歸併,用兩個輸入緩衝。

  8. 歸併的方法相似於歸併排序的歸併算法。增長的是對緩衝的監視,對於輸入,一旦緩衝空,要到相應文件讀後續數據,對於輸出緩衝,一旦緩衝滿,要將緩衝內容寫到文件中去。
  9. 外排序和內排序不僅是考慮內外排序算法的性能,還要考慮IO數據交換效率的問題,內存存取速度遠遠高於外存。影響外排序的時間因素主要是內存與外設交換信息的總次數

有效的算法設計

  1. 貪心法。Dijkstra的最短路徑(時間複雜度O(n2));Prim求最小生成樹鄰接表存儲時是O(n+e),圖O(n2);關鍵路徑及關鍵活動的求法。
  2. 回溯法
  3. 分支限界法
  4. 分治法。分割、求解、合併。二分查找、歸併排序、快速排序。
  5. 動態規劃。Floyd-Warshall算法求解圖中全部點對之間最短路徑時間複雜度爲O(n3)

動態規劃解題的方法是一種高效率的方法,其時間複雜度一般爲O(n2),O(n3)等,能夠解決至關大的信息量。(數塔在n<=100層時,能夠在很短的時間內獲得問題解)

  • 適用的原則:原則爲優化原則,即總體優化能夠分解爲若干個局部優化。
  • 動態規劃比窮舉法具備較少的計算次數
  • 遞歸算法須要很大的棧空間,而動態規劃不須要棧空間

貪心和動態規劃的差異:

  1. 所謂貪心選擇性質是指所求問題的總體最優解能夠經過一系列局部最優的選擇,即貪心選擇來達到。這是貪心算法可行的第一個基本要素,也是貪心算法與動態規劃算法的主要區別。
  2. 在動態規劃算法中,每步所做的選擇每每依賴於相關子問題的解。於是只有在解出相關子問題後,才能做出選擇。而在貪心算法中,僅在當前狀態下做出最好選擇,即局部最優選擇。而後再去解做出這個選擇後產生的相應的子問題。
  3. 貪心算法所做的貪心選擇能夠依賴於以往所做過的選擇,但決不依賴於未來所做的選擇,也不依賴於子問題的解。正是因爲這種差異,動態規劃算法一般以自底向上的方式解各子問題,而貪心算法則一般以自頂向下的方式進行,以迭代的方式做出相繼的貪心選擇,每做一次貪心選擇就將所求問題簡化爲一個規模更小的子問題。

P問題

    1. P問題,若是它能夠經過運行多項式次(即運行時間至可能是輸入量大小的多項式函數的一種算法得到解決),能夠找到一個能在多項式的時間裏解決它的算法。—-肯定性問題
    2. NP問題,雖然能夠用計算機求解,可是對於任意常數k,它們不能在O(nk)時間內獲得解答,能夠在多項式的時間裏驗證一個解的問題。全部的P類問題都是NP問題。
    3. NP徹底問題,知道有效的非肯定性算法,可是不知道是否存在有效的肯定性算法,同時,不能證實這些問題中的任何一個不存在有效的肯定性算法。這類問題稱爲NP徹底問題。原文地址:https://blog.csdn.net/qq_31196849/article/details/78529724
相關文章
相關標籤/搜索