字符串

一、定義

(1)、串

  串(String)是零個或多個字符組成的有限序列。通常記爲:node

S="a1a2……an"

其中:
  a、S是串名;
  b、雙引號括起的字符序列是串值;ios

  c、ai(1≤i≤n)能夠是字母、數字或其它字符;
  d、串中所包含的字符個數稱爲該串的長度。
注意:算法

  將串值括起來的雙引號自己不屬於串,它的做用是避免串與常數或與標識符混淆。
    【例】"123"是數字字符串,它不一樣於整常數123
      【例】"xl"是長度爲2的字符串,而xl一般表示一個標識符。數組

(2)、空串和空白串

  長度爲零的串稱爲空串(Empty String),它不包含任何字符。
  僅由一個或多個空格組成的串稱爲空白串(Blank String)。
注意:
  空串和空白串的不一樣。

  【例】″ ″和″″分別表示長度爲1的空白串和長度爲0的空串。app

(3)、子串和主串

  串中任意個連續字符組成的子序列稱爲該串的子串。包含子串的串相應地稱爲主串函數

  一般將子串在主串中首次出現時,該子串首字符對應的主串中的序號定義爲子串在主串中的序號(或位置)。
  【例】設A和B分別爲性能

A="This is a string" 
            B="is"

則B是A的子串,B在A中出現了兩次。其中首次出現對應的主串位置是3。所以稱B在A中的序號(或位置)是3。
 注意:
  a、
空串是任意串的子串;spa

  b、任意串是其自身的子串。
指針

(4)、串變量和串常量

    一般在程序中使用的串可分爲:串變量和串常量。
a、串變量code

  串變量和其它類型的變量同樣,其取值是能夠改變的。

b、串常量
  串常量和整常數、實常數同樣,在程序中只能被引用但不能改變其值。即只能讀不能寫。

  ①  串常量由直接量來表示的:

  【例】Error("overflow")中"overflow"是直接量。
  ②  串常量命名
  有的語言容許對串常量命名,以使程序易讀、易寫。
  【例】C++中,可定義串常量path

const char path[]="dir/bin/appl";

二、基本運算

對於串的基本運算,不少高級語言均提供了相應的運算符或標準的庫函數來實現。
爲敘述方便,先定義幾個相關的變量:

   char s1[20]="dir/bin/appl",s2[20]="file.asm",s3[30],*p;
      int result;

下面以C語言中串運算介紹串的基本運算 :

(1)、求串長

int strlen(char *s);//求串s的長度

【例】

printf("%d",strlen(s1)); //輸出s1的串長12

(2)、串複製   

char *strcpy(char *to,*from);//將from串複製到to串中,並返回to開始處指針
//注意:這裏是把第二個參數複製給第一個參數,不要誤覺得第一個參數賦值給第二個參數

【例】

strcpy(s3,s1);  //s3="dir/bin/appl",s1串不變
char *strcat(char *to,char *from);//將from串複製到to串的末尾,並返回to串開始處的指針

【例】

strcat(s3,"/"); //s3="dir/bin/appl/"
strcat(s3,s2); //s3="dir/bin/appl/file.asm"

(4)、串比較   

int strcmp(char *s1,char *s2);//比較s1和s2的大小,當s1<s二、s1>s2和s1=s2時,分別返回小於0、大於0和等於0的值

【例】

result=strcmp("baker","Baker");  //result>0
result=strcmp("12","12");  //result=0
result=strcmp("Joe","joseph")  //result<0

(5)、字符定位   

char *strchr(char *s,char c);//找c在字符串s中第一次出現的位置,若找到,則返回該位置,不然返回NULL

【例】

p=strchr(s2,'.'); //p指向"file"以後的位置
if(p) strcpy(p,".cpp"); //s2="file.cpp"

 注意:
  ①  上述操做是最基本的,其中後 4個操做還有變種形式:strncpy,strncath和strnchr。
  ②  其它的串操做見C的<string.h>。在不一樣的高級語言中,對串運算的種類及符號都不盡相同
  ③  其他的串操做通常可由這些基本操做組合而成
【例】求子串的操做可以下實現:

void substr(char *sub,char *s,int pos,int len){
    //s和sub是字符數組,用sub返回串s的第pos個字符起長度爲len的子串
    //其中0<=pos<=strlen(s)-1,且數組sub至少可容納len+1個字符。
     if (pos<0||pos>strlen(s)-1||len<0)
       Error("parameter error!");
     strncpy(sub,&s[pos],len);//從s[pos]起復制至多len個字符到sub
    }//substr

 

拓展:字符串的增刪改查

(1)、插入字符串

insert() 函數能夠在 string 字符串中指定的位置插入另外一個字符串,它的一種原型爲:

