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

news2025/1/18 12:02:03

目录

单链表(邻接表) 

 双链表

 数组模拟栈、队列

单调栈 

单调队列(滑动窗口) 

KMP

一、KMP算法基本概念与核心思想

二、next数组的含义

三、匹配的思路

四、求next数组


 

单链表(邻接表) 

#include <iostream>

using namespace std;

const int N = 100010;

int n;
int h[N], e[N], ne[N], head, idx;

//对链表进行初始化
void init(){
    head = -1;//最开始的时候,链表的头节点要指向-1,
    //为的就是在后面进行不断操作后仍然可以知道链表是在什么时候结束
    /*
    插句题外话,我个人认为head其实就是一个指针,是一个特殊的指针罢了。
    刚开始的时候它负责指向空结点,在链表里有元素的时候,它变成了一个指向第一个元素的指针

    当它在初始化的时候指向-1,来表示链表离没有内容。
    */
    idx = 0;//idx在我看来扮演两个角色,第一个是在一开始的时候,作为链表的下标,让我们好找
    //第二在链表进行各种插入,删除等操作的时候,作为一个临时的辅助性的所要操作的元素的下
    //标来帮助操作。并且是在每一次插入操作的时候,给插入元素一个下标,给他一个窝,感动!
    /*
    再次插句话,虽然我们在进行各种操作的时候,元素所在的下标看上去很乱,但是当我们访问的
    时候,是靠着指针,也就是靠ne[]来访问的,这样下标乱,也就我们要做的事不相关了。
    另外,我们遍历链表的时候也是这样,靠的是ne[]
    */
}
//将x插入到头节点上
void int_to_head(int x){//和链表中间插入的区别就在于它有head头节点
    e[idx] = x;//第一步,先将值放进去
    ne[idx] = head;//head作为一个指针指向空节点,现在ne[idx] = head;做这把交椅的人换了
    //先在只是做到了第一步,将元素x的指针指向了head原本指向的
    head = idx;//head现在表示指向第一个元素了,它不在是空指针了。(不指向空气了)
    idx ++;//指针向下移一位,为下一次插入元素做准备。
}

//将x插入到下标为k的点的后面
void add(int k, int x){
    e[idx] = x;//先将元素插进去
    ne[idx] = ne[k];//让元素x配套的指针,指向它要占位的元素的下一个位置
    ne[k] = idx;//让原来元素的指针指向自己
    idx ++;//将idx向后挪
    /*
    为了将这个过程更好的理解,现在
    将指针转变的这个过程用比喻描述一下,牛顿老师为了省事,想插个队,队里有两个熟人
    张三和李四,所以,他想插到两个人中间,但是三个人平时关系太好了,只要在一起,就
    要让后面的人的手插到前面的人的屁兜里。如果前面的人屁兜里没有基佬的手,将浑身不
    适。所以,必须保证前面的人屁兜里有一只手。(张三在前,李四在后)
    这个时候,牛顿大步向前,将自己的手轻轻的放入张三的屁兜里,(这是第一步)
    然后,将李四放在张三屁兜里的手抽出来放到自己屁兜里。(这是第二步)
    经过这一顿骚操作,三个人都同时感觉到了来自灵魂的战栗,打了个哆嗦。
    */
}

//将下标是k的点后面的点个删掉
void remove(int k){
    ne[k] = ne[ne[k]];//让k的指针指向,k下一个人的下一个人,那中间的那位就被挤掉了。
}
int main(){
    cin >> n;
    init();//初始化
    for (int i = 0; i < n; i ++ ) {
        char s;
        cin >> s;
        if (s == 'H') {
            int x;
            cin >> x;
            int_to_head(x);
        }
        if (s == 'D'){
            int k;
            cin >> k;
            if (k == 0) head = ne[head];//删除头节点
            else remove(k - 1);//注意删除第k个输入后面的数,那函数里放的是下标,k要减去1
        }
        if (s == 'I'){
            int k, x;
            cin >> k >> x;
            add(k - 1, x);//同样的,第k个数,和下标不同,所以要减1
        }
    }

    for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ' ;
    cout << endl;

    return 0;
}

 双链表

