acwing学习笔记-数据结构

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

2024年7月8号到7月13号学习内容


一、数据结构


单链表的操作

//初始化
void init()
{
head = -1;
idx = 0;
}
//向表头插入元素,e[]-元素的值,ne[]元素的指针域,idx表示用到底几个下标
add_to_head(int x)
{
e[idx] = x;//将x赋值给第idx个节点
ne[idx] = head;//将idx的指针域指向head头指针指向的位置
head = idx;//将头指针指向idx节点
idx++;
}

//指定位置插入元素
add(int x)
{
e[idx] = x;//将x赋值
ne[idx] = ne[k]//将第idx的指针域指向第k个元素指向的位置,即第k+1的元素的位置
ne[k] = idx;//将第k个元素的指针域指向第idx个元素
idx++;
}

//删除元素,将第k个元素的指针域的指向改变即可
remove(int k)
{
ne[k] = ne[ne[k]];
}

双链表的操作

//r[]:元素的后继指针,l[]:元素的前驱指针
//初始化
void init()
{
r[0] = 1;//0的后继指针指向下标为1的元素
l[1] = 0;//1的前驱指针指向下标为0的元素,形成双向链表
idx = 2;//已经用了两个节点
}

//在下标为k的右边插入x
void add(int x,int k)
{
e[idx] = x;
r[idx] = r[k];//将idx的后继指针指向第k个元素的后继指针指向的位置
l[idx] = k;//将idx的前驱指针指向第k个元素的前驱指针指向的位置
l[r[k]] = idx;//将第k个元素的后继指针指向的元素的前驱指针指向idx;
r[k] = idx;//将第k个元素的后继指针指向idx;
idx++;
}

//删除第k个元素
void remove(int k)
{
l[r[k]] = l[k];
r[l[k]] = r[k];
}

数组模拟栈

int stk[N],tt = 0;//stk[]代表栈,tt表示栈顶指针,当栈内没有元素是tt=0,并且从第一个位置开始存
void push(int x)
{
stk[++tt] = x;//将栈顶指针向上移动,并赋值
}
void pop()
{
tt--;//并不是将栈顶元素删除而是将栈顶指针移动,当栈内新增元素时tt会自增从而将先前的元素覆盖
}
//取出栈顶元素stk[tt];
int empty()
{
return tt;
}

数组模拟队列

int q[N],hh= 0,tt = -1;//q[]代表队列,hh表示队头,tt表示队尾,当队列内没有元素是hh==tt,并且从第一个位置开始存
void push(int x)
{
q[++tt] = x;//将栈顶指针向上移动,并赋值
}
void pop()
{
hh++;//将队首指针向后移动即可
}
//取出栈顶元素q[tt];
int empty()
{
return hh==tt;//当队首指针等于队尾指针时,队列为空
}

单调栈

计算每一个数左边距离最近且比他小的数的位置

