【算法与数据结构】常用算法模板2

链表

单链表

一般做题中,采用数组模拟链表的方式,效率高

#include <iostream>
using namespace std;
const int N = 100010;

//head表示头结点
//value[i]存放i的值
//ne[i]存放i的next指针
//idx表示当前已经用到了多少节点
int head, value[N], ne[N], idx;

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

//将x插入到头结点
void appendHead(int x){
    value[++idx]  = x;
    ne[idx] = head;
    head = idx;
}
//在第k个节点后,插入x
void insert(int x, int k){
    value[++idx] = x;
    ne[idx] = ne[k];
    ne[k] = idx;
}
//删除第k个节点的下一个节点
void move(int k){
    ne[k] = ne[ne[k]];
}

int main(){
    int m;
    cin>>m;
    init();
    while(m--){
        char op;
        cin>>op;
        if(op == 'H'){
            int x;
            cin>>x;
            appendHead(x);
        }else if(op == 'I'){
            int k, x;
            cin>>k>>x;
            insert(x, k);
        }else if(op == 'D'){
            int k;
            cin>>k;
            if(!k) head = ne[head];
            else move(k);
        }
    }
    //尾结点的指针域为-1
    for(int i = head; i != -1; i = ne[i]) cout<<value[i]<<' ';
    cout << endl;
}

双链表

双链表存在两个指针域,一个指向它的前一个节点,一个指向它的后一个节点

#include <iostream>
const int N = 100010;
using namespace std;

int value[N], l[N], r[N], idx;

//令0为左边的头结点  1为右边的头结点
void init(){
    r[0] = 1;
    l[1] = 0;
    idx = 2;
}

//在第k个位置的右边插入x
void insert(int k, int x){
    value[idx] = x;
    l[idx] = k;
    r[idx] = r[k];
    
    
    l[r[k]] = idx;
    r[k] = idx++;
}

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

int main(){
    int m;
    cin>>m;
    init();
    while(m--){
        string op;
        cin>>op;
        int x, k;
        if(op == "L"){
            cin>>x;
            insert(0, x);
        }else if(op == "R"){
            cin>>x;
            insert(l[1], x);
        }else if(op == "D"){
            cin>>k;
            move(k + 1);
        }else if(op == "IL"){
            cin>>k>>x;
            insert(l[k + 1], x);
        }else if(op == "IR"){
            cin>>k>>x;
            insert(k + 1, x);
        }
    }
    for(int i = r[0]; i != 1; i = r[i]) cout<<value[i]<<" ";
    cout<<endl;
    
}

栈和队列

后进先出:栈顶插入和删除

#include <iostream>

const int N = 100010;
using namespace std;

int stk[N], top;

//入栈
//stk[++top] = x;

//出栈
//top--;

//判断空
//top > 0


//获取栈顶元素
//stk[top]
int main(){
    int m;
    cin>>m;
    top = 0;
    while(m--){
        string op;
        int x;
        cin>>op;
        if(op == "push"){
            cin>>x;
            stk[++top] = x;
        }else if(op == "pop"){
            top--;
        }else if(op == "empty"){
            cout<< (top ? "NO" : "YES")<<endl;
        }else if(op == "query"){
            if(top > 0) x = stk[top];
            cout<<x<<endl;
        }
    }
    return 0;
}

队列

先进先出:队头删除元素,队尾插入元素

#include <iostream>
using namespace std;
const int N = 100010;

int queue[N], tail, head;



//入队
//queue[++tail] = x;

//出队
//queue[head++] = x;


//对头
//x = queue[tail];

//判断空
//tail < head;

int main(){
    int m;
    cin>>m;
    head =  1;
    while(m--){
        string op;
        cin>>op;
        int x;
        if(op == "push"){
            cin>>x;
            queue[++tail] = x;
            
        }else if(op == "pop"){
            head++;
        }else if(op == "empty"){
            cout<<(tail < head ? "YES" : "NO") <<endl;
        }else if(op == "query"){
            if(tail >= head) x = queue[head];
            cout<<x<<endl;
        }
    }
}

单调栈

#include <iostream>
const int N = 100010;
using namespace std;


int stk[N], top;

int main(){
    int n;
    cin >> n;
    while(n--){
        int x;
        cin >> x;
        while(top && stk[top] >= x) top--;
        if(!top) cout << "-1" << " ";
        else cout << stk[top] << " ";
        // for(int i = 0; i <= top; i++)
        //     cout << stk[i] <<" ";
        // cout<<endl;
        stk[++ top] = x;
        
    }
    return 0;
}

单调队列

