大話數據結構筆記——第五章:串


串(string)是由零個或者多個字符串組成的有限序列,又叫字符串。

定義

串(string)是由零個或者多個字符串組成的有限序列,又叫字符串。一般記爲s="a1a_1a2a_2……ana_n",s是串的名稱,aia_i(1<=i<=n),串中的字符數目n稱爲串的長度,零個字符的串稱爲空串(null string),子串與主串,串中任意個數的連續字符組成的子序列稱爲該串的子串,相應的包含子串的串稱爲主串。

串的比較

計算機中常用字符是使用標準的ACSII編碼的,由8位二進制數表示一個字符,總共可以表示256個字符,由於256個字符不夠,所以有了Unicode編碼,常用由16位二進制表示一個字符,總共可以表示216個字符,約是6.5萬多個字符。爲了兼容ASCII,Unicode前256個字符與ASCII碼完全相同。
大小比較定義:
給定兩個串:s="a1a_1a2a_2……ana_n",t="b1b_1b2b_2……bnb_n",當滿足以下條件之一時,s<t。

  1. n<m,且aia_i=bib_i(i=1,2,……,n)。
  2. 當存在某個k<=min(m,n),使得aia_i=bib_i(i=1,2,……,k-1),aka_k<bkb_k

串的抽象數據類型

ADT 串(string)
Data
	串中元素僅由一個字符組成,相鄰元素具有前驅和後繼關係
Operation.
	StrAssign(T,*chars):生成一個其值等於字符串常量chars的串T。
	StrCopy(T,S):串S存在,由串S複製得串T。
	ClearString(S):串S存在,將串清空。
	StringEmpty(S):若串S爲空,返回true,否則返回false。
	StrLength(S):返回串S得元素個數,即串的長度。
	StrCompare(S,T):根據S與T的大小關係,返回>0,=0,<0的值
	Concat(T,S1,S2):用T返回由S1和S2聯結而成的新串。
	SubString(Sub,S,pos,len):用Sub返回串S的第pos個字符起長度爲len的子串。
	Index(S,T,pos):若主串S中存在和串T值相同的子串,則返回它在主串S中第pos個
	字符之後第一次出現的位置,否則返回0
	Replace(S,T,V):串S,T,V存在,T是非空串。用V替換主串S中出現的所有與T相等的
	不重疊的子串。
	StrInsert(S,pos,T):在串S的第pos個字符之前插入串T
	StrDelete(S,pos,len):從串S中刪除第pos個字符起長度爲len的子串。
endADT

Index的實現算法:

/*T爲非空串,若主串S中第pos個字符之後存在與T相等的子串*/
/*返回第一個這樣的子串在S中的位置,否則返回0*/
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相等子串給sub
			if (StrCompare(sub,T)!=0) //如果兩串不相等
			{
				++i;
			}
			else
				return i;
		}
	}
	return 0;//若無子串與T相等,返回0
}

串的存儲結構

串的存儲結構與線性表相同,分爲兩種

串的順序存儲結構

使用一組地址連續的存儲單元來存儲串中的字符序列。爲串分配固定長度的存儲區。在對字符串進行操作時(如:Concat,StrInsert等)都由可能使串序列的長度超過數組的長度MaxSize。所有串的順序存儲結構有一定的侷限性。

串的鏈式存儲結構

串的鏈式存儲結構與線性表是相似的,但由於串結構的特殊性,結構中的每個元素數據是一個字符,如果也簡單的應用鏈表存儲串值,一個結點對應一個字符,就會存在很大的空間浪費。所以,一個結點可以考慮放多個字符,當最後一個結點如果沒有被佔滿時,可以用“#”或其他非串值字符補全。串的鏈式存儲結構除了在連接字符串與串操作時有一定方便外,總的來說不如順序存儲結構靈活,性能也不如順尋存儲結構好。

樸素的模式匹配算法

