数组模拟实现单链表、双链表、栈、队列

news2024/11/20 4:43:11

文章目录

前引 

一、数组模拟实现单链表

1、1 数组模拟的单链表解析

1、2 数组模拟实现单链表例题

二、数组模拟实现双链表 

2、1 数组模拟实现双链表解析

2、2 数组模拟实现双链表例题

三、数组模拟实现栈

3、1 数组模拟实现栈解析

3、2 数组模拟实现栈例题

 四、数组模拟实现队列

4、1 数组模拟实现队列解析

4、2 数组模拟实现队列例题


标题:数组模拟实现单链表、双链表、栈、队列

作者:@Ggggggtm

寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景

前引 

  我们在数据结构中都学到过单链表、双链表、栈和队列,当我们实现的时候时使用结构体指针实现的。定义一个结构体,结构体中存储指针变量和存放数值的变量。当然,C++的STL库中已经有实现好的栈和队列,我们可以直接用。但是在做算法题时,有时候我们会发现超出时间限制。原因是我们用STL库中的栈和队列容器时,效率相对来说较慢。我们这时就引出用数组模拟实现栈和队列。用数组模拟实现的使用起来效率更高、更方便。当然,我们也会讲到用数组模拟实现单链表和双链表。

一、数组模拟实现单链表

1、1 数组模拟的单链表解析

  用结构体实现单链表时,我们会在结构体中定义一个存放数据的变量和一个存放下一个数据地址的指针。那我们用数组模拟实现怎么找到下一个数据的呢?用数组实现单链表,我们定义两个数组即可。一个数组存放数据,另一个数组存放下一数据的下标(充当结构体中的指针)。我们之直节看代码,理解更加容易。

//e[i] 表示点i的值
//ne[i] 表示节点i的下一个数据的下标
//head 表示栈头下标
//idx 当前已经存储到第几个数据了
int head,e[N],ne[N],idx;

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

//头插
void InsertHead(int x)
{
    e[idx]=x;
    ne[idx]=head;
    head=idx;
    idx++;
}

//在地k个节点后插入一个元素

void Insert(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]];
}

   我们再结合着一个例题看一下。

1、2 数组模拟实现单链表例题

实现一个单链表,链表初始为空,支持三种操作:

  1. 向链表头插入一个数;
  2. 删除第 kk 个插入的数后面的数;
  3. 在第 kk 个插入的数后插入一个数。

现在要对该链表进行 MM 次操作,进行完所有操作后,从头到尾输出整个链表。

注意:题目中第 kk 个插入的数并不是指当前链表的第 kk 个数。例如操作过程中一共插入了 nn 个数,则按照插入的时间顺序,这 nn 个数依次为:第 11 个插入的数,第 22 个插入的数,…第 nn 个插入的数。

输入格式:

第一行包含整数 MM,表示操作次数。

接下来 MM 行,每行包含一个操作命令,操作命令可能为以下几种:

  1. H x,表示向链表头插入一个数 xx。
  2. D k,表示删除第 kk 个插入的数后面的数(当 kk 为 00 时,表示删除头结点)。
  3. I k x,表示在第 kk 个插入的数后面插入一个数 xx(此操作中 kk 均大于 00)。

输出格式:

共一行,将整个链表从头到尾输出。

数据范围:

1≤M≤1000001≤M≤100000
所有操作保证合法。

输入样例:

10
H 9
I 1 1
D 1
D 0
H 6
I 3 6
I 4 5
I 4 5
I 3 4
D 6

输出样例:

6 4 6 5

  我们看一下这道题的答案,代码如下:

 

#include<iostream>
using namespace std;

const int N=100010;
//e[i] 表示点i的值
//ne[i] 表示节点i的下一个数据的下标
//head 表示栈头下标
//idx 当前已经存储到第几个数据了
int head,e[N],ne[N],idx;

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

//头插
void InsertHead(int x)
{
    e[idx]=x;
    ne[idx]=head;
    head=idx;
    idx++;
}

//在地k个节点后插入一个元素

