學習C語言的時候,老師反復說過一個事情——C語言沒有字符串變量這一說!
那么,我們寫的“hello world”是什么呢?
——是字符串常量
在C語言中如果要用到這種數據類型,就只能用數組來實現。
從這可以看出,串和數組的區別。字符串可以簡稱為串,但是其本質也是只能包含字符類型,數組可以表示不同類型,但同一個的數組的各元素類型都是相同的。可以把串看作數組的一種。
串
串(string):零個或者多個任意字符組成的有限序列
- 子串:一個串中連續字符組成的子序列,包含空串
- 真子串:不包含自身的所有子串
- 字符位置:字符在序列中的序號,1開頭
- 子串位置:子串第一個字符在主串的位置
- 空格串:由一個或者多個空格組成的串,與空串不同
- 串相等:當且僅當兩個串長度相等,對應位置的字符都相等
串同樣可以采用順序和鏈式兩種表示方法,和定義一般數組一樣。
關於串的相關操作,C語言有一個string.h頭文件包含了大部分。比較復雜的是串的匹配算法,在文件校驗,密碼確認等方面有廣泛應用。有BF和KMP兩種算法。
BF算法
BF算法全稱Brute-Froce算法,又叫暴力破解法,窮舉法,就是一個個比對
比如這個字符串,就從第一個開始比對,i和j同時進行循環
發現有不一樣的,則主串從第二個開始,即i-j+2的位置,子串又回到第一個開始,即為j置為1,這個過程叫做回溯
以此類推,直到匹配到完全相同的,結束條件就是主串或者子串走到盡頭,主串走到盡頭表示匹配失敗,子串走到盡頭表示匹配成功
//字符串結構 typedef struct String{ char ch[MaxLen-1]; int length; }SString; int index_BF(SString S,SString T){ int i=1,j=1;//S為主串,T子串 while(i<=S.length && j<=T.length){ if(S.ch[i]==T.ch[j]) { ++i;++j; } else { i=i-j+2; j=1; } } if(j>=T.length) return i-T.length; else return 0; }
最壞情況下,主串每個元素都要停下來讓子串走完一邊,復雜度為長度乘積。若設主串長度為n,子串長度為m,則O(n*m)
KMP算法
由於BF算法可能需要回溯的次數太多導致效率很低,事實上,並不是每一次都需要回溯的,比如這個例子
前4個已經匹配了,第5個不一樣,但是之前出現了相等的情況,第二次比較直接挪到主串位置3上才是合理的做法,按照BF算法,顯然太費時間了
所以又提出了KMP算法,減少不必要的回溯,也就是上述的第二種移動方法,復雜度可縮短到O(n+m)。
但是,怎么確定這個移動的位置呢?這里定義一個數組next[j],來存放這個值
這里舉一個例子來計算一個主串的next[j]的值
后面各個位上的值依次計算
next[j]的值等於對應位置上前n-1個數中前綴與后綴相同的最大位數加一
viod get_next(SString T,int &next[]){ int i = 1; next[1] = 0;//next數組的位置0不使用,從1開始 int j = 0; while(i<T,length){ if(j==0 || T.ch[i]=T.ch[j]) { ++i;++j;//i和j同時向前推進 next[i]=j;/*i后一位其對應的next數組的值等於j向前推進的值*/ } else { j=next[j];/*如果不相等,i的位置不變,j后退到所在位置的next值*/ } } }
求出了next[]的值,然后帶入原來的算法中
int index_KMP(SString S,SString T,int pos){ int i=pos,j=1;//S為主串,T子串 while(i<=S.length && j<=T.length){ if(S.ch[i]==T.ch[j]) { i++;j++; } else { j=next[j] } } if(j>=T.length) return i-T.length; else return 0; }
這就是完整的KMP算法,其實仔細比較發現區別就在於回溯方面上,KMP雖然去除了部分繁瑣的操作,但還是不夠完美。
改進的KMP算法
可以改進一下,這里直接引用王卓老師的源課件
大概原理就是增加一次將所在位的值與next值位的比較,如果相同的話則進一步取其next位,算法的代碼如下
void get_nextval(SString T,int &nextval[]){ int i=1; int j=0; nextval[1] = 0; while(i<T.length){ if(j==0 || T.ch[i]==T.ch[j]){ ++i;++j; if(T.ch[i] != T.ch[j])nextval[i]=j; else nextval[i]=nextval[j]; } else j = nextval[j]; } }
數組
數組
數組:按一定格式排列起來的,具有相同類型的數據元素的集合
其實數組這種類型用的比字符串多,在處理一組數據的時候,通常都會用數組,而且數組也可以用來表示字符串,所以數組還是比較常見的。
數據類型 變量名稱[長度]…
數組的特點:連續順序結構,結構固定,維數和界數不變
基本操作:初始化,銷毀,取數據,修改元素值,一般不做插入和刪除操作
數組可以是多維的,但是存儲結構是一維的,所以這里就有了一個多維關系映射到一維的問題。以二維數組為例,可以采用“一行一行的”存取,即以行序為主(C、JAVA、BASIC);或者“一列一列的”存取,即以列序為主(FORTRAN)。
行序和列序為主其實差別不大,最主要的就是定位元素的位置,假設一個[m][n]的數組,則元素a[i][j]的位置為:
行序為主:LOC( i , j ) = LOC( 0 , 0 ) + ( n * i + j )*L
列序為主:LOC( i , j ) = LOC( 0 , 0 ) + ( m* i + j )*L
多維矩陣的存儲
其實不只是多維數組,在許多結構中,存在多個數據元素相同,或者空值太多的時候,就會有許多空間看起來像是浪費了,重復的元素太多就會很沒有價值。
一般可壓縮的矩陣有:對稱矩陣、對角矩陣、三角矩陣、稀疏矩陣
例如三角矩陣,由於有一半的部分是相同的,只需要占有一個空間就可以了,對於一個n*n的三角矩陣,壓縮后就只需要n(n+1)/2+1個元素空間,節省了幾乎一半。但是要注意的是,不能再用原來的公式來確定元素的位置了,要根據情況而定。
三元組表示法
對於稀疏矩陣,矩陣中的大部分元素都是0,就只需要把其中非零元素取出來,標出位置,例如

typedef struct { int i,j; //儲存非零元素的行和列信息 ElementType e; //非零元素的值 } Triple; //定義三元組類型 typedef struct { int mu,nu,tu; //矩陣的行、列和非零元素的個數 Triple data[SMAX]; //三元組表 } TSMatrix;
十字鏈表
十字鏈表的特點是能夠靈活方便的插入和刪除元素,比起三元組多了兩個指針,分別指向下一個元素,結構如下
用十字鏈表表示的矩陣是這樣的

這樣的結構在插入和刪除元素的時候,就只需要修改指針就行了。但是對於非稀疏矩陣來說,就比較浪費空間了。
廣義表
廣義表(又稱列表lists)是n>=0個元素a0,a1,a2,……的有限序列,其中的a既可以是原子,也可以是廣義表。記為:
LS= ( a0 , a1 , a2 , a3 , …… , an)
常用定義有:
- 表頭:LS(n>=1)的第一個元素a1就是表頭
- 表尾:除表頭之外的其余元素組成的表就是表尾
- 長度:最外層所包含的元素的個數
- 深度:廣義表展開后括號的重數(就是套娃層數)
- 廣義表可以共享和遞歸,遞歸表深度是無窮值,長度是有限值