int e[N], l[N], r[N], idx;
//e[N]存放数据
//l[N]前一个节点
//r[N]后一个节点
//idx存储当前已经用到了哪个点 

 如何理解l[N]和r[N]:l[0]代表第一个节点的左端点,1[1]代表第二个节点的左端点,r[0]代表第一个节点的右端点,r[1]代表第二个节点的右端点。

 初始化

初始化相当于让最左边俩个连接起来 

void Init()
{
	//0表示左端点,1表示右端点
	r[0] = 1;
	l[1] = 0;
	idx = 2;
}

#include<iostream>
using namespace std;
const int N = 100010;
int m;
int e[N], l[N], r[N], idx;
//e[N]存放数据
//l[N]前一个节点
//r[N]后一个节点
//idx存储当前已经用到了哪个点 
//初始化
void Init()
{
	//0表示左端点,1表示右端点
	r[0] = 1;
	l[1] = 0;
	idx = 2;
}
void add_right(int k,int x)//在第k点后面插入x
{
	e[idx] = x;//先建立一个新的节点
	r[idx] = r[k];//让新节点的右边等于k的右边
	l[idx] = k;//让左边指向k
	l[r[k]] = idx;//把k的下一个节点的左边指向idx
	r[k] = idx;//让k的右边指向idx
	idx++;
}
void add_left(int k, int x)//在第k节点前面插入x
{
	add_right(l[k], x);
}
void remove(int k)//删除第k个点
{
	r[l[k]] = r[k];//让k的前面的节点指向k后面节点
	l[r[k]] = l[k];//让k后面节点指向k前面节点
	//不需要对k的左右节点进行处理
}

 数组模拟栈、队列

栈 

#include<iostream>
using namespace std;
const int N = 100010;
int stk[N], tt;//tt表示栈顶下标

 

 队列

hh是队头,tt队尾

 

单调栈 

 如有一组数字3 4 2 7 5,我们返回每个元素左边离它最近且比它小的数,若左边没有比它小的数返回-1

注意这组数据里的4和2,当我们要找比7小的数字时,左边3,4,2都比7小,但我们找的是离7最近的2,此时返回2即可,但3和4此时没用,在找比5小的数字时,3,4也没用,3和4便可以删除。因为3和4大于2。 

 

 

#include<iostream>
using namespace std;
const int N = 100010;
int n;
int stk[N], tt;
int main()
{
	cin >> n;
	for (int i = 0; i < n; ++i)
	{
		int x;
		cin >> x;
		while (tt && stk[tt] >= x)//若tt不为0,并且栈顶元素>=x就--,直到找到比x小的或把栈减没
			tt--;
		if (tt) cout << stk[tt] <<' ';
		else//说明栈是空的,即左边没有任何数比x小
			cout << -1 <<' ';
		stk[++tt] = x;//把x插到栈里面去
	}
	return 0;
}

单调队列(滑动窗口) 

 

 把所有长度为3的窗口里面的最大值和最小值输出,这个窗口会移动

 以找窗口中的最小值为例:3和-1都比-3大,而且这俩个数字在窗口滑动的时候都会比-3提前被窗口弹出,所以我们可以删掉这俩个元素,即:在找最小的数只要队列里面前面的数比后面的数大,则前面的数一定没有用,这样我们就可以把大的数删掉,当整个数组都这样操作时,队列就会变成一个单调递增的队列

队列的最小值就在左下方,即我们找到队头就可以找到最小的数

q中存的是a中元素对应的下标 

#include<iostream>
using namespace std;
const int N = 1000010;
int n,k;
int a[N], q[N];//q单调队列
int main()
{
	scanf("%d%d", &n,&k);//k是窗口长度
              	int hh = 0, tt = -1;
	for (int i = 0; i < n; ++i)
	{
		scanf("%d", &a[i]);
	}
	//找窗口中最小元素
	for (int i = 0; i < n; ++i)
	{
		//判断队头是否已经划出窗口
		if (hh <= tt && i - k + 1 > q[hh])//i-k+1是窗口的起始位置,如果起始位置>对头位置,说明q[hh]已经出窗口了
			hh++;//因为q是单调队列,q[hh]是最小值,要让q[hh]一直位于窗口中
		while (hh <= tt && a[q[tt]] >= a[i])//如果队尾的数大于当前插入的数,则队尾--即删除该数字
			tt--;
		q[++tt] = i;
		if (i >= k - 1)
			printf("%d ", a[q[hh]]);
	
	}
	puts("");
	//找窗口中最大值
	hh = 0, tt = -1;
	for (int i = 0; i < n; ++i)
	{
		//判断队头是否已经划出窗口
		if (hh <= tt && i - k + 1 > q[hh])//i-k+1是窗口的起始位置,如果起始位置>对头位置,说明q[hh]已经出窗口了
			hh++;//因为q是单调队列,q[hh]是最小值,要让q[hh]一直位于窗口中
		while (hh <= tt && a[q[tt]] <= a[i])//如果队尾的数大于当前插入的数,则队尾--即删除该数字
			tt--;
		q[++tt] = i;
		if (i >= k - 1)
			printf("%d ", a[q[hh]]);

	}
	return 0;
}

KMP

 

#include<iostream>
using namespace std;
const int N = 10010,M=100010;
char s[M], p[N],ne[N];
int main()
{
	int n,m;
cin >> n >> p+1>>m>>s+1;
//NE数组
//ne数组求法:是通过模板串自己与自己匹配出来的
for (int i = 2, j = 0; i <= n; ++i)
{
	while (j && p[i] != p[i + 1]) j = ne[j];
	if (p[i] == p[j + 1]) j++;
	ne[i] = j;
}
{

}
//匹配部分
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++;//如果下一个地方能匹配上,则把j++
	 if (j == n)//整个模板串都匹配上了,更新一下位置,进行下一次匹配
	 {
		 printf("%d ", i - n);//打印出匹配到的首字符下标,由于i从1开始,所以这里不需要+1
		 j = ne[j];
	 }
}
	return 0;
}

一、KMP算法基本概念核心思想

基本概念

  • ① s[ ]是 模式串:较长字符串,
  • ② p[ ]是 模板串,较短字符串。
  • ③ “非平凡前缀”:指 除了最后一个字符以外,一个字符串的 全部头部组合(前面连续的部分
  • ④ “非平凡后缀”:指 除了第一个字符以外,一个字符串的 全部尾部组合。(后面均简称为 前/后缀)
  • ⑤ “部分匹配值”:前缀和后缀 的 最长共有元素 的 长度。
  • ⑥ next[ ]是“部分匹配值表”,即next数组,它存储的是每一个下标对应的“部分匹配值”,是KMP算法的核心

核心思想

在每次失配时,不是把p串往后移一位,而是把p串往后移动至下一次可以和前面部分匹配的位置,

这样就可以 跳过大多数的失配步骤

每次p串移动的步数通过查找next[ ]数组确定的。

二、next数组的含义

含义next[ j ] 表示p[ 1, j ]串中前缀和后缀相同最大长度部分匹配值),

即:p[ 1, next[ j ] ] = p[ j - next[ j ] + 1, j ](前后缀相同,两者都达到最大)

举个例子,例如:
在这里插入图片描述

为了对next数组有更清晰的认知,我们手动模拟一下next数组

假设 模板串 p = “abcab”,

则对于next[1]:前缀集合为空,后缀集合为空,next[1] = 0;

next[2]:前缀集合 { “a” },后缀集合 { “b” },两集合中无匹配字符串,next[2] = 0;

next[3]:前缀集合 { “a”, “ab” },后缀集合 { “c”, “bc” },两集合中无匹配字符串,next[3] = 0;

next[4]:前缀集合 { “a”, “ab”, “abc” },后缀集合 { “a”, “ca”, “bca” },两集合中最长匹配字符串为 “a”,next[4] = 1;

next[5]:前缀集合 { “a”, “ab”, “abc”, “abca” },后缀集合 { “b”, “ab”, “cab”, “bcab” },两集合中最长匹配字符串为 “ab”,next[5] = 2;

(注意以上说的前后缀都指的是“非平凡”)

得到以下表格:
在这里插入图片描述

三、匹配的思路

KMP算法主要分 两步next数组匹配字符串

对于匹配字符串,其思路是这样子的:

模式s串 和 模板p串都人为规定为 从1开始。

i 从1开始,j 从0开始,每次将s[ i ] p[ j + 1 ]比较

下方的图中,红色的字符串代表模式s串,蓝色和紫色的串代表匹配过程中后移的模板p串,

在这里插入图片描述

当匹配过程到上图所示时,

s[ a , b ] = p[ 1, j ] && s[ i ] != p[ j + 1 ] 此时要移动p串(不只移动1格,而是直接移动到下次能匹配的位置

其中上图中的 ①串 为[ 1, next[ j ] ],③串 为[ j - next[ j ] + 1 , j ]。由匹配可知 ①串 等于 ③串,③串 等于 ②串。所以 直接移动p串,使 ① 到 ③ 的位置 即可。这个操作可由 j = next[ j ] 直接完成。 如此往复下去, j == m时匹配成功(此时就能够完全匹配上了:p串最后一个元素p[m]s相配了)。

匹配过程的代码片段:

for(int i=1, j=0; i<=n; ++i)
{
        while(j && s[i]!=p[j+1]) j = ne[j];
        //如果j有对应p串的元素, 且s[i] != p[j+1], 则失配, 移动p串
    	//用while是由于移动后可能仍然失配,所以要继续移动直到匹配或整个p串移到后面(j = 0)

        if(s[i]==p[j+1]) ++j;
        //当前元素匹配,j移向p串下一位
        if(j==m)//满足匹配条件
        {
                //匹配成功,进行相关操作
                        
                j = ne[j];//继续匹配下一个子串
        }
}

四、求next数组

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

始终记住一点: next[ ]是“部分匹配值表”,即next数组,它存储的是字符串中(一般是模板串p中)每一个下标对应的“部分匹配值”,是KMP算法的核心

在这里插入图片描述
代码和匹配操作的代码几乎一样,关键在于:每次移动 i 前, i 前面已经匹配的长度记录到next数组中。

代码片段如下:

void get_next()//核心是求模式串p的next数组(记住next数组是相对于模式串而言的)
{
        for(int i=2, j=0; i<=m; ++i)//i从2开始,因为ne[1]=0,无需计算
        {
                while(j && p[i]!=p[j+1]) j = ne[j];
                if(p[i]==p[j+1]) ++j;//此时匹配到了前后缀相等
                ne[i] = j;//赶紧记录下来
        }
}

二、求next[ ]数组的代码
next[ ]数组的求法,是通过模式串T自己与自己进行匹配得出来的(代码和下文“匹配字符串”的操作几乎一样)。

for(int i=1, j=0; i<=n; ++i)
{
        while(j && s[i]!=p[j+1]) j = ne[j];
        //如果j有对应p串的元素, 且s[i] != p[j+1], 则失配, 移动p串
    	//用while是由于移动后可能仍然失配,所以要继续移动直到匹配或整个p串移到后面(j = 0)

        if(s[i]==p[j+1]) ++j;
        //当前元素匹配,j移向p串下一位
        if(j==m)//满足匹配条件
        {
                //匹配成功,进行相关操作
                        
                j = ne[j];//继续匹配下一个子串
        }
}

下图是T[a,b]=T[1,j]时,模式串T的状态。


若执行语句 if(t[i]==t[j+1]) j++; 后,将产生下图的状态,再结合next[ ]数组的定义,分析可得 next[i]=j;若注意到下图中,绿色虚线框内的元素是相同的,再结合next[ ]数组的定义就更好理解所得结论了。 



三、匹配字符串的代码
next[ ]数组在某字符处对应的值的大小,即图中黄色花括号表示的大小。

for(int i=1, j=0; i<=n; i++) { //匹配操作。i从1开始,j从0开始
	while(j && s[i]!=t[j+1]) j=ne[j];
	if(s[i]==t[j+1]) j++;
	if(j==m) {
		cout<<i-m<<" ";
		j=ne[j]; //再次继续匹配
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/428095.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux操作基础(系统安全及应用)

文章目录一 、账号安全基本措施1.1 系统账号清理1.2 密码安全控制1.21 设置密码有效期1.3 命令历史限制1.31 修改history命令条数1.32 清空history的方式1.33 设置终端自动注销二 、使用su命令切换用户2.1 限制使用su命令切换用户2.2 sudo命令—提升执行权限三 、系统引导和登录…

OpenCV:介绍 SURF(加速稳健特征)以及其使用

我们将了解 SURF 的基础知识 我们将了解 OpenCV 中的 SURF 功能 理论 在上一章中,我们学习了 SIFT 用于关键点检测和描述的方法。但它相对较慢,人们需要更快速的版本。2006年,Bay, H., Tuytelaars, T. 和 Van Gool, L 发表了另一篇论文 "SURF: 加速稳健特征",介…

机器学习:多项式拟合分析中国温度变化与温室气体排放量的时序数据

文章目录1、前言2、定义及公式3、案例代码1、数据解析2、绘制散点图3、多项式回归、拟合4、注意事项1、前言 ​ 当分析数据时&#xff0c;如果我们找的不是直线或者超平面&#xff0c;而是一条曲线&#xff0c;那么就可以用多项式回归来分析和预测。 2、定义及公式 ​ 多项式…

《花雕学AI》哪种技能5年10年后还会被市场需要? 该如何提高这些能力?

随着AI人工智能、ChatGPT等新的技术革新的发展&#xff0c;未来职业场景确实会发生变化&#xff0c;一些传统的职业可能会被取代&#xff0c;而一些新的职业可能会出现。根据世界经济论坛所发布的《未来就业报告》&#xff0c;一半的劳动力需要在2025年之前完成技能重塑。那么&…

Harmony OS 开发指南——DevEco Device Tool 安装配置

本文介绍如何在Windows主机上安装DevEco Device Tool工具。 坑点总结&#xff1a; 国内部分网络环境下&#xff0c;安装npm包可能会很慢或者超时&#xff0c;推荐使用国内npm源&#xff08;如淘宝源、华为源等&#xff09;&#xff1b;serialport这个npm包安装的过程中需要编…

C/C++笔记-记录一次对qmake生成的Makefile的分析(2023-02-07)

如下Qt代码&#xff1a; ConsoleDemo.pro QT core QT - guiTARGET ConsoleDemo CONFIG console CONFIG - app_bundleTEMPLATE appSOURCES main.cpp main.cpp #include <QCoreApplication> #include <QDebug>int main(int argc, char *argv[]) {QCoreApplic…

安装Kafka 基础命令

目录 解压 改名 修改配置文件 创建目录用于存放日志 创建修改myid 添加环境变量 刷新环境变量 测试 启动zookeeper 启动kafka 关闭kafka kafka基础命令 查看消息队列 创建消息队列 查看队列详情 查询指定队列消息数量 生产者消费者 生产消息 消费消息 解压 …

简单分享婚庆小程序开发怎么做

婚庆行业的服务范围不再仅限于婚纱照、婚礼主持等服务&#xff0c;消费者希望在婚庆服务商获得更多的服务&#xff0c;导致行业服务范围不断扩大、服务类目越发丰富&#xff0c;而商家在此基础上&#xff0c;更需要考虑提高服务质量与效率&#xff0c;合理利用资源提供服务。小…

Jmeter5.1的安装

1.由于&#xff0c;jmeter 是用纯java开发的一个可跨平台的绿色软件&#xff0c;所以&#xff0c;我们在使用jmeter之前&#xff0c;必须要安装基于Windows下的jdk环境。下面安装jdk,检查是否安装jdk方法&#xff08;cmd运行输入java -version&#xff09;&#xff0c;如下图显…

81.qt qml-Canvas深入学习之好看的水纹波自定义控件V2

界面如下所示(外观参考ECharts 源码通过QML控件实现): 效果如下所示: 支持自定义颜色、自定义波峰数量、增幅、速度、水平偏移等 1.介绍 在我们之前38章38.qt quick-QML水纹波进度条_诺谦的博客-CSDN博客 写过一个简单的水纹波: 所以本质差不多. 2.QianRippleChartPage代码 该…

webgl-矩阵、旋转、平移、缩放

关键代码 旋转 /* * [ * cosB, -sinB, 0, 0, * sinB, cosB, 0, 0, * 0, 0, 1, 0, * 0, 0, 0, 1 * ] * * 矩阵本该是这个但是由于webgl的矩阵行和列是颠倒的所以我们传入的矩阵也需要倒置 */ rotationMatrix [ cosB, sinB, 0, 0, -sinB, cos…

[Python工匠]输出③容器类型

在Python中&#xff0c;最常见的内置容器类型有四种&#xff1a;列表、元组、字典、集合。 列表&#xff08;list&#xff09;是一种非常经典的容器类型&#xff0c;通常用来存放多个同类对象&#xff0c;比如从1到10的所有整数&#xff1a; 元组&#xff08;tuple&#xff09;…

phpstudy本地环境搭建图文教程

作者&#xff1a;Eason_LYC 悲观者预言失败&#xff0c;十言九中。 乐观者创造奇迹&#xff0c;一次即可。 一个人的价值&#xff0c;在于他所拥有的。可以不学无术&#xff0c;但不能一无所有&#xff01; 技术领域&#xff1a;WEB安全、网络攻防 关注WEB安全、网络攻防。我的…

chatgpt VS 文心一言使用对比实测

chatgpt VS 文心一言使用对比实测 什么是文心一言 文心一言&#xff08;英语&#xff1a;ERNIE Bot&#xff09;是由百度公司开发的聊天机器人&#xff0c;能够与人交互、回答问题及协作创作。该产品被传媒称为国际著名聊天机器人ChatGPT的中国版及其竞争对手[1][2]。目前已开…

机器学习——数据处理

机器学习简介 机器学习是人工智能的一个实现途径深度学习是机器学习的一个方法发展而来 机器学习&#xff1a;从数据中自动分析获得模型&#xff0c;并利用模型对未知数据进行预测。 数据集的格式&#xff1a; 特征值目标值 比如上图中房子的各种属性是特征值&#xff0c;然…

数据结构——哈希表相关题目

数据结构——哈希表相关题目242. 有效的字母异位词1.暴力解法2.排序后比较3.哈希表383. 赎金信哈希解法49. 字母异位词分组438. 找到字符串中所有字母异位词3. 无重复字符的最长子串76. 最小覆盖子串349. 两个数组的交集1.排序双指针2.哈希表350. 两个数组的交集 II1.排序双指针…

C++缺省参数详解

在C中&#xff0c;我们经常需要定义函数并给它们传递一些参数。有时候&#xff0c;某些参数的值是不经常改变或者只有特殊情况下才需要指定&#xff0c;这时候我们可以用缺省参数来简化代码。 什么是缺省参数&#xff1f; 缺省参数是声明或定义函数时为函数的参数指定一个缺省…

javascript 中使用 ActiveMQ

javascript 中使用 ActiveMQ 1. 参考文档 https://www.eclipse.org/paho/files/jsdoc/index.html 2. html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" con…

SpringBoot——短信发送、手机验证码登录

目录 一、短信发送 1.1 阿里云短信服务 1.1.1 设置短信签名 1.1.2 模板管理 1.1.3 设置AccessKey 1.2 短信发送——代码开发 1.2.1 导入maven坐标 1.2.2 调用API 1.2 手机验证码登录 1.2.1 用户数据库表 1.2.2 修改过滤器 1.2.3 随机生成验证码的工具类 1.2.4 手机验证码登…

【Linux系统查找错误单词项目和vim的三种模式(命令,输入和底线命令模式)和项目的心得体会】

查找文章中拼写错误的单词的C语言程序 编写的C语言程序&#xff1a; #include<stdio.h> #include<string.h> int main() { FILE *fpfopen("essay.txt","r"); FILE *fp1fopen("words.txt","r"); if(fpNULL&&fp1NU…