数据结构与算法 模板 数据结构部分

本文详细介绍了数据结构中的单链表、双链表、栈、队列(包括普通队列和循环队列)、单调栈和队列、KMP算法、Trie树、并查集(朴素、维护size和距离)、堆以及哈希表(拉链法和开放寻址法)和字符串哈希的基本概念和实现方法。
摘要由CSDN通过智能技术生成

Data Structure and Algorithm Template

Chapter2. 数据结构

单链表

// head存储链表头, e[]存储节点的值, ne[]存储节点的next指针, idx表示当前用到了哪个节点
int head, e[maxn], ne[maxn], idx;

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

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

// 在位置k插入一个数a
void insert(int k, int a) {
    e[idx] = a;
    ne[idx] = ne[k];
    ne[k] = idx++;
}

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

// 在位置k删除节点
void remove(int k) {
    ne[k] = ne[ne[k]];
}

双链表

// e[]表示节点的值, l[]表示节点左指针, r[]表示节点右指针
int e[maxn], l[maxn], r[maxn], idx;

// 初始化
void init() {
    r[0] = 1;
    l[1] = 0;
    idx = 2;
}

// 在节点a右边插入一个数x
void insert(int a, int x) {
    e[idx] = x;
    l[idx] = a, r[idx] = r[a];
    l[r[a]] = idx, r[a] = idx++;
}

// 删除节点a
void remove(int a) {
	l[r[a]] = l[a];
    r[l[a]] = r[a];
}

// tt表示栈顶
int stk[maxn], tt = 0;

// 向栈顶插入一个数
stk[++tt] = x;

// 从栈顶弹出一个数
tt--;

//栈顶的值
stk[tt];

//判断栈是否为空
tt > 0 ? false : true;

队列

普通队列
// hh表示队头, tt表示队尾
int q[maxn], hh = 0, tt = -1;

// 向队尾插入一个数
q[++tt] = x;

// 从队头删除一个数
hh--;

// 队头的值
q[hh];

// 判断队列是否为空
hh <= tt ? false : true;
循环队列
// hh表示队头, tt表示队尾的后一个位置
int q[maxn], hh = 0, tt = -1;

// 向队尾插入一个数
q[tt++] = x;
if (tt == maxn) tt = 0;

// 从队头弹出一个数
hh++;
if (hh == maxn) hh = 0;

// 队头的值
q[hh];

//判断队列是否为空
hh != tt ? false : true;

单调栈

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

单调队列

// 常见模型 : 找出滑动窗口中的最大最小值
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;
}

KMP

// s[]是长文本, p[]是模式串, n是s的长度, m是p的长度
// 求模式串的next数组
for (int i = 2, j = 0; i <= m; i++) {
    while (j && p[i] != p[j + 1]) {
    	j = ne[j];
    }
    if (p[i] == p[j + 1]) j++;
    ne[i] = j;
}

// 匹配
for (int i = 1, j = 0; i <= n; i++) {
    while (j && s[i] != s[j + 1]) {
        j = ne[j];
    }
    if (s[i] == p[j + 1]) {
        j++;
    }
    if (j == m) {
        j = ne[j];
        // 匹配成功之后的逻辑
    }
}

Trie树

int son[maxn][26], cnt[maxn], idx;
// 0号点既是根节点, 又是空节点
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量

// 插入一个字符串
void insert(const *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];
}

并查集

并查集是用于管理元素所属集合的数据结构

并查集支持两种操作

  1. 合并: 合并两个元素所属集合
  2. 查询: 查询某个元素所属集合, 这可以用来判断两个元素是否属于同一集合
// (1) 朴素并查集
	int p[maxn]; // 存储每个点的祖宗节点
	
	// 返回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[maxn], size[maxn];
	// 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] = 1;
        size[i] = 1;
    }

	// 合并a和b所在的两个集合
	size[find(b)] += size[find(a)];
	p[find(a)] = find(b);

// (3) 维护到祖宗节点距离的并查集
	int p[maxn], d[maxn];
	// p[]存储每个点的祖宗节点, d[]存储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)的偏移量

// h[maxn]存储在堆中的值, h[1]是堆顶, x的左儿子是2x, 右儿子是2x+1
// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
int h[maxn], ph[maxn], hp[maxn], 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 = 2 * u;
    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;
    }
}

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

一般哈希 / 散列表

// (1) 拉链法
	int h[maxn], e[maxn], ne[maxn], idx;
	
	// 向哈希表中插入一个数
	void insert(int x) {
        int k = (x % maxn + maxn) % maxn;
        e[idx] = x;
        ne[idx] = h[k];
        h[k] = idx++;
    }

	// 在哈希表中查找某个数是否存在
	bool find(int x) {
        int k = (x % maxn + maxn) % maxn;
        for (int i = h[k]; i != -1; i = ne[i]) {
            if (e[i] == x) {
                return true;
            }
        }
        return false;
    }

// (2) 开放寻址法
	int h[maxn];

	// 如果x在哈希表中, 返回x的下标; 如果x不在哈希表中, 返回x应该插入的位置
	int find(int x) {
        int t = (x % maxn + maxn) % maxn;
        while (h[t] != null && h[t] != x) {
            t++;
            if (t == maxn) {
                t = 0;
            }
        }
        return t;
    }

字符串哈希

// 核心思想: 将字符串看成p进制数, p的值为131或13331, 取这两个值哈希冲突概率低
// 取模的数用2^64, 这样直接用unsigned long long存储, 溢出的结果就是取模的结果

typedef unsigned long long ull;
ull h[maxn], p[maxn];

// 初始化
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];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值