前言:
計算機上的應用程序幾乎都是以字符串數據作為處理對象,然而,現今我們使用的計算機的硬件結構主要是反映數值計算的需要的,因此,在處理字符串數據時比處理整數和浮點數要復雜得多。而且,在不同類型的應用中,所處理的字符串具有不同的特點,要有效地實現字符串的處理,就必須根據具體情況使用合適的存儲結構。這一章,我們將討論一些基本的串處理操作 和 幾種不同的存儲結構。
目錄:
1.串類型的定義
2.串的表示和實現
2.1.定長順序存儲表示
2.2.堆分配存儲表示
2.3.串的塊鏈存儲表示
3.串的模式匹配算法
4.串操作應用劇烈
正文:
串類型的定義
串(string)(或字符串)是由 零個或多個字符 組成的有限序列,一般記為:
s=' a1a2...an '
注意:由一個或多個空格組成的串,稱為空格串。而不是空串。
串 和 字符序列(char * ='hello')的區別:
串是一種數據結構,是字符的集合,實現並提供對這種集合操作的各種方法。
char 是c 的一種基本數據類型,沒有已實現的對字符序列的復雜操作。
串的邏輯結構和線性表極為相似,區別在於:
1.串的數據對象約束為字符集。
2.在線性表的基本操作中,以“單個數據元素” 為操作對象。 在串中 以 “串的整體” 作為操作對象,例如:查找子串、插入及刪除子串。
串的表示及實現
串有3種機內表示方法:
1.定長順序存儲 表示
類似於線性表的順序存儲結構,用一組地址連續的存儲單元存儲串值的字符序列。在串的定長順序存儲結構中,按照預定義的大小,為每個定義的串變量分配一個固定長度的存儲區,則可用定長數組如下描述之。
存儲表示:
#define MAXSTRLEN 255 //定義最大串長
typedef unsigned char SString [MAXSTRLEN +1]; //0單元存放串的長度
串的實際長度可在這預定義長度的范圍內隨意, 超出的部分被舍棄,稱之為 “截斷” 。
弊端:當合並兩個 串的時候,如果長度超過 預定義最大串長MAXSTRLEN ,其它部分將會丟失即 “截斷”。
解決方案:使用不限定串長的最大長度, 即動態分配串值的存儲空間。
2.堆分配存儲表示
特點:仍以一組地址連續的存儲單元存放串值字符序列,但它們的存儲空間是在程序執行過程中動態分配而得。
在 C 語言中,存在一個稱之為 “堆” 的自由存儲區, 並由C 語言的動態分配函數 malloc() 和 free()來管理。利用malloc 函數為每個新產生的串分配一塊實際串長所需的存儲空間,若分配成功,則返回一個指向起始地址的指針,作為串的基址,同時,為了處理方便,約定串長也作為存儲結構的一部分。
堆分配存儲表示:
typedef struct {
char *ch; //若是非空串,則按串長分配存儲區,否則 ch 為 NULL
int length; // 串長度
}HSring;
代碼實現:
#include<stdio.h> #include<stdlib.h> #define TRUE 1 #define FALSE 0 #define OK 1 #define ERROR 0 #define INFEASIBLE -1 #define OVERFLOW -2 #define MAXQSIZE 5 //Status是函數的類型,其值是函數結果狀態碼 typedef int Status; typedef struct{ char *ch; //若是非空串,則按串長分配存儲區,否則 ch 為 NULL int length; //字符串長度 }HString; //生成一個其值等於串常量 chars 的串T Status StrAssign(HString &S,char * chars){ int i; for(i=0;chars[i];i++){} //if(S.ch){ // free(S.ch); // S.ch=NULL; //} if(!i){ S.ch=NULL; S.length=0; }else{ S.ch=(char *)malloc(i*sizeof(char)); if(!S.ch) exit(OVERFLOW); S.length=i; for(int j=0;j<i;j++) S.ch[j]=chars[j]; } return OK; } //返回串長度 int StrLength(HString &S){ return S.length; } //比較大小,若 S>T 返回值>0。相等 返回0 ,否則返回 <0 int StrCompare(HString S,HString T){ for(int i=0;i<S.length&&i<T.length;i++){ if(S.ch[i]!=T.ch[i]) return S.ch[i]-T.ch[i]; } return S.length-T.length; } //清空串S為空串,並釋放所占空間 Status ClearString(HString &S){ if(S.ch){ free(S.ch); S.ch=NULL; } S.length=0; return OK; } //連接兩個字符串,生成一個新的字符串 Status Concat(HString &T,HString S1,HString S2){ //if(T.ch) free(T.ch); T.ch=(char *)malloc((S1.length+S2.length)*sizeof(char)); if(!T.ch) exit(OVERFLOW); T.length=S1.length+S2.length; for(int i=0;i<S1.length;i++) T.ch[i]=S1.ch[i]; for(i=0;i<S2.length;i++) T.ch[i+S1.length]=S2.ch[i]; return OK; } //字符串截取,返回截取的串 Status SubString(HString &sub,HString S,int pos,int len){ if(pos<1||pos>S.length||len<0||(S.length-pos+1)<len) return ERROR; //if(sub.ch) free(sub.ch); sub.ch=(char *)malloc(len*sizeof(char)); if(!sub.ch) exit(OVERFLOW); for(int i=0;i<len;i++) sub.ch[i]=S.ch[i+pos-1]; sub.length=len; return OK; } void printV(HString &S){ for(int i=0;i<S.length;i++){ printf("地址:%p,",&S.ch[i]); printf("值:%c\n",S.ch[i]); } } void prints(HString &S){ for(int i=0;i<S.length;i++){ printf("%c",S.ch[i]); } printf("%s\n"," "); } void main(){ HString S1; char *c="hello"; StrAssign(S1,c); ClearString(S1); c="hello"; StrAssign(S1,c); printf("%s","S1:"); prints(S1); printV(S1); HString S2; c="China"; StrAssign(S2,c); printf("%s","S2:"); prints(S2); printV(S2); HString T; Concat(T,S1,S2); printf("%s","T:"); prints(T); printV(T); HString sub; SubString(sub,T,6,5); printf("%s","sub:"); prints(sub); printV(sub); }
運行結果:
串的表示和實現
串的塊鏈存儲表示:
和線性表的鏈式存儲表示相似,串也可以采用鏈表方式存儲串值。由於串結構的特殊性,存儲時一個結點可以存放一個字符也可以存放多個字符。
當結點大小大於1時,由於 串值可能不是結點大小的整數倍,則鏈表最后一個結點可能無法填滿,此時通常補上 “#” 或其他非串值 字符。如下圖:
定義:
為了便於進行串的操作,當以鏈表存儲串值時,除頭指針外還可附設一個尾指針指示鏈表中的最后 一個結點,並給出當前串的長度,稱如此定義的串存儲結構為塊鏈結構。
串的塊鏈存儲表示
#define CHUNKSIZE 80; //結點大小,用戶自己隨便定義
typedef struct Chunk{ //結點定義
char ch[CHUNKSIZE];
struct Chunk *next;
}Chunk;
typedef struct{
Chunk *head,*tail; //串的頭指針,和尾指針
int curlen; //串的長度
}
注:設置尾指針的目的是 便於進行串的連接操作,但要注意連接時需處理第一個串尾的無效字符(#)。
鏈式串中,結點的大小直接影響着串處理的效率。
存儲密度 = 串值所占的存儲位 / 實際分配的存儲位
對於固定的串a ,其串值存儲位是固定的,而實際分配的存儲位根據結點的大小而改變。
顯然當存儲密度最小時,(即結點大小為1)串的運算處理最方便,但是其占用的存儲量大。
總結:
串的鏈式存儲,對鏈接操作等有一定的方便之處, 但總的來說不如另外兩種結構靈活,它占用存儲量大且操作復雜。