数据结构(9)-字符串

213 阅读5分钟

定义

串(String)是由零个或者多个字符组成的有限序列,又叫字符串。

一般记为

s = "a1a2a3a4a5a6a7"

其中s的字符串的名称;ai是单个字符,可以是数字、字母或者其他字符;n是指字符串的长度。字符串需要使用双引号(单引号)括起来。零个字符的字符串称为空串,空串的长度为0。

  • 空格串,只包含空格的串,它与空串是有区别的,空格串是有内容有长度的,而且可以是不止一个空格。
  • 子串,子串是串中任意个数的连续字符组成的子序列。

字符串的比较是通过组成串的字符之间的编码来进行的,而字符的编码是指的字符在对应字符集中的序号。计算机常用的字符标准是ASCII编码。

字符串的抽象数据类型

字符串的逻辑结构和线性表很相似,不同之处在于字符串针对的是字符集,线性表操作的是单个元素。

ADT 字符串(String)

Data

  • 字符串中的元素仅由一个字符组成,相邻的元素具有前驱和后继关系。

Operation操作

  • StrAssign(T, *chars):生成一个其值等于字符串常量chars的字符串T
  • StrCopy(T, S):由串S复制得到串T
  • ClearStr(S):清空字符串
  • StrIsEmpty(Q):判断是否是空字符串
  • StrLenth(Q):获取字符串的长度
  • StrCompare(T, S):比较字符串的大小
  • StrConcat(T, S1, S2):将字符串S1S2拼接成T
  • SubString(Sub, S, pos, len):获取子串,用Sub返回字符串S的第pos个字符起长度为len的子串
  • StrIndex(S, T, pos):若串S中存在子串和T相等,则返回它在主串S中第pos个位置之后第一次出现的位置
  • StrReplace(S, T, V):用V替换在S中出现的T
  • StrInsert(S, pos, T):在字符串S的第pos位置插入串T
  • StrDelete(S, pos, len):从字符串S中删除第pos个字符起len长度的子串

endADT

字符串的存储结构

字符串的存储结构和线性表相同,也分为两种,一种是顺序存储结构,一种为链式存储结构。

字符串的顺序存储结构

字符串的顺序存储结构是用一组地址连续的存储单元来存储传中的字符序列的。一般是用定长数组来定义。定长数组则会有一个预定义长度和字符串的实际长度,我们可以将字符串的实际长度存在字符串的第一个位置,这样方便使用。另外,我们需要在串的结尾加一个'\0'来表示字符串的结束。下面例子都是用第一个字符来存储长度,因此使用strlen()来获取我们定义的字符串的长度会多1。

#define T_MAX_SIZE 100
typedef char SeqString[T_MAX_SIZE];


// 生成一个其值等于字符串常量`chars`的字符串`T`
bool StrAssign(SeqString T, char * chars) {
    int len = (int)strlen(chars);
    if (len >= T_MAX_SIZE) {
        return false;
    }
    
    T[0] = len;
    for (int i = 0; i < len; i++) {
        T[i+1] = *(chars+i);
    }
    
    return true;
}

// 由串`S`复制得到串`T`
bool StrCopy(SeqString T,SeqString S) {
    if (S[0] >= T_MAX_SIZE) {
        return false;
    }
    
    for (int i = 0; i < S[0]; i++) {
        T[i] = S[i];
    }
    return true;
}

// 清空字符串
void ClearStr(SeqString T) {
    T[0] = 0;
}

// 字符串判空
bool StrIsEmpty(SeqString T) {
    return T[0] = 0;
}

// 获取字符串的长度
int StrLenth(SeqString T) {
    return T[0];
}

// 将字符串`S1`和`S2`拼接成`T`
bool StrConcat(SeqString T, SeqString S1, SeqString S2) {
    if (S1[0] + S2[0] > T_MAX_SIZE) {
        return false;
    }
    T[0] = S1[0] + S2[0];
    for (int i = 1; i < S1[0]; i++) {
        T[i] = S1[i];
    }
    for (int i = 1; i < S2[0]; i++) {
        T[S1[0] + i] = S2[i];
    }
    return true;
}

// 获取子串,用`Sub`返回字符串`S`的第`pos`个字符起长度为`len`的子串
bool SubString(SeqString Sub, SeqString S, int pos, int len) {
    if (pos < 1 || pos > S[0] || len < 0 || len > S[0] - pos + 1) {
        return false;
    }

    Sub[0] = len;
    for (int i = pos; i < pos + len; i++) {
        Sub[i-pos+1] = S[i];
    }
    
    return true;
}

// 13 1234
// 若串`S`中存在子串和`T`相等,则返回它在主串`S`中第`pos`个位置之后第一次出现的位置 否则返回0
int StrIndex(SeqString S, SeqString T, int pos) {
   
    int i = pos;
    int j = 1;
    while (i <= S[0] && j < T[0]) {
        if (S[i] == T[j]) {
            if (j == T[0]) {
                return i - T[0] + 1;
            }
            i += 1;
            j += 1;
        } else {
            i = i - j + 2;
            j = 1;
        }
    }
    
    return 0;
}

// 从串S中 第pos个位置起删除len长度的子串
bool StrDelete(SeqString S, int pos, int len) {
    if (pos < 1 || S[0] < pos + len - 1 || len < 0) {
        return false;
    }
    for (int i = pos + len; i <= S[0]; i++) {
        S[i-len] = S[i];
    }
    S[0] -= len;
    
    return true;
}

// 在字符串S的第pos个位置插入字符串T
bool StrInsert(SeqString S, int pos, SeqString T) {
    if (pos < 1 || S[0] + T[0] + 1 > T_MAX_SIZE) {
        return false;
    }
    for (int i = S[0]+T[0]; i > pos; i--) {
        S[i] = S[i-T[0]];
    }
    for (int i = 1; i <= T[0]; i++) {
        S[i+pos-1] = T[i];
    }
    S[0] += T[0];
    return true;
}

字符串的顺序存储结构存在一个问题,就是内存空间的问题,因为是预申请的,所以可能存在大了浪费,小了不够用,这时候就需要我们使用动态申请内存来进行相关的处理。

字符串的链式存储结构

字符串的链式存储结构与线性表是相似的。但是如果一个结点存储一个字符的话,这样会造成比较大的空间浪费,所以一个结点存储多少个字符就显得比较重要。

typedef struct StringNode {
    char data;
    struct StringNode *next;
}StringNode, * LinkString;

bool initLinkString(LinkString *T) {
    *T = (LinkString)malloc(sizeof(StringNode));
    if (!*T) {
        return false;
    }
    (*T)->data = '#';
    (*T)->next = NULL;
    return true;
}

bool StrAssign(LinkString *T, char *chars) {
    if (*T == NULL) {
        return false;
    }
    int len = (int)strlen(chars);
    LinkString tril = *T;
    
    for (int i = 0; i < len; i++) {
        LinkString p = (LinkString)malloc(sizeof(StringNode));
        p->data = chars[i];
        p->next = NULL;
        tril->next = p;
        tril = p;
    }
    return true;
}

串的链式存储的操作和单链表的操作比较类似,此处就不在赘述。

总的来说,字符串的链式存储结构,除了在拼接、插入、删除等操作有一定的方便之外,整体不如顺序存储结构灵活、性能也不如顺序结构好。所以一般我们还是主要使用顺序结构。