資料結構學習之路——————串
1.串型別的定義
串(string)(或字串)是由零個或多個字元組成的有限序列,一般記為
n稱為串的長度。零個字元的串稱為空串,長度為0。串中任意個連續字元組成的子序列稱為該串的子串。包含子串的串相應地稱為主串。通常稱字元在序列中的序號為該字元在串中的位置。稱兩個串相等時,長度相同,對應位置字元相同。串值需由單引號括起來,但單引號不屬於串。
串的邏輯結構和線性表極為相似,區別在於串的資料物件約束為字符集。然而基本操作有很大區別。線上性表的基本操作中,大多以“單個元素”作為操作物件;而串中,通常以“串的整體”為操作物件。
2.串的表示和實現
2.1 定長順序儲存表示
類似線性表的順序儲存結構,用一組連續的儲存單元儲存串值得字串序列。按照預定義的大小,每個定義的串將被分配一個固定長度的儲存區。
//------------串的定長順序儲存表示-------------
#define MAXSTRLEN 255 //使用者可在255以內定義最大串長
typedef unsigned char SString[MAXSTRLEN + 1];//0號單元存放串的長度
串的實際長度可在預定義長度的範圍內隨意,超過預定義長度的串值則被捨去,超過預定義長度的串值則被捨去,稱之為“截斷”。對串長有兩種表示方法:一是如上述定義描述那樣,以下標為0的陣列分量存放串的實際長度;二是在串值後面加一個不記入串長的結束標記字元,如“\0”作為結束標記字元。
2.2 堆分配儲存表示
這種儲存方式的特點是,仍以一組地址連續的儲存單元存放串值字元序列,但他們的空間是在程式執行過程中動態分配而得的。在C語言中,存在一個稱之為“堆”的自由儲存區,並由C語言的malloc()和free()來管理(C++中新增new)。
//--------------串的堆分配儲存表示------------------
typedef struct {
char *ch; //若為非空串,則按串長分配儲存區,否則ch為NULL
int length;
}HString;
#include<string> #include<iostream> #include <cstdlib> #include <cstdio> #include<memory> //------------串的定長順序儲存表示------------- #define MAXSTRLEN 255 //使用者可在255以內定義最大串長 typedef unsigned char SString[MAXSTRLEN + 1];//0號單元存放串的長度 //--------------串的堆分配儲存表示------------------ typedef struct { char *ch; //若為非空串,則按串長分配儲存區,否則ch為NULL int length; }HString; //--------------------------基本操作的演算法描述-------- bool StrAssign(HString &T,char *chars){ int n; //生成一個其值等於串常量chars的串T if (T.ch)free(T.ch);//釋放T原有空間 for (int i = 0, char *c = chars; *c; c++, i++) { n = i; }//求chars的長度i if (!n) { T.ch = NULL; T.length = 0; } else { if (T.ch = (char*)malloc(n * sizeof(char))) exit(OVERFLOW); for (int i = 0; i < n; i++) { T.ch[i] = chars[i]; } T.length = n; } return 1; } int StrLength(HString S) { return S.length; } bool ClearString(HString &S) { //將S清為空串 if (S.ch) { free(S.ch); S.ch = NULL; } return 1; } bool Concat(HString &T, HString S1, HString S2) { //用T返回由S1和S2聯接而成新串 if (T.ch)free(T.ch); if (!(T.ch = (char*)malloc((S1.length + S2.length) * sizeof(char)))) exit(OVERFLOW); for (int i = 0; i < S1.length; i++) { T.ch[i] = S1.ch[i]; T.length = S1.length + S2.length; for (int i = S1.length,int j=0; i < T.length,j<S2.length; j++,i++) { T.ch[i] = S2.ch[j]; } } return 1; } bool Subtring(HString &Sub, HString S, int pos, int len) { //用sub返回串S的第pos個字元起長度為len的子串 //其中1<=pos<=StrLength(S)且0<=len<=StrLength(S)-pos+1 if (pos<1 || pos>S.length || len<0 || len>S.length - pos + 1) return 0; if (Sub.ch)free(Sub.ch); if (!len) { Sub.ch = NULL; Sub.length = 0; } else { Sub.ch = (char*)malloc(len * sizeof(char)); for (int i = 0, int j = pos - 1; i < len, j < pos+len - 1; i++, j++) { Sub.ch[i] = S.ch[j]; Sub.length = len; } } return 1; }
2.3 串的塊鏈顯示
和線性表的鏈式儲存結構相類似,結構中的每個資料元素是一個字元,則用連結串列儲存的結點可放一個或多個字元。當結點大小大於1,由於串長不一定是結點的整數倍,則連結串列中的最後一個結點不一定被串值佔滿,此時通常不上“#”或其他非串值字元。
為了便於進行串的操作,當以連結串列儲存串值時,除頭指標外還可附設一個尾指標指示連結串列中的最後一個結點,並給出當前串的長度。稱如此定義的串儲存結構為塊鏈結構。
#define CHUNKSIZE 80 //可由使用者定義的塊的大小
typedef struct Chunk {
char ch[CHUNKSIZE];
struct Chunk *next;
}Chunk;
typedef struct {
Chunk *head, *tail; //串的頭指標和尾指標
int curlen; //串的當前長度
}LString;
一般情況下,對串進行操作時,只需從頭向尾順序掃描即可,則對串值不必建立雙向連結串列。設尾指標是為了便於進行聯結操作,但應注意聯結時需處理第一個串尾的無效字元。
儲存密度=串值所佔的儲存位/實際分配的儲存位
2.4 串的模式匹配演算法
子串的定位操作通常被稱為串的模式匹配(其中T稱為模式串)。
int Index(SString &S, SString T, int pos) {
//返回子串T在主串S中第pos個字元之後的位置。
int i = pos; int j = 1;
if (i <= S[0] && j <= T[0]) {
if (S[i] == T[j]) { i++; j++; }
else {
i = i - j + 2; j = 1;
}//如不等指標後退重新開始匹配,是退到下一個字元上
}
if (j > T[0]) return i - T[0]; //i為匹配的子串在主串的最後位置,減去T長度即為位置
else return 0;
}
模式匹配的一種演算法:如上述程式碼所示。其最壞複雜度為O(n*m),n,m分別為主串和模式串的長度。
模式匹配的一種改進演算法:KMP演算法
此演算法可以在O(n+m)的時間數量級上完成串的模式匹配操作。其改進在於:每當一趟匹配過程中出現的字元比較不等時,不需回溯i指標,而是利用所得的“部分匹配”的結果將模式串儘可能遠地向右滑動一段距離後,再進行比較。
改進演算法解決下述問題:當匹配過程中產生失配,模式串“向右滑動”的距離有多遠,即當主串中第i個字元與模式中第j個字元失配時,主串中第i個字元(i指標不回溯)應與模式中的哪個字元比較。
現在討論一般情況:假設此時與模式中第K個字元繼續比較,則前k-1個字元要相匹配,即滿足下列關係式
而已得到的匹配結果是:(?先比完整段模式串?)
由上式推得:
反之,若模式串存在滿足上式的兩個子串,則當匹配過程中,主串中的第i個字元與模式串中的第j個字元比較不等時,僅需將模式向右滑行到模式中第k個字元和主串中第i個字元對齊,此時,模式中頭k-1個字元的子串必定與主串中第i個字元之前長度為k-1的子串相等,由此僅需從模式第k個字元與主串第i個字元比較起繼續進行。(翻譯一下,在失配字元前即模式串中包含兩端相同的子串且與主串匹配,下次移動時就可直接把前一段串移動到後面相等的串的位置上)。
若令next[j]=k,則next[j]=k表明當模式中第j個字元與主串“失配”時,在模式中需要重新和主串中該字元進行比較的字元的位置。
0 當j=1
next[j]=MAX{k|1<k<j且}
1 其他情況
上式可求出next[j] 陣列的值。求得該函式之後,匹配可如下進行:假設以指標 i 和 j 分別指示主串和模式中正待比較的字元,令i的初值為pos,j 的初值為1。若在匹配過程,則 i 和 j 分別增1,否則 i 不變,j 再退到下一個next值的位置,以此類推,直至下列兩種可能:一種是 j 退到某個next值時字元比較相等,則指標各自增1,繼續進行匹配;另一種是 j 退到0,則此時需將模式繼續向右滑動一個位置。
int Index_KMP(SString S, SString T, int pos,int next[]) {
int i = pos;int j = 1;
while (i <= S[0] && j <= T[0]) {
if (j == 0 || S[i] == T[i]) { i++; j++; }
else j = next[j];
}
if (j > T[0])return i - T[0];
else return 0;
}
int get_next(SString T, int next[]) {
//求模式串T的next函式值並存入
int i = 1; next[1] = 0;
int j = 0;
while (i < T[0]) {
if (j == 0 || T[i] == T[j]) { ++i; ++j; next[i] = j; }
else j = next[j];
}
}
void get_nextval(SString T, int nextval[]) {
//求模式串T的next函式修正值並存入nextval中
int i = 1; nextval[1] = 0;; int j = 0;
while (i < T[0]) {
if (j == 0 || T[i] == T[j]) {
++j; ++i;
if (T[i] != T[j])nextval[i] = j;//增加了一個判別條件,相等時nextval為前值
else nextval[i] = nextval[j];
}
else j = nextval[j];
}
}