1. 程式人生 > >資料結構(c語言)——串

資料結構(c語言)——串

串的一些個儲存結構:

  • 順序儲存結構的串
#define MAXSIZE 255
// 0下標位置的長度存放這個串的長度
typedef unsigned char String[MAXSIZE+1];
  • 鏈式儲存的串:
#define MAXSIZE 1024

typedef struct{
    char *chars;
    //定義一個串長度
    int length;
}String;
  • 塊鏈式儲存結構的串:一個或若干個字元形成一個塊,並佔用一個節點,串為這樣的節點相連
// 每個塊的大小
#define CHUNKSIZE 128
typedef struct Chunk{
    char charArr[CHUNKSIZE];

    struct Chunk *next;
}Chunk;
typedef struct{
    Chunk *head,tail;
    // 當前串長度
    int curlen;
}String;

本篇博文關於串的操作都是 鏈式儲存結構的。

串的操作

初始化一個串:

String* InitString(){
    String *str = (String *)malloc(sizeof(String));
    str->chars = (char *)malloc(sizeof(char) * MAXSIZE);
    str->length = 0;
    return str;
}

字元陣列->串

void charArr2String(String *s,char *string){
    int strLength = strlen(string);

    if(strLength >= MAXSIZE){
        s->chars = (char *)malloc(sizeof(char) * MAXSIZE * 2);
    }

    // 設定串的當前長度
    s->length=strLength;
    s->chars = string;
}

判斷串是否為空

bool StringEmpty(String *str){
    return str->length == 0;
}

擷取子串

/*
從s串中指定位置開始取長度為len的字串,用sub返回。
sub:返回的字串
s:主串
pos:指定下標
len:要擷取的長度
*/
bool SubString(String *sub,String *s,int pos,int len){

    bool flag = pos < 0||pos >= s->length||len < 1||pos + len > s->length;
    if(flag){
        return false;
    }

    for(int i = pos; i < (pos + len); i++){
        sub->chars[i-pos] = s->chars[i];
    }

    sub->chars[len] = '\0';
    sub->length = len;

    return true;
}

比較倆個串的大小

int StringCompare(String *s1,String *s2){
    if(StringEmpty(s1)){
        return -1;
    } else(StringEmp2y(s2)){
        return 1;
    }

    int i=0;
    while(i < s1->length && i < s2->length){
        if(s1->chars[i] == s2->chars[i]){
            i++;
            continue;
        } else{
            return s1->chars[i] - s2->chars[i];
        }

    }
    return 0;
}

在主串s中第pos個字元後查詢與t串相等的字串

int Index(String *s,String *t,int pos){
    int i;
    String *sub = InitString();
    i = pos;
    if(pos >= s->length || pos < 0){
        return -1;
    }

    while(i <= s->length - t->length){
        SubString(sub,s,pos++,t->length);

        if(StringCompare(sub,t) == 0){
            return i;
        } else{
            // 若取出的串不相等,繼續
            i++;
        }
    }
    return -1;
}

 在主串s中第pos個字元後查詢與t串相等的字串(不考慮用串的其他操作,只用基本的陣列來實現同樣的演算法)

int Index_Arr(String *s,String *t,int pos){
    int j = pos;
    int i = 0;

    if(pos >= s->length || pos < 0){
        return -1;
    }
    while(j < s->length && i< t->length){
        if(s->chars[j] == t->chars[i]){
            ++i;
            ++j;
        } else{
            j = j-i+1;
            i = 0;
        }
    }
    if(i >= t->length){
        return j - t->length;
    }
    return -1;
}
/*
不足:效率低

看這種情況:
s1:000000000000001
s2:001
串2的前兩個字元,和串1的字元1之前都匹配,需要匹配13*3才可能匹配上,效率低下

所以就有幾個牛人,構思出了一個演算法(KMP演算法),來提高效率。
*/

 kmp演算法程式碼實現:獲取模式串的next陣列

