算法及數據結構(下)

一、排序算法 

      所謂排序,就是按照某種次序,重新排列某一序列中的所有元素。爲此,任意一對元素之間都應該能夠比較大小,即在所有元素之間可以定義一個全序關係。排序算法種類繁多,根據其處理數據的規模與存儲特點,可分爲內部排序和外部排序算法,前者處理的數據規模不大,內存足以容納,後者處理的數據規模很大,必須將數據存放於外部存儲器中,根據輸入不同的形式,排序算法可以劃分爲脫機算法與在線算法,在前一種情況下,待排序的數據是以批處理的形式給出的,而在網絡計算之類的環境中,待排序的數據則是實時生成的,在排序算法開始運行時,數據並未完全就緒,而是隨着排序算法本身的進行而逐步給出的,針對不同的體系結構,也需要採用不同的排序算法,由此又可以劃分爲串行和並行兩大類排序算法,另外,根據排序算法是否採用隨機策略,還有確定式和隨機式之分。

      1、冒泡排序
      2、選擇排序
      3、插入排序
      4、堆排序
      5、歸併排序

       ①分治策略:爲了解決一個規模較大的問題,我們可以將其分解爲兩個子問題,並藉助遞歸分別得到它們的解,然後將子問題的解合併成原問題的解,這就是分治策略。爲了保證分治策略的效率,首先必須保證子問題的劃分及其解的合併都能快速完成,通常,都要求這兩部分計算可以在線性時間內完成,另外,劃分出來的子問題應該是相互獨立的,也就是說,每個子問題的解不受其它子問題的影響,最後,子問題的規模不能相差懸殊,最後能夠相等或者接近,事實上,歸併排序算法完全滿足上述要求。首先,以居中的位置爲界,只需O(n)時間即可將待排序的序列均勻地劃分爲左、右兩個子序列,而且各子序列的排序不受另一子序列的影響。更重要的是,根據這兩個子序列各自的排序結果,可以在線性時間內獲得整個序列的排序結果。 
       ②歸併算法:所謂歸併操作,就是將兩個有序子序列合併爲一個整體有序的序列。

      6、快速排序

       快速排序是分治策略的又一典型引用,歸併排序算法的主要計算量集中於有序子序列的歸併,而快速排序算法正好相反,它可以在常數的時間複雜度內由子問題的解直接得到原問題的解,但爲了將原問題劃分爲兩個子問題,快速排序算法卻需要線性的時間複雜度,同時,雖然快速排序算法可以確保子任務的相互獨立性,但並不能保證子任務的規模大體相當,甚至有可能極不平衡,但該算法易於實現,而且其平均時間複雜度足夠低,在實際應用中往往成爲首選的排序算法。
       ①軸點:在每個長度不小於 3 的序列S[lo..hi]中,對於任何lo < mi < hi,以每一個元素p = S[mi]爲界,都可以將該序列分割爲前、後兩個子序列S1= S[lo..mi-1]和S2= S[mi+1..hi] ,若S1中元素均不大於 p,S2中元素均不小於 p,則元素 p 稱作序列 S 的一個軸點(pivot) 。
       ②劃分算法:獲取軸點的算法:
{
  while (lo < hi) {
  while ((lo < hi) && (S[lo] ≤ S[hi]) hi--; while ((lo < hi) && (S[lo] ≤ S[hi]) lo++;
  swap(S[lo], S[hi]);
  swap(S[lo], S[hi]);
}
  return lo;}
       當然也可以隨機獲取三個元素,挑選其中大小居中的元素。

二、數據結構之串結構

       串是由有限個字符組成的一種線性結構,它的兩個突出特點是結構簡單,規模龐大。

      1、串模式匹配

       由 n 個字符構成的串記作 S = "aa1... an-1",其中 ai∈Σ。這裏的Σ是所有可用字符的集合,稱作字母表,n 稱爲 S 的長度,記作|S| = n ,長度爲零的串稱爲空串 。所謂 S 的子串(Sub-string),就是起始於任一位置 i 的連續 k 個字符,記作 substr(S, i, k)="aiai+1...ai+k-1",0≤i<n,0≤k。起始於位置 0、長度爲 k 的子串稱爲前綴(Prefix),記作 prefix(S, k) = substr(S, 0, k),終止於位置 n-1、長度爲 k 的子串稱爲後綴(suffix),記作 suffix(S, k) = substr(S, n-k, k)。空串是任何串的子串,也是任何串的前綴、後綴。任何串都是自己的子串,也是自己的前綴、後綴。空串以及串本身亦稱作平凡子串(前綴、後綴)。空串以及非平凡子串(前綴、後綴)稱作真子串(前綴、後綴)。串 S = "aa1a2... an-1"和 T = "b0b1b2... bm-1"稱作相等,當且僅當二者長度相等,且對應的字符分別相同,即 n = m 且對任何 0 ≤ i < n 都有 ai= bi 
   給定串T(稱作主串)和串P(稱作模式串),T中是否存在的某個子串與P相同?如果存在,找到該子串在T中的起始位置,這類操作,都屬於串模式匹配的範疇,簡稱串匹配
      串匹配是一個經典的問題,有名字的算法不下三十餘種。由於串結構自身的特點,在設計和分析此類算法時也需做特殊的考慮,可行的一種策略是,隨機選取主串 T,但僅僅測試那些匹配成功的情況。爲此,可以從 T 中隨機取出長度爲 m 的子串作爲 P。

      2、蠻力算法

       蠻力匹配算法是最直接、直觀的方法:

      3、Knuth-Morris-Pratt算法

       在最壞情況下蠻力算法的運行時間爲主串、模式串長度的乘積,因此只適用於小規模的串匹配應用。對最壞情況稍加觀察即可發現,之所以需要大量的時間,是因爲存在大量的局部匹配(每一輪的 m 次比較中,只有最後一次是失敗的),實際上,絕大部分的這類字符比較操作都是不必要的,因爲關於主串中此前曾經比較成功過的字符,我們已經掌握了它們的所有信息。只要充分利用這些信息,就可以大大提高匹配算法的效率。 

       如上圖所示,用T[i]和P[j]分別表示當前正在接受比較的一對字符。當輪比較進行到最後一對字符並發現失配後,蠻力算法將會讓這兩個字符指針回退(即令i = i-j+1j = 0 ),然後從這一位置繼續比較。事實上,指針i完全不必回退⎯⎯因爲通過前一輪比較我們已經清楚地知道,主串的子串T[i-j..i-1]完全是由字符'0'組成的。因此,在回退之後緊接下來的一輪迭代中,j-1次比較將註定會成功。既然如此,完全可以讓指針i保持不動並且令j = j-1,然後繼續比較。請注意,如此將可以省去j-1次比較!

       上述“i保持不動並且令 j = j-1”的含義,可以理解爲 P相對於 T 向右移動一個單元,然後從剛纔失配的位置繼續比較。實際上,利用以往的成功比較所提供的信息,不僅可以避免主串字符指針的回退,而且有可能使模式串儘可能大跨度地右移。

       當本輪比較中發現T[i] ≠ P[7]失配後,應該將模式串P右移多少個單元呢?有必要逐個單元地右移嗎?稍加觀察即可發現,在這一情況下,移動一個、兩個或三個單元都是徒勞的。事實上,根據以往的比較結果,必然有T[i-7..i] = P[0..7] = "CHINCHI" 。如果在此局部能夠實現匹配,則至少在 T[i]左側的那些字符應該是匹配的⎯⎯比如, P[0] T[i-3]對齊時,就屬於這樣的情況。如果再注意到i-3是能夠如此匹配的最靠左位置,就可以放心地將模式串右移 7-3 = 4個單元,使 i保持不變, j= 3,然後繼續比較。

       相對於蠻力算法,KMP算法可以避免很多不必要的比較操作,KMP算法的運行時間爲O(n+m),其中 n 和 m 分別文本串和模式串的長度。

      4、BM算法

       KMP算法的思想可以總結爲:不斷將模式串與文本串比較,一旦局部失配,則利用此前比較給出的信息,儘可能長距離的移動模式串,在比較模式串與文本串時,掃描方向是自左向右,實際上,很多模式匹配算法採用了其他的掃描方向,比如從右向左或者從中間向兩邊,BM算法採用的就是從右向左的掃描次序,該算法的構思是不斷自右向左地比較模式串P與主串T,一旦發現失配,則利用此前掃描所提供的信息,將P右移一定距離,然後重新自右向左掃描比較,該算法有兩種啓發式策略:藉助壞字符和好後綴確定移動的距離,也可將二者結合起來,同時採用。
       ①壞字符

        如 圖九.7 所示,在自右向左比較模式串P[0..m-1]與主串的子串T[i..i+m-1]的過程中,假設在P[j]處首次發現失配:T[i+j] = 'b' ≠ 'a' = P[j]。此時,我們應該用P中的哪個字符對準T[i+j]並重新自右向左比較呢?我們注意到,若 P 能夠與 T 的某一(包括 T[i+j]在內的)子串匹配,則必然也應在 T[i+j] = 'b'處匹配;反之,若與 T[i+j]對準的字符不是'b',則不可能匹配。因此,只需將 P 中的每一字符'b'對準T[i+j],然後重新自右向左比較。爲了避免 P 的左移,我們可以選用 P 中最靠右的字符'b'(如果存在的話),將其與 T[i+j]對齊,然後重新做一遍自右向左的掃描比較。具體來說,若 P 中最靠右的字符'b'爲 P[k] = 'b',則 P 的右移量爲 j - k。

  特殊情況: 

       如 圖九.8 所示,P串中不含字符'b',此時可以直接將該串整體移過,用P[0]對準T[i+j+1],然後自右向左繼續比較。 另外,即使 P 串中含有字符'b',卻也有可能出現的位置太靠右,使得 k = BC['b'] ≥ j。在這一情況下,j-k 將不再是正數,若以此距離進行右移,實際效果將是左移⎯⎯顯然,這是不必要的。因此,爲處理這一情況,只需簡單地將 P 串右移一個字符,然後重新自右向左掃描比較。

       ②好後綴策略

       BM算法的思想,是儘可能的利用此前已進行過的比較所提供的信息,以加速模式串的移動,上述壞字符策略,就很好的體現了這一構思,然而,仔細分析後我們發現,壞字符策略只利用了此前失敗的比較所提供的信息,實際上,在失敗之前往往還會有一系列成功的比較,他們也能提供大量的信息,我們利用這些信息加速模式串右移,稱之爲好後綴策略。

三、數據結構之圖

       在衆多的實際應用中,圖結構都是描述與解決應用問題的一個基本而強有力的工具,這裏的圖可以表示爲G=(V,E),其中集合V中的對象稱作頂點,而集合E中的每一個元素對應於V中的某一對頂點,說明這兩個頂點之間存在某種關係,稱作邊,這裏的圖,並非通常所指的圖形、報表或者數學上的函數圖像之類。
       ①無邊圖、混合圖及有向圖
       圖中的邊可以使沒有方向的,也可以是有方向的,如果邊e=(u,v)所對應的頂點u和v不是對等的,或者存在某種次序,就稱e爲有向邊,如果u和v的次序無所謂,e就是一條無向邊,注意無向邊(u,v)也可以記作(v,u),而有向邊(u,v)和(v,u)則是不同的兩條邊若E中所有的邊都沒有方向,則稱G爲無向圖,若E中同時包含無向邊和有向邊,則稱G爲混合圖,若E中只含有有向邊,則稱G爲有向圖,相對而言,有向圖的通用性更強,因爲無向圖和混合圖都可以轉化爲有向圖。
       ②度:

       若邊e = (u, v),則頂點u和v也稱作e的端點(End vertices或Endpoints)。如果e是從u指向v的有向邊,則u稱作起點(Origin)或尾端點(Tail),v稱作終點(Destination)或頭端點(Head)。我們也稱u和v是相鄰的(Adjacent),稱e與v、u是相關聯的(Incident)。頂點v的關聯邊的總數,稱爲v的度數(Degree),記作deg(v)。以 圖十.1(a)爲例,有deg(a) = deg(c) = 3。在有向圖中,以u爲起點的有向邊稱作u的出邊(Outgoing edge),以v爲終點的邊則稱作v的入邊(Incoming edge )。v的出邊總數稱作v的出度(Out-degree),記作outdeg(v);入邊總數稱作入度(In-degree),記作indeg(v)。以 圖十.1(c)爲例,有outdeg(a) = indeg(a) = outdeg(c) = indeg(c) =3。 



       ③簡單圖

       圖中所含的邊並不見得能構成一個集合(Set),準確地說它們構成了一個復集(Multiset)⎯⎯其中允許出現重複的元素。比如,若在某對頂點之間有多條無向邊,或者兩條有向邊的起點和終點完全一樣,就屬於這種情況。這類重複的邊也稱作平行邊(Parallel e dges)或多重邊(Multipleedges)。例如,要是用頂點表示城市,用邊表示城市之間的飛機航線,則有可能在某一對城市之間存在多條航線。無論是無向圖還是有向圖,還有另一種特殊情況:與某條邊關聯的是同一個頂點(如 圖十.1 中的頂點a)⎯⎯這樣的邊稱作自環(Self-loop)。在某些特定的應用問題中,這類邊的確是有意義的⎯⎯比如在城市交通圖中,沿着某條街道,有可能會不經過任何交叉路口而直接返回原處。不過,這些特殊情況通常並不多見。不含上述特殊邊的圖,稱作簡單圖(Simple graph)。對簡單圖而言,其中的邊必然構成一個集合,而且每條邊只能聯接於不同的頂點之間。

      ④圖的複雜度

      設簡單圖G包含n個頂點和m條邊。若 G 是無向圖,則有 m ≤ n(n-1)/2;若 G 是有向圖,則有 m ≤ n(n-1)。

      ⑤子圖、生成子圖、限制子圖

      設G=(V,E)和G‘=(V',E'),如果 V' ⊆ V 且 E' ⊆ E,則稱 G'是 G 的一個子圖,如果 V' = V 且 E' ⊆ E,則稱 G'是 G 的一個生成子圖,若 U ⊆ V,則在刪除 V\U 中的頂點及其關聯邊之後所得到的 G 的子圖,稱爲 G 限制在 U上的子圖,記做 G|U= (U, E|U) 
 
       ⑥通路、環路及可達分量

       所謂圖中的一條通路或路徑,就是由(不一定互異的)m+1個頂點與m條邊交替構成的一個序列ρ = {v0, e1, v1, e2, v2, ..., em, vm},m ≥ 0,而且 ei= (vi-1, vi),1 ≤ i ≤ m,m稱作該通路的長度,長度m≥1的路徑,若第一個頂點與最後一個頂點相同,則稱之爲環路如果組成通路ρ的所有頂點各不相同,則稱之爲簡單通路,如果在組成環路的所有頂點中,除v0= v m外均各不相同,則稱之爲簡單環路,如果組成通路ρ的所有邊都是有向邊,而且每一ei都是從vi-1指向vi,1 ≤ i ≤ m,則稱ρ爲有向通路,類似的也可定義有向環路。在圖G=(V,E)中,若記n=|V|,則簡單路徑長度不超過 n-1;長度爲 k 的簡單路徑的總數不超過 n!/(n-k-1)!,1 ≤ k ≤ n-1,G中簡單路徑的數目是有限的。

       在有向圖G中,若從頂點s到v有一條通路,則稱v是“從s可達的”,對於指定的頂點s,從s可達的所有頂點所組成的集合,稱作s在G中對應的可達分量,記作Vr(G, s)。 

       ⑦連通性、等價類與連通分量

       考察圖 G = ( V, E)。在頂點 u, v ∈ V 之間,如果既存在一條從 u 到 v 的通路ρ(u, v),也存在一條從v到u的通路ρ(v, u),則稱u和v是連通的,記作u ~ v。實際上,若 G 是無向圖,則“ρ(u, v)存在”當且僅當“ρ(v, u)存在”。而對於有向圖 G 來說,“ρ(u,v)和ρ(v, u)同時存在”即意味着“存在一條同時經過 u 和 v 的環路”。故此,有向圖中相互連通的頂點也被稱爲是互相“強連通的”。在有向圖中,由一組相互強連通的頂點構成的極大集合,稱作一個強連通分量。若u~v,則必然存在一條經過u和v的環路,而且對於該環路上的任一頂點w,都有w~u和w~v。連通關係“~”滿足如下性質:反身性:對於任何頂點 v,都有 v ~ v 成立;對稱性:u~v僅當v~u;傳遞性:對於任何頂點u、v和w,只要u~v且v~w,則必有u~w。 

       ⑧森林、樹及無向圖的生成樹

       考察無向圖G=(V,E),若G中不含任何環路,則稱之爲森林,連通的森林(任何兩個頂點都是連通的)稱作樹。若G爲由n個頂點與m條邊組成一幅無向圖,若G是連通的,則m≥n-1,若G是一棵樹,則m=n-1,若G是森林,則m≤n-1。若G的某一生成子圖G‘爲一棵樹,則稱G’爲G的一顆生成樹。

       ⑨有向圖的生成樹

       在有向圖G=(V,E)中,若存在某個頂點s滿足Vr(G, s) = V(即從s可到達所有頂點)且s到任一頂點的通路唯一,同時G中不含迴路,則稱G爲一顆以s爲根的有向樹。

       ⑩帶權網絡

       有些時候,圖不僅需要表示元素之間是否存在某種關係,而且還需要表示這一關係的某一細節,以鐵路運輸爲例,可以用頂點表示城市,用頂點之間的邊表示城市之間是否有鐵路聯接,然而,爲了更爲細緻地描述鐵路運輸網,還需要記錄每段鐵路的長度、運輸成本等信息,爲適應這類應用的需求,我們需要爲每條邊設置相應的數據域,以記錄對應的信息,對於任一邊 e ∈ E,weight(e)稱作 e 的權重 ,引入權重函數之後的圖G(V,E,weight()),稱作帶權圖或帶權網絡,有時也簡稱爲網絡。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章