链表
单链表
一般做题中,采用数组模拟链表的方式,效率高
#include <iostream>
using namespace std;
const int N = 100010;
//head表示头结点
//value[i]存放i的值
//ne[i]存放i的next指针
//idx表示当前已经用到了多少节点
int head, value[N], ne[N], idx;
//初始化
void init(){
head = -1;
idx = 0;
}
//将x插入到头结点
void appendHead(int x){
value[++idx] = x;
ne[idx] = head;
head = idx;
}
//在第k个节点后,插入x
void insert(int x, int k){
value[++idx] = x;
ne[idx] = ne[k];
ne[k] = idx;
}
//删除第k个节点的下一个节点
void move(int k){
ne[k] = ne[ne[k]];
}
int main(){
int m;
cin>>m;
init();
while(m--){
char op;
cin>>op;
if(op == 'H'){
int x;
cin>>x;
appendHead(x);
}else if(op == 'I'){
int k, x;
cin>>k>>x;
insert(x, k);
}else if(op == 'D'){
int k;
cin>>k;
if(!k) head = ne[head];
else move(k);
}
}
//尾结点的指针域为-1
for(int i = head; i != -1; i = ne[i]) cout<<value[i]<<' ';
cout << endl;
}
双链表
双链表存在两个指针域,一个指向它的前一个节点,一个指向它的后一个节点
#include <iostream>
const int N = 100010;
using namespace std;
int value[N], l[N], r[N], idx;
//令0为左边的头结点 1为右边的头结点
void init(){
r[0] = 1;
l[1] = 0;
idx = 2;
}
//在第k个位置的右边插入x
void insert(int k, int x){
value[idx] = x;
l[idx] = k;
r[idx] = r[k];
l[r[k]] = idx;
r[k] = idx++;
}
//删除第k个节点
void move(int k){
l[r[k]] = l[k];
r[l[k]] = r[k];
}
int main(){
int m;
cin>>m;
init();
while(m--){
string op;
cin>>op;
int x, k;
if(op == "L"){
cin>>x;
insert(0, x);
}else if(op == "R"){
cin>>x;
insert(l[1], x);
}else if(op == "D"){
cin>>k;
move(k + 1);
}else if(op == "IL"){
cin>>k>>x;
insert(l[k + 1], x);
}else if(op == "IR"){
cin>>k>>x;
insert(k + 1, x);
}
}
for(int i = r[0]; i != 1; i = r[i]) cout<<value[i]<<" ";
cout<<endl;
}
栈和队列
栈
后进先出:栈顶插入和删除
#include <iostream>
const int N = 100010;
using namespace std;
int stk[N], top;
//入栈
//stk[++top] = x;
//出栈
//top--;
//判断空
//top > 0
//获取栈顶元素
//stk[top]
int main(){
int m;
cin>>m;
top = 0;
while(m--){
string op;
int x;
cin>>op;
if(op == "push"){
cin>>x;
stk[++top] = x;
}else if(op == "pop"){
top--;
}else if(op == "empty"){
cout<< (top ? "NO" : "YES")<<endl;
}else if(op == "query"){
if(top > 0) x = stk[top];
cout<<x<<endl;
}
}
return 0;
}
队列
先进先出:队头删除元素,队尾插入元素
#include <iostream>
using namespace std;
const int N = 100010;
int queue[N], tail, head;
//入队
//queue[++tail] = x;
//出队
//queue[head++] = x;
//对头
//x = queue[tail];
//判断空
//tail < head;
int main(){
int m;
cin>>m;
head = 1;
while(m--){
string op;
cin>>op;
int x;
if(op == "push"){
cin>>x;
queue[++tail] = x;
}else if(op == "pop"){
head++;
}else if(op == "empty"){
cout<<(tail < head ? "YES" : "NO") <<endl;
}else if(op == "query"){
if(tail >= head) x = queue[head];
cout<<x<<endl;
}
}
}
单调栈
#include <iostream>
const int N = 100010;
using namespace std;
int stk[N], top;
int main(){
int n;
cin >> n;
while(n--){
int x;
cin >> x;
while(top && stk[top] >= x) top--;
if(!top) cout << "-1" << " ";
else cout << stk[top] << " ";
// for(int i = 0; i <= top; i++)
// cout << stk[i] <<" ";
// cout<<endl;
stk[++ top] = x;
}
return 0;
}
单调队列
#include <iostream>
const int N = 1000020;
using namespace std;
int n, k;
int queue[N], a[N], tail = 0, head = 1;
int main(){
cin>>n>>k;
for(int i = 0; i< n; i++)
cin >> a[i];
for(int i = 0; i < n; i ++){
if(head <= tail && i - k + 1 > queue[head]) head ++;
while(head <= tail && a[queue[tail]] >= a[i]) tail --;
queue[++ tail] = i;
if(i >= k - 1) cout << a[queue[head]] << " ";
}
cout<<endl;
head = 1, tail = 0;
for(int i = 0; i < n; i ++){
if(head <= tail && i - k + 1 > queue[head]) head ++;
while(head <= tail && a[queue[tail]] <= a[i]) tail --;
queue[++ tail] = i;
if(i >= k - 1) cout<<a[queue[head]]<<" ";
}
cout<<endl;
}
KMP
-
暴力算法如何做
//S[N]长串, p[M]短串 //暴力做法 for(int i = 1; i <= n; i++) { bool flag = true; for(int j = 1; j <= m; j++) if(s[i + j - 1] != p[j]) { flag = false; break; } } //next[i] //字符串的前缀集合与后缀集合的交集中最长元素的长度 //模式串中相等的前缀与后缀的最大长度 //以i为终点的后缀和从1开始的前缀相等,而且后缀的长度最长 //p[1, j] = p[i - j + 1, i];
-
如何去优化
//借助next数组 #include <iostream> using namespace std; const int N = 10010, M = 100010; int n, m; char p[N], s[M]; int ne[N]; int main() { cin >> n >> p + 1 >> m >> s + 1; //求ne[j]的过程 for(int i = 2, j = 0; i <= m; i++) { while(j && p[i] != p[j + 1]) j = ne[j]; if(s[i] == p[j + 1]) j++; ne[i] = j; } //KMP匹配过程 for(int i = 1, j = 0; i <= m; i++) { //j没有退回起点 //当前的节点与下一个节点不匹配 while(j && s[i] != p[j + 1]) j = ne[j]; if(s[i] == p[j + 1]) j++; if(j == n) { //匹配成功 printf("%d ", i - n); j = ne[j]; } } }
Trie
#include <iostream>
using namespace std;
const int N = 100010;
//x节点的所有儿子存到了son[x]中
//son[x][0] x的第0个儿子
//son[x][1] x的第1个儿子
//cnt[X]以X结尾的单词个数有多少个
int son[N][26]; //每个节点的所有儿子是什么
int cnt[N]; //以当前这个节点结尾的单词数目
int idx; //当前用到了哪一个下标
//下标是0的点,既是根节点,又是空节点
char str[N];
void insert(char str[])
{
int p = 0;
for(int i = 0; str[i]; i++)
{
int u = str[i] - 'a';
if(!son[p][u]) son[p][u] = ++idx;
p = son[p][u];
}
cnt[p]++;
}
int query(char str[])
{
int p = 0;
for(int i = 0; str[i]; i++)
{
int u = str[i] - 'a';
if(!son[p][u]) return 0;
p = son[p][u];
}
return cnt[p];
}
int main()
{
int n;
scanf("%d", &n);
while(n -- )
{
char op[2];
scanf("%s%s", op, str);
if(op[0] == 'I') insert(str);
else cout<<query(str)<<endl;
}
return 0;
}
并查集
两个操作(近乎O(1)的时间复杂度):
-
将两个集合合并
- 询问两个元素是否在一个集合当中
基本原理:每个集合用一颗树来表示,树根的编号就是整个集合的编号,每个节点存储它的父节点,p[x]表示x的父节点。
问题1. 如何判断树根 if(p[x] == x)
问题2. 如何求x的集合编号:while(p[x] != x) x = p[x];
路径压缩的思想优化: 遍历到某个节点都让该节点的父节点指向整个集合
问题3. 如何合并两个集合:p[x]是x的集合编号,p[y]是y的集合编号,p[x] = y;
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int p[N]; //存放的i的父亲节点
int find(int x) //返回x的祖宗节点 + 路径压缩
{
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) p[i] = i;
while(m--)
{
char op[2]; //读入字符串可以过滤掉字符串和回车
int a, b;
scanf("%s%d%d", op, &a, &b);
if(op[0] == 'M') p[find(a)] = find(b);
else
{
if(find(a) == find(b)) puts("Yes");
else puts("No");
}
}
return 0;
}
并查集除了以上之外,可以额外维护一个信息。
堆
如何手写一个堆?
-
插入一个数
heap[++size] = x; up(size);
-
求集合当中最小值
heap[1]
-
删除最小值
将最后一个元素覆盖到堆顶元素heap[1],再将堆顶down一遍。
原因:一维数组,删除头结点非常困难,删除尾结点比较方便size--
heap[1] = heap[size]; size--; down(1);
-
删除任意一个元素
- 方法1
x = heap[k]; heap[k] = heap[size]; size--; if(heap[k] > x) down(k); else up(k);
2)方法2
heap[k] = heap[size]; size--; down(k), up(k);//只会执行一个
-
修改任意一个元素
heap[k] = x; down(k); up(k);
堆是一个完全二叉树。除了最后一层叶子节点外,上面节点都是非空的,叶子节点是按照从左到右的顺序排列的。
小根堆:每个点都是小于等于左右儿子的
大根堆:每个点都是大于等于左右儿子的
堆的存储:一维数组存储,1为根节点,x的左儿子2x, 右儿子2x+1
down(x): 往下调整,如果某个点的值变大了,将它往下沉,下沉的过程中与左右子节点中的最小值进行交换
int h[N], size;
void down(int u)
{
int t = u;
//判断左儿子
if(u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
//判断右儿子
if(u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if(u != t)
{
swap(h[u], h[t]);
down(t);
}
}
up(x): 往上调整,如果某个点的值变小了,将它往上走,上移的过程中,与父节点比较即可,如果小于父节点,交换
int h[N], size;
void up(int u)
{
while(u / 2 && h[u / 2] > h[u])
{
swap(h[u / 2], h[u]);
u /= 2;
}
}
1~5由down和up实现
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n, m;
int h[N], size;
void down(int u)
{
int t = u;
if(u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
if(u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if(u != t)
{
swap(h[u], h[t]);
down(t);
}
}
int main()
{
scanf("%d", n);
for(int i = 1; i <= n; i++) scanf("%d", &h[i]);
size = n;
for(int i = n/ 2; i; i--) down(i); //O(n)
while(m--)
{
printf("%d ", h[1]);
h[1] = h[size];
size--;
down(1);
}
return 0;
}
哈希表
拉链法
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100003;
int h[N], e[N], ne[N], idx;
void insert(int x)
{
int k = (x % N + N) % N;
e[idx] = x;
ne[idx] = h[k];
h[k] = idx++;
}
bool find(int x)
{
int k = (x % N + N) % N;
for(int i = h[k]; i!= -1; i = ne[i])
if(e[i] == x) return true;
return false;
}
int main()
{
int n;
cin>>n;
memset(h, -1, sizeof h);
while(n--)
{
char op[2];
int x;
cin>>op>>x;
if(*op == 'I') insert(x);
else
{
if(find(x)) puts("Yes");
else puts("No");
}
}
return 0;
}
开放寻址法
#include <iostream>
#include <cstring>
using namespace std;
const int N = 200003, null = 0x3f3f3f;
int h[N];
bool find(int x)
{
int k = (x % N + N) % N;
while(h[k] != null && h[k] != x)
{
k++;
if(k == N) k = 0;
}
return k;
}
int main()
{
int n;
cin>>n;
memset(h, 0x3f, sizeof h);
while(n--)
{
char op[2];
int x;
cin>>op>>x;
int k = find(x);
if(*op == 'I')
h[k] = x;
else
{
if(h[k] != null) puts("Yes");
else puts("No");
}
}
return 0;
}
字符串哈希
#include <iostream>
using namespace std;
tyedef unsigned long long ULL;
const int N = 100010, P = 131;
int n, m;
char str[N];
ULL h[N], p[N];
ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - 1 + 1];
}
int main()
{
scanf("%d%d%s", &n, &m, str + 1);
p[0] = 1;
for(int i = 1; i <= n; i++)
{
p[i] = p[i - 1] * P;
h[i] = h[i - 1] * P + str[i];
}
while(m--)
{
int l1, r1, l2, r2;
scanf("%d%d%d%d", l1, r1, l2, r2);
if(get(l1, r1) == get(l2, r2)) puts("Yes");
else puts("No");
}
return 0;
}
STL常用的库函数
/*
vector, 变长数组,倍增的思想
size() 返回元素个数
empty() 返回是否为空
clear() 清空
倍增的思想:系统为某个程序分配空间时,所需时间与空间大小无关,与申请次数有关。
front() back() 第一个数和最后一个数
begin() end() 第0个数和最后一个数的下一个数
string, 字符串, substr(), c_str()
queue, 队列,push(), front(), pop()
priority_queue, 优先队列,push(),top(),pop()
stack, 栈,push(), top(), pop()
deque, 双端队列
set, map, multiset, multimap 基于平衡二叉树(红黑树)来实现, 动态维护有序序列
unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
bitset,压位
*/
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>