大話數據結構讀書筆記系列(五)串

5.1 開場白

同窗們,你們好!咱們開始上新的一課。算法

咱們古人沒有電影電視,沒有遊戲網絡,因此文人們就會想出一些文字遊戲來娛樂。好比宋代的李禺寫了這樣一首詩:"枯眼望遙山隔水,往來曾見幾心知?壺空怕酌一杯酒,筆下難成和韻詩。途路阻人離別久,迅音無雁寄回遲。孤燈夜守長寥寂,夫憶妻兮父憶兒。"顯然這個老公想念老婆和兒子的詩句。曾經和妻兒在一塊兒,盡享天倫之樂,如今一我的長久沒有回家,也不見書信返回,望着油燈想念親人,能不傷感嗎?編程

可再仔細一讀發現,這首詩居然能夠倒過來讀:"兒憶父兮妻憶夫,寂寥長守夜燈孤。遲迴寄雁無音訊,久別離人阻路途。詩韻和成難下筆,酒杯一酌怕空壺。知心幾見曾來往,水隔山遙望眼枯。"這表達了什麼意思呢?呵呵,表達了妻子對丈夫的思念。老公離開很久,路途遙遠,難以相見。寫信不知道寫什麼,獨自喝酒也沒什麼興致。只能和兒子夜夜守在家裏一盞孤燈下,苦等老公的歸來。數組

這種詩體叫作迴文詩。它是一種能夠倒讀或反覆迴旋閱讀的詩體。剛纔這首就是正讀是丈夫思念妻子,倒讀是妻子思念丈夫的古詩。是否是感受很奇妙呢?網絡

在英語單詞中,一樣有神奇的地方。"即便是lover也有個over,即便是friend也有個end,即便是believe也有個lie"。你會發現,原本不相干,甚至對立的兩個詞,卻有某種神奇的聯繫。這多是創造這幾個單詞的那麼智者們也沒有想到的問題。數據結構

今天咱們就要來談談這些單詞或句子組成字符串的相關問題。app

5.2 串的定義

早先的計算機在被髮明時,主要做用是作一些科學和工程的計算工做,也就是如今咱們理解的計算器,只不過它比小小計算器功能更強大、速度更快一些。後來發現,在計算機上做非數值處理的工做愈來愈多,使得咱們不得不須要引入對字符的處理。因而就有了字符串的概念。編程語言

好比咱們如今經常使用的搜索引擎,當咱們在文本框中輸入"數據"時,它已經把咱們想要的"數據結構"列在下面了。顯然這裏網站做了一個字符串查找匹配的工做,如圖5-2-1所示。 函數

今天咱們就是來研究"串"這樣的數據結構。先來看定義。性能

串(String)是由零個或多個字符組成的有限序列,又名叫字符串。學習