/*
通過迴圈,不斷的將字首的單個字元與字尾的單個字元進行比較,
    若相等,則將其儲存到陣列中。改變記錄字首和字尾的下標變數。
    不相等,將陣列中的值給字首的下標記錄。
*/
void getNextArr(String str,int *nextArr){
    // i表示字首的單個字元,j表示字尾的單個字元
    int i,j;
    i = -1;
    j = 0;
    nextArr[0] = -1;

    // 這個字尾的單個字元的下標是不會回溯的,只有字首的單個字元的下標會進行回溯。
    while(j < str.length - 1){
        if(i == -1 || str.chars[i] == str.chars[j]){
            nextArr[++j] = ++i;
        } else {
            // 若值不相等,則i值進行回溯
            i = nextArr[i];
        }
    }
}

 那麼在使用kmp演算法之後的index應該如何寫呢

int Index_KMP(String S, String P, int pos){

    int next[255];
    getNextArr(P, next);

    int i = pos;  // S 的下標
    int j = 0;  // P 的下標
    int s_len = S.length;
    int p_len = P.length;

    while (i < s_len && j < p_len){
        if (j == -1 || S.chars[i] == P.chars[j]){
            // P 的第一個字元不匹配或 S[i] == P[j]
            i++;
            j++;
        }
        else{
            j = next[j];
            // 當前字元匹配失敗,退回到合適位置
        }
    }
    if (j == p_len)  // 匹配成功
        return i - j;
    return -1;
}

/*
那麼這個演算法有什麼不足或者說缺陷呢:
    在aaaabcdefghijk中找
    串aaaaab
    當出現不匹配(a和b)的時候,也就是next[j]等於5,
    進行回溯,next[j]成了4,不匹配,繼續回溯,直到第next[j]等於0。
    b不相等的時候回溯了多次,因為前面5個字元都是相等的,那麼這樣一來之前的判斷回溯就都是多餘的了
*/

 改進一下KMP模式匹配演算法

void getNextValArr(String str,int *nextArr){
    int i,j;
    i = -1;
    j = 0;
    nextArr[0] = -1;

    while(j < str.length - 1){
        if(i == -1 || str.chars[i] == str.chars[j]){
            // 修改的結果:

            if (str.chars[++i] != str.chars[++j]){
                // 當前字元和字首字元不同的時候,當前的i為next在j位置的值。
                nextArr[j] = i;
            } else {
                // 字首和當前字元相同將字首字元的nextArr的值賦值給nextArr在i位置的值
                nextArr[j] = nextArr[i];
            }
        } else {
            // 若值不相等,則i值進行回溯
            i = nextArr[i];
        }
    }
}
/*
總結改進過的kmp:它是在計算花出next值的同時,
如果a位字元與它next值指向的b位字元相等,則該a位的nextArr就儲存的b位的nextArr值
如果不等,則該a位的nextArr就是它自己a位的nextArr的值
*/

在兩串中找出最大長度的共同串(可指定長度),若存在,返回這個串

void Intersection(String *res,String *s1, String *s2, int len){
    if(StringEmpty(s1) ||StringEmpty(s2) || len > s1->length || len > s2->length || len == 0){
        return;
    }

    // 從第i個位置開始拿子串
    // 從第j個位置開始找
    int i,j;

    // 要取的字串長度
    int tagetLen = s2->length;

    // 儲存s2的子串
    String *s2sub = InitString();
    // 給子串賦初始值
    SubString(s2sub, s2, 0, tagetLen);

    while(tagetLen >= len){
        i = 0;
        while(i <=s2->length - tagetLen){
            SubString(s2sub,s2,i,tagetLen);
            j = 0;
            // 取出字串到s1串中查詢
            while(j <= s1->length - s2sub->length){
                int index = Index(s1,s2sub,j++);
                if(index != -1){
                    // 找到後進行賦值,然後結束迴圈
                    res->chars = s2sub->chars;
                    res->length = tagetLen;
                    return;
                }
            }
            // 沒找著從下一個位置開始拿子串
            i++;
        }

        // 長度為tagetLen的所有字串在串s1中沒找到,將要取的字串長度自減
        tagetLen--;
    }
}