静态链表
- 单链表
int e[N], ne[N], head, idx; // e[i]存结点数据,en[i]存下个结点的坐标
//初始化
void init(){
head = -1; //head 是当前首个存数据的结点坐标。设 NULL 为 -1
idx = 0; //idx 从0起,按创建顺序记录最后一个坐标
} //切记:下标即坐标,与链表顺序无关
void add_to_head(int x){
}
void add(int k, int x){
} //默认向 k 点的后面插,O(1)。若从前面插就只能遍历,O(n)。注意 k ≥ 0
void remove(int k){
} //内存泄漏,所以仅限算法竞赛
静态链表用空间换时间,动态链表用时间换空间,各有所长。
静态链表的结点地址是固定的,所以不需要结构体来绑定数据
原理基本相同,函数体略
- 双向链表
int e[N], l[N], r[N], idx;
void init(){
r[0] = 1, l[1] = 0;
idx = 2; //没有头结点,初始化时定义两个起始结点
}
//函数思路和单链表相同,但 en[i] 变为 l[i] 和 r[i],函数写其中一个方向就够了,略
链接数据只需要一个方向。另一个方向是方便双向读取
栈和队列
栈和队列,但是用数组模拟(梅开二度)
//栈,只有一端,先进后出
int stk[N], tt; //补充一下,全局变量默认为0
cin >> stk[tt++]; //入栈
cout << stk[tt], tt--; //出栈
//队列,有两端,先进先出
int queue[N], hh, tt;
cin >> stk[tt++]; //入列
cout << stk[hh], hh++; //出列
栈例:输入长度 n 的数列,输出每个数左边第一个小于他的数,若没有,输出-1
for(i = 0; i < n; i++)
{
scanf("%d", &x);
if(tt && stk[tt] >= x) tt--; //由题知,连续输出是升序列,升序片段起点是-1
if(tt) printf("%d ", stk[tt]);
else printf("-1 ");
stk[tt+] = x; //时间O(n)
}
队例:输入长度 n 的数列,输出所有长度为 k 的滑动窗口中最小值
//分析:滑动窗口是一个队列,优化方式和栈一样,屏蔽掉没用的数,但队列还要右进左出
int q[N]; //hh 和 tt 保存的值为窗口中有效序列的首尾
for(i = 0; i < n; i++) scanf("%d", &a[i]);
for(i = 0; i < n; i++){
if(hh <= tt && q[hh] < i - k + 1) hh++; //窗口移动时 hh 匹配下一个有效队头
while(hh <= tt && a[q[tt]] > a[i]) tt--; //新数不满足升序,踢出旧数
q[tt++] = i; //加入新数,暂时满足升序
if(i >= k - 1) printf("%d", a[q[hh]]); //窗口完整,则开始输出
}
栈和队列按出入方式区分,利用其特点解决相应问题。
先用暴力思路解决,利用数据性质去除无效数据,就可以显著优化算法。
KMP 算法
先举个栗子:有数组 a[N] 和 p[M],N>M,现在要判断在 a 中能否找到与 p 相同的一段。
于是有朴素算法:从
i = 1
开始遍历枚举,遇到不同元素就 break,我们只看break前的部分,可以发现:设 p 右移后再次匹配 a ,且与原序列有交集,说明
i
前的部分满足首尾有相同序列。于是可以认为解决问题的门路是,找到短数组中最长的首尾相同序列。——KMP算法
- (知识点参考——王道论坛)
-
初步优化:在朴素算法基础上,不必重置 i ,遇到不匹配的项,只重置 j 即可
-
二次优化:j 不一定要回到起点,若 j 前面有首位相同序列,优先回到首位序列的下一位进行判定。出于这种考虑,新增一个
next[]
数组记录 j 可以跳到的位置,下标从1开始,记录每一项匹配失败后能回到最靠后的下标。
计算next数组:
从图形角度:数组相对自己的复制体整体右移,使前后的相同序列对齐并继续判定,复制体上的 j 不动,所以 j 相对数组是左移,此时 j 项对齐的数组本体位置就是
next[j]
。由于如果第一项就不匹配,则直接右移一步,再将 i i i 和 j j j 自增,匹配下一项,于是有next[j] = 0
。
从算法角度:利用双指针,其中 i i i 遍历数组, j j j 对每个 i i i 寻找
next[i]
,其实就是对自己KMP
int i = 1, j = 0; //i 和 j 错开一格,是为了保证真前缀和真后缀,仅限p对自己KMP找next
while(i < M){
if(j == 0 || p[i] == p[j]){
++i, ++j; //j 为每个 i 扫描相同序列,匹配就假设下一个失配然后记录 next
next[i] = j; //考研的时候手算,即为最大相同序列长度+1
}
else j = next[j]; //i j 前面是一样的,所以退回 next,自然 next 为 0 时才会重置
}
三次优化:有时候,next可能会退回前一个相同字符的位置,导致增加了无意义的判定,因此
int nextval[n]; nextval[1] = 0;
//在求出一个点的next值后追加
if(p[next[j]] == p[j]) nextval[j] = nextval[next[j]];
else nextval[j] = next[j]; //于是每一位的next都能一次归位
p[M]
对自己KMP,就可以找到自己在数组匹配中的移动方式,从而和a[N]
进行KMP
get_next(p, nextval); //上面那个KMP封成一个函数,参数为2个数组
int i = 1, j = 1;
while(i <= n && j <= m){
if(j == 0 || a[i] == p[j]){
++i; ++j;
}else{
j = next[j];
}
}
if(j > n) return i - n; //利用KMP,完美找到 a 中的 p,返回起点
else return 0; //a 中没有匹配的 p