#include <iostream>
const int N = 1000020;
using namespace std;
int n, k;
int queue[N], a[N], tail = 0, head = 1;
int main(){
    cin>>n>>k;
    for(int i = 0; i< n; i++)
        cin >> a[i];
    for(int i = 0; i < n; i ++){
        if(head <= tail &&  i - k + 1 > queue[head]) head ++;
        while(head <= tail && a[queue[tail]] >= a[i]) tail --;
        queue[++ tail] = i;
        if(i >= k - 1) cout << a[queue[head]] << " ";
    }
    cout<<endl;
    
    head = 1, tail = 0;
    for(int i = 0; i < n; i ++){
        if(head <= tail &&  i - k + 1 > queue[head]) head ++;
        while(head <= tail && a[queue[tail]] <= a[i]) tail --;
        queue[++ tail] = i;
        if(i >= k - 1) cout<<a[queue[head]]<<" ";
    }
    cout<<endl;   
}

KMP

  1. 暴力算法如何做

    //S[N]长串, p[M]短串
    //暴力做法
    for(int i = 1; i <= n; i++)
    {
        bool flag = true;
    	for(int j = 1; j <= m; j++)
            if(s[i + j - 1] != p[j])
            {
                flag = false;
                break;
            }
    }    
    //next[i]
    //字符串的前缀集合与后缀集合的交集中最长元素的长度
    //模式串中相等的前缀与后缀的最大长度
    //以i为终点的后缀和从1开始的前缀相等,而且后缀的长度最长
    //p[1, j] = p[i - j + 1, i];
    
  2. 如何去优化

    //借助next数组
    #include <iostream>
    
    using namespace std;
    
    const int N = 10010, M = 100010;
    
    int n, m;
    char p[N], s[M];
    int ne[N];
    
    int main()
    {
        cin >> n >> p + 1 >> m >> s + 1;
        //求ne[j]的过程
        for(int i = 2, j = 0; i <= m; i++)
        {
            while(j && p[i] != p[j + 1]) j = ne[j];
        	if(s[i] == p[j + 1]) j++;
            ne[i] = j;
        }
        
        //KMP匹配过程
        for(int i = 1, j = 0; i <= m; i++)
        {
            //j没有退回起点
            //当前的节点与下一个节点不匹配
            while(j && s[i] != p[j + 1]) j = ne[j];
            if(s[i] == p[j + 1]) j++;
            if(j == n)
            {
                //匹配成功
                printf("%d ", i - n);
                j = ne[j];
            }
        }
    }
    
    
    
    

Trie

#include <iostream>

using namespace std;

const int N = 100010;

//x节点的所有儿子存到了son[x]中
//son[x][0] x的第0个儿子
//son[x][1] x的第1个儿子

//cnt[X]以X结尾的单词个数有多少个
int son[N][26]; //每个节点的所有儿子是什么
int cnt[N]; //以当前这个节点结尾的单词数目
int idx;  //当前用到了哪一个下标
//下标是0的点,既是根节点,又是空节点

char str[N];

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];
}
int main()
{
    int n;
    scanf("%d", &n);
    while(n -- )
    {
        char op[2];
        scanf("%s%s", op, str);
        if(op[0] == 'I') insert(str);
        else cout<<query(str)<<endl;
    }
    return 0;
}

并查集

两个操作(近乎O(1)的时间复杂度):

  1. 将两个集合合并

    1. 询问两个元素是否在一个集合当中

基本原理:每个集合用一颗树来表示,树根的编号就是整个集合的编号,每个节点存储它的父节点,p[x]表示x的父节点。

问题1. 如何判断树根 if(p[x] == x)

问题2. 如何求x的集合编号:while(p[x] != x) x = p[x];

路径压缩的思想优化: 遍历到某个节点都让该节点的父节点指向整个集合

问题3. 如何合并两个集合:p[x]是x的集合编号,p[y]是y的集合编号,p[x] = y;

#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int p[N]; //存放的i的父亲节点

int find(int x) //返回x的祖宗节点 + 路径压缩
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}
int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) p[i] = i;
    while(m--)
    {
        char op[2]; //读入字符串可以过滤掉字符串和回车
        int a, b;
        scanf("%s%d%d", op, &a, &b);
        if(op[0] == 'M') p[find(a)] = find(b);
        else
        {
            if(find(a) == find(b)) puts("Yes");
            else puts("No");
        }
    }
    return 0;
}
    

并查集除了以上之外,可以额外维护一个信息。