在主串中對子串的定位操作通常稱做串的模式匹配。
**樸素模式匹配算法:**對每個主串的每一個字符作爲子串開頭,與要匹配的字符串進行匹配。對主串做大循環,每個字符開頭做T的長度的小循環,直到匹配成功或全部遍歷完成爲止。
實現代碼(區別與前面Index,不考慮用串的其他操作):

/*返回子串T在主串S中第pos個字符之後的位置。若不存在,則返回0*/
/*T非空,1<=pos<=StrLength(S)*/
int Index(String S,String T,int pos)
{
	int i = pos;//i用於主串S中當前位置下標
	//若pos不爲1,則從pos位置開始匹配
	int j = 1; //j用與子串T中當前位置下標值
	while(i <= S[0] && j<= T[0])//若i<S的長度且j小於T的長度時循環
	{
		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];
	}
	else
	{
		return 0;
	}
}

這種匹配算法最壞情況的時間複雜度爲O((n-m+1)*m)。

KMP模式匹配算法

這種算法可以大大避免重複遍歷的情況,我們把它稱之爲KMP算法。

原理

在樸素的模式匹配算法中,主串的i值是不斷回溯的來完成的,而KMP模式匹配算法就是爲了讓着沒必要的回溯不發生,也就是不可以變小,所以要考慮就是j的值。把j值得變化定義爲一個數組next,那麼next的長度就是T串的長度。
於是得到下面的函數定義:
在這裏插入圖片描述
我們可以根據經驗得到如果前後綴一個字符相等,k值是2,兩個字符k值是3,n個相等k值就是n+1。

算法實現

/*通過計算返回子串T的next數組*/
void get_next(String T,int *next)
{
	int i,j;
	i=1;
	j=0;
	next[1]=0;
	while(i<T[0])
	{
		if (j==0 || T[i]=T[j]) //T[i]表示後綴的單個字符
			//T[j]表示前綴的單個字符
		{
			++i;
			++j;
			next[i]=j;
		}
		else
			j = next[j]; //若字符不相同,則j值回溯
	}
}

/*返回子串T在主串S中第pos個字符之後的位置,不存在則返回0*/
/*T非空,1<=pos<=StrLength(S)*/
int Index_KMP(String S,String T,int pos)
{
	int i = pos;//i用於主串S中當前位置下標
	//若pos不爲1,則從pos位置開始匹配
	int j = 1; //j用於子串T中當前位置下標值
	int next[255]; //定義一個next數組
	get_next(T,next); //對串T做分析,得到next數組
	while(i<=S[0] && j<=T[0])/*循環繼續條件,i,j不能越界*/
	{
		if (j==0 || S[i] == T[j])//兩字母相等則繼續,增加j=0
		{
			++i;
			++j;
		}
		else //指針後退重新開始匹配
		{
			j = next[j]; //j退回到合適位置,i值不變
		}
	}
	if (j > T[0])
	{
		return i-T[0];
	}
	else
		return 0;
}

若T的長度爲m,因只涉及到簡單的單循環,其時間複雜度爲O(m),而由於i值不回溯,while循環的時間複雜度爲O(n)。因此整個算法的時間複雜度爲O(n+m)。
這裏需要注意的是,KMP算法僅當模式與主串之間存在許多“部分匹配”的情況下才體現出它的優勢,否則兩者的差異並不明顯。

KMP模式匹配算法改進

子串中如果有元素連續相同,還可以再改進。
next改進算法代碼:

/*求模式串T的next函數修正值並存入數組nextval*/
void get_nextval(String T,int *nextval)
{
	int i,j;
	i=1;
	j=0;
	nextval[1]=0;
	while(i<T[0])
	{
		if(j==0 ||T[i]==T[j])//T[i]表示後綴的單個字符
			//T[j]表示前綴的單個字符
		{
			++i;
			++j;
			if (T[i]!=T[j])//若當前字符與前綴字符不同
			{
				nextval[i]=j;//則當前的j爲nextval在i位置的值
			}
			else
			{
				nextval[i]=nextval[j];
			//若與前綴字符相同,則將前綴字符的nextval值賦值給
				//nextval在i位置的值
			}
		}
		else
		{
			j=nextval[j] //j值回溯
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章