for(int i = 0;i<n;i++)
{
int x;
cin>>x;
while(tt&&stk[tt]>=x)
    tt--;//出栈
if(tt)
    cout<<stk[tt];
else
    cout<<"-1";

KMP字符串匹配

//朴素做法:暴力
for(int i = 1;i<=n;i++)
{
    int flag = 1;
    for(int j = 1;j<=m;j++)
    {
        if(s[i] != p[j];
        {    
            flag = 0;
            break;
        }
    }
}

//求ne[]的过程,针对的是模式串,起始位置为1
//求的是第i个元素的ne[],即当模式串中的第i个元素与主串的某个位置不匹配时,应当回到模式串中的哪个位置重新开始比较
for(int i = 2,j = 0;i<=n;i++)
{
while(j&&p[i]!=p[j+1])//当发现不匹配时,就回到之前的位置
    j = ne[j];
if(p[i] == p[j+1])
    j++;
ne[i] = j;//当不匹配时ne[i] = ne[j]
}
//匹配过程
for(int i = 1,j = 0;i<=n;i++)
{
while(j&&s[i]!=p[j+1])
    j = ne[j];
if(s[i]==p[j+1])
    j++;
if(j==m)
{
cout<<i - m + 1);//打印主串中与模式串匹配的子串的第一个元素的下标
}

//ne数组是记录当s[i] != p[j+1]时,j应该回退多少

Tire树

快速的存储和查找字符串
在这里插入图片描述

#include<iostream>
using namespace std;
const int N = 100010;
int idx; 
int son[N][26];
int cnt[N];

void insert(string a)
{
  int p = 0;
  for (int i = 0; a.size(); i++)
  {
    int u = a[i] - 'a';
    if (!son[p][u])
      son[p][u] = ++idx;
    p = son[p][u];
  }
  cnt[p]++;
}

int query(string a)
{
  int p = 0;
  for (int i = 0; i < a.size(); i++)
  {
    int u = a[i] - 'a';
    if (!son[p][u])
      return 0;
    p = son[p][u];
  }
  return cnt[p];
}
int main()
{
  int n;
  cin >> n;
  while (n--)
  {
    char op;
    cin >> op;
    string x;
    if (op == 'I')
    {
      cin >> x;
      insert(x);
    }
    else
    {
      cin >> x;
      cout << query(x) << endl;

    }
  }
  return 0;
}

并查集(优化:路径压缩)

1、将两个集合合并

2、询问两个元素是否在一个集合内

原理:

每个集合用一棵树来表示,树根的编号就是整个集合的编号,每个节点存储他的父节点,p[x]表示x的父节点

①如何判断树根
if(p[x]==x)
②如何求x的集合编号
int find(int x)					//查找x的祖宗节点
{
	if(pre[x] == x) return x;		//递归出口:x的上级为 x本身,即 x为根结点        
    return pre[x] = find(pre[x]);				
}

并查集

堆(小根堆)

主要是堆如何手写(用一维数组存储堆)
若当前节点为x则左儿子的下标为2x,右儿子的下标为2x+1(满足完全二叉树)
主要的操作有:
①向集合中插入一个数

heap[++size] = x;//先将节点的值加入数组中
up(size);//再用up函数将元素向上移动,因为是存储在此时数组下的最后一个位置,所以向前寻找应该存放的位置

②求集合中的最小值

heap[1];//小根堆最小值在堆顶

③删除最小值

heap[1] = heap[size]//将堆顶用最后一个元素覆盖
size--;//数组长度减一
down(1);//将覆盖后的堆顶元素用down函数向下寻找合适的位置

④删除任意一个元素

hepa[k] = heap[size];//用最后一个元素将第k个元素覆盖
size--;//数组长度减一
down(k),up(k);//用down函数向下寻找第k个元素的位置,若向下找到合适的位置,up函数不会执行

⑤修改任意一个元素

heap[k] = x;
down(k),up(k);

小根堆:每个节点均小于本身的子节点

基本操作

void down(int u)
{
int t = u;
if(u*2<=size&&heap[u*2]<heap[t]) t = u*2;//判断是否有左儿子和是否比左儿子大,若大则更改下标,下同
if(u*2_1<=size&&heap[u*2+1]<h[t])t = u*2+1;
if(u!=t)//说明下标变了·
heap_swqp(h[u],h[t]);//交换两个元素;
down(t);//将第t个元素继续下移
}

void up(int k)
while(u/2&&h[u/2]>h[u])//判断是否存在父节点并且父节点是否比自己大
{
heap_swap(heap[u/2],heap[u]);//大的话就交换
u/=2;//下标向上移动
}
//ph[k]:第k个插入的数的下标
//hp[k]:堆中的某个点是第几个插入的点
void heap_swap(int a,int b)
{
swap(ph[hp[a]],ph[hp[b]]);//下标交换
swap(hp[a],hp[b]);//插入的次序交换
swap(heap[a],heap[b]);//值交换
}

哈希表

十的负九次方到十的九次方中的数映射到0到十的五次方(利用取模,用质数进行取模)

拉链法

void insert(int x)
{
int k = (x%N+N)%N;//让负数取模变为正数
e[idx] = x;
ne[idx] = h[k];//将第idx个元素的指针指向第k个元素的指针指向的位置
h[k] = idx++;//将第k个元素的指针指向idx;
//h[k]:表示的是第k个链表的下标
}
bool find(int x)
{
int k = (x%N+N)%N;//让负数取模变为正数
for(int i = h[k];i!=-1;i = ne[i])//找到元素x取模的值在h[]数组中的位置
{
    if(e[i]==x)//若对应链表下存在x
        return true;
}
return false;
}

开放寻址法

元素若存在返回下标,若不存在,返回应当插入的位置的下标

int null = 0x3f3f3f3f;
memset(h,0x3f,sizeof(h));//memset按字节初始化
int find(int x)
{
int k = (x%N+N)%N;
while(h[k]!= null&&h[k]!=x)//查询时:判断有人并且不是x,就往后找,如果是x就返回下标
//插入时:判断该位置上是否存储元素,若没有元素即可插入元素
{
    k++;
    if(k==N)
        k==0;//已经找到最后一个了,就回到开头重新找
}
//找不到的话一定会因为h[[k]==null而停止,因为数组开的足够大
return k;

字符串哈希

核心:将字符串看成P进制的数,再转换成十进制,结果对Q取模,p取131或13331,去这两个值的冲突概率较低,mod用2^64,这样直接用unsigned long long 存储,溢出的结果就是取模的结果

typedef unsigned long long ULL;
UUL h[N],p[K]//h[k]存储字符串前k个字母的哈希值,p[k]存储p^kmod2^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[1~r]的哈希值
UUL get(int l,int r)
{
return h[r]-h[l-1]*p[r-l+1];
}

C++ STL 简介

vector, 变长数组,倍增的思想
    size()  返回元素个数
    empty()  返回是否为空
    clear()  清空
    front()/back()
    push_back()/pop_back()
    begin()/end()
    支持比较运算,按字典序
    vectot<int> a(10,3)  定义一个长度为10的数组并初始化为3
pair<int, int>
    first, 第一个元素
    second, 第二个元素
    支持比较运算,以first为第一关键字,以second为第二关键字(字典序)
    p = make_pair{10,"hrx"}或p = {10,"hrx"}
    某个元素的某个属性需要排序可以放在first不需要排序的放在second

string,字符串
    size()/length()  返回字符串长度
    empty()
    clear()
    substr(起始下标,(子串长度))  返回子串
    c_str()  返回字符串所在字符数组的起始地址

queue, 队列
    size()
    empty()
    push()  向队尾插入一个元素
    front()  返回队头元素
    back()  返回队尾元素
    pop()  弹出队头元素

priority_queue, 优先队列,默认是大根堆
    size()
    empty()
    push()  插入一个元素
    top()  返回堆顶元素
    pop()  弹出堆顶元素
    定义成小根堆的方式:priority_queue<int, vector<int>, greater<int>> q;
stack,size()
    empty()
    push()  向栈顶插入一个元素
    top()  返回栈顶元素
    pop()  弹出栈顶元素

deque, 双端队列
    size()
    empty()
    clear()
    front()/back()
    push_back()/pop_back()
    push_front()/pop_front()
    begin()/end()
set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列
    size()
    empty()
    clear()
    begin()/end()
    ++, -- 返回前驱和后继,时间复杂度 O(logn)
set/multiset  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()
    lower_bound()/upper_bound()

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

bitset, 圧位
    bitset<10000> s;
    count()  返回有多少个1
    any()  判断是否至少有一个1
    none()  判断是否全为0
    set()  把所有位置成1
    set(k, v)  将第k位变成v
    reset()  把所有位变成0
    flip()  等价于~
    flip(k) 把第k位取反

总结

2024年7月17日完毕

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值