string& insert (size_t pos, const string& str);

pos表示要插入的位置,也就是下標;str 表示要插入的字符串,它能夠是 string 變量,也能夠是C風格的字符串。
請看下面的代碼:

#include <iostream>
#include <string>
using namespace std;
int main(){
    string s1, s2, s3;
    s1 = s2 = "1234567890";
    s3 = "aaa";
    s1.insert(5, s3);
    cout<< s1 <<endl;
    s2.insert(5, "bbb");
    cout<< s2 <<endl;
    return 0;
}

運行結果:

12345aaa67890
12345bbb67890

insert() 函數的第一個參數有越界的可能,若是越界,則會產生運行時異常,應該知道如何捕獲這個異常。
更多 insert() 函數的原型和用法請參考:http://www.cplusplus.com/reference/string/string/insert/

(2)、刪除字符串

erase() 函數能夠刪除 string 變量中的一個子字符串。它的一種原型爲:

string& erase (size_t pos = 0, size_t len = npos);

pos 表示要刪除的子字符串的起始下標,len 表示要刪除子字符串的長度。若是不指明 len 的話,那麼直接刪除從 pos 到字符串結束處的全部字符(此時 len = str.length - pos)。
請看下面的代碼:

#include <iostream>
#include <string>
using namespace std;
int main(){
    string s1, s2, s3;
    s1 = s2 = s3 = "1234567890";
    s2.erase(5);
    s3.erase(5, 3);
    cout<< s1 <<endl;
    cout<< s2 <<endl;
    cout<< s3 <<endl;
    return 0;
}

運行結果:

1234567890
12345
1234590

有讀者擔憂,在 pos 參數沒有越界的狀況下, len 參數也可能會致使要刪除的子字符串越界。但實際上這種狀況不會發生,erase() 函數會從如下兩個值中取出最小的一個做爲待刪除子字符串的長度:

len 的值;
字符串長度減去 pos 的值。

說得簡單一些,待刪除字符串最多隻能刪除到字符串結尾。

(3)、提取子字符串

substr() 函數用於從 string 字符串中提取子字符串,它的原型爲:

string substr (size_t pos = 0, size_t len = npos) const;

pos 爲要提取的子字符串的起始下標,len 爲要提取的子字符串的長度。
請看下面的代碼:

#include <iostream>
#include <string>
using namespace std;
int main(){
    string s1 = "first second third";
    string s2;
    s2 = s1.substr(6, 6);
    cout<< s1 <<endl;
    cout<< s2 <<endl;
    return 0;
}

運行結果:

first second third
second

系統對 substr() 參數的處理和 erase() 相似:

若是 pos 越界,會拋出異常;
若是 len 越界,會提取從 pos 到字符串結尾處的全部字符。

(4)、字符串查找

string 類提供了幾個與字符串查找有關的函數,以下所示。

a、find() 函數

find() 函數用於在 string 字符串中查找子字符串出現的位置,它其中的兩種原型爲:

size_t find (const string& str, size_t pos = 0) const;
size_t find (const char* s, size_t pos = 0) const;

第一個參數爲待查找的子字符串,它能夠是 string 變量,也能夠是C風格的字符串。第二個參數爲開始查找的位置(下標);若是不指明,則從第0個字符開始查找。
請看下面的代碼:

#include <iostream>
#include <string>
using namespace std;
int main(){
    string s1 = "first second third";
    string s2 = "second";
    int index = s1.find(s2,5);
    if(index < s1.length())
        cout<<"Found at index : "<< index <<endl;
    else
        cout<<"Not found"<<endl;
    return 0;
}

運行結果:

Found at index : 6

find() 函數最終返回的是子字符串第一次出如今字符串中的起始下標。本例最終是在下標6處找到了 s2 字符串。若是沒有查找到子字符串,那麼會返回一個無窮大值 4294967295。

b、rfind() 函數

rfind() 和 find() 很相似,一樣是在字符串中查找子字符串,不一樣的是 find() 函數從第二個參數開始日後查找,而 rfind() 函數則最多查找到第二個參數處,若是到了第二個參數所指定的下標尚未找到子字符串,則返回一個無窮大值4294967295。
請看下面的例子:

#include <iostream>
#include <string>
using namespace std;
int main(){
    string s1 = "first second third";
    string s2 = "second";
    int index = s1.rfind(s2,6);
    if(index < s1.length())
        cout<<"Found at index : "<< index <<endl;
    else
        cout<<"Not found"<<endl;
    return 0;
}

運行結果:

Found at index : 6

c、find_first_of() 函數

find_first_of() 函數用於查找子字符串和字符串共同具備的字符在字符串中首次出現的位置。請看下面的代碼:

