链表与邻接表,栈与队列,kmp

1. 链表与邻接表

用数组模拟

单链表

用来写邻接表,主要用于存储图和树

#include <iostream>

using namespace std;

const int N = 100010;

// 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;
	idx ++;
}

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

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

int main()
{
	
}

例题

单链表

#include <iostream>

using namespace std;

const int N = 100010;

// 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;
	idx ++;
}

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

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

int main()
{
	int m;
	cin >> m;
	
	init();
	
	while (m--)
	{
	    char op;
	    int k, x;
	    cin >> op;
	    if (op == 'H')
	    {
	        cin >> x;
	        add_to_head(x);
	    }
	    else if (op == 'D')
	    {
	        cin >> k;
	        if (!k) head = ne[head];
	        remove(k - 1);
	    }
	    else
	    {
	        cin >> k >> x;
	        add(k - 1, x);
	    }
	}
	
	for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';
	cout << endl;
	
	return 0;
}

双链表

优化某些问题

例题

双链表

#include <iostream>

using namespace std;

const int N = 100010;

int m;
int e[N], l[N], r[N], idx;

// 初始化
void init()
{
    // 0表示左端点(head),1表示右端点(tail)
    r[0] = 1, l[1] = 0;
    idx = 2; //0和1已经用了2个结点,其他的结点从2开始
}

// 在下标是k的结点的右边插入x
void add(int k, int x)
{
    e[idx] = x;
    r[idx] = r[k];
    l[idx] = k;
    l[r[k]] = idx; //顺序不能反
    r[k] = idx;
    idx ++;
}
// 在下标是k的结点的右边插入x,就直接调用 add(l[k], x);


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

int main()
{
    cin >> m;
    init();
    while (m--)
    {
        string op;
        int k,x;
        cin >> op;
        if (op == "L")
        {
            cin >> x;
            add(0, x);
        }
        else if (op == "R")
        {
            cin >> x;
            add(l[1], x);
        }
        else if (op == "D")
        {
            cin >> k;
            remove(k + 1); //idx从2开始,即第1个插入的数下标为2
        }
        else if (op == "IL")
        {
            cin >> k >> x;
            add(l[k + 1], x);
        }
        else
        {
            cin >> k >> x;
            add(k + 1, x);
        }
    }
    
    for (int i = r[0]; i != 1; i = r[i]) cout << e[i] << ' ';
    
    return 0;
}

2. 栈与队列

先进后出

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

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

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

// 栈顶的值
stk[tt];

// 判断栈是否为空,如果 tt > 0,则表示不为空
if (tt > 0)
{

}

单调栈

单调栈
每个元素最多只会入栈一次出栈一次,时间复杂度是O(n)的。

#include <iostream>

using namespace std;

const int N = 1e5 + 10;
int n;
int stk[N], tt;

int main()
{
    scanf("%d", &n);
    while (n--)
    {
        int x;
        scanf("%d", &x);
        
        // 如果栈顶元素大于等于待入栈元素,则出栈
        while (tt && stk[tt] >= x) tt--;
        if (tt) printf("%d ", stk[tt]); //如果栈不空,栈顶元素就是第一个小于它的元素
        else printf("-1 ");
        stk[++ tt] = x; //当前元素入栈
    }
    
    return 0;
}

队列

先进先出

普通队列

// hh 表示队头,tt表示队尾
int q[N], hh = 0, tt = -1;

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

// 从队头弹出一个数
hh ++ ;

// 队头的值
q[hh];

// 判断队列是否为空,如果 hh <= tt,则表示不为空
if (hh <= tt)
{

}

循环队列

// hh 表示队头,tt表示队尾的后一个位置
int q[N], hh = 0, tt = 0;

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

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

// 队头的值
q[hh];

// 判断队列是否为空,如果hh != tt,则表示不为空
if (hh != tt)
{

}

单调队列

例题

滑动窗口

#include <iostream>

using namespace std;

const int N = 1e6 + 10;
int n, k;
int a[N], q[N]; //q是单调队列,存的是元素的下标

int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; i++) scanf("%d", &a[i]);
    
    int hh = 0, tt = -1;
    for (int i = 0; i < n; i++)
    {
        // 判断队头是否已经滑出窗口,如果是,队头++,滑动到下一个窗口
        // 这里本来应该用while,但是每次循环只会往后移一位,可以用if
        if (hh <= tt && i - k >= q[hh]) hh ++;
        
        // 如果队尾元素大于等于即将插入的元素,弹出队尾
        while (hh <= tt && a[q[tt]] >= a[i]) tt --;
        
        q[++ tt] = i; //插入元素坐标
        if (i >= k - 1) printf("%d ", a[q[hh]]); //当区间大于等于k时,输出队头
    }
    printf("\n");
    
    hh = 0, tt = -1;
    for (int i = 0; i < n; i++)
    {
        if (hh <= tt && i - k >= q[hh]) hh ++;
        while (hh <= tt && a[q[tt]] <= a[i]) tt --;
        q[++ tt] = i;
        if (i >= k - 1) printf("%d ", a[q[hh]]);
    }
    printf("\n");
    
    return 0;
}

3. kmp

时间复杂度是O(n)

KMP字符串
字符串S和模式串P下标都从1开始,iS的指针)从1开始,j P的指针)从0开始,每次s[i]p[j+1]比较,是因为:
这里的next数组表示的是包含j的前部分串中前缀和后缀的最大长度,当从j+1开始不匹配时,应该将j退回到ne[j],接着比较s[i]p[j+1]

next数组的求法是通过模板串自己与自己进行匹配操作得出来的(代码和匹配操作几乎一样)。

#include <iostream>

using namespace std;

const int N = 1e5 + 10, M = 1e6 + 10;
int n, m;
char p[N], s[M];
int ne[N]; //next数组,最好不要写next,是某些头文件里的保留字

int main()
{
    cin >> n >> p + 1 >> m >> s + 1; //数组下标从1开始存
    
    // 求next数组
    for (int i = 2, j = 0; i <= n; i++) //i=1时只有1个元素,ne[1]=0
    {
        while (j && p[i] != p[j + 1]) j = ne[j]; //当j没有退到第1个元素前且不等时,继续退
        if (p[i] == p[j + 1]) j++;
        ne[i] = j;
    }
    
    //kmp匹配过程
    for (int i = 1, j = 0; i <= m; i++)
    {
        while (j && s[i] != p[j + 1]) j = ne[j];
        if (s[i] == p[j + 1]) j++;
        if (j == n)
        {
            printf("%d ", i - n); //因为题目要求下标从0开始,我们定义时下标从1开始,i-n+1-1
            j = ne[j]; //第一次匹配完成后,再退回j
        }
    }
    
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值