【算法基础课】笔记01——基础数据结构

基础数据结构

链表与邻接表

数组模拟单链表(邻接表)

下标从零开始
第k个插入点下标为k-1

int head,v[N],ne[N],i;//头节点下标,该节点值,节点的next指针,当前点下标

void init(){
	head=-1;//指空
	i=0;
}

void insert(int a){
	v[i]=a,ne[i]=head,head=idx++;
}//头插

void insert_k(int k,int x){
	v[i]=x,ne[i]=ne[k],ne[k]=i++;
}

void remove(){
	head =ne[head];
}//删头结点的时候记得特判一下

void remove_k(int k){
	ne[k]=ne[ne[k]];
}

数组模拟双链表(优化用)

int v[N],l[N],r[N],i;

void init(){
	l[0]=0,r[0]=1;
	i=2;
}

//右插
void insert(int k,int x){
	v[i]=x;
	l[i]=k,r[i]=r[k];
	l[r[k]]=i,r[k]=i++;
}//左插直接把k变为l[k]

void remove(int k){
	l[r[k]]=l[k];
	r[l[k]]=r[k];
}//左变右,右变左

栈与队列

数组模拟(快

单调xx:过滤没有用的数据

单调栈

找临近比它大/小的数

stack<int>q;

for (int i = 1; i <= n; i++) {
		//cin >> a[i];
		while (!q.empty() && q.top() <= a[i])
			q.pop();
	
	ans += q.size();
	q.push(a[i]);
}//满足某种性质才入栈

//POJ - 3250 http://poj.org/problem?id=3250

单调队列

滑动窗口最值

deque<int>q;

for(int i=1;i<=n;i++){
	while(!q.empty()&&i-k>=q.front())
		q.pop_front();
	while(!q.empty()&&a[q.back()]>=a[i])//min
		q.pop_back();
	
	q.push_back(i);
	if(i>=k)
		cout<<a[q.front()]<<' ';
}//max就改个符号,中间记得重置deque

应用:多重背包优化

kmp

资料:https://oi-wiki.org/string/kmp/
Next[i], 匹配

  1. 计算前缀函数
    含义:最长的相等的真前缀与真后缀的长度
vector<int> prefix_function(string s) {
  int n = (int)s.length();//总长度
  vector<int> pi(n);
  for (int i = 1; i < n; i++) {
    int j = pi[i - 1];
    while (j > 0 && s[i] != s[j]) j = pi[j - 1];//不满足就接着往回找
    if (s[i] == s[j]) j++;//后推
    pi[i] = j;//长度
  }
  return pi;
}

  1. KMP
// s是长文本,p是模式串
//求模式串的Next数组:
void Next_pre(string p,vector<int>&Next){
    for (int i = 1, j = 0; i < (int)p.size(); i ++ ){
        //i是当前遍历到的后缀,j是前缀
    	while (j && p[i] != p[j]) 
            j = Next[j-1];//前后缀不相等就一直跳转
    	if (p[i] == p[j]) 
            j ++ ;//前后缀相等就往后
    	Next[i] = j;
	}
}

// 匹配
int KMP_match(string s,string p,int begin){
    vector<int>Next(p.length());
    Next_pre(p,Next);
    for(int i=begin,j=0;i<(int)s.length();i++){
        while (j && s[i] != p[j])
            j = Next[j-1];//匹配不成功,一直回跳
   		if (s[i] == p[j])
        	j ++ ;//匹配成功
    	if (j == p.length())
            return i-p.length()+1;//完全匹配成功
    }
}

Trie树

高效存储、查找字符串集合
从root,出发,按树的方式存;结尾打标记

int son[N][26],cnt[N],i;//下标0的点,是根节点,也是空节点;存节点的子节点;cnt[]存储以每个节点结尾的单词数量

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]==++i;
		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];
}

并查集

拓展应用:https://oi-wiki.org/topic/dsu-app/

1. 朴素并查集

初始化
void makeSet(int size) {
  for (int i = 0; i < size; i++) fa[i] = i; 
}
路径压缩(查找)
int find(int x) {
  if (x != fa[x])  
    fa[x] = find(fa[x]); 
  return fa[x];
}
合并
void Union(int x, int y) {
  fa[find(x)] = find(y);  
}
启发式合并

https://oi-wiki.org/ds/dsu-complexity/

2. 维护size

加一个数组size[N](仅祖宗节点有意义)

初始化
void makeSet(int size) {
	for(int i=1;i<=n;i++){
		fa[i]=i;
		size[i]=1;
	}
}
查找

不变

合并
void Union(int x, int y) {
	if(find(x)==find(y))
		return;
	size[find(y)]+=size[find(x)];
	fa[find(x)] = find(y);  
}

3. 维护到祖宗节点距离

初始化
void makeSet(int size) {
	for(int i=1;i<=n;i++){
		fa[i]=i;
		size[i]=0;//自己到自己
	}
}
查找
int find(int x){
	if(fa[x]!=x){
		int u=find(fa[x]);
		d[x]+=d[fa[x]];
		fa[x]=u;
	}
	return fa[x];
}
合并
void Union(int x, int y) {
	fa[find(x)] = find(y); 
	d[find(x)]=distance;
}