#include <iostream>
#include <string>
using namespace std;
int main(){
    string s1 = "first second second third";
    string s2 = "asecond";
    int index = s1.find_first_of(s2);
    if(index < s1.length())
        cout<<"Found at index : "<< index <<endl;
    else
        cout<<"Not found"<<endl;
    return 0;
}

運行結果:

Found at index : 3

本例中 s1 和 s2 共同具備的字符是 's',該字符在 s1 中首次出現的下標是3,故查找結果返回3。

三、順序存儲結構

(1) 、順序串  

  串的順序存儲結構簡稱爲順序串。
  與順序表相似,順序串是用一組地址連續的存儲單元來存儲串中的字符序列。所以可用高級語言的字符數組來實現,按其存儲分配的不一樣可將順序串分爲以下兩類:
  a、靜態存儲分配的順序串
     b、動態存儲分配的順序串

(2)、靜態存儲分配的順序串

a、直接使用定長的字符數組來定義    

  該種方法順序串的具體描述:

#define MaxStrSize 256  //該值依賴於應用,由用戶定義
      typedef char SeqString[MaxStrSize];  //SeqString是順序串類型
      SeqString S;  //S是一個可容納255個字符的順序串

  注意:
  ①  串值空間的大小在編譯時刻就已肯定,是靜態的。難以適應插入、連接等操做

  ②  直接使用定長的字符數組存放串內容外,通常可以使用一個不會出如今串中的特殊字符放在串值的末尾來表示串的結束。因此串空間最大值爲MaxStrSize時,最多隻能放MaxStrSize-1個字符。
  【例】C語言中以字符'\0'表示串值的終結。

b、相似順序表的定義   

  直接使用定長的字符數組存放串內容外,可用一個整數來表示串的長度。此時順序串的類型定義徹底和順序表相似:

typedef struct{
        char ch[MaxStrSize]; //可容納256個字符,並依次存儲在ch[0..n]中
        int length;
      }SeqString; 
  SeqString s;

注意:
  ①  串的長度減1的位置就是串值的最後一個字符的位置;
  ②  這種表示的優勢是涉及串長的操做速度快。

(3)、動態存儲分配的順序串

  順序串的字符數組空間可以使用C語言的malloc和free等動態存儲管理函數,來根據實際須要動態地分配和釋放。
  這樣定義的順序串類型亦有兩種形式。

a、較簡單的定義

 typedef char *string; //C中的串庫<string.h>至關於使用此類型定義串

b、複雜定義

typedef struct{
        char *ch;//若串非空,則按實際的串長分配存儲區,不然ch爲NULL,這裏的ch是char類型的指針,只是一個首地址
        int length;
     }HString;
  HString *s;

四、鏈式存儲結構

(1)、鏈串

用單鏈表方式存儲串值,串的這種鏈式存儲結構簡稱爲鏈串。

(2)、鏈串的結構類型定義

typedef struct node{
        char data;
        struct node *next;
      }LinkStrNode;  //結點類型
    typedef LinkStrNode *LinkString; //LinkString爲鏈串類型
    LinkString S; //S是鏈串的頭指針(只定義了一個LinkString類型的存儲結構還不夠,必須給出頭指針,這樣纔會知道起始的位置) 

注意:
  ① 鏈串和單鏈表的差別僅在於其結點數據域爲單個字符:
  ② 一個鏈串由頭指針惟一肯定。 

(3)、鏈串的結點大小

  一般,將結點數據域存放的字符個數定義爲結點的大小。結點的大小的值越大,存儲密度越高

a、結點大小爲1的鏈串

  【例】串值爲"abcdef"的結點大小爲1的鏈串S以下圖所示。 

  這種結構便於進行插入和刪除運算,但存儲空間利用率過低

b、結點大小>1的鏈串

  【例】串值爲"abcdef"的結點大小爲4的鏈串S以下圖所示。

 

注意:

  ① 爲了提升存儲密度,可以使每一個結點存放多個字符。
  ② 當結點大小大於1時,串的長度不必定正好是結點大小的整數倍,所以要用特殊字符來填充最後一個結點,以表示串的終結
  ③ 雖然提升結點的大小使得存儲密度增大,可是作插入、刪除運算時,可能會引發大量字符的移動,給運算帶來不便
  【例】上圖中,在S的第3個字符後插入「xyz」時,要移動原來S中後面4個字符的位置,結果見下圖。

五、串運算的實現

  串是特殊的線性表,故順序串和鏈串上實現的運算分別與順序表和單鏈表上進行的操做相似。

  C語言的串庫<string.h>裏提供了豐富的串函數來實現各類基本運算,所以咱們對各類串運算的實現不做討論。