如何手写一个堆?

  1. 插入一个数

    heap[++size] = x; up(size);
    
  2. 求集合当中最小值

    heap[1]
    
  3. 删除最小值

    将最后一个元素覆盖到堆顶元素heap[1],再将堆顶down一遍。

    原因:一维数组,删除头结点非常困难,删除尾结点比较方便size--

    heap[1] = heap[size]; size--; down(1);
    
  4. 删除任意一个元素

    1. 方法1
    x = heap[k];
    heap[k] = heap[size]; size--;
    
    if(heap[k] > x) down(k);
    else up(k);
    

    2)方法2

    heap[k] = heap[size]; size--;
    down(k), up(k);//只会执行一个
    
  5. 修改任意一个元素

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

堆是一个完全二叉树。除了最后一层叶子节点外,上面节点都是非空的,叶子节点是按照从左到右的顺序排列的。

小根堆:每个点都是小于等于左右儿子的

大根堆:每个点都是大于等于左右儿子的

堆的存储:一维数组存储,1为根节点,x的左儿子2x, 右儿子2x+1

down(x): 往下调整,如果某个点的值变大了,将它往下沉,下沉的过程中与左右子节点中的最小值进行交换

int h[N], size;

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(x): 往上调整,如果某个点的值变小了,将它往上走,上移的过程中,与父节点比较即可,如果小于父节点,交换

int h[N], size;
void up(int u)
{
	while(u / 2 && h[u / 2] > h[u])
	{
		swap(h[u / 2], h[u]);
		u /= 2;
	}
}

1~5由down和up实现

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;

int n, m;
int h[N], size;

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

int main()
{
	scanf("%d", n);
	for(int i = 1; i <= n; i++) scanf("%d", &h[i]);
	size = n;
	
	for(int i = n/ 2; i; i--) down(i); //O(n)
    
    while(m--)
    {
        printf("%d ", h[1]);
        h[1] = h[size];
        size--;
        down(1);
    }
    return 0;
	
}

哈希表

拉链法

#include <iostream>
#include <cstring>
using namespace std;
const int N = 100003;

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

int main()
{
    int n;
    cin>>n;
    memset(h, -1, sizeof h);
    while(n--)
    {
        char op[2];
        int x;
        cin>>op>>x;
        if(*op == 'I') insert(x);
        else
        {
            if(find(x)) puts("Yes");
            else puts("No");
        }
    }
    return 0;
    
}

开放寻址法

#include <iostream>
#include <cstring>
using namespace std;
const int N = 200003, null = 0x3f3f3f;

int h[N];

bool find(int x)
{
    int k = (x % N + N) % N;
	while(h[k] != null && h[k] != x)
	{
		k++;
		if(k == N) k = 0;
	}
   return k;
}

int main()
{
    int n;
    cin>>n;
    memset(h, 0x3f, sizeof h);
    while(n--)
    {
        char op[2];
        int x;
        cin>>op>>x;
        int k = find(x);
        if(*op == 'I') 
      		h[k] = x;
        else
        {
            if(h[k] != null) puts("Yes");
            else puts("No");
        }
    }
    return 0;
}

字符串哈希

#include <iostream>
using namespace std;
tyedef unsigned long long ULL;
const int N = 100010, P = 131;
int n, m;
char str[N];
ULL h[N], p[N];

ULL get(int l, int r)
{
	return h[r] - h[l - 1] * p[r - 1 + 1]; 
}

int main()
{
	scanf("%d%d%s", &n, &m, str + 1);
	p[0] = 1;
	for(int i = 1; i <= n; i++)
	{
		p[i] = p[i - 1] * P;
		h[i] = h[i - 1] * P + str[i];
	}
	while(m--)
	{
		int l1, r1, l2, r2;
		scanf("%d%d%d%d", l1, r1, l2, r2);
		if(get(l1, r1) == get(l2, r2)) puts("Yes");
		else puts("No");
	}
	return 0;
}

STL常用的库函数

/*
vector, 变长数组,倍增的思想
	size() 返回元素个数
	empty() 返回是否为空
	clear() 清空
	倍增的思想:系统为某个程序分配空间时,所需时间与空间大小无关,与申请次数有关。
	front() back() 第一个数和最后一个数
	begin() end() 第0个数和最后一个数的下一个数
	 
string, 字符串, substr(), c_str()
queue, 队列,push(), front(), pop()
priority_queue, 优先队列,push(),top(),pop()
stack, 栈,push(), top(), pop()
deque, 双端队列
set, map, multiset, multimap 基于平衡二叉树(红黑树)来实现, 动态维护有序序列
unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
bitset,压位
*/

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>


猜你喜欢

转载自www.cnblogs.com/Trevo/p/12577309.html