《大话数据结构》读书笔记-串
寫在前面:本文僅供個(gè)人學(xué)習(xí)使用?!洞笤挃?shù)據(jù)結(jié)構(gòu)》通俗易懂,適合整體做筆記輸出,構(gòu)建體系。并且文中很多圖片來(lái)源于該書(shū)。
文章目錄
- 5.2 串的定義
- 5.3串的比較
- 5.4串的抽象數(shù)據(jù)類型
- 5.5 串的存儲(chǔ)結(jié)構(gòu)
- 5.5.1 串的順序存儲(chǔ)結(jié)構(gòu)
- 5.5.2 串的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)
- 5.6樸素的模式匹配算法
- 5.7 KMP模式匹配算法
- 5.7.1 KMP模式匹配算法原理
- 5.7.2 next數(shù)組值推導(dǎo)
- 5.7.3 KMP模式匹配算法實(shí)現(xiàn)
- 5.7.4 KMP模式匹配算法改進(jìn)
- 5.7.5 nextval數(shù)組值推導(dǎo)
串:串(string)是由零個(gè)或多個(gè)字符組成的有限序列,又名叫字符串。
5.2 串的定義
早先的計(jì)算機(jī)在被發(fā)明時(shí),主要作用是做一些科學(xué)和工程的計(jì)算工作,也就是現(xiàn)在我們理解的計(jì)算器,只不過(guò)它比小小的計(jì)算器功能更強(qiáng)大、速度更快一些。后來(lái)發(fā)現(xiàn),在計(jì)算機(jī)上作非數(shù)值處理的工作越來(lái)越多,使得我們不得不需要引入對(duì)字符的處理。 于是就有了字符串的概念。
比如我們現(xiàn)在常用的搜索引擎,當(dāng)我們?cè)谖谋究蛑休斎搿皵?shù)據(jù)”時(shí),它已經(jīng)把我們想要的“數(shù)據(jù)結(jié)構(gòu)“列在下面了。顯然這里網(wǎng)站作了一個(gè)字符串查找匹配的工作,如圖所示。
今天我們就來(lái)研究”串“這樣的數(shù)據(jù)結(jié)構(gòu),先來(lái)看定義。串(string) 是由零個(gè)或多個(gè)字符組成的有限序列,又名叫字符串。
一般記為s="a1a2...an(n≥0)"s="a_1a_2...a_n(n≥0)"s="a1?a2?...an?(n≥0)",其中,s是串的名稱,用雙引號(hào)括起來(lái)的字符序列是串的值,注意引號(hào)不是串的內(nèi)容。aia_iai?可以是字母、數(shù)字或其他字符,i就是該字符在串中的位置。串中的字符數(shù)目n稱為串的長(zhǎng)度,定義中談到”有限“是指長(zhǎng)度n是一個(gè)有限的數(shù)值。零個(gè)字符的串稱為空串(null string),它的長(zhǎng)度為零,可以直接用兩雙引號(hào)”""“表示,也可以用希臘字母Φ\PhiΦ來(lái)表示。所謂的序列,說(shuō)明串的相鄰字符之間具有前驅(qū)和后繼的關(guān)系。
還有一些概念需要解釋。
空格串,是只包含空格的串。注意它與空串的區(qū)別,空格串是有內(nèi)容有長(zhǎng)度的,而且可以不止一個(gè)空格。
子串與主串,串中任意個(gè)數(shù)的連續(xù)字符組成的子序列稱為該串的子串,相應(yīng)地,包含子串的串稱為主串。
子串在主串中的位置就是子串的第一個(gè)字符在主串中的序號(hào)。
5.3串的比較
兩個(gè)數(shù)字,很容易比較大小。2比1大,這完全正確,可是兩個(gè)字符串如何比較? 比如”silly“ ”stupid“ 這樣的同樣表達(dá)”愚蠢的“的單詞字符串,它們?cè)谟?jì)算機(jī)中的大小其實(shí)取決于它們挨個(gè)字母的前后順序。 它們的第一個(gè)字母都是”s“,我們認(rèn)為不存在大小差異,而第二個(gè)字母,由于”i“字母比”t“字母要靠前,所以”i“<“t”,于是我們說(shuō)”silly“<“stupid”。
事實(shí)上,串的比較是通過(guò)組成串的字符之間的編碼來(lái)進(jìn)行的,而字符的編碼指的是字符在對(duì)應(yīng)字符集中的序號(hào)。
計(jì)算機(jī)中常用的字符使用ASCII編碼,更準(zhǔn)確一點(diǎn),由7位二進(jìn)制數(shù)表示一個(gè)字符,總共可以表示128個(gè)字符。后來(lái)發(fā)現(xiàn)由于一些特殊字符的出現(xiàn),128個(gè)不夠用,于是擴(kuò)展ASCII碼由8位二進(jìn)制數(shù)表示一個(gè)字符,總共可以表示256個(gè)字符,這已經(jīng)足夠滿足以英語(yǔ)位主的語(yǔ)言和特殊符號(hào)進(jìn)行輸入、存儲(chǔ)、輸出等操作的字符需要了。 可是,單我們國(guó)家就有除漢族外的滿、回、藏、蒙古等多個(gè)少數(shù)民族文字,換作全世界估計(jì)要有成百上千種語(yǔ)言和文字,顯然這256個(gè)字符是不夠的,因此后來(lái)就有了Unicode編碼,比較常用的是由16位的二進(jìn)制數(shù)表示一個(gè)字符,這樣總共就可以表示2162^{16}216個(gè)字符,約是65萬(wàn)多個(gè)字符,足夠表示世界上所有語(yǔ)言的所有字符了。當(dāng)然,為了和ASCII兼容,Unicode的前256個(gè)字符與ASCII碼完全相同。
所以,如果我們要在C語(yǔ)言中比較兩個(gè)串是否相等,必須是它們串的長(zhǎng)度以及它們各自對(duì)應(yīng)位置的字符都相等時(shí),才算是相等。 即給定兩個(gè)串:s="a1a2...an"s="a_1a_2...a_n"s="a1?a2?...an?",t="b1b2...bm"t="b_1b_2...b_m"t="b1?b2?...bm?",當(dāng)且僅當(dāng)n=m,且a1=b1,a2=b2,...an=bma_1=b_1,a_2=b_2,...a_n=b_ma1?=b1?,a2?=b2?,...an?=bm?時(shí),我們認(rèn)為s=t。
那么對(duì)于兩個(gè)不相等的串,如何比較它們的大小呢?我們這樣定義:
給定兩個(gè)串:s="a1a2...an"s="a_1a_2...a_n"s="a1?a2?...an?",t="b1b2...bm"t="b_1b_2...b_m"t="b1?b2?...bm?",當(dāng)滿足以下條件之一時(shí),有s<t.
說(shuō)白了,就是字典序,即字典中排列的順序。
5.4串的抽象數(shù)據(jù)類型
串的邏輯結(jié)構(gòu)和線性表很相似,不同之處在于串針對(duì)的是字符集,也就是串中的元素都是字符。因此,對(duì)于串的基本操作和線性表的操作是有很大差別的。線性表更關(guān)注的是單個(gè)元素的操作,比如查找一個(gè)元素,插入或刪除一個(gè)元素,但串中更多的是查找子串的位置、得到指定位置的子串、替換子串等操作。
ADT 串(string) Data串中元素僅由一個(gè)個(gè)字符組成,相鄰元素具有前驅(qū)和后繼關(guān)系。 OperationStrAssign(T,*chars):生成一個(gè)其值等于字符串常量chars的串T。StrCopy(T,S):串s存在,有串s復(fù)制得到串TClearString(S):串s存在,將串清空StringEmpty(S):若串S為空,返回true,否則返回falseStrLength(S):返回串S的元素個(gè)數(shù),即串的長(zhǎng)度StrCompare(S,T):若S>T,返回值>0,若S=T,返回0;若S<T,返回值<0Concat(T,S1,S2):用T返回由S1和S2連接而成的新串SubString(Sub,S,pos,len):串S存在,1≤pos≤StrLength(S),其0≤len≤StrLength(S)-pos+1,用Sub返回串S的第pos個(gè)字符起長(zhǎng)度為len的子串Index(S,T,pos):串S和T存在,T是非空串,1≤pos≤StrLength(S),若主串S中存在和串T值相同的子串,則返回它在主串S中第pos個(gè)字符之后第一次出現(xiàn)的位置,否則返回0Replace(S,T,V):串S、T、V存在,T是非空串。用V替換主串S中出現(xiàn)的所有與T相等的不重疊的子串。StrInsert(S,pos,T):串S和T存在,1≤pos≤StrLength(S)+1.在串S的第pos個(gè)字符之前插入串TStrDelete(S,pos,len):串S存在,1≤pos≤StrLength(S)-len+1,從串S中刪除第pos個(gè)字符起長(zhǎng)度為len的子串。 endADT對(duì)于不同的高級(jí)語(yǔ)言,對(duì)于串的基本操作會(huì)有不同的定義方法,所以在用某個(gè)語(yǔ)言操作字符串時(shí),需要先查看它的參考手冊(cè)關(guān)于字符串的基本操作。不過(guò)還好,不同語(yǔ)言除方法名之外,操作實(shí)質(zhì)都是類似的。比如C#語(yǔ)言,字符串操作就還有ToLower轉(zhuǎn)小寫,ToUpper轉(zhuǎn)大寫,IndexOf從左查找子串位置,LastIndexOf從右查找子串位置,Trim去除兩邊空格等比較方便的操作,它們其實(shí)就是前面這些基本操作的擴(kuò)展函數(shù)。
我們來(lái)看一個(gè)操作Index 的實(shí)現(xiàn)算法。
int Index(String S ,String T,int pos){int n,m,i;String sub;if(pos>0){n=StrLength(S);//得到主串S的長(zhǎng)度m=StrLength(T);//得到子串T的長(zhǎng)度i=pos;while(i<=n-m+1){SubString(sub,S,i,m);//取主串第i個(gè)位置上長(zhǎng)度與T相等的子串給subif(StrCompare(sub,T)!=0) ++i;// 如果兩串不相等else return i; //如果兩串相等}}return 0; //如果子串與T相等,返回0 }其中用到了StrLength,SubString,StrCompare等基本操作來(lái)實(shí)現(xiàn)。
5.5 串的存儲(chǔ)結(jié)構(gòu)
串的存儲(chǔ)結(jié)構(gòu)和線性表相同,分為兩種。
5.5.1 串的順序存儲(chǔ)結(jié)構(gòu)
串的順序存儲(chǔ)結(jié)構(gòu)是用一組地址連續(xù)的存儲(chǔ)單元來(lái)存儲(chǔ)串中的字符序列的。按照預(yù)定義的大小,為每個(gè)定義的串變量分配一個(gè)固定長(zhǎng)度的存儲(chǔ)區(qū)。一般用定長(zhǎng)數(shù)組來(lái)定義。
既然是定長(zhǎng)數(shù)組,就存在一個(gè)預(yù)定義的最大串長(zhǎng)度,一般可以將實(shí)際的串長(zhǎng)度值保存在數(shù)組0下標(biāo)位置,有的書(shū)中也會(huì)定義存儲(chǔ)在數(shù)組的最后一個(gè)下標(biāo)位置。但是有些編程語(yǔ)言不想這么干,覺(jué)得存?zhèn)€數(shù)占空間麻煩。它規(guī)定在串值后面加一個(gè)不計(jì)入串長(zhǎng)度的結(jié)束標(biāo)記字符,比如“\0”來(lái)表示串值的終結(jié),這個(gè)時(shí)候,你想要知道此時(shí)的串長(zhǎng)度,就需要遍歷計(jì)算一下才知道,其實(shí)這還是需要占用一個(gè)空間,何必呢。
剛才講的串的順序存儲(chǔ)結(jié)構(gòu)其實(shí)是有問(wèn)題的,因?yàn)樽址牟僮?#xff0c;比如兩串的連接Concat,兩串的插入StrInsert,以及字符串的替換Replace,都有可能使得串序列的長(zhǎng)度超過(guò)了數(shù)組的長(zhǎng)度MaxSize。
于是對(duì)于串的順序存儲(chǔ),有一些變化,串值的存儲(chǔ)空間可在程序執(zhí)行過(guò)程中動(dòng)態(tài)分配而得。比如在計(jì)算機(jī)中存在一個(gè)自由存儲(chǔ)區(qū),叫做“堆”。這個(gè)堆可由C語(yǔ)言的動(dòng)態(tài)分配函數(shù)malloc()和free()來(lái)管理。
5.5.2 串的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)
對(duì)于串的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu),與線性表是相似的,但由于串結(jié)構(gòu)的特殊性,結(jié)構(gòu)中的每個(gè)元素?cái)?shù)據(jù)是一個(gè)字符,如果也簡(jiǎn)單的應(yīng)用鏈表存儲(chǔ)串值,一個(gè)結(jié)點(diǎn)對(duì)應(yīng)一個(gè)字符,就會(huì)存在很大的空間浪費(fèi)。因此,一個(gè)結(jié)點(diǎn)可以存放一個(gè)字符,也可以考慮存放多個(gè)字符,最后一個(gè)結(jié)點(diǎn)若是未被占滿時(shí),可以用“#”或其他非串值字符補(bǔ)全,如圖所示
當(dāng)然,這里一個(gè)結(jié)點(diǎn)存多少個(gè)字符才合適就變得很重要,這會(huì)直接影響著串處理的效率,需要根據(jù)實(shí)際情況做出選擇。
但串的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)除了在連接串與串操作時(shí)有一定方便之外,總的來(lái)說(shuō)不如順序存儲(chǔ)靈活,性能也不如順序存儲(chǔ)結(jié)構(gòu)好。
5.6樸素的模式匹配算法
在計(jì)算機(jī)文獻(xiàn)中常用的英文單詞有哪些呢?想寫個(gè)程序,只要輸入一些英文的文檔,就可以計(jì)算出這當(dāng)中所用頻率最高的詞匯是哪些。當(dāng)然,實(shí)際操作時(shí)有很多困難,不過(guò),這里面最重要其實(shí)就是去找一個(gè)單詞在一篇文章(相當(dāng)于一個(gè)大字符串)中的定位問(wèn)題。這種子串的定位操作通常稱作串的模式匹配,應(yīng)該算是串中最重要的操作之一。
假設(shè)我們要從下面的主串S="goodgoogle"中,找到T="google"這個(gè)子串的位置。我們通常需要下面的步驟。
1 主串S第一位開(kāi)始,S與T前三個(gè)字母都匹配成功,但S的第四個(gè)字母是d而T的字母是。第一位匹配失敗。如下圖所示,其中豎直連線表示相等,閃電狀彎折線表示不等。
2 主串S第二位開(kāi)始,主串S首字母是o,要匹配的T首字母是g,匹配失敗。如下圖所示
3 主串S第三位開(kāi)始,主串S首字母是o,要匹配的T首字母是g,匹配失敗,如下圖所示
4 主串S第四位開(kāi)始,主串S首字母是d,而T首字母是g,匹配失敗,如下圖
5 主串S第五位開(kāi)始,S與T,6個(gè)字母全匹配,匹配成功,如下圖所示
簡(jiǎn)單地說(shuō),就是對(duì)主串的每個(gè)字符作為子串的開(kāi)頭,與要匹配的字符串進(jìn)行匹配。對(duì)主串做大循環(huán),每個(gè)字符開(kāi)頭做T的長(zhǎng)度的小循環(huán),直到匹配成功或全部遍歷完成為止。
前面我們已經(jīng)用串的其他操作實(shí)現(xiàn)了模式匹配算法Index?,F(xiàn)在考慮不用串的其他操作,而是只用基本的數(shù)組來(lái)實(shí)現(xiàn)同樣的算法。注意我們假設(shè)主串S和要匹配的子串T的長(zhǎng)度存在S[0]和T[0]中,實(shí)現(xiàn)的代碼如下
//返回子串T在主串S中第pos個(gè)字符之后的位置。若不存在,則函數(shù)返回值為0int Index(String S,String T,int pos){int i=pos;int j=1;while(i<=S[0]&&j<=T[0]){if(S[i]==T[j]) ++i,++j;else{指針后退重新開(kāi)始匹配i=i-j+2;//i回退到上次匹配首位的下一位j=1;//j回退到子串T的首位}}if(j>T[0]) return i-T[0];//T[0]是子串的長(zhǎng)度else return 0;}分析一下,最好的情況是什么?那就是一開(kāi)始就匹配成功,時(shí)間復(fù)雜度為O(1)。稍差一些,就像第二、三位一樣,每次都是首字母不匹配,那么對(duì)T串的循環(huán)就不必進(jìn)行了,比如“abcdef”里面找"goo"。那么時(shí)間復(fù)雜度為O(n+m),其中n為主串長(zhǎng)度,m為要匹配的字串長(zhǎng)度。一般情況,根據(jù)等概率原則,平均是m+n2\frac{m+n}{2}2m+n?次查找,時(shí)間復(fù)雜度為O(m+n).
那么最壞的情況呢? 就是每次不成功的匹配都發(fā)生在串T的最后一個(gè)字符。舉一個(gè)極端的例子。主串S為"00…000001",而要匹配的子串T為"0000000001",前者是有49個(gè)0和1個(gè)1的主串,后者是9個(gè)0和1個(gè)1的子串。在匹配時(shí),每次都得將T中字符循環(huán)到最后一位才發(fā)現(xiàn):哦,原來(lái)它們不匹配啊。這樣等于T串需要在S串的前40個(gè)位置都需要判斷10次,并得出不匹配的結(jié)論,如下圖所示
直到最后第41個(gè)位置,因?yàn)槿科ヅ涑晒?#xff0c;所以不需要再繼續(xù)下去,如下圖所示。如果最終沒(méi)有可匹配的子串,比如T是“00000000002”,到了第41個(gè)位置判斷不匹配后同樣不需要繼續(xù)比對(duì)下去。因此最壞情況的時(shí)間復(fù)雜度是O((n-m+1)*m)。
不要以為這是很少見(jiàn)的情況,相反,在實(shí)際運(yùn)用中,對(duì)于計(jì)算機(jī)來(lái)說(shuō),處理的都是二進(jìn)制位的01串,一個(gè)字符的ASCII碼也可以看成是8位的二進(jìn)制位01串,當(dāng)然,漢字等所有的字符也都可以看成是多個(gè)01串。再比如像計(jì)算機(jī)圖形也可以理解成是由許許多多個(gè)0和1的串組成的。所以在計(jì)算機(jī)的運(yùn)算當(dāng)中,模式匹配操作可以說(shuō)是隨處可見(jiàn),而剛才的這個(gè)算法,就顯得太低效率。
5.7 KMP模式匹配算法
在很多年前,我們的科學(xué)家們,覺(jué)得像這種有多個(gè)0和1重復(fù)字符的字符串,卻需要挨個(gè)遍歷的算法是非常糟糕的事情。于是有三位前輩,D.E.Knuth,J.H.Morris 和V.R.Pratt 發(fā)表一個(gè)模式匹配算法,可以大大避免重復(fù)遍歷的情況,我們把它稱為克努特-莫里斯-普拉特算法,簡(jiǎn)稱KMP算法。
(未完待續(xù))
5.7.1 KMP模式匹配算法原理
5.7.2 next數(shù)組值推導(dǎo)
5.7.3 KMP模式匹配算法實(shí)現(xiàn)
5.7.4 KMP模式匹配算法改進(jìn)
5.7.5 nextval數(shù)組值推導(dǎo)
總結(jié)
以上是生活随笔為你收集整理的《大话数据结构》读书笔记-串的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 《C和指针》读书笔记-第六章指针
- 下一篇: 日申月赎什么意思