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