头图

课程内容链接
KMP部分内容链接
并查集图片链接

数据结构

链表与邻链表

struct Node
{
    int val;
    Node *next
}

new Node(); //非常慢

数组模拟单链表 静态链表

int head; //头节点
int e[N]; //值
int ne[N]; //next指针
int idx; //数组用到第几个点

// 初始化
void init()
{
    head = -1;
    idx = 0;
}

// 在链表头插入一个数a
void insert_head(int a)
{
    e[idx] = a;
    ne[idx] = head;
    head = idx;
    idx++ ;
}

// 将头结点删除,需要保证头结点存在
void remove()
{
    head = ne[head];
}

// 将a插入到第k个点后面
void add(int k, int a)
{
    e[idx] = a;
    ne[idx] = ne[k];
    ne[k] = idx;
    idx ++;
}
//将下标是k的后边的点删掉
void remove(int k)
{
    ne[k] = ne[ne[k]];
}

数组模拟双链表(优化某些问题)

每一个点有两个指针

int e[N];
int l[N];
int r[N];

//初始化
void init()
{
    // 0表示左端点(head), 1表示右端点(tail)
    r[0] = 1;
    l[1] = 0;
    idx = 2;
}

//在下标是k的点的右边,插入x
void add(int k, int x)
{
    e[idx] = x;
    r[idx] = r[k]; //不能用 k + 1
    l[idx] = k;
    
    l[r[k]] = idx; //不能用 k + 1
    r[k] = idx;
    
}

//在k的左边插入x
add(l[k], x);

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

邻接表 存储树和图

image
image

栈与队列(吐出来的是栈,拉出来的是队列)

数组模拟栈(先进后出)

int stk[N], tt;

//插入
stk[++ tt] = x;

//弹出
tt --;

//判断栈是否为空
if(tt > 0) 不空

//栈顶
skt[tt]; 

数组模拟队列(先进先出)

//队尾插入,队头弹出
int q[N];
int hh;  //头
int tt = -1;  //尾

//插入
q[++tt] = x

//弹出
hh++;

//判断队列是否为空
if(hh <= tt) 不空

//取出队头元素
q[hh]

单调栈和单调队列(优化暴力,去掉中间没有用的元素,剩下元素有单调性可以做优化)

单调栈

eg:(左\右边有没有大\小的数) 变成严格单调上升的栈

单调队列

eg:求滑动窗口的最大值\最小值

kmp算法(克努特-莫里斯-普拉特操作)

  • kmp整体思路

问题:在文本串中找子串

例子:在aabaabaaf中找aabaaf

遍历子串得到next数组(最大相等前后缀长度)

文本串中寻找子串存在的位置

根据next数组更新位置

next数组情况分析:

010120最大相等前后缀长度j = next[j - 1]
-101012右移一位j = next[j]
-10-101-1每个位置-1j = next[j] + 1

void getNext(vector<int>& next, string x){
        //i:后缀尾位置,j:前缀尾位置(也是长度)
        int j = 0;
        //初始化next数组
        next.push_back(0);
        
        for(int i = 1; i < x.length(); i++){
            while(j > 0 && x[i] != x[j] ) j = next[j - 1]; //处理不同情况
            if(x[i] == x[j]) j++; //处理相同情况
            next.push_back(j); //更新next数组
        }
    }
    int strStr(string text, string son) {
        //i:文本串位置,j:子串位置
        vector<int> next;
        getNext(next, son);
        int j = 0; 
        for(int i = 0; i < text.size(); i++){
            while(j > 0 && text[i] != sonson[j]) j = next[j - 1]; //处理不同情况
            if(text[i] ==son[j]) j++; //处理相同情况
            ... //根据题目要求操作
        }
        return -1;
    }

Trie树(字典树)

  • 快速存储和查找字符串集合的数据结构
  • 按照每个字符串顺序进行建树,标记字符串结尾的地方/标记出现次数

image

字符串集合是{in, inn, int, tea, ten, to}

//每个点存的是数字


int son[N][26], cnt[N], idx;
// 0号点既是根节点,又是空节点
// son[][]存储树中每个节点的子节点
// 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] = ++ 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];
}

并查集(近乎O(1))

  1. 功能:
  • 将两个集合合并
  • 询问两个元素是否在一个集合当中
  1. 基本原理
  • 每个集合用一颗树来表示,树根编号就是整个集合的编号,每个节点存储他的父节点(p[x])
  1. 操作
  • 问题1:如何判断树根:if(p[x] == x)
  • 问题2:如何求x的集合编号:while(p[x] != x) x = p[x]
  • 问题3:如何合并两个集合,p[x]是x的集合编号,p[y]是y的集合编号,p[x] = y
  1. 优化
  • 查一次之后直接指向祖先(路径压缩)

image
image

路径压缩:

image
image

  1. 统计一个集合中数的数量(size[i])
  • 合并集合:if(b的根节点 != a的根节点) size[b的根节点] += size[a的根节点]
(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)统计每个集合有多少个元素的并查集:

    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);

堆(是一个完全二叉树:除最后一层,都是满的,最后一层从左向右排列)

eg: 小根堆,每一个点小于左右儿子

  • 用一维数组存储, 左儿子2x, 右儿子2x+1
  • 下标从1开始
操作代码
插入一个数heap[++size] = x up(size)
求集合中的最小值heap[1]
删除最小值heap[1] = heap[size]; size--; down(1)
删除任意一个元素heap[k] = heap[size]; size--; down[k]; up[k]
修改任意一个元素heap[k] = x; down[k]; up[k]
int h[N], size;

//down
//把一个值变大时候down
//跟左右节点的最小值交换
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
//把一个值变小时候up
//跟父节点交换,因为小根堆
void up(int u)
{
    while (u / 2 && h[u] < h[u / 2])
    {
        swap(h[u], h[u / 2]);
        u >>= 1;
    }
}

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

哈希表

存储结构
开放寻址法
拉链法
字符串哈希

哈希函数 10^9 - 10^9 映射到0 - 10^5

x mod 10^5

会发生冲突,处理冲突

  1. 拉链法:

添加,查找

  1. 开放寻址法

待学习...

STL

STL单独总结部分


Prince_H_23
4 声望0 粉丝