目录
1、单链表
//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)
{
//1、将next指针指向head节点指向的值 2、将head指针删除,指向插入节点x指针 3、当前idx节点,将x值存起来 4、idx走到下一个位置
e[idx] = x, ne[idx] = head, head = idx, idx ++;
}
//将x插到下标是k的点后面
void add(int k, int x)
{
//1、将当前点next指针指向k点下一个位置 2、将k点下一个位置指向第二个点
e[idx] = x;
ne[idx] = ne[k];//1、next指针指向k->next
ne[k] = idx;//2、k点下一个位置指向idx
idx ++;
}
//把k后面的点删除掉
void remove(int k)
{
ne[k] = ne[ne[k]];//ne移到ne->ne后面
}
2、双链表
#include <iostream>
using namespace std;
const int N = 100010;
int m;
int e[N], l[N], r[N], idx;
//初始化
void init()
{
//0表示左端点,1表示右端点
r[0] = 1, l[1] = 0;
idx = 2;
}
//在下标是k的点的右边,插入x.如果是插入到左边输入这个:add(l[k],x)
void add(int k, int x)
{
e[idx] = x;//1、先赋值
r[idx] = r[k];//2、红颜色点右边指针指好=r[k]
l[idx] = k;//3、红颜色点左边=k
l[r[k]] = idx;//4、右边的l[k]=红颜色的指针
r[k] = idx;//5、k的r=红颜色指针
}
//删除第k个节点
void remove(int k)
{
r[l[k]] = r[k];//点k的左边的右边 = k的右边
l[r[k]] = l[k];//点k的右边的左边 = k的左边
}
3、栈和队列
概念:
1、栈:先进后出
2、队列先进先出
//栈的操作
//插入
stk[++ tt] = x;
//弹出
tt --;
//判断栈是否为空
if (tt > 0) not empty
else empty
//栈顶
stk[tt];
//Example--输出每个数左边第一个比它小的数
#include <iostream>
using namespace std;
const int N = 100010;
int n;
int stk[N], tt;
int main()
{
cin >> n;
for (int i = 0; i < n; i ++)
{
int x;//读入x
cin >> x;
while(tt && stk[tt] >= x) tt --;//如果栈不空且栈顶元素大于x
if (tt) cout << stk[tt] << ' ';//如果tt不是空的,存在输出那个值,将其输出
else cout << -1 << ' ';//否则栈不是空的所以输出-1
stk[++ tt] = x;//插入
}
return 0;
}
//队列
//插入
q[++ tt] = x;
//弹出
hh ++;
//判断队列是否为空
if (hh <= tt) not empty
else empty
//取出队头元素
q[hh]
q[tt]
//Example--确定滑动窗口位于每个位置时,窗口中的最大值和最小值。
//1、思考使用普通队列怎么做。
//2、将队列没有用的元素删除掉。
//3、可以用O(1)的时间从队头/队尾取最值
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1000010;
int n, k;
int a[N], q[N];//a表示原数组,q表示队列
int main()
{
//第一行包含两个整数n和k,分别代表数组长度和滑动窗口的长度
scanf("%d%d", &n, &k);
//第二行有n个整数,代表数组的具体数值。同行数据之间用空格隔开。
for (int i = 0; i < n; i ++) scanf("%d", &a[i]);
int hh = 0, tt = -1;//队头hh,对尾tt
//找最小值
for (int i = 0; i < n; i ++)
{
//判断队头是否在窗口内部,如果不在就删除
if (hh <= tt && q[hh] < i - k + 1) hh ++;// 若队首出窗口,hh加1
//判断队尾元素>=当前数,
while (hh <= tt && a[q[tt]] >= a[i]) tt --;//若队尾不单调,tt减1
q[++ tt] = i;//把当前数插入进来
if (i >= k - 1) printf("%d ", a[q[hh]]);
}
puts("");
hh = 0; tt = -1; //初始化队列 // 重置
//找最大值
for (int i = 0; i < n; i ++)
{
if (hh <= tt && q[hh] < i - k + 1) hh ++;
while(hh <= tt && a[q[tt]] <= a[i]) tt --;
q[++ tt] = i;
if (i >= k - 1) printf("%d ", a[q[hh]]);
}
puts("");
return 0;
}
4、KMP算法
//字符串匹配
#include <iostream>
using namespace std;
const int N = 10010, M = 100010;
int n, m;
char p[N], s[M];
int ne[N];
int main()
{
//输入参数:
/*
第一行输入整数N,表示字符串P的长度。
第二行输入字符串P。
第三行输入整数M,表示字符串S的长度。
第四行输入字符串S。
*/
cin >> n >> p + 1 >> m >> s + 1;
//求next过程
for (int i = 2, j = 0; i <= n; i ++)//i从第二个数开始,j从0开始
{
while (j && p[i] != p[j + 1]) j = ne[j];//如果j与i不匹配,j往后走知道匹配为止
if (p[i] == p[j + 1]) j ++;//如果匹配,j++,下一位继续匹配
ne[i] = j;//next = j ++
}
//kmp过程
for (int i = 1, j = 0; i <= m; i ++)
{
while (j && s[i] != p[j + 1]) j = ne[j];//j匹配
if (s[i] == p[j + 1]) j ++;//匹配到就往后走
if (j == n)//知道匹配完
{
printf("%d ", i - n);//输出结果
j = ne[j];
}
}
return 0;
}
5、trie树-高效的存储和查找字符串集合的数据结构
//节点个数100010
#include <iostream>
using namespace std;
const int N = 100010;
//trie树一般存son[N][26]所有点的儿子,26是因为字符串仅包含小写英文字母和cnt[N]存以当前节点结尾的单词有多少个
int son[N][26], cnt[N], 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节点没有u节点则创建新的子节点
p = son[p][u];//存在则将找到节点下标给p,继续往循环下查找
}
cnt[p] ++;//添加以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;//从0开始找u如果没找到返回0
p = son[p][u];//存在则将找到节点下标给p,继续往循环下查找
}
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 printf("%d\n", query(str));
}
return 0;
}
6、并查集
每一个集合用树来表示,树根的编号就是整个集合的编号。每个节点存储他的父节点,p[x]表示x的父节点。
(1)朴素并查集:
int p[N]; //存储每个点的祖宗节点
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
(2)维护size的并查集:
int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
size[i] = 1;
}
// 合并a和b所在的两个集合:
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
(3)维护到祖宗节点距离的并查集:
int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x)
{
int u = find(p[x]);
d[x] += d[p[x]];
p[x] = u;
}
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
d[i] = 0;
}
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量
8、堆
// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1
// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
int h[N], ph[N], hp[N], size;
void heap_swap(int u,int v)
{
swap(h[u],h[v]);
swap(hp[u],hp[v]);
swap(ph[hp[u]],ph[hp[v]]);
}
void down(int u)
{
int t = u;
if (u * 2 <= size && h[u * 2] < h[t]) t = u*2;//判断左子树是否小于h[t],是就交换小的
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);//递归往下走
}
}
void up(int u)
{
if(u/2>0&&h[u]<h[u/2])
{
heap_swap(u,u/2);
up(u>>1);
}
}
//步骤:
//定义h[],size,h是heap,size是存在h[]数组上有多少个元素
//down:判断左右子树是否小于h[t],是就交换小的,然后交换,递归down
//从小到大输出比m小的数:h[1]最小,删除最小值h[1] = h[size1];size--;然后down(1);
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int p[N], size1;//p是heap,size是存在h[]数组上有多少个元素
void down(int u){
int t = u;
if (u *2 <= size1 && p[u * 2] <= p[t]) t = u * 2;//这里不是p[t * 2],而是p[u * 2],下面一样的,因为t是变的
if (u *2 + 1 <= size1 && p[u * 2 + 1] <= p[t]) t = u * 2 + 1;
if (t != u){
swap(p[u], p[t]);
down(t);//忘了,应该是t,因为t变了
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++) cin >>p[i];
size1 = n;
//建堆
for (int i = n / 2; i ; i --) down(i);//这里是i = n / 2,因为是树的形式,所以得除2,而且是down(i)
while (m --){
cout << p[1] << ' ';//h[1]最小
p[1] = p[size1];//删除最小值
size1 --;
down(1);
}
}
9、哈希表
具体参考:https://blog.csdn.net/qq_27262727/article/details/104390583
(1) 拉链法
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;
}
(2) 开放寻址法//
int h[N];
// 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
int find(int x)
{
int t = (x % N + N) % N;
while (h[t] != null && h[t] != x)
{
t ++ ;
if (t == N) t = 0;
}
return t;
}
(3)字符串哈希
核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64
// 初始化
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{
h[i] = h[i - 1] * P + str[i];
p[i] = p[i - 1] * P;
}
// 计算子串 str[l ~ r] 的哈希值,按照公式来
ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}