串的定長順序存儲類似於線性表的順序存儲結構,用一組連續的存儲單元存儲串值的字符序列。
在串的定長順序存儲結構中,按照預定義的大小,為每個定義的串變量分配一個固定長度的存儲區,則可以用定長數組表示:
1 /*串定長順序存儲表示*/ 2 #define MAXSTRLEN 255 //串在MAXSTRLEN大小 3 typedef unsigned char SString[MAXSTRLEN + 1]; //所有串的0號單元存儲串的長度
串的基本操做函數如下:
1 /*生成一個其值等於chars的串T*/ 2 Status StrAssign(SString &T, char chars[]) 3 { 4 if(strlen(chars) > MAXSTRLEN) 5 return ERROR; 6 T[0] = strlen(chars); 7 for (int i = 0; i <= T[0]; i++) 8 T[i+1] = chars[i]; 9 return OK; 10 } 11 12 /*由串S賦值的到串T*/ 13 Status StrCopy(SString &T, SString S) 14 { 15 int i = 1; 16 for (; i <= S[0]; i++) 17 T[i] = S[i]; 18 T[0] = S[0]; 19 return OK; 20 } 21 22 /*判斷串S是否為空,若S為空串側返回true,否則返回false*/ 23 bool StrEmpty(SString s) 24 { 25 if(s[0] == 0) 26 return true; 27 else 28 return false; 29 } 30 31 /*若S>T,則返回值大於0;若S=T,則返回值等於0;若S<T則返回值小於0*/ 32 Status StrCompare(SString S, SString T) 33 { 34 for (int i = 1; i <= S[0] && i <= T[0]; ++i) 35 if(S[i] != T[i]) 36 return S[i] - T[i]; 37 return S[0] - T[0]; 38 } 39 40 /*返回串的長度*/ 41 int StrLength(SString S) 42 { 43 return S[0]; //串的第0個存儲放的串的長度 44 } 45 46 /*重置串為空串*/ 47 Status ClearString(SString &S) 48 { 49 S[0] = 0; //因為是順序存儲,只要將長度標志設為0即是代表已經清空串 50 return OK; 51 } 52 53 /*用T返回由串S1和串S2連接成的新串。若未截斷,則返回true,否則返回false*/ 54 bool Concat(SString &T, SString S1, SString S2) 55 { 56 bool uncut = true; 57 if(S1[0] + S2[0] <= MAXSTRLEN) //未截斷 58 { 59 for (int i = 1; i <= S1[0]; i++) 60 T[i] = S1[i]; 61 for (int i = 1; i <= S2[0]; i++) 62 T[i + S1[0]] = S2[i]; 63 T[0] = S1[0] + S2[0]; 64 uncut = true; 65 } 66 else if(S1[0] < MAXSTRLEN) //截斷;只取S2的一部分 67 { 68 int i = 1; 69 for ( ; i <= S1[0]; i++) 70 T[i] = S1[i]; 71 for (i = 1; i <= MAXSTRLEN-S1[0]; i++) 72 T[i + S1[0]] = S2[i]; 73 T[0] = MAXSTRLEN; 74 uncut = false; 75 } 76 else //截斷;僅取S1 77 { 78 for (int i = 0; i <= MAXSTRLEN; i++) 79 T[i] = S1[i]; 80 uncut = false; 81 } 82 return uncut; 83 } 84 85 /*用sub返回串S的第pos個字符起的長度為len的子串*/ 86 Status SubString(SString &Sub, SString S, int pos, int len) 87 { 88 if(pos < 1 || pos > S[0] || len < 0 || len > S[0]-pos+1) 89 return ERROR; 90 for (int i = 1; i <= len; i++) 91 Sub[i] = S[pos+i-1]; 92 Sub[0] = len; 93 return OK; 94 } 95 /*用串V替換主串中出現的所有*/ 96 Status Replace(SString &S, SString T, SString V) 97 { 98 if(StrEmpty(T)) 99 return ERROR; 100 int i = 1, j = 1; 101 int m = StrLength(T), n = StrLength(V); 102 while(i <= S[0]) 103 { 104 j = Index(S,T,i); 105 StrDelete(S,j,m); 106 StrInsert(S,j,V); 107 i+=n+1; 108 } 109 return OK; 110 } 111 112 /*在串S的pos位置之前插入串T*/ 113 Status StrInsert(SString &S, int pos, SString T) 114 { 115 if(pos < 1 || pos > S[0]+1) 116 return ERROR; 117 if(S[0]+T[0] <= MAXSTRLEN) //完全插入 118 { 119 for (int i = S[0]; i >= pos; i--) 120 S[i+T[0]] = S[i]; 121 for (int i = pos; i < pos+T[0]; i++) 122 S[i] = T[i-pos+1]; 123 S[0] = S[0]+T[0]; 124 return OK; 125 } 126 else //不完全插入 127 { 128 for (int i = MAXSTRLEN; i >= pos; i--) 129 S[i] = S[i-T[0]]; 130 for (int i = pos; i < pos+T[0]; i++) 131 S[i] = T[i-pos+1]; 132 S[0] = MAXSTRLEN; 133 return ERROR; 134 } 135 } 136 137 /*從串S中刪除第pos個字符起的長度為len的子串*/ 138 Status StrDelete(SString &S, int pos, int len) 139 { 140 if(pos < 1 || pos > S[0]-len+1 || len < 0) 141 return ERROR; 142 for (int i = pos+len; i <= S[0]; i++) 143 S[i-len] = S[i]; 144 S[0] -= len; 145 return OK; 146 } 147 148 /*銷毀串S*/ 149 Status DestroyString(SString &S) 150 { 151 free(S); 152 return OK; 153 } 154 155 /*輸出串*/ 156 void StrPrint(SString T) 157 { 158 for (int i = 1; i <= T[0]; i++) 159 printf("%c",T[i]); 160 printf("\n"); 161 }
還有一個函數Index函數。做字符串匹配用,這里拿出來單獨討論
1 /*若主串S中存在和串T值相同的子串; 2 /*則返回他在主串S中第pos個字符之后第一次出現的位置; 3 /*否則函數值為0*/ 4 int Index(SString S, SString T, int pos) 5 { 6 /*調用串基本操作的方法,也是Index函數的思想步驟*/ 7 //if(pos > 0) 8 //{ 9 // int n = StrLength(S); //獲取串的長度 10 // int m = StrLength(T); 11 // int i = pos; 12 // SString sub; 13 // while(i <= n-m+1) //循環值從pos到串S的最后一個T長度位置 14 // { 15 // SubString(sub,S,i,m); //獲取子串,S的第i個位置開始長度m-1的子串 16 // if(StrCompare(sub,T) != 0) //判斷獲取的子串與串T是否不等;如果相等返回i的值也就是第一次出現的位置 17 // ++i; 18 // else 19 // return i; 20 // } 21 //} 22 //return 0; //沒有出現返回0 23 24 /*定位函數Index的模式匹配算法 25 /*算法的基本思想: 26 /*從主串S的第pos個字符起和模式的第一個字符比較之, 27 /*若相等,則繼續逐個比較后續字符; 28 /*否則從主串的下一個字符起再重新和模式的字符比較之。 29 /*以此類推,直至模式T中的每個字符依次和主串S中的一個連續的字符序列相等,則匹配成功; 30 /*函數值為和模式T中的第一個字符相等的字符在主串S中的序號。 31 /*否則匹配不成功,函數值為零。*/ 32 int i = pos,j = 1; 33 while(i <= S[0] && j <= T[0]) 34 { 35 if(S[i] == T[j]) //繼續比較后續字符 36 { 37 i++; 38 j++; 39 } 40 else //指針后退重新開始匹配 41 { 42 i = i-j+2; 43 j = 1; 44 } 45 } 46 if(j > T[0]) 47 return i - T[0]; //匹配成功 48 return 0; //匹配失敗 49 }
其中這個函數內寫了兩種方法:第一種調用基本函數的方法,第二種模式匹配算法。
但模式匹配算法還有一個很是經典的算法模式匹配的改進算法-KMP算法;
其改進在於:每當一趟匹配過程中出現字符比較不等時,不需回溯i指針,而是利用已經得到的“部分匹配”的結果將模式向右滑動盡可能遠的一段距離后,繼續進行比較。
KMP算法的基本思想就是這樣,剩下的百度搜索全是我就不再細說,還是按我的習慣直接上代碼。
1 void get_next(SString T, int *next) { 2 int i=1; 3 next[1]=0; 4 int j=0; 5 while (i<T[0]) { 6 if(j==0 || T[i]== T[j]) { 7 ++i; ++j; next[i] = j; 8 } else j= next[j]; 9 } 10 } 11 12 int Index_KMP(SString S, SString T, int pos) { 13 // 利用模式串T的next函數求T在主串S中第pos個字符之后的位置的 14 // KMP算法。其中,T非空,1≤pos≤StrLength(S)。 15 int next[255]; 16 int i = pos; 17 int j = 1; 18 get_next(T, next); 19 while (i <= S[0] && j <= T[0]) { 20 if (j == 0 || S[i] == T[j]) { // 繼續比較后繼字符 21 ++i; ++j; 22 } else j = next[j]; // 模式串向右移動 23 } 24 if (j > T[0]) return i-T[0]; // 匹配成功 25 else return 0; 26 }
KMP算法時間復雜度O(m)。通常,模式串的長度m比主串的長度n要小的多,因此對整個匹配算法來說,所增加的這點時間是值得的。
但是,雖然模式匹配算法時間復雜度是O(n*m),但是一般情況下,其實際的實行時間近似於O(n+m),因此至今仍被采用。
KMP算法僅當模式與主串之間存在許多“部分匹配”的情況下才顯得比模式匹配算法快得多。但是KMP算法的最大特點是指主串的指針不需回溯,整個匹配過程中,對主串僅需從頭至尾掃描一遍,這對處理從外設輸入的龐大文件很有效,可以邊讀入邊匹配,而無需回溯重讀。
但對於next函數前面所寫的在某些情況下有一個缺陷,例如在模式aaaab,主串aaabaaaab匹配時,當i=4,j=4時ch[4]!=t.ch[4]。由next[j]的指示還需要進行i=4,j=3;i=4,j=2;i=4,j=1這三次比較。實際上,因為模式中第1,2,3個字符和第4個字符都相等,因此不必要在和主串中第4個字符相比較,而可以將模式一下子滑動4個位置。直接進行i=5,j=1時的字符比較。
j | 1 2 3 4 5 |
模式 | a a a a b |
next[j] | 0 1 2 3 4 |
nextval[j] | 0 0 0 0 4 |
改進的next函數如下:此時匹配算法不變;
void get_nextval(SString T, int *nextval) { // 求模式串T的next函數修正值並存入數組nextval。 int i = 1; int j = 0; nextval[1] = 0; while (i<T[0]) { if (j==0 || T[i]==T[j]) { ++i; ++j; if (T[i]!=T[j]) nextval[i] = j; else nextval[i] = nextval[j]; } else j = nextval[j]; } }