小根堆(根节点最小)

#include<queue>
//默认大根堆
    top() 访问堆顶元素(此时优先队列不能为空)
    empty() 询问容器是否为空
    size() 查询容器中的元素数量

    push(x) 插入元素,并对底层容器排序
    pop() 删除堆顶元素(此时优先队列不能为空)
//小根堆
priority_queue<int, deque<int>, greater<int> > q3;

堆拓展:https://oi-wiki.org/ds/heap/

Hash表

https://oi-wiki.org/ds/hash/
x mod 质数(超出数据范围的最小质数)

存储结构(解决冲突)

  1. 开放寻址法
int h[N];//开一个两倍的数组

int find(int x){
	int t=(x%N+N)%N;
	while(h[t]!=null&&h[t]!=x){//null表示一个不可能的数
		t++;
		if(t==N)
			t=0;//循环
	}
	return t;
}
  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;
    }

封装后

struct hash_map {  // 哈希表模板
  struct data {
    long long u;
    int v, nex;
  };                // 前向星结构
  data e[SZ << 1];  // SZ 是 const int 表示大小
  int h[SZ], cnt;
  int hash(long long u) { return u % SZ; }
  int& operator[](long long u) {
    int hu = hash(u);  // 获取头指针
    for (int i = h[hu]; i; i = e[i].nex)
      if (e[i].u == u) return e[i].v;
    return e[++cnt] = (data){u, -1, h[hu]}, h[hu] = cnt, e[cnt].v;
  }
  hash_map() {
    cnt = 0;
    memset(h, 0, sizeof(h));
  }
};

字符串哈希

快速判断两个字符串是否相等(替KMP
妙用:https://www.acwing.com/problem/content/843/
不能映射成0

核心思想:将字符串看成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];
}

数组模拟STL

队列

  1. 普通队列
// hh 表示队头,tt表示队尾
int q[N], hh = 0, tt = -1;
// 向队尾插入一个数
q[ ++ tt] = x;
// 从队头弹出一个数
hh ++ ;
// 队头的值
q[hh];
// 判断队列是否为空
if (hh <= tt)
{

}
  1. 循环队列
// 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];
// 判断队列是否为空
if (hh != tt)
{

}
  1. 单调队列
常见模型:找出滑动窗口中的最大值/最小值
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
    while (hh <= tt && check_out(q[hh])) hh ++ ;  // 判断队头是否滑出窗口
    while (hh <= tt && check(q[tt], i)) tt -- ;
    q[ ++ tt] = i;
}

// tt表示栈顶
int stk[N], tt = 0;
// 向栈顶插入一个数
stk[ ++ tt] = x;
// 从栈顶弹出一个数
tt -- ;
// 栈顶的值
stk[tt];
// 判断栈是否为空
if (tt > 0)
{

}
  1. 单调栈
常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;
for (int i = 1; i <= n; i ++ )
{
    while (tt && check(stk[tt], i)) tt -- ;
    stk[ ++ tt] = i;
}

priority_queue

// 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 a, int b)
{
    swap(ph[hp[a]],ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}

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)//根节点不是最小值
    {
        heap_swap(u, t);
        down(t);
    }
}

void up(int u)//大的上浮
{
    while (u / 2 && h[u] < h[u / 2])
    {
        heap_swap(u, u / 2);
        u >>= 1;//UP
    }
}

// O(n)建堆
for (int i = n / 2; i; i -- ) down(i);

STL补充

set, map, multiset, multimap,

基于平衡二叉树(红黑树),动态维护有序序列

++, -- 返回前驱和后继,时间复杂度 O(logn)
set/multiset
    insert()  插入一个数
    find()  查找一个数
    count()  返回某一个数的个数
    erase()
    (1) 输入是一个数x,删除所有x   O(k + logn)         (2) 输入一个迭代器,删除这个迭代器
    lower_bound()/upper_bound()
    lower_bound(x)  返回大于等于x的最小的数的迭代器
    upper_bound(x)  返回大于x的最小的数的迭代器
map/multimap
    insert()  插入的数是一个pair
    erase()  输入的参数是pair或者迭代器
    find()
    [] 注意multimap不支持此操作 时间复杂度是 O(logn)
    lower_bound()/upper_bound()

unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
和上面类似,增删改查的时间复杂度是 O(1)
不支持 lower_bound()/upper_bound(),迭代器的++,--

bitset, 压位

https://oi-wiki.org/lang/csl/bitset/

bitset<10000> s;
~, &, |, ^
>>, <<
==, !=
[]
count()  返回有多少个1
any()  判断是否至少有一个1
none()  判断是否全为0
set()  把所有位置成1
set(k, v)  将第k位变成v
reset()  把所有位变成0
flip()  等价于~
flip(k) 把第k位取反
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值