数据结构总结
1. 简介
数据结构三要素:数据的逻辑结构、数据的存储结构和数据操作。
- 数据逻辑结构。例如:集合、线性结构、树结构、图结构等。
- 数据存储结构。
1). 顺序存储结构:用一组连续的存储单元来存放数据元素,逻辑上相邻的元素在物理上也是相邻的。
2). 链式存储结构:数据元素的存放位置由编译器随机分配,节点之间的逻辑关系通过指针表示。
3). 索引存储结构:在存储结点的同时增加一个索引表,索引表中的每一项称为一个索引项,索引项包含一个结点的关键码和结点的存储位置。
4). 散列存储结构: - 数据的操作。数据元素在具体存储结构下的实现算法。
2. 线性表
线性表分为顺序表和链表两种。顺序表是用一组地址连续的存储单位依次存储线性表中的各元素,通过位置来表示数据元素之间的线性逻辑关系;链表是用一组任意的存储单位存储线性表中的各位置,通过指针来表示数据元素之间的线性逻辑关系。
2.1 顺序表
顺序表结构的定义:
typedef int DataType;
struct List{
int Max; //最大元素个数
int n;//实际元素个数
DataType *elem;//首地址
};
2.2 链表
链表结构的定义:
typedef int DataType
struct Node{
DataType data;
struct Node* next;
}LinkNode;
void InitList(LinkNode * &L) {
L = (LinkNode *)malloc(sizeof(LinkNode));
L -> next = NULL;
}
3. 栈和队列
3.1 栈结构
规则:后进先出,栈顶进栈顶出。
应用
-
递归
斐波那契数列数列:
int Fib(int n){ if(n == 0){ return 0; //边界条件 }else if(n == 1){ return 1; //边界条件 }else{ return Fib(n-1) + Fib(n-2); //递归表达式 } }
在递归调用的过程中,系统为每一层的返回点、局部变量、传入实参等开辟了递归工作栈来进行数据存储,递归次数过多容易造成栈溢出等。而其效率不高的原因是递归调用过程中包含很多重复的计算。下面以n=5为例,列出递归调用执行过程,如图所示:
-
进制转换
在进制转换中,最先计算得到的余数结果的最低位,最后计算得到的玉树是结果的最高位,即结果的顺序是计算顺序的逆序,正好符合栈后进先出的特点。例:十进制转八进制 void conversion(Stack s,int n){ while(n){ s.push(n%8); n=n/8; } while(!s.empty()){ printf("%d",s.top()); s.pop(); } }
-
括号匹配
若是左括号则进栈,若是右括号则弹出栈顶元素,判断与当前元素是否匹配,若是不匹配则可判定为此括号串不匹配。class Solution { public: bool isValid(string s) { stack<char> v; for (int i = 0; i < s.size(); i++) { //左括号进栈 if (s[i] == '(' || s[i] == '[' || s[i] == '{') { v.push(s[i]); } else if (s[i] == ')' || s[i] == ']' || s[i] == '}') { // 栈空则不匹配 if (v.empty()) { return false; } switch (s[i]) { case ')': { if (v.top() == '(') { v.pop(); } else return false; break; } case ']': { if (v.top() == '[') { v.pop(); } else return false; break; } case '}': { if (v.top() == '{') { v.pop(); } else return false; break; } } } } if (v.empty()) return true; else return false; } };
-
后缀表达式求值
后缀表达式也称逆波兰式。中缀表达式不仅依赖运算符的优先级,而且还要处理括号。后缀表达式的运算符在操作数后面,在后缀表达式中已考虑了运算符的优先级,没有括号,只有操作数和运算符。例如中缀表达式 A + B ∗ ( C − D ) − E / F 所对应的后缀表达式为 A B C D − ∗ + E F / − 。后缀表达式 A B C D − ∗ + E F / − 求值的过程需要12步,如下表所示:
class Solution { public: int isValid(string s) { stack<int> v; for(int i=0;i<s.size();i++){ //如果是数字字符,则将其压栈(转为int)。isdigit()自己定义的函数 if(isdigit(s[i])) v.push(s[i]-'0'); //是运算符 else{ int val1 = v.top(); v.pop(); int val2 = v.top(); v.pop(); switch(s[i]){ case +: v.push(val1+val2);break; case -: v.push(val1-val2);break; case *: v.push(val1*val2);break; case /: v.push(val1/val2);break; } } } return v.top(); } }
-
中缀表达式转后缀表达式
例:将中缀表达式 a + b − a ∗ ( ( c + d ) / e − f ) + g 转化为相应的后缀表达式。
#include <iostream> #include <stack> // 判断是否是操作符 bool isOperator(char ch) { if(ch == '+' || ch == '-' || ch == '*' || ch == '/') return true; return false; // 否则返回false } // 获取优先级 int getPriority(char ch) { int level = 0; // 优先级 switch(ch) { case '(': level = 1; break; case '+': case '-': level = 2; break; case '*': case '/': level = 3; break; default: break; } return level; } int main(int argc, const char * argv[]) { int num; char arr[250]; // 一个一个的读取表达式,直到遇到'\0' std::stack<char> op; // 栈op:存储操作符 while(1) { std::cin.getline(arr,250); int len, i; char c; // c存储从栈中取出的操作符 len = (int)strlen(arr); // strlen()输出的是:unsigned long类型,所以要强制转换为int类型 i = 0; while(i < len) { if(isdigit(arr[i])) { // 如果是数字 num = 0; //计算第一个数字 do { num = num * 10 + (arr[i] - '0'); // ch - 48根据ASCAII码,字符与数字之间的转换关系 i++; // 下一个字符 }while(isdigit(arr[i])); std::cout << num << " "; } else if(arr[i] == '(') { // (:左括号压栈 op.push(arr[i]); i++; } else if(isOperator(arr[i])) { // 操作符 if(op.empty()) {// 如果栈空,直接压入栈 op.push(arr[i]); i++; } else { // 比较栈op顶的操作符与ch的优先级 // 如果ch的优先级高,则直接压入栈 // 否则,推出栈中的操作符,直到操作符小于ch的优先级,或者遇到(,或者栈已空 while(!op.empty()) { c = op.top(); if(getPriority(arr[i]) <= getPriority(c)) { // 优先级低或等于 std::cout << c << " "; op.pop(); } else // ch优先级高于栈中操作符 break; } // while结束 op.push(arr[i]); // 防止不断的推出操作符,最后空栈了;或者ch优先级高了 i++; } // else } else if(arr[i] == ')') { // 如果是右括号,一直推出栈中操作符,直到遇到左括号( while(op.top() != '(') { std::cout << op.top() << " "; op.pop(); } op.pop(); // 把左括号(推出栈 i++; } else // 如果是空白符,就进行下一个字符的处理 i++; } // 第二个while结束 while(!op.empty()) { // 当栈不空,继续输出操作符 std::cout << op.top() << " "; op.pop(); } std::cout << std::endl; } // 第一个while结束 return 0; }
3.2 队列结构
规则:先进先出,队尾进队头出。
应用
-
循环队列
为了区分队空还是队满的情况,可采取处理牺牲一个单元来区分队空和队满,入队时少用一个队列单元,这是种较为普遍的做法,约定以“队头指针在队尾指针的下一位置作为队满的标志”,如图 ( d2 )所示。队满条件: (Q->rear + 1)%Maxsize == Q->front
队空条件仍: Q->front == Q->rear
队列中元素的个数: (Q->rear - Q ->front + Maxsize)% Maxsize
4. 树
4.1 简介
4.2 树的四种遍历
-
层次遍历
// 层序遍历 vector<int> leverOrder(TreeNode* root) { queue<TreeNode*> que; if(root!=nullptr) que.push(root); vector<int> result; while (!que.empty()) { TreeNode* node = que.front(); result.push_back(node->val); if (node->left)que.push(node->left); if (node->right)que.push(node->right); } return result; }
-
前序遍历
//前序递归 void PreOrder(TreeNode* bt) { if (bt) { cout << bt->val; PreOrder(bt->left); PreOrder(bt->right); } }
// 前序非递归 vector<int> PreOrderF(TreeNode* root) { vector<int> result; stack<TreeNode*> st; if (root != nullptr)st.push(root); while (!st.empty()) { TreeNode* node = st.top(); if (node != nullptr) { st.pop(); if (node->right)st.push(node->right); //放右孩子 if (node->left)st.push(node->left); //放左孩子 st.push(node); //放父亲 st.push(nullptr); //放标志位 } else { st.pop();//剔除标志位nullptr node = st.top(); // 取栈顶元素 st.pop(); // 此元素出栈 result.push_back(node->val); } } return result; }
-
中序遍历
//中序递归 void InOrder(TreeNode* bt) { if (bt) { PreOrder(bt->left); cout << bt->val; PreOrder(bt->right); } }
//中序非递归 vector<int> InOrderF(TreeNode* root) { vector<int> result; stack<TreeNode*> st; if (root != nullptr)st.push(root); while (!st.empty()) { TreeNode* node = st.top(); if (node != nullptr) { st.pop(); if(node->right)st.push(node->right); st.push(node); st.push(nullptr); if (node->left)st.push(node->left); } else { st.pop();//剔除标志位nullptr node = st.top(); // 取栈顶元素 st.pop(); // 此元素出栈 result.push_back(node->val); } } return result; }
-
后序遍历
//后序递归 void PostOrder(TreeNode* bt) { if (bt) { PreOrder(bt->left); PreOrder(bt->right); cout << bt->val; } }
//后序非递归 vector<int> PostOrderF(TreeNode* root) { vector<int> result; stack<TreeNode*> st; if (root != nullptr)st.push(root); while (!st.empty()){ TreeNode* node = st.top(); if (node!= nullptr) { st.pop(); st.push(node); st.push(nullptr); if (node->right)st.push(node->right); if (node->left)st.push(node->left); } else { st.pop(); node = st.top(); st.pop(); result.push_back(node->val); } } return result; }
4.3 哈夫曼树与哈夫曼编码
- 在频率集合中找出字符中最小的两个;小的在左边,大的在右边;这两个兄弟组成二叉树。他们的双亲为他们的频率(权值)之和。
- 在频率表中删除此次找到的两个数,并加入此次最小两个数的频率和(把他们的双亲加入,然后排序)。
例:假设(a,b,c,d,e)出现的频率为(18,25,13,12,32),构造哈夫曼树。
- 第一步:选择最小的12,13,再排序(18,25,25,32)
- 第二步,选择18,25构造,再排序(25,32,43)
- 第三步,选择25,32构造,再排序(43,57)
- 第四步:选择43,57构造
对上述进行编码(哈夫曼编码,左子树为边值0,右子树边值为1)
a:00;b:10;c:011;d:010;e:11;
4.4 二叉排序树
- 二叉排序树:BST ,对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
- 如果有相同的值,可以将该节点放在左子节点或右子节点。
4.5 平衡二叉树
插入平衡二叉树的四种破坏情况:
- LL 型:插入左孩子的左子树,右旋;
- RR 型:插入右孩子的右子树,左旋;
- LR 型:插入左孩子的右子树,先左旋,再右旋;
- RL 型:插入右孩子的左子树,先右旋,再左旋;
4.6 B树
B树:一种平衡的多路查找树,B树中所有结点的子树的最大值称为B树的阶,用m表示。一棵m阶的B树,满足以下特性:
- 树中每个结点最多有m-1个节点,即m棵子树;
- 树中除根结点和叶结点之外,其他结点至少含有ceil(m/2)-1个,即ceil(m/2)棵子树;
- 所有叶结点都在同一层(绝对平衡);
- 所有非叶结点的结构为 [n|p0|k0|…|pn|pk];
4.7 B+树
B树的特点:
- B+树的索引结点并不会保存记录,只用于索引,所有的数据都保存在B+树的叶子结点中;
- B+树的叶子结点都会被连成一条链表。叶子本身按索引值的大小从小到大进行排序;
- m阶的B+树有k个元素(B树中是k-1个元素),且有k个子树;
B+树相比B树的优势:
1.单一节点存储更多的元素,使得查询的IO次数更少;
2.所有查询都要查找到叶子节点,查询性能稳定;
3.所有叶子节点形成有序链表,便于范围查询。
4.8 红黑树:平衡的二叉查找树
特点:
-
结点是红色或黑色。
-
根结点是黑色。
-
每个叶子结点都是黑色的空结点(NIL结点)。
-
每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
-
从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。
例:
简便记法:
根结点必黑,新增为红色;
只能黑连黑,不能红连红;
爸叔通红就变色,爸红叔黑就旋转;
哪边黑往哪边转。
参考链接:
https://blog.csdn.net/bjweimengshu/article/details/106345677?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164871163716781683912078%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=164871163716781683912078&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-106345677.142v5pc_search_result_control_group,143v6control&utm_term=%E7%BA%A2%E9%BB%91%E6%A0%91&spm=1018.2226.3001.4187
5. 图
5.1 简介
- 邻接矩阵
G=(V,E)是一个n个顶点,e条边的图,则图G的邻接矩阵是一个n*n的二维数组arc。如果(Vi,Vj)是图的一条边,则arc[i][j]=1,否则arc[i][j]=0。例如:
如果是有权图,则按照以下规则。
例:
- 邻接表
5.2 遍历
深度优先遍历(DFS)
- 邻接矩阵的DFS
Boolean visited[MAXVEX]; /* 访问标志的数组 */ void DFS(MGraph G, int i) { int j; visited[i] = TRUE; printf("%c ", G.vexs[i]);/* 打印顶点,也可以其它操作 */ for(j = 0; j < G.numVertexes; j++) if(G.arc[i][j] == 1 && !visited[j]) DFS(G, j);/* 对为访问的邻接顶点递归调用 */ } void DFSTraverse(MGraph G) { int i; for(i = 0; i < G.numVertexes; i++) visited[i] = FALSE; /* 初始所有顶点状态都是未访问过状态 */ for(i = 0; i < G.numVertexes; i++) if(!visited[i]) /* 对未访问过的顶点调用DFS,若是连通图,只会执行一次 */ DFS(G, i); }
- 邻接表的DFS
Boolean visited[MAXVEX]; /* 访问标志的数组 */ void DFS(MGraph GL, int i) { EdgeNode *p; visited[i] = TRUE; printf("%c ", GL->adjList[i].data);/* 打印顶点,也可以其它操作 */ p=GL->adjList[i]firstedge; while(p) { if(!visited[p->adjvex]) DFS(GL.P->adjvex); p=p->next; } } void DFSTraverse(MGraphList GL) { int i; for(i = 0; i < GL.numVertexes; i++) visited[i] = FALSE; /* 初始所有顶点状态都是未访问过状态 */ for(i = 0; i < GL.numVertexes; i++) if(!visited[i]) /* 对未访问过的顶点调用DFS,若是连通图,只会执行一次 */ DFS(GL, i);
广度优先遍历(BFS)
邻接表的BFS
void BFSTraverse(MGraph G)
{
int i, j;
Queue Q;
for(i = 0; i < G.numVertexes; i++)
visited[i] = FALSE;
InitQueue(&Q); /* 初始化一辅助用的队列 */
for(i = 0; i < G.numVertexes; i++) /* 对每一个顶点做循环 */
{
if (!visited[i]) /* 若是未访问过就处理 */
{
visited[i]=TRUE; /* 设置当前顶点访问过 */
printf("%c ", G.vexs[i]);/* 打印顶点,也可以其它操作 */
EnQueue(&Q,i); /* 将此顶点入队列 */
while(!QueueEmpty(Q)) /* 若当前队列不为空 */
{
DeQueue(&Q,&i); /* 将队对元素出队列,赋值给i */
for(j=0;j<G.numVertexes;j++)
{
/* 判断其它顶点若与当前顶点存在边且未访问过 */
if(G.arc[i][j] == 1 && !visited[j])
{
visited[j]=TRUE; /* 将找到的此顶点标记为已访问 */
printf("%c ", G.vexs[j]); /* 打印顶点 */
EnQueue(&Q,j); /* 将找到的此顶点入队列 */
}
}
}
}
}
}
5.3 最小生成树
Prim算法:选点
例:
选择V0,在选择于V0连接顶点中权值最小的V6
再选择于V0和V6相邻且权值最小的,即V1
重复以上过程,得到最小生成树如下
Kruskal算法:选边
例:
- 开始的时候,认为每个点是孤立的,分属n个独立的集合。有5个集合{{1},{2},{3},{4},{5}}。
- 选择最小的一条边,而且这条边的两个顶点属于两个不同的集合,并将连接的点和为一个集合。集合{{1,2},{3},{4},{5}}
- 再选择剩下的边中最小的且连接两个集合的。集合{{1,2},{3},{4,5}}
- 重复以上步骤,最小生成树
5.4 最短路径
Dijkstra算法
例:
distance[]和path[]数组变化为表所示。
循环 | S | min | V0 | V1 | V2 | V3 | V4 | V5 |
---|---|---|---|---|---|---|---|---|
初始 | 0 | 5,0 | 30,0 | 35,0 | ∞,0 | ∞,0 | ||
1 | {0} | 1 | 0 | 5,0 | 29,1 | 35,0 | 34,1 | 15,1 |
2 | {0,1} | 5 | 0 | 5,0 | 29,1 | 23,5 | 27,5 | 15,1 |
3 | {0,1,5} | 3 | 0 | 5,0 | 29,1 | 23,5 | 27,5 | 15,1 |
4 | {0,1,5,3} | 4 | 0 | 5,0 | 29,1 | 23,5 | 27,5 | 15,1 |
5 | {0,1,5,3,4} | 2 | 0 | 5,0 | 29,1 | 23,5 | 27,5 | 15,1 |
6 | {0,1,5,3,4,2} | 0 | 5,0 | 29,1 | 23,5 | 27,5 | 15,1 |
由表可知,从V0到各个顶点的最短路径可通过distance和path数组获得。例如distance[2]=29,得知V0→V2的最短路径长度为29,由path[2]=1和path[1]=0,得出V0到V2的最短路径为(V0,V1,V2) 。再例如,distance[4]=27,得知V0→V2的最短路径长度为27,path[4]=5,path[5]=1,path[1]=0,得知V0到V4的最短路径为(V0,V1,V5,V4)。
Floyd算法
Dijkstra算法主要用于求单源最短路径,Floyd算法用于求多源最短路径。
其中第2步的状态转移方程为:
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j])//dp[i][j]表示从i到j的最短路径
例:
- 初始状态
- 加入A更新。
- 加入B更新。
- 加入C更新。
- 加入D更新。
- 加入E更新
- 加入F更新。
- 加入G更新。
5.5 拓扑排序(AOV网)
注:只有有向无环图才有拓扑排序。
拓扑排序过程:
- 从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。
- 从图中删除该顶点和所有以它为起点的有向边。
- 重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。
例:
拓扑排序过程:
拓扑排序的结果: { 1, 2, 4, 3, 5 }
5.6 关键路径(AOE网)
关键路径简介
如图所示,边A1→A6表示学习的课程,也就是“活动“,边权表示课程学习需要的时间;顶点V1→V6表示到目前位置的课程已经学完,也就是”事件“。
在AOE网中仅有一个入度为0的顶点,称为开始顶点(源点),它表示整个工程的开始;网中也仅存在一个出度为0的顶点,称为结束顶点(汇点),它表示整个工程的结束。
关键路径算法
五个变量定义:
- 事件的最早发生时间(Ve(i)):事件Vi的最早发生时间是从顶点V0到顶点Vi的最长路径长度。计算时采用正向递推方式。max
- 事件的最迟发生时间(Vl(i)):事件Vi的最迟发生时间指在不推迟整个工期的前提下事件Vi可发生的最晚事件。计算时采用反向地推方式。min
- 活动的最早发生时间(e(i)):表示指该活动的起点所表示事件最早发生时间。如果< Vi,Vj >表示活动ai,则e(i)=Ve(i)。
- 活动的最迟发生时间(l(i)):表示该活动的终点所表示的事件最迟发生时间与该活动所需时间之差。如果边< Vi,Vj >表示活动ai,则有l(i)=vl(j)-weight(Vi,Vj)。
- 活动的时间余量:d[i] = l(i)-e(i)。
例:
事件时间 | V0 | V1 | V2 | V3 |
---|---|---|---|---|
Ve | 0 | 4 | 6 | 10 |
Vl | 0 | 4 | 6 | 10 |
活动时间 | a0 | a1 | a2 | a3 | a4 |
---|---|---|---|---|---|
e | 0 | 0 | 4 | 6 | 4 |
l | 0 | 3 | 4 | 6 | 4 |
d | 0 | 3 | 0 | 0 | 0 |
d为0的为关键路径,即a0,a2,a3,a4。由图可知,此图有两条关键路径,分别为,a0→a2→a3和 a0→a4。
6. 散列表
6.1简介
例:关键字集合{12,7,26,40,16,34,18},散列函数h(key)=key mod 13,构造散列表。
-
计算关键字的散列地址{12,7,0,1,3,8,5}。
-
构建表
散列地址 0 1 2 3 4 5 6 7 8 9 10 11 12 关键字 26 40 16 18 7 34 12
6.2 散列函数
装载因子
装载因子=填入数组的元素个数/散列数组长度;
6.3 冲突
解决冲突的方法有两大类:开地址法、拉链法。
开地址法
(1)线性探测:冲突位置向后找
(2)二次探测:平方位置前后找,(1,-1,4,-4,…)
(3)双重散列:多个散列函数
拉链法
散列表中,每个“桶(bucket)”会对应一条链表,所有散列值相同的元素我们都放到相同槽位对应的链表中:
7. 排序
排序方法 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 |
---|---|---|---|---|---|
插入排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
希尔排序 | O(n) | O(n) | O(n²) | O(1) | 不稳定 |
选择排序 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
冒泡排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n²) | O(nlogn) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
基数排序 | O(d*(r+n)) | O(d*(r+n)) | O(d*(r+n)) | O(d*r+n) | 稳定 |
桶排序 | O(M+N) | O(M+N) | 取决于桶内的排序方法 | O(M+N) | 稳定 |
插入排序
void insertSort(int* arr, int len) {
if (len < 2)return;
int t;
for (int i = 1; i < len; i++) {
t = arr[i];//待排序元素
// 从已排序的部分从后向前找位置
for (int j = i - 1; j >= 0; j--) {
if (arr[j] <= t)break;//小于或等于,即找到了插入位置
arr[j + 1] = arr[j];//元素后移
}
arr[i + 1] = t;
}
}
选择排序
void selectSort(int* arr, int len) {
if (len < 2)return;
int min;
for (int i = 0; i < len - 1; i++) {
min = i;
for (int j = i + 1; j < len; j++) {
if (arr[j] < arr[min])min = j;
}
if (min != i)swap(arr[i],arr[min]);
}
}
冒泡排序
void bubbleSort(int *arr,int len) {
if (len < 2)return;
int t;
for (int j = len - 1; j > 0; j--) {
for (int i = 0; i < j; i++) {
if (arr[j] > arr[i]) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
}
}
快速排序
// 一次快排
int OneQuickSort(int A[], int left, int right) {
int temp = A[left];
while (left<right)
{
while (left<right && A[right] >= temp)right--;
A[left] = A[right];
while (left<right && A[left] <= temp)left++;
A[right] = A[left];
}
A[left] = temp;
return left;
}
void QuickSort(int A[],int left,int right) {
if (left < right) {
int pos = OneQuickSort(A,left,right);
QuickSort(A, left, pos - 1);
QuickSort(A, pos + 1, right);
}
}
8. 其他
8.1 朴素的模式匹配算法
原串:t=“ABABBABABACDDAB”。模式p=“ABABACDD”。
int bruteForce(string s, string t, int pos) {
int i = pos, j = 0;
while (i < s.size() && j < t.size()) {
if (s[i] == t[j]) {
i++;
j++;
} else {
i = i - j + 1;
j = 0;
}
}
return j >= t.size() ? i - t.size() : 0;
}
8.2 KMP算法
https://blog.csdn.net/weixin_46007276/article/details/104372119?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164967023216780357296341%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=164967023216780357296341&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-104372119.142v7control,157v4control&utm_term=KMP&spm=1018.2226.3001.4187