(1)、子串定位

  子串定位運算相似於串的基本運算中的字符定位運算。只不過是找子串而不是找字符在主串中首次出現的位置。此運算的應用很是普遍。

  【例】在文本編輯中,咱們常常要查找某一特定單詞在文本中出現的位置。解此問題的有效算法能極大地提升文本編輯程序的響應性能。

  子串定位運算又稱串的模式匹配或串匹配。

(2)、目標(串)和模式(串)

  在串匹配中,通常將主串稱爲目標(串),子串稱爲模式(串)。

     假設T 爲目標串,P爲模式串,且不妨設:

T="t0t1t2…tn-1" 
              P="p0p1p2…pm-1"(0<m≤n)

(3)、串匹配

     串匹配就是對於合法的位置(又稱合法的位移)0≤i≤n-m,依次將目標串中的子串"titi+1…ti+m-1"和模式串"p0p1p2…pm-1"進行比較:

  ①若"titi+1…ti+m-1"="p0p1p2…pm-1",則稱從位置i開始的匹配成功,或稱i爲有效位移。
  ②若"titi+1…ti+m-1"≠"p0p1p2…pm-1",則稱從位置i開始的匹配失敗,或稱i爲無效位移。 

  所以,串匹配問題可簡化爲找出某給定模式串P在給定目標串T中首次出現的有效位移。

  注意:

  有些應用中要求求出P在T中全部出現的有效位移。

(4)、順序串上的子串定位運算

a、樸素的串匹配算法的基本思想

  即用一個循環來依次檢查n-m+1個合法的位移i(0≤i≤n-m)是否爲有效位移。

b、順序串上的串匹配算法

  如下以第二種定長的順序串類型做爲存儲結構。給出串匹配的算法:

#define MaxStrSize 256  //該值依賴於應用,由用戶定義
typedef struct{
  char ch[MaxStrSize]; //可容納256個字符,並依次存儲在ch[0..n]中
  int length;
}SeqString;

int Naive StrMatch(SeqString T,SeqString P)
{//找模式P在目標T中首次出現的位置,成功返回第1個有效位移,不然返回-1
  int i,j,k;
  int m=P.length;  //模式串長度
  int n=T.length; //目標串長度   for(i=0;i<=n-m;i++){ //0<=i<=n-m是合法的位移     j=0;k=i; //下面用while循環斷定i是否爲有效位移     while(j<m&&T.ch[k]==P.ch[j]{       k++;j++;     }     if(j==m) //既T[i..i+m-1]=P[0..m-1]       return i; //i爲有效位移,不然查找下一個位移 }//endfor return -1; //找不到有效位移,匹配失敗 }//NaiveStrMatch

c、算法分析

①  最壞時間複雜度

  該算法最壞狀況下的時間複雜度爲O((n-m+1)m)。

  分析:當目標串和模式串分別是"an-1b"和"am-1b"時,對全部n-m+1個合法的位移,均要比較m個字符才能肯定該位移是否爲有效位移,所以所需比較字符的總次數爲(n-m+1)m。

②  模式匹配算法的改進

  樸素的串匹配算法雖然簡單,但效率低。其緣由是在檢查位移i是否爲有效位移時,沒有利用檢查位移i-1,i,…,0時的部分匹配結果。

  若利用部分匹配結果,模式串右滑動的距離就不會是每次一位,而是每次使其向右滑動得儘量遠。這樣可以使串匹配算法的最壞時間控制在O(m+n)數量級上。

(5)、鏈串上的子串定位運算

  用結點大小爲1的單鏈表作串的存儲結構時,實現樸素的串匹配算法很簡單。只是如今的位移shift是結點地址而非整數,且單鏈表中沒有存儲長度信息。若匹配成功,則返回有效位移所指的結點地址,不然返回指針。具體算法以下:

LinkStrNode *LinkStrMatch(LinkString T,LinkString P)
{
    //在鏈串上求模式P在目標T首次出現的位置
    LinkStrNode * shift,*t,*p;
    shift=T;              //shift表示位移
     t=shift;p=P;
     while(t&&p)  
    {
         if(t->data==p->data)
        {  //繼續比較後續結點中字符
                     t=t->next;
                     p=p->next;
         }
        else
        {  //已肯定shift爲無效位移
                    shift=shift->next;  //模式右移,繼續斷定shift是否爲有效位移
                    t=shift;
                    p=P;
         }
    }//endwhile
    if(p==NULL)
        return shift;  //匹配成功
    else
        return NULL;  //匹配失敗
}

  該算法的時間複雜度與順序表上樸素的串匹配算法相同。

相關文章
相關標籤/搜索