void Insert(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;
        cin>>op;
        if(op=='H')
        {
            int x;
            cin>>x;
            InsertHead(x);
        }
        else if(op=='D')
        {
            int k;
            cin>>k;
            if(!k)
                head=ne[head];
            else
                remove(k-1);
        }
        else
        {
            int k,x;
            cin>>k>>x;
            Insert(k-1,x);
        }
    }
    for(int i=head;i!=-1;i=ne[i])
    {
        printf("%d ",e[i]);
    }
}

二、数组模拟实现双链表 

2、1 数组模拟实现双链表解析

  数组模拟实现双链表与数组模拟实现单链表大同小异。数组模拟实现双链表时我们需要定义三个数组,一个数组存放数据,一个数组存放该数据左边数据的下标(左指针),一个数组存放该数据右边数据的下标(右指针)。我们直接看代码:

//e[i] 是表示点i的值
//l[i] 表示节点i的左边指针是多少
//r[i] 表示节点i的右边指针是多少
//idx 存储当前已经用到那个点了
int e[N],l[N],r[N],idx;

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

//在下标为k的右边插入一个元素
void Insert(int k,int x)
{
    e[idx]=x;
    r[idx]=r[k];
    l[idx]=k;
    l[r[k]]=idx;
    r[k]=idx;
    idx++;
}

//删除下标为k的元素
void remove(int k)
{
    r[l[k]]=r[k];
    l[r[k]]=l[k];
}

   我们发现,上面代码并没有定义在下标为k的左边插入一个数据,我们只定义了在下标为k的右边插入一个数据。为什么呢?因为可以用在下标为k的右边插入一个数据函数实现在下标为k的左边插入一个数据。我们只需要在下标为k的左边的数据的右边插入一个数据就相当于实现了在下标为k的左边插入一个数据。如下图,我们想在下标为3的左边插入一个数据,其实就是在下标为2的右边插入一个数据。

   我们结合着一个例题理解一下。

2、2 数组模拟实现双链表例题

实现一个双链表,双链表初始为空,支持 55 种操作:

  1. 在最左侧插入一个数;
  2. 在最右侧插入一个数;
  3. 将第 kk 个插入的数删除;
  4. 在第 kk 个插入的数左侧插入一个数;
  5. 在第 kk 个插入的数右侧插入一个数

现在要对该链表进行 MM 次操作,进行完所有操作后,从左到右输出整个链表。

注意:题目中第 kk 个插入的数并不是指当前链表的第 kk 个数。例如操作过程中一共插入了 nn 个数,则按照插入的时间顺序,这 nn 个数依次为:第 11 个插入的数,第 22 个插入的数,…第 nn 个插入的数。

输入格式:

第一行包含整数 MM,表示操作次数。

接下来 MM 行,每行包含一个操作命令,操作命令可能为以下几种:

  1. L x,表示在链表的最左端插入数 xx。
  2. R x,表示在链表的最右端插入数 xx。
  3. D k,表示将第 kk 个插入的数删除。
  4. IL k x,表示在第 kk 个插入的数左侧插入一个数。
  5. IR k x,表示在第 kk 个插入的数右侧插入一个数。

输出格式:

共一行,将整个链表从左到右输出。

数据范围:

1≤M≤1000001≤M≤100000
所有操作保证合法。

输入样例:

10
R 7
D 1
L 3
IL 2 10
D 3
IL 2 7
L 8
R 9
IL 4 7
IR 2 2

输出样例:

8 7 7 3 2 9

  我们看一下答案,代码如下:

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

//e[i] 是表示点i的值
//l[i] 表示节点i的左边指针是多少
//r[i] 表示节点i的右边指针是多少
//idx 存储当前已经用到那个点了
int e[N],l[N],r[N],idx;

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

//在下标为k的右边插入一个元素
void Insert(int k,int x)
{
    e[idx]=x;
    r[idx]=r[k];
    l[idx]=k;
    l[r[k]]=idx;
    r[k]=idx;
    idx++;
}

//删除下标为k的元素
void remove(int k)
{
    r[l[k]]=r[k];
    l[r[k]]=l[k];
}
int main()
{
    int m;
    cin>>m;
    Init();
    while(m--)
    {
        string op;
        int x,k;
        cin>>op;
        if(op=="L")
        {
            cin>>x;
            Insert(0,x);
        }
        else if(op=="R")
        {
            cin>>x;
            Insert(l[1],x);
        }
        else if(op=="D")
        {
            cin>>k;
            remove(k+1);
        }
        else if(op=="IL")
        {
            cin>>k>>x;
            Insert(l[k+1],x);
        }
        else
        {
            cin>>k>>x;
            Insert(k+1,x);
        }
    }
    for (int i = r[0]; i != 1; i = r[i]) 
        cout << e[i] << ' ';
    return 0;
}

