1. 链表与邻接表
用数组模拟
单链表
用来写邻接表,主要用于存储图和树
#include <iostream>
using namespace std;
const int N = 100010;
// head 表示头结点的下标
// e[i] 表示结点i的值
// ne[i] 表示结点i的next指针
// idx 存储当前已经用到了哪个点
int head, e[N], ne[N], idx;
// 初始化
void init()
{
head = -1;
idx = 0;
}
// 将x插到头结点
void add_to_head(int x)
{
e[idx] = x;
ne[idx] = head;
head = idx;
idx ++;
}
// 将x插到下标为k的点后面
void add(int k, int x)
{
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx;
idx ++;
}
// 将下标是k的点的后面的点删掉
void remove(int k)
{
ne[k] = ne[ne[k]];
}
int main()
{
}
例题
#include <iostream>
using namespace std;
const int N = 100010;
// head 表示头结点的下标
// e[i] 表示结点i的值
// ne[i] 表示结点i的next指针
// idx 存储当前已经用到了哪个点
int head, e[N], ne[N], idx;
// 初始化
void init()
{
head = -1;
idx = 0;
}
// 将x插到头结点
void add_to_head(int x)
{
e[idx] = x;
ne[idx] = head;
head = idx;
idx ++;
}
// 将x插到下标为k的点后面
void add(int k, int x)
{
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx;
idx ++;
}
// 将下标是k的点的后面的点删掉
void remove(int k)
{
ne[k] = ne[ne[k]];
}
int main()
{
int m;
cin >> m;
init();
while (m--)
{
char op;
int k, x;
cin >> op;
if (op == 'H')
{
cin >> x;
add_to_head(x);
}
else if (op == 'D')
{
cin >> k;
if (!k) head = ne[head];
remove(k - 1);
}
else
{
cin >> k >> x;
add(k - 1, x);
}
}
for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';
cout << endl;
return 0;
}
双链表
优化某些问题
例题
#include <iostream>
using namespace std;
const int N = 100010;
int m;
int e[N], l[N], r[N], idx;
// 初始化
void init()
{
// 0表示左端点(head),1表示右端点(tail)
r[0] = 1, l[1] = 0;
idx = 2; //0和1已经用了2个结点,其他的结点从2开始
}
// 在下标是k的结点的右边插入x
void add(int k, int x)
{
e[idx] = x;
r[idx] = r[k];
l[idx] = k;
l[r[k]] = idx; //顺序不能反
r[k] = idx;
idx ++;
}
// 在下标是k的结点的右边插入x,就直接调用 add(l[k], x);
// 删除第k个点
void remove(int k)
{
r[l[k]] = r[k];
l[r[k]] = l[k];
}
int main()
{
cin >> m;
init();
while (m--)
{
string op;
int k,x;
cin >> op;
if (op == "L")
{
cin >> x;
add(0, x);
}
else if (op == "R")
{
cin >> x;
add(l[1], x);
}
else if (op == "D")
{
cin >> k;
remove(k + 1); //idx从2开始,即第1个插入的数下标为2
}
else if (op == "IL")
{
cin >> k >> x;
add(l[k + 1], x);
}
else
{
cin >> k >> x;
add(k + 1, x);
}
}
for (int i = r[0]; i != 1; i = r[i]) cout << e[i] << ' ';
return 0;
}
2. 栈与队列
栈
先进后出
// tt表示栈顶
int stk[N], tt = 0;
// 向栈顶插入一个数
stk[ ++ tt] = x;
// 从栈顶弹出一个数
tt -- ;
// 栈顶的值
stk[tt];
// 判断栈是否为空,如果 tt > 0,则表示不为空
if (tt > 0)
{
}
单调栈
单调栈
每个元素最多只会入栈一次出栈一次,时间复杂度是O(n)的。
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n;
int stk[N], tt;
int main()
{
scanf("%d", &n);
while (n--)
{
int x;
scanf("%d", &x);
// 如果栈顶元素大于等于待入栈元素,则出栈
while (tt && stk[tt] >= x) tt--;
if (tt) printf("%d ", stk[tt]); //如果栈不空,栈顶元素就是第一个小于它的元素
else printf("-1 ");
stk[++ tt] = x; //当前元素入栈
}
return 0;
}
队列
先进先出
普通队列
// hh 表示队头,tt表示队尾
int q[N], hh = 0, tt = -1;
// 向队尾插入一个数
q[ ++ tt] = x;
// 从队头弹出一个数
hh ++ ;
// 队头的值
q[hh];
// 判断队列是否为空,如果 hh <= tt,则表示不为空
if (hh <= tt)
{
}
循环队列
// hh 表示队头,tt表示队尾的后一个位置
int q[N], hh = 0, tt = 0;
// 向队尾插入一个数
q[tt ++ ] = x;
if (tt == N) tt = 0;
// 从队头弹出一个数
hh ++ ;
if (hh == N) hh = 0;
// 队头的值
q[hh];
// 判断队列是否为空,如果hh != tt,则表示不为空
if (hh != tt)
{
}
单调队列
例题
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n, k;
int a[N], q[N]; //q是单调队列,存的是元素的下标
int main()
{
scanf("%d%d", &n, &k);
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
int hh = 0, tt = -1;
for (int i = 0; i < n; i++)
{
// 判断队头是否已经滑出窗口,如果是,队头++,滑动到下一个窗口
// 这里本来应该用while,但是每次循环只会往后移一位,可以用if
if (hh <= tt && i - k >= q[hh]) hh ++;
// 如果队尾元素大于等于即将插入的元素,弹出队尾
while (hh <= tt && a[q[tt]] >= a[i]) tt --;
q[++ tt] = i; //插入元素坐标
if (i >= k - 1) printf("%d ", a[q[hh]]); //当区间大于等于k时,输出队头
}
printf("\n");
hh = 0, tt = -1;
for (int i = 0; i < n; i++)
{
if (hh <= tt && i - k >= q[hh]) hh ++;
while (hh <= tt && a[q[tt]] <= a[i]) tt --;
q[++ tt] = i;
if (i >= k - 1) printf("%d ", a[q[hh]]);
}
printf("\n");
return 0;
}
3. kmp
时间复杂度是O(n)
KMP字符串
字符串S
和模式串P
下标都从1开始,i
(S
的指针)从1开始,j
(P
的指针)从0开始,每次s[i]
和p[j+1]
比较,是因为:
这里的next
数组表示的是包含j
的前部分串中前缀和后缀的最大长度,当从j+1
开始不匹配时,应该将j
退回到ne[j]
,接着比较s[i]
和p[j+1]
。
next
数组的求法是通过模板串自己与自己进行匹配操作得出来的(代码和匹配操作几乎一样)。
#include <iostream>
using namespace std;
const int N = 1e5 + 10, M = 1e6 + 10;
int n, m;
char p[N], s[M];
int ne[N]; //next数组,最好不要写next,是某些头文件里的保留字
int main()
{
cin >> n >> p + 1 >> m >> s + 1; //数组下标从1开始存
// 求next数组
for (int i = 2, j = 0; i <= n; i++) //i=1时只有1个元素,ne[1]=0
{
while (j && p[i] != p[j + 1]) j = ne[j]; //当j没有退到第1个元素前且不等时,继续退
if (p[i] == p[j + 1]) j++;
ne[i] = j;
}
//kmp匹配过程
for (int i = 1, j = 0; i <= m; i++)
{
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j++;
if (j == n)
{
printf("%d ", i - n); //因为题目要求下标从0开始,我们定义时下标从1开始,i-n+1-1
j = ne[j]; //第一次匹配完成后,再退回j
}
}
return 0;
}