通常記爲s="a1a2...an"(n>0),其中,s是串的名稱,用雙引號(有些書中也用單引號)括起來的字符序列是串的值,注意單引號不屬於串的內容。ai(1<=i<=n)能夠是字母、數字或其餘字符,i就是該字符在串中的位置。串中的字符數目n稱爲串的長度,定義中談到"有限"是指長度n是一個有限的數值。零個字符的串稱爲空串(null string),它的長度爲零,能夠直接用兩雙引號""""表示,也能夠用希臘字母"ø"來表示。所謂的序列,說明串的相鄰字符之間具備前驅和後繼的關係。

還有一些概念須要解釋。

  • 空格串,是隻包含空格的串。注意它與空串的區別,空格串是有內容有長度的,並且能夠不止一個空格。
  • 子串與主串,串中任意個數的連續字符組成的子序列稱爲該串的子串,相應地,包含子串的串稱爲主串。
  • 子串在主串中的位置就是子串的第一個字符在主串中的序號。
  • 開頭我所提到的"over"、"end"、"lie"其實能夠認爲是"lover"、"friend"、"believe"這些單詞字符串的子串。

5.3 串的比較

兩個數字,很容易比較大小。2比1大,這徹底正確,但是兩個字符串如何比價?好比"silly"、"stupid"這樣的一樣表達"愚蠢的"的單詞字符串,它們在計算機中的大小其實取決於它們挨個字母的先後順序。它們的第一個字母都是"s",咱們認爲不存在大小差別,而第二個字母,因爲"i"字母比"t"字母要靠前,因此"i"<"t",因而咱們說"silly"<"stupid"。

事實上,串的比較是經過組成串的字符之間的編碼來進行的,而字符的編碼指的是字符在對應字符集中的序號

計算機中的經常使用字符是使用標準的ASCII編碼,更準確一點,由7位二進制數表示一個字符,總共能夠表示256個字符,這已經足夠知足以英語爲主的語言的特殊符號進行輸入、存儲、輸出等操做的字符須要了。但是,單咱們國家就有除漢族外的滿、回、藏、蒙古、維吾爾等多個少數民族文字,換做全世界估計要有成百上千種語言與文字,顯然這256個字符是不夠的,所以後來就有了Unicode編號,比較經常使用的是由16位的二進制表示一個字符,這樣總共就能夠表示216個字符,約是65萬多個字符,足夠表示世界上全部語言的全部字符了。固然,爲了和ASCII碼兼容,Unicode的前256個字符與ASCII碼徹底相同。

因此若是咱們要在C語言中比較兩個串是否相等,必須是它們串的長度以及它們各個對應位置的字符都相等時,纔算是相等。即給定兩個串:s="a1a2...an",t="b1b2...bm",當且僅當n=m,且a1=b1,a2=b2,...,an=bm時,咱們認爲s=t

那麼對於兩個串不相等時,如何斷定它們的大小呢。咱們這樣定義:

  1. n<m,且ai=bi(i=1,2,...,n)。 例如當s="hap",t="happy",就有s<t。由於t比s多出了兩個字母。
  2. 存在某個k<=min(m,n),使得ai=bi(i=1,2,...,k-1),ak<bk。 例如當s="happen",t="happy",由於兩串的前4個字母均相同,而兩串第5個字母(k值),字母e的ASCII碼是101,而字母y的ASCII碼是121,顯然e<y,因此s<t。(>)

有同窗若是對這樣的數學定義很不爽的話,那我再說一個字符串比較的應用。

咱們的英語詞典,一般都是上萬個單詞的有序排列。就大小而言,前面的單詞比後面的要小。你在查找單詞的過程,其實就是在比較字符串大小的過程。

5.4 串的抽象數據類型

串的邏輯結構和線性表很類似,不一樣之處在於串針對的是字符集,也就是串中的元素都是字符,哪怕串中的字符是"123"這樣的數字組成,或者"2010-10-10"這樣的日期組成,它們都只能理解爲長度爲3和長度爲10的字符串,每一個元素都是字符而已。

所以,對於串的基本操做與線性表是有很大差異的,線性表更關注的是單個元素的操做,好比查找一個元素,插入或刪除一個元素,但串中更多的是查找子串位置、獲得指定位置子串、替換子串等操做

對於不一樣的高級語言,其實對串的基本操做會有不一樣的定義方法,因此同窗們在用某個語言操做字符串時,須要先查看它的參考手冊關於字符串的基本操做有哪些。不過還好,不一樣語言除方法名稱外,操做實質都是相相似的。好比C#中,字符串操做就還有ToLower轉小寫、ToUpper轉大寫、IndecOf從左查找子串位置(操做名有修改)、LastIndexOf從右查找子串位置、Trim去除兩邊空格等比較方便的操做,他們其實就是前面這些基本操做的擴展函數。

咱們來看一個操做Index的實現算法。

當中用到了StrLength、SubString、StrCompare等基本操做來實現。

5.5 串的存儲結構

串的存儲結構與線性表相同,分爲兩種。

5.5.1 串的順序存儲結構

串的順序存儲結構是用一組地址連續的存儲單元來存儲串中的字符序列的。按照預約義的大小,爲每一個定義的串變量分配一個固定長度的存儲區。通常是用定長數組來定義

既然是定長數組,就存在一個預約義的最大串長度,通常能夠將實際的串長度值保存在數組的0下標位置,有的書中也會定義存儲在數組的最後一個下標位置。但也有些編程語言不想這麼幹,以爲存個數字佔個空間麻煩。它規定在串值後面加一個不計入串長度的結束標記字符,好比"\0"來表示串值的終結,這個時候,你要想知道此時的串長度,就須要遍歷計算一下才知道了,其實這仍是須要佔用一個空間,何須呢

剛纔講的串的順序存儲方式實際上是有問題的,由於字符串的操做,好比兩串的鏈接Concat、新串的插入StrInsert,以及字符串的替換Replace。都有可能使得串序列的長度超過了數組的長度MaxSize。

說說我當年的一件囧事。手機發送短信時,運營商規定每條短信限制70個字。大約八年前,個人手機每當學了超過70個字後,它就提示"短信過長,請刪減後重發"。後來我換了一個手機後再也沒有這樣見鬼的提示了,我很高興。一次,由於一點小矛盾須要向當時的女朋友解釋一下,我準備發一條短信,一共打了79個字。最後的部分字實際上"......只會說好聽的話,像'我恨你'這種話是不可能的"。點發送。後來得知對方收到的,只有70個字,短信結尾是"......只會說好聽的話,像'我恨你'"。

有這樣截斷的嗎?我後來知道這個狀況後,巴不得把手機砸了。顯然,不管是上溢提示報錯,仍是對多出來的字符串截尾,都不是什麼好辦法。但字符串操做中,這種狀況比比皆是。

因而對於串的順序存儲,有一些變化,串值的存儲空間可再程序執行過程當中動態分配而得。好比在計算機中存在一個自由存儲區,叫作"堆"。這個堆可由C語言的動態分配函數malloc()和free()來管理。

串的鏈式存儲結構

對於串的鏈式存儲結構,與線性表是類似的,但因爲串結構的特殊性,結構中的每一個元素數據是一個字符,若是也簡單的應用鏈表存儲串值,一個結點對於一個字符,就會存在很大的空間浪費。所以,一個結點能夠存放一個字符,也能夠考慮存放多個字符最後一個結點如果未被佔滿時,能夠用"#"或其餘非串值字符補全,如圖5-5-3所示。

固然,這裏一個結點存多少個字符才合適就變得很重要,這會直接影響着串處理的效率,須要根據實際狀況做出選擇。

但串的鏈式存儲結構除了在鏈接串與串操做時有必定方便以外,總的來講不如順序存儲靈活,性能也不如順序存儲結構好。

5.6 樸素的模式匹配算法

記得我在剛作軟件開發的時候,須要閱讀一些英文的文章或幫助。此時才發現學習英語不僅是爲了過四六級,工做中它仍是挺重要的。而我那隻爲應付考試的英語,早已經忘得差很少了。因而我想在短期內突擊一些,很明顯,找一本詞典從頭開始背不是什麼好的辦法。要背也得背那些最經常使用的,至少是計算機文獻中經常使用的,因而我就想本身寫一個程序,只要輸入一些英文的文檔,就能夠計算出這當中所用頻率最高的詞彙是哪些。把它們都背好了,基本上閱讀也就不成問題了。

固然,說說容易,要實現這一需求,當中會有不少困難,有興趣的同窗,不妨去試試看。不過,這裏面最重要其實就是去找一個單詞咋一篇文章(至關於一個大字符串)中的定位問題。這種子串的定位操做一般稱作串的模式匹配,應該算是串中最重要的操做之一。

假設咱們要從下面的主串S="goodgoogle"中,找到T="google"這個子串的位置。咱們一般須要下面的步驟。

  1. 主串S第一位開始,S與T前三個字母都匹配成功,但S第四個字母是d而T的是g。第一位匹配失敗。如圖5-6-1所示,其中豎直連線表示相等,閃電狀彎折連線表示不等。

  2. 主串S第二位開始,主串S首字母是o,要匹配的T首字母是g,匹配失敗,如圖5-6-2所示。

  3. 主串S第三位開始,主串S首字母是o,要匹配的T首字母是g,匹配失敗,如圖5-6-3所示。

  4. 主串S第四位開始,主串S首字母是d,要匹配的T首字母是g,匹配失敗,如圖5-6-4所示。

  5. 主串S第五位開始,S與T,6個字母全匹配,匹配成功,如圖5-6-5所示。

簡單的說,就是對主串的每個字符做爲子串開頭,與要匹配的字符串進行匹配。對主串作大循環,每一個字符開頭作T的長度的小循環,直到匹配成功或所有遍歷完成爲止

前面咱們已經用串的其餘操做實現了模式匹配的算法Index。如今考慮不用串的其餘操做,而是隻用基本的數組來實現一樣的算法。注意咱們假設主串S和要匹配的子串T的長度存在S[0]與T[0]中。實現代碼以下:

分析一下,最好的狀況是什麼?那就是一開始就匹配成功,好比"googlegood"中去找"google",時間複雜度爲O(1)。稍差一些,若是像剛纔例子中第2、3、四位同樣,每次都是首字符就不匹配,那麼對T串的循環就沒必要進行了,好比"abcdefgoogle"中去找"google"。那麼時間複雜度爲O(n+m),其中n爲主串長度,m爲要匹配的子串長度。根據等機率原則,平均是(n+m)/2次查找,時間複雜度爲O(n+m)。

那麼最壞的狀況又是什麼?就是每次不成功的匹配都發生在串T的最後一個字符。舉一個很極端的例子。主串爲S="000000000000000000000000000000001",而要匹配的子串爲T="0000000001",前者是有49個"0"和1個"1"的主串,後者是9個"0"和1個"1"的子串。在匹配時,每次都得將T中字符循環到最後一位才發現:哦,原來它們是不匹配的。這樣等於T串須要在S串的前40個位置都須要判斷10次,並得出不匹配的結論,如圖5-6-6所示。

直到最後第41個位置,由於所有匹配相等,因此不須要再繼續進行下去,如圖5-6-7所示。若是最終沒有可匹配的子串,好比是T="0000000002",到了第41位置判斷不匹配後一樣不須要繼續比對下去。所以最壞狀況的時間複雜度爲O((n-m+1)*m)。

不要覺得我這只是危言聳聽,在實際運用中,對於計算機來講,處理的都是二進位的0和1的串,一個字符的ASCII嗎也能夠當作是8位的二進位01串,固然,漢字等全部的字符也均可以當作是多個0和1串。再好比像計算機圖形也能夠理解爲是由許許多多個0和1的串組成。因此在計算機的運算當中,模式匹配操做可說是隨處可見,而剛纔的這個算法,就顯得過低效了。

5.7 KMP模式匹配算法

大家能夠忍受樸素模式匹配算法的低效嗎?也許不能夠、也許無所謂。但在不少年前咱們的科學家們,以爲像這種有多個0和1重複字符的字符串,卻須要挨個遍歷的算法是很是糟糕的事情。因而有三位前輩,D.E.Knuth、J.H.Morris和V.R.Pratt(其中Knuth和Pratt共同研究,Morris獨立研究)發表一個模式匹配算法,能夠大大避免重複遍歷的狀況,咱們把它稱之爲克努特-莫里斯-普拉特算法,簡稱KMP算法

5.7.1 KMP模式匹配算法原理

爲了能講清楚KMP算法,咱們不直接講代碼,那樣很容易形成理解困難,仍是從這個算法的研究角度來理解爲何它比樸素算法要好。

若是主串S="abcdefgab",其實還能夠更長一些,咱們就省略掉只保留前9位,咱們要匹配的T="abcdex",那麼若是用前面的樸素算法的話,前5個字母,兩個串徹底相等,直到第6個字母,"f"與"x"不等,如圖5-7-1的(1)所示。

接下來,按照樸素模式匹配算法,應該是如圖5-7-1的流程(2)(3)(4)(5)(6)。即主串中當i=2,3,4,5,6時,首字符與子串T的首字符均不等。

彷佛這也是理所固然,原來的算法就是這樣設計的。可仔細觀察發現。對於要匹配的子串T來講,"abcdex"首字母"a"與後面的串"bcdex"中任意一個字符都不相等。也就是說,既然"a"不與本身後面的子串中任何一字符相等,那麼對於圖5-7-1的(1)來講,前五位字符分別相等,意味着子串T的首字符"a"不可能與S串的第2位到第5位的字符相等。在圖5-7-1中,(2)(3)(4)(5)的判斷都是多餘。

注意這裏是理解KMP算法的關鍵。若是咱們知道T串中首字符"a"與T中後面的字符均不相等(注意這是前提,如何判斷後面再講)。而T串的第二位的"b"與S串中第二位的"b"在圖5-7-1的(1)中已經判斷是相等的,那麼也就意味着,T串中首字符"a"與S串中的第二位"b"是不須要判斷也知道它們是不可能相等了,這樣圖5-7-1的(2)這一步判斷是能夠省略的,如圖5-7-2所示。

一樣道理,在咱們知道T串中首字符"a"與T中後面的字符均不相等的前提下,T串的"a"與S串後面的字符均不相等的前提下,T串的"a"與S串後面的"c","d","e"也均可以在(1)以後就能夠肯定是不相等的,因此這個算法當中(2)(3)(4)(5)沒有必要,只保留(1)(6)便可,如圖5-7-3所示。

之因此保留(6)中的判斷是由於在(1)中T[6]!=S[6],儘管咱們已經知道T[1]!=T[6],但也不能判定T[1]必定不等於S[6],所以須要保留(6)這一步。

有人就會問,若是T串後面也含有首字符"a"的字符怎麼辦呢?

咱們來看下面一個例子,假設S="abcabcabc",T="abcabx"。對於開始的判斷,前5個字符徹底相等,第6個字符不等,如圖5-7-4的(1)。此時,根據剛纔的經驗,T的首字符"a"與T的第二位字符"b",第三位字符"c"均不等,因此不須要作判斷,圖5-7-4的樸素算法步驟(2)(3)都是多餘。

由於T的首位"a"與T第四位的"a"相等,第二位的"b"與第五位的"b"相等。而在(1)時。第四位的"a"與第五位的"b"已經與主串S中的相應位置比較過了,是相等的,所以能夠判定,T的首字符"a"、第二位的字符"b"與S的第四位字符和第五位字符也不須要比較了,確定也是相等的--以前比較過了,還判斷什麼,因此(4)(5)這兩個比較得出字符相等的步驟也能夠省略。

也就是說,對於在子串中有與首字符相等的字符,也是能夠省略一部分沒必要要的判斷步驟。如圖5-7-5所示,省略掉右圖的T串前兩位"a"與"b"同S串中的四、5位置字符匹配操做

對比這兩個例子,咱們會發如今(1)時,咱們的i值,也就是主串當前位置的下標是6,(2)(3)(4)(5),i值是2,3,4,5,到了(6),i值才又回到了6。即咱們在樸素的模式匹配算法中,主串的i值是不斷地回溯來完成的。而咱們的分析發現,這種回溯實際上是能夠不須要的--正所謂好馬不吃回頭草,咱們的KMP模式匹配算法就是爲了讓這不必的回溯不發生。

既然i值不回溯,也就是不能夠變小,那麼要考慮的變化就是j值了。經過觀察也可發現,咱們屢屢提到了T串的首字符與自身後面字符的比較,發現若是有相等字符,j值的變化就會不相同。也就是說,這個j值的變化與主串其實沒什麼關係,關鍵就取決於T串的結構中是否有重複的問題。

好比圖5-7-3中,因爲T="abcdex",當中沒有任何重複的字符,因此j就由6變成了1.而圖5-7-4中,因爲T="abcabx",前綴的"ab"與最後"x"以前串的後綴"ab"是相等的。所以j就由6變成了3.所以,咱們能夠得出規律,j值的多少取決於當前字符之間的串的先後綴的類似度。

咱們把T串各個位置的j值的變化定義爲一個數組next,那麼next的長度就是T串的長度。因而咱們能夠獲得下面的函數定義:

5.7.2 next數組推導

具體如何推導出一個串的next數組值呢,咱們來看一些例子。

  1. T="abcdex"(如表5-7-1所示)
  • 1)當j=1時,next[1]=0;
  • 2)當j=2時,j由1到j-1就只有字符"a",屬於其餘狀況next[2]=1;
  • 3)當j=3時,j由1到j-1串是"ab",顯然"a"與"b"不相等,屬其餘狀況,next[3]=1;
  • 4)之後同理,因此最終此T串的next[j]爲011111。
  1. T="abcabx"(如圖5-7-2所示)
  • 1)當j=1時,next[1]=0;
  • 2)當j=2時,同上例說明,next[2]=1;
  • 3)當j=3時,同上,next[3]=1;
  • 4)當j=4時,同上,next[4]=1;
  • 5)當j=5時,此時j由1到j-1的串是"abca",前綴字符"a"與後綴字符"a"相等,所以可推算出k值爲2(由'p1...p(k-1)'='p(j-k+1)...p(j-1)',獲得p1=p4),所以next[5]=2;
  • 6)當j=6時,j由1到j-1的串是"abcab",因爲前綴字符"ab"與後綴"ab"相等,因此next[6]=3。

咱們能夠根據經驗獲得若是先後綴一個字符相等,k值是2,兩個字符k值是3,n個字符相等k值就是n+1。

  1. T="ababaaaba"(如表5-7-3所示)
  • 1)當j=1時,next[1]=0;
  • 2)當j=2時,同上next[2]=1;
  • 3)當j=3時,同上next[3]=1;
  • 4)當j=4時,j由1到j-1的串是"aba",前綴字符"a"與後綴字符"a"相等,next[4]=2;
  • 5)當j=5時,j由1到j-1的串是"abab",因爲前綴字符"ab"與後綴"ab"相等,因此next[5]=3;
  • 6)當j=6時,j由1到j-1的串的"ababa",因爲前綴字符"aba"與後綴"aba"相等,因此next[6]=4;
  • 7)當j=7時,j由1到j-1的串是"ababaa",因爲前綴字符"ab"與後綴"aa"並不相等,只有"a"相等,因此next[7]=2;
  • 8)當j=8時,j由1到j-1的串是"ababaaa",只有"a"相等,因此next[8]=2;
  • 9)當j=9時,j由1到j-1的串是"ababaaab",因爲前綴字符"ab"與後綴"ab"相等,因此next[9]=3。
  1. T="aaaaaaaab"(如表5-7-4所示)
  • 1)當j=1時,next[1]=0;
  • 2)當j=2時,同上next[2]=1;
  • 3)當j=3時,j由1到j-1的串是"aa",前綴字符"a"與後綴字符"a"相等,next[3]=2;
  • 4)當j=4時,j由1到j-1的串是"aaa",因爲前綴字符"aa"與後綴"aa"相等,因此next[4]=3;
  • 5)......
  • 6)當j=9時,j由1到j-1的串是"aaaaaaaa",因爲前綴字符"aaaaaaa"與後綴"aaaaaaa"相等,因此next[9]=8。

5.7.3 KMP模式匹配算法實現

說了這麼多,咱們能夠來看看代碼了。

這段代碼的目的就是爲了計算出當前要匹配的串T的next數組。

加粗的爲相對於樸素匹配算法增長的代碼,改動不算大,關鍵就是去掉了i值回溯的部分。對於get_next函數來講,若T的長度爲m,因只涉及到簡單的單循環,其時間複雜度爲O(m),而因爲i值的不回溯,使得index_KMP算法效率獲得了提升,while循環的時間複雜度爲O(n)。所以,整個算法的時間複雜度爲O(n+m)。相較於樸素模式匹配算法的O((n-m+1)*m)來講,是要好一些。

這裏也須要強調,KMP算法僅當模式與主串之間存在許多"部分匹配"的狀況下才體現出它的優點,不然二者差別並不明顯。

5.7.4 KMP模式匹配算法改進

後來有人發現,KMP仍是有缺陷的。好比,若是咱們的主串S="aaaabcde",子串T="aaaaax",其next數組值分別爲012345,在開始時,當i=五、j=5時,咱們發現"b"與"a"不相等,如圖5-7-6的(1),所以j=next[5]=4,如圖中的(2),此時"b"與第4位置的"a"依然不等,j=next[4]=3,如圖中的(3),後依次是(4)(5),直到j=next[1]=0時,根據算法,此時i++,j++,獲得i=6,j=1,如圖中的(6)。

咱們發現,當中的(2)(3)(4)(5)步驟,實際上是多餘的判斷。因爲T串的第2、3、4、五位置的字符都與首位的"a"相等,那麼能夠用首位next[1]的值去取代與它相等的字符後續next[j]的值,這是個很好的辦法。所以咱們對求next函數進行了改良。

假設取代的數組爲nextval,增長了加粗部分,代碼以下:

實際匹配算法,只須要將"get_next(T,next)";改成"get_nextval(T,next);"便可,這裏再也不重複。

5.7.5 nextval數組值推導

改良後,咱們以前的例子nextval值就與next值不徹底相同了。好比:

  1. T="ababaaaba"(如表5-7-5所示)

先算出next數組的值分別是001234223,而後再分別判斷。

  • 1)當j=1時。nextval[1]=0;
  • 2)當j=2時,因第二位字符"b"的next值是1,而第一位就是"a",他們不相等,因此nextval[2]=next[2]=1,維持原值。
  • 3)當j=3時,由於第三位字符"a"的next值爲1,因此與第一位的"a"比較得知它們相等,因此nextval[3]=nextval[1]=0;如圖5-7-7所示。

4)當j=4時,第四位的字符"b",next值爲2,因此與第二位的"b"相比較獲得結果是相等,所以nextval[4]=nextval[2]=1;如圖5-7-8所示。

  • 5)當j=5時,next值爲3,第五個字符"a"與第三個字符"a"相等,所以nextval[5]=nextval[3]=0;
  • 6)當j=6時,next值爲4,第六個字符"a"與第四個字符"b"不相等,所以nextval[6]=4;
  • 7)當j=7時,next值爲2,第七個字符"a"與第二個字符"b"不相等,所以nextval[7]=2;
  • 8)當j=8時,next值爲2,第八個字符"b"與第二個字符"b"相等,所以nextval[8]=nextval[2]=1;
  • 9)當j=9時,next值爲3,第九個字符"a"與第三個字符"a"相等,所以nextval[9]=nextval[3]=1。
  1. T="aaaaaaaab"(如表5-7-6)

先算出next數組的值分別爲012345678,而後再分別判斷。

  • 1)當j=1時,nextval[1]=0;
  • 2)當j=2時,next值爲1,第二個字符與第一個字符相等,因此nextval[2]=nextval[1]=0;
  • 3)一樣的道理,其後都爲0....;
  • 4)當j=9時,next值爲8,第九個字符"b"與第八個字符"a"不相等,因此nextval[9]=9。

總結改進過的KMP算法,它是在計算出next值的同時,若是a位字符與它next值指向的b位字符相等,則該a位的nextval就指向b位的nextval值,若是不等,則該a位的nextval值就是它本身a位的next的值。

5.8 總結回顧

這一章節咱們重點講了"串"這樣的數據結構,串(string)是由零個或多個字符組成的有限序列,又名字符串。本質上,它是一種線性表的擴展,但相對於線性表關注一個個元素來講,咱們對串這種結構更多的是關注它子串的應用問題,如查找、替換等操做。如今的高級語言都有針對串的函數能夠調用。咱們在使用這些函數的時候,同時也應該要理解它當中的原理,以便於在碰到複雜的問題時,能夠更加靈活的使用,好比KMP模式匹配算法的學習,就是更有效地去理解index函數當中的實現細節。多用心一點,說不定有一天,能夠有以你的名字命令的算法流傳於後世。

相關文章
相關標籤/搜索