第二章 数据结构

一、链表

1.单链表

AcWing 826. 单链表

	//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){
		e[idx]=x;
		ne[idx]=head;
		head=idx++;
	} 

	//将x插到下标为k的节点后面
	void add(int k,int x){
		e[idx]=x;
		ne[idx]=ne[k];
		ne[k]=idx++;
	} 

	//将下标为k的点后面的点删掉
	void remove(int k){
		ne[k]=ne[ne[k]];
	} 

2.双链表

AcWing 827. 双链表

//l[i],r[i] 表示i节点左,右指针 
int e[N],l[N],r[N],idx;

//初始化 
	void init(){
		r[0]=1;
		l[1]=0;
		idx=2;
	}
	
	//在下标为k的点右面插入一个点 
	void add(int k,int x){
		e[idx]=x;
		r[idx]=r[k];
		l[idx]=k;
		l[r[k]]=idx;
		r[k]=idx++;
	}

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

二、栈

AcWing 828. 模拟栈

	//用stk数组模拟栈 tt为栈顶指针 
	int stk[N],tt;

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

	//弹出 
	tt--;

	//判断是否为空 
	if(tt>0) not empty
	else empty

	//获取栈顶元素 
	int top=stk[tt];

三、队列

1.普通队列

AcWing 829. 模拟队列

	//hh表示队头,tt表示队尾
	int q[N],hh,tt;

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

	//弹出 
	hh++; 

	//判断是否为空 
	if(tt>=hh) not empty
	else empty 

	//取出队头元素
	int head=q[hh];

	//取出队尾元素
	int tail=q[tt]; 

2.循环队列

hh 表示队头,tt表示队尾的后一个位置

	int q[N], hh = 0, tt = 0;

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

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

	// 队头的值
	int head = q[hh];

	// 判断队列是否为空
	if (hh != tt) not empty
	else empty

三、KMP

AcWing 831. KMP字符串

主要思想:寻找模式串中以下标 i 为结尾的最长相等子前后缀

	//s[]是长文本,p[]是模式串,ne[i]存储以p[i]结尾的模式串中最长相等子前后缀的长度
	//n,m为s[],p[]的长度
	char s[N],p[M],ne[M];
	int n,m;

	//求模式串的ne数组:
	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] != p[j + 1]) j = ne[j];
	    if (s[i] == p[j + 1]) j ++ ;
	    if (j == m){
	        j = ne[j];
	        ...//根据题目发挥
	    }
	}

四、Trie树

AcWing 835. Trie字符串统计

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

	// 插入一个字符串
	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];
	}

五、并查集

AcWing 836. 合并集合
AcWing 837. 连通块中点的数量
AcWing 240. 食物链

基本原理:
把每个集合用一棵树表示树的根节点就是集合的编号,且每个节点都存储它的父节点,用 p[x] 表示 x 节点的父节点(定义根节点的父节点是它本身)。

Q&A:
Q1:如何判断树根?
A1:if(p[x]==x)
Q2:如何求x的集合编号?
A2:while(p[x]!=x) x=p[x];
Q3:如何合并两个集合
A3:将一棵树的根节点连在另一棵树的任意一个节点上
p[x]=y;//x,y是两棵树的根节点

优化:路径压缩:
当遍历一棵树时,将其所有节点的父节点都赋值为这棵树的根节点,即可将时间复杂度近似降为 O ( 1 ) O(1) O(1)

(1)朴素并查集:

	//存储每个点的根节点
	int p[N]; 

    //返回x的根节点+路径压缩
    int find(int x){
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

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

	//判断是否在同一集合中
	if(find(a) == find(b)) 

(2)维护集合size的并查集:

    //p[]存储每个点的根节点, size[]只有根节点的有意义,表示根节点所在集合中的点的数量
	int p[N], size[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;
        size[i] = 1;
    }

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

(3)维护到根节点距离的并查集:

    //p[]存储每个点的根节点, d[x]存储x到p[x]的距离
	int p[N], d[N];
	
    //返回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)的偏移量

六、堆

AcWing 838. 堆排序
AcWing 839. 模拟堆

利用数组存储一个完全二叉树,其中下标为x的节点左儿子为 2 x 2x 2x,右儿子为 2 x + 1 2x+1 2x+1

小根堆:每个节点存储的数小于等于其两个子节点(根节点存储最小的数

	// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1
	// ph[k]存储第k个插入的点在堆中的位置
	// hp[k]存储堆中下标是k的点是第几个插入的
	int h[N], ph[N], hp[N], size;

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

	// 交换两个点,及其映射关系
	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 /= 2;
	    }
	}

	//插入一个数
	h[++size]=x;
	up(x);

	//求数组中最小值 
	h[1];

	//删除最小值
	h[1]=h[size--];
	down(1);

	//删除下标为k的数
	h[k]=h[size--];
	down(k),up(k);

	//修改下标为k的数
	h[k]=x;
	down(k),up(k);

七、哈希

将一组范围很大的数据映射到另一个小范围里
一般操作:对一个质数取模

1.一般哈希

AcWing 840. 模拟散列表

(1)拉链法

基本思想:插入时存在冲突用链表存储

    const int N = 100003;
    int h[N], e[N], ne[N], idx;

    //向哈希表中插入一个数
    void insert(int x){
        //负数取模还是负数,+N后%N变为正数
        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)开放寻址法

基本思想:非空就找下一位

当定义一个无穷大( n > 1 0 9 n>10^9 n>109)时,经验是定义为0x3f3f3f3f,可以用memset(h,0x3f,sizeof(h))赋初始值

	//N开到元素个数的2~3倍,null开到数据范围之外
	const int N=200003,null=0x3f3f3f3f;
    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;
    }
	int main(){
		memset(h,0x3f,sizeof(h));
	}

2.字符串哈希

AcWing 841. 字符串哈希
基本思想:将字符串看成P进制数,P的经验值是131或13331,冲突概率低
小技巧:取模的数用 2 64 2^{64} 264,这样直接用unsigned long long存储,溢出后的结果就是取模后的结果,(不过这里假定RP MAX无冲突)

	const int N=100003;
	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];
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值