三、数组模拟实现栈

3、1 数组模拟实现栈解析

  我们用数组模拟实现栈是相对简单的。我们只要满足栈的先进后出的性质即可。我们直接看代码,如下:

//********************* 模拟栈
int stack[N],top=0;

//往栈中插入元素
stack[top++];

//拿出栈顶元素
top--;

//栈顶元素
stack[top-1];
//判断栈是否为空
if(top>0)
{
    printf("notempty\n");
}
else
{
    printf("empty\n");
}

  我们这里给出一个用到单调栈的例题。 

3、2 数组模拟实现栈例题

给定一个长度为 NN 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1−1。

输入格式:

第一行包含整数 NN,表示数列长度。

第二行包含 NN 个整数,表示整数数列。

输出格式:

共一行,包含 NN 个整数,其中第 ii 个数表示第 ii 个数的左边第一个比它小的数,如果不存在则输出 −1−1。

数据范围:

1≤N≤1051≤N≤105
1≤数列中元素≤1091≤数列中元素≤109

输入样例:

5
3 4 2 7 5

输出样例:

-1 3 -1 2 2

  我们看一下答案,代码如下:

#include<iostream>
using namespace std;

const int N=100010;

int stack[N],top=0;
int main()
{
    int n;
    scanf("%d",&n);
    while(n--)
    {
        int x=0;
        scanf("%d",&x);
        while(top&&stack[top-1]>=x)
        {
            top--;
        }
        if(!top)
            printf("-1 ");
        else
        {
            printf("%d ",stack[top-1]);
        }
        stack[top++]=x;
    }
    return 0;
}

 四、数组模拟实现队列

4、1 数组模拟实现队列解析

   同样,我们用数组模拟实现队列也是很简单的。我们只要满足队列的先进先出的性质即可。我们直接看代码,如下: 

//********************* 模拟对列
int queue[N],head,tail=0;

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

//弹出
head++;

//判断队列是否为空
if(head<tail) not empty;
else empty;

//取出对头,队尾元素
queue[head];
queue[tail-1];

  我们这里给出一道用到队列的例题,相对来说难一点,我们看一下。

4、2 数组模拟实现队列例题

给定一个大小为 n≤106n≤106 的数组。

有一个大小为 kk 的滑动窗口,它从数组的最左边移动到最右边。

你只能在窗口中看到 kk 个数字。

每次滑动窗口向右移动一个位置。

以下是一个例子:

该数组为 [1 3 -1 -3 5 3 6 7],kk 为 33。

窗口位置最小值最大值
[1 3 -1] -3 5 3 6 7-13
1 [3 -1 -3] 5 3 6 7-33
1 3 [-1 -3 5] 3 6 7-35
1 3 -1 [-3 5 3] 6 7-35
1 3 -1 -3 [5 3 6] 736
1 3 -1 -3 5 [3 6 7]37

你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。

输入格式:

输入包含两行。

第一行包含两个整数 nn 和 kk,分别代表数组长度和滑动窗口的长度。

第二行有 nn 个整数,代表数组的具体数值。

同行数据之间用空格隔开。

输出格式:

输出包含两个。

第一行输出,从左至右,每个位置滑动窗口中的最小值。

第二行输出,从左至右,每个位置滑动窗口中的最大值。

输入样例:

8 3
1 3 -1 -3 5 3 6 7

输出样例:

-1 -3 -3 -3 3 3
3 3 5 5 6 7

  我们看一下答案,代码如下:

#include<iostream>
using namespace std;

const int N=1000010;
int a[N],q[N];
int head,tail;
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
    }
    head=0;
    tail=0;
    for(int i=0;i<n;i++)
    {
        //判断对头是否已经划出窗口
        if(head<tail&&i-k+1>q[head])
            head++;
        //对头确定最小数
        while(head<tail&&a[q[tail-1]]>=a[i])
            tail--;
        q[tail++]=i;
        if(i>=k-1)
        printf("%d ",a[q[head]]);
    }
    printf("\n");
    head=0;
    tail=0;
    for(int i=0;i<n;i++)
    {
        //判断对头是否已经划出窗口
        if(head<tail&&i-k+1>q[head])
            head++;
        //对头确定最大数
        while(head<tail&&a[q[tail-1]]<=a[i])
            tail--;
        q[tail++]=i;
        if(i>=k-1)
        printf("%d ",a[q[head]]);
    }
    return 0;
}

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

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

相关文章

【C++学习】基础语法(一)

1、背景知识 1.1 什么是C C语言是结构化和模块化的语言&#xff0c;长用于处理较小规模的程序&#xff1b;对于规模较大、问题复杂的程序&#xff0c;则需要高度的抽象和建模&#xff0c;此时C语言不合适处理这类问题。为了解决此类影响软件的问题&#xff0c;20世纪80年代&am…

Python | Matplotlib | 不完全总结

本文对 Matplotlib 的用法进行不完全总结。 更新&#xff1a; 2023 / 1 / 4 Python | Matplotlib | 不完全总结ImageMagick导库画布创建多子图动图2D柱状图基本&#xff1a;水平 / 垂直柱形主题&#xff1a;颜色、文字、网格线动图线图基本动图3D柱状图基本线图动图参考链接Im…

Web文件操作:上传与下载

文件上传与下载文件上传文件上传的实现表结构设计UploadFileServiceImpl的实现上传文件遇到的问题与解决文件下载文件上传 文件上传的表单中&#xff0c;需要注意的三个地方&#xff1a; 1.表单的请求方式必须为post&#xff1b; 2.表单域必须有file类型&#xff1b; 3.表单的e…

活动星投票奋斗青春,使命必达网络评选微信的投票方式线上免费投票

“奋斗青春&#xff0c;使命必达”网络评选投票_如何进行投票推广_参与投票活动_小程序的投票发展现在来说&#xff0c;公司、企业、学校更多的想借助短视频推广自己。通过微信投票小程序&#xff0c;网友们就可以通过手机拍视频上传视频参加活动&#xff0c;而短视频微信投票评…

英伟达528.02驱动发布支持4070 Ti!GFE新增9款游戏

自GTX 4070 Ti显卡发售后&#xff0c;英伟达便随即发布了支持该新显卡的Game Ready 528.02驱动&#xff0c;同时为《战意》和《达喀尔沙漠拉力赛》两款新游戏带来DLSS 3的支持&#xff0c;DLSS 3的队伍再度壮大&#xff01; 驱动人生现已支持英伟达Game Ready 528.02驱动&…

围绕http请求头中Referer展开的一些知识

1. 什么是referer&#xff1f; <点击以获取跳转信息 >跳转过去记得按一下f12点击网络请求详情&#xff0c;再刷新一下&#xff0c;就可以看见referer字段&#xff1a; 当我们尝试在浏览器内部直接输入这熟悉的网址时&#xff0c;此时刷新后则是这样一番景象&#xff1…

C++类和对象的基本概念

目录 1.c和c中struct的区别 2.类的封装 3.类的访问权限 1.c和c中struct的区别 c语言中结构体中不能存放函数,也就是数据(属性)和行为(方 法)是分离的 c中结构体中是可以存放函数的,也就是数据(属性)和行为 (方法)是封装在一起的 #define _CRT_SECURE_NO_WARNINGS #include …

基于Python tensorflow机器学习的人脸识别登陆系统源码、人脸注册系统源码

face_login 代码下载地址&#xff1a;基于Python tensorflow机器学习的人脸识别登陆系统源码、人脸注册系统源码 介绍 本项目基于tensorflow机器学习&#xff0c;实现web端人脸识别登陆&#xff0c;人脸注册。 提供手机端页面(face_login_app)和网页端页面(vue_element-adm…

JUC并发编程学习笔记(六)线程池及分支合并框架

10 ThreadPool 线程池&#xff08;重点&#xff09; 10.1 线程池简介 回顾以前的连接池概念 连接池是创建和管理一个连接的缓冲池的技术&#xff0c;这些连接准备好被任何需要它们的线程使用 线程池&#xff08;英语&#xff1a;thread pool&#xff09;&#xff1a;一种线程…

实时数仓,为什么不可代替?

什么是实时数据仓库&#xff1f;它有哪些不可替代之处&#xff1f; 大数据时代中&#xff0c;数据仓库解决了商业智能分析过程中的数据管理问题&#xff0c;但是存在烟囱式、冗余高的弊端 随着商业智能的兴起和数据时代的到来&#xff0c;越来越多的企业开始汇总、整合和分析自…

ArcGIS基础实验操作100例--实验62点、线、面状符号

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验62 点、线、面状符号 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&…

C/C++中二级指针传递参数【个人遇到内存值发生改变现象的记录及相关修正方法】

目录 0、前言 1、二级指针传参奇怪现象 2、分析 3、解决方法 0、前言 在c/c中&#xff0c;时常会使用到主调函数通过参数去获取被调函数中的数值情况。针对这种情况&#xff0c;我前面也写过C/C主调函数从被调函数中获取&#xff08;各种类型&#xff09;数据内容方式的梳理…

【ONE·R || 两次作业(一):R基础数据处理】

总言 两次作业汇报&#xff1a;其一。    文章目录总言1、作业一&#xff1a;1.1 、任务一&#xff1a;各项数据建立1.2 、任务二&#xff1a;去除缺失值1.3 、任务三&#xff1a;返回性别为女生&#xff0c;年龄<20的学生及成绩1.4、 任务四&#xff1a;统计性别为女生&a…

【Python百日进阶-数据分析】Day149 - plotly直方图:go.histogram()

文章目录4.2 利用 go.Histogram 的直方图4.2.1 基本直方图4.2.2 归一化直方图4.2.3 水平直方图4.2.4 叠加直方图4.2.5 堆叠直方图4.2.6 风格直方图4.2.7 直方图条形文本4.2.8 累积直方图4.2.9 指定聚合函数4.2.10 自定义分箱4.2.11 在直方图之间共享 bin4.2.12 按类别顺序排序直…

深度学习(一)-环境安装

前言&#xff1a; 最近电脑重装了下系统&#xff0c;然后所有环境啥的都得重新配置一遍&#xff0c;刚好趁着这个时间记录下整个环境的配置过程 注意&#xff1a;本文记录的仅为window系统的配置过程! 一、Anaconda安装及相关配置 Anaconda下载地址&#xff0c;根据需要选择需…

TypeScript 中 Class incorrectly implements interface 错误

当一个类在没有指定接口上定义的所有属性和方法的情况下实现接口时&#xff0c;会发生错误“Class incorrectly implements interface”。 要解决该错误&#xff0c;需要确保定义并键入接口的所有必需属性和方法。 下面是产生上述错误的示例代码 interface Employee {id: num…

Linux学习记录——유 gcc/g++基础知识

文章目录一、程序翻译二、gcc使用1、-o2、预处理-E3、编译-S4、汇编-c5、链接三、库四、库的部分实际操作五、Linux项目自动化构建工具 make/Makefile1、规则一、程序翻译 C语言中&#xff0c;写出代码后&#xff0c;编译器会经过四个阶段才会生成可执行文件。 预处理&#x…

计算数组中元素的加权平均值 numpy.average()

【小白从小学Python、C、Java】【计算机等级考试500强双证书】【Python-数据分析】计算数组中元素的加权平均值numpy.average()[太阳]选择题对于以下python代码最后输出的结果是?import numpy as npa np.array([1, 2, 3, 4])print("【显示】a")print(a)print("…

如何进行Java 单元测试

什么是单元测试 维基百科中是这样描述的&#xff1a;在计算机编程中&#xff0c;单元测试又称为模块测试&#xff0c;是针对程序模块来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中&#xff0c;一个单元就是单个程序、函数、过程等&#xff1b;…

架构师课程笔记day04——Nginx

大纲 1.从单体到集群过渡 2.Nginx 2.1什么是nginx 2.2常见服务器 2.3nginx在架构中所处位置 2.4使用率&#xff0c;性能&#xff0c;市场占有率等信息 2.5正反向代理啥意思 正向代理 反向代理 示例 2.6安装步骤 Nginx安装步骤 常用命令等 2.7请求链路 2.8进程模型 通用模型 …