第4章 串、数组和广义表

4.1 串的定义、存储结构和运算

1.定义
串的定义

子串的位置:子串第一个字符在主串中的位置。
2.基本操作

// StrAssign (&T,chars) 串赋值
// StrCompare (s,T) 串比较
// StrLength 求串长
// Concat(&TS1,S2) 串连结
// SubString(&Sub,s,pos,len)求子串
// StrCopy(&T,S) 串拷贝
// StrEmpty(S) 串判空
// ClearString (&S) 清空串
// Index(S,T,pos) 子串的位置/ 字符串的匹配运算


// Replace(&S,T,V) 串替换
// Strlnsert(&s,pos,T) 子串插入
// StrDelete(&s,pos,len) 子串删除

3.分类

1)顺序串
一个顺序串通常包含一个存储字符的数组+串的长度
顺序串

2)链串
多个字符组成块存放在一个结点中,可以解决存储密度低的问题。
链串
链串的存储结构

4.串的模式匹配算法
算法目的:确定主串中所含子串(模式串)第一次出现的位置
算法应用:搜索引擎、拼写检查、语言翻译、数据压缩
算法种类:
1)BF算法(简单匹配算法、穷举法)
算法思路:从正文串的每一个字符开始依次与模板串的字符进行匹配。假设主子串长度为n,模板串长度为m,最坏情况需要比较次数为(n-m+1)m次,若n>>m,则算法复杂度为O(nm)。
BF算法1
BF算法2
BF算法3

// BF算法实现:将主串的第pos个字符和模式串的第一个字符比较,若相等则继续逐个比较后续字符,若不等则从主串的下一字符起,重新与模式串的第一个字符比较(匹配失败后回溯),直到主串的一个连续子串字符序列与模式串相等,即匹配成功。
int Index_BF(SString S, SString T){
  int i=1, j=1;
  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;  // 模式匹配不成功
  }
}
// 解释:i=i-j+2——>j从1到j走了j-1步 i也走了j-1步 i-(j-1)+1就回到了原位置后一个位置

2)KMP算法(速度快)
利用已经部分匹配的结果而加快模式串的滑动速度,主串指针i不必回溯,时间复杂度O(n+m)!
i不用回溯(不会重复扫描),j不一定回溯到第一个位置进行匹配。
方法:定义next[j]函数,表明当模式串T中第j个字符与主串S中相应字符不匹配时,在模式串中需要重新和主串该字符进行比较的字符的位置。

KMP算法
比较过程:模式串从头开始k-1个字符,与j左边的k-1个字符是否匹配。若匹配,记录下k到next[j]中,否则填1。这个方法可以确保j前部分已经成功匹配的字符无需再次匹配。

当主串中的前缀与模板串中j前面的k-1个元素相匹配时,下一次的j就取k的最大值的下一个字符进行比较。
next[j]的计算过程

void get_next(SString T, int &next[]){
  i=1; next[1]=0; j=0;
  while(i<T.length){
  if(j==0 || T.ch[i]==T.ch[j]){
      ++i; ++j;
      next[i] = j;
    }
   else j = next[j];   
  }
}

KMP的改进算法:引进nextval[j]
KMP的改进算法
KMP的改进算法实现

【案例4.1】病毒感染检测:研究者将人的DNA和病毒DNA均表示成由一些字母组成的字符串序列。检测某种病毒DNA序列是否在患者DNA序列中出现过,若出现过则认为此人感染了该病毒。

// 人的DNA序列是线性的,病毒的DNA序列是环状的
// 假设病毒的DNA房列为baa(其他存在形式:aab、aba),患者1的DNA序列为aaabbba,则感染,患者2的DNA序列为babbba,则未感染
// 用BF算法实现
#include <iostream>
#include <string>
using namespace std;

// 环状BF算法实现
bool circularBFMatch(const string& dna, const string& virus) {
    int n = dna.length();
    int m = virus.length();
    
    // 处理环状病毒DNA:考虑所有可能的起始位置
    for (int start = 0; start < m; ++start) {
        bool matched = true;
        for (int i = 0; i < n; ++i) {
            // 环状访问病毒DNA
            if (dna[i] != virus[(start + i) % m]) {
                matched = false;
                break;
            }
        }
        if (matched && n >= m) {  // 确保病毒长度不超过DNA长度
            return true;
        }
    }
    return false;
}

int main() {
    string dna, virus;
    cout << "请输入患者DNA序列: ";
    cin >> dna;
    cout << "请输入环状病毒DNA序列: ";
    cin >> virus;
    
    if (circularBFMatch(dna, virus)) {
        cout << "检测结果: 感染" << endl;
    } else {
        cout << "检测结果: 未感染" << endl;
    }
    
    return 0;
}
// 用KMP算法实现
#include <iostream>
#include <string>
#include <vector>
using namespace std;

// 计算部分匹配表
vector<int> computeLPS(const string& pattern) {
    int m = pattern.length();
    vector<int> lps(m, 0);
    int len = 0;
    int i = 1;
    
    while (i < m) {
        if (pattern[i] == pattern[len]) {
            len++;
            lps[i] = len;
            i++;
        } else {
            if (len != 0) {
                len = lps[len - 1];
            } else {
                lps[i] = 0;
                i++;
            }
        }
    }
    return lps;
}

// KMP算法实现
bool KMPMatch(const string& dna, const string& virus) {
    int n = dna.length();
    int m = virus.length();
    vector<int> lps = computeLPS(virus);
    
    int i = 0; // dna的索引
    int j = 0; // virus的索引
    
    while (i < n) {
        if (virus[j] == dna[i]) {
            i++;
            j++;
        }
        
        if (j == m) {
            return true;
        } else if (i < n && virus[j] != dna[i]) {
            if (j != 0) {
                j = lps[j - 1];
            } else {
                i++;
            }
        }
    }
    return false;
}

// 环状KMP算法
bool circularKMPMatch(const string& dna, const string& virus) {
    int m = virus.length();
    
    // 考虑所有可能的环状展开
    for (int start = 0; start < m; ++start) {
        string rotatedVirus = virus.substr(start) + virus.substr(0, start);
        if (KMPMatch(dna, rotatedVirus)) {
            return true;
        }
    }
    return false;
}

int main() {
    string dna, virus;
    cout << "请输入患者DNA序列: ";
    cin >> dna;
    cout << "请输入环状病毒DNA序列: ";
    cin >> virus;
    
    if (circularKMPMatch(dna, virus)) {
        cout << "检测结果: 感染" << endl;
    } else {
        cout << "检测结果: 未感染" << endl;
    }
    
    return 0;
}

用m个字符存储病毒的DNA,将病毒DNA序列字符串长度扩展成2m个字符,执行m次循环可以线性取到所有长度为m的 可能的模式串。
病毒感染检测

4.2 数组

1.定义
按照一定格式排列、具有相同类型的数据元素的集合。数组的每个元素都占用等长的存储空间。
一维数组:线性表
二维数组:一维数组中的数据元素又是一维数组结构。

// 二维数组的定义
typedef elemtype array1[n];
typedef array1 array2[m];
// 等价于
typedef elemtype array2[m][n];

n维数组:第n-1维数组的元素是一个一维数组

2.基本操作:取值和改值,一般不做插入和删除
n维数组的抽象数据类型
n为数组的维数,bi为数组第i维的长度,ji为数组元素第i维的下标。

// InitArray (&A,n,bound1,...boundn) 销毁数组A 
// DestroyArray (&A) 构造数组A
// Value(A,&e,index1.,indexn) 取数组元素值
// Assign (A,&e,index1...indexn)ADT Array 给数组元素赋值 / 修改元素值

3.分类
1)顺序组
二维数组行优先排序,三维数组按页/行/列存放(页优先的顺序存储)
三维数组

2)特殊矩阵的压缩存储
常规存储可以随机存取元素,存储密度为1;非常规存储的矩阵(值相同的元素很多且呈某种规律分布、零元素较多)。
压缩存储:多个数据元素值相同,只分配一个元素值的存储空间;零元素不占存储空间。

-对称矩阵:仅需处理下三角/上三角匀速,以行序为主序,从顶部往底部一行行存,存储到一维数组sa[n(n+1)/2]中。
对称矩阵
\(a_{n1}\)在一维数组sa[n(n-1)/2]的位置,第n行有n个元素。

-三角矩阵:对角线以下/上的数据元素(不包括对角线)全部为统一常数C。将三角矩阵对应到一个一维数组中,重复常数C只占用一个位置。
三角矩阵
序号 = 所有在它前面元素的个数 = 它所在行前面行的所有元素+它所在行它前面的元素(增量)。

-对角矩阵(带状矩阵):非零元素集中在以主对角线为中心的带状区域中,区域外的值全为0。
实现过程:使用二维数组存储,以对角线为0,上下对称存储。
对角矩阵

-稀疏矩阵:存各非零元的值、行列位置和矩阵的行列数,形成三元组顺序表。顺序存储依行序存储很方便,但不能随机查取,需要从头遍历;链式存储(十字链表)可以灵活插入新的非零元素,删除新的非零元素。
稀疏矩阵
十字链表存储稀疏矩阵:right用于链接同一行中的下一个非零元素;down用于链接同一列中的下一个非零元素。
十字链表的节点结构

4.3 广义表

1.定义
Lists,n个元素组成的有限序列,每一个\(a_{i}\)是原子或广义表,不一定是同一个类型。习惯上用大写字母表示广义表,小写字母表示原子。表尾的概念和线性表不一样!表尾是一个表。
广义表

2.性质
广义表的长度:最外层所包含的元素个数。
广义表深度:广义表展开后所含的括号重数。e.g. 递归表的深度是无限值,长度是有限值。

广义表可以看成是线性表的区别,可兼容线性表、数组、树和有向图等。

3.基本运算
广义表的基本运算
由于广义表中的每个元素所需的存储空间不一定相等,一般用链表存储。

posted @ 2025-07-08 23:18  soffiya  阅读(39)  评论(0)    收藏  举报