数据结构__链表,栈,队列

数据结构:是相互之间存在一种或多种特定关系的数据元素的集合。

目录

基本概念

逻辑结构与物理结构

数组实现

单链表

826. 单链表

 双链表

827. 双链表

 栈

828. 模拟栈

 830. 单调栈

 队列

829. 模拟队列

 154. 滑动窗口


基本概念

数据:是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合。(数据不仅仅包括整型,实型等数值类型,还包括字符及声音,图像,视频等非数值类型。

数据元素:是组成数据的,有一定意义的基本单位,在计算机中通常作为整体处理,也被称为记录。

数据项:一个数据元素可以有若干个数据项组成。

注:数据项是数据不可分割的最小单位。但数据元素才是数据结构中建立数据模型的着眼点。

数据对象:是性质相同的数据元素的集合,是数据的子集。

逻辑结构与物理结构

(1). 逻辑结构:是指数据对象中数据元素之间的相互关系。

        1.根据数据元素之间关系的不同特性,通常有四种基本结构:集合结构,线性结构,树形结构和图状结构。

        a. 集合结构:集合结构中的数据元素除了同属于一个集合外,它们之间没有其他关系。

         b.线性结构:线性结构中的数据元素中间是一对一的关系。

        c.树形结构:树形结构中的数据元素之间存在一种一对多的层次关系。

        d. 图状结构:图形结构的数据元素是多对多的关系。

        2.根据数据元素之间关系的不同特性,数据结构又可分为线性结构和非线性结构。

        a.线性结构:线性表,栈,队列,字符串,数组和广义表。

        b.非线性结构:树和图。

(2).物理结构(存储结构):是指数据的逻辑结构在计算机中的存储形式。

        a.顺序存储结构:是把数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的。

        b.链式存储结构:是把数据元素存放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的。

注:存储器主要是针对内存而言的,像硬盘,软盘,光盘等外部存储器的数据组织通常用文件结构来描述。

注:逻辑结构是面向问题的,而物理结构就是面向计算机的。

数组实现

单链表

826. 单链表

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

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

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

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

输入格式

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

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

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

输出格式

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

数据范围

1≤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;


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

int main()
{
    int m;
    cin >> m;

    init();

    while (m -- )
    {
        int k, x;
        char op;

        cin >> op;
        if (op == 'H')
        {
            cin >> x;
            add_to_head(x);
        }
        else if (op == 'D')
        {
            cin >> k;
            if (!k) head = ne[head];
            else 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;
}

 双链表

827. 双链表

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

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

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

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

输入格式

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

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

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

输出格式

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

数据范围

1≤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 <bits/stdc++.h>

using namespace std;

const int N = 100010;

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

//初始化
void init()
{
    l[1] = 0, r[0] = 1;  //0是左端点,1是右端点
    idx = 2;  //idx已经用掉了0和1
}

//在节点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 ++;
}

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

int main()
{
    init();
    
    cin >> n;
    while (n --)
    {
        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);
        }
        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]) printf("%d ", e[i]);
    
    return 0;
}

 栈

828. 模拟栈

实现一个栈,栈初始为空,支持四种操作:

  1. push x – 向栈顶插入一个数 x;
  2. pop – 从栈顶弹出一个数;
  3. empty – 判断栈是否为空;
  4. query – 查询栈顶元素。

现在要对栈进行 M 个操作,其中的每个操作 3 和操作 4 都要输出相应的结果。

输入格式

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

接下来 M 行,每行包含一个操作命令,操作命令为 push xpopemptyquery 中的一种。

输出格式

对于每个 empty 和 query 操作都要输出一个查询结果,每个结果占一行。

其中,empty 操作的查询结果为 YES 或 NOquery 操作的查询结果为一个整数,表示栈顶元素的值。

数据范围

1≤M≤100000,
1≤x≤10^9
所有操作保证合法。

输入样例:

10
push 5
query
push 6
pop
query
pop
empty
push 4
query
empty

输出样例:

5
5
YES
4
NO
#include <bits/stdc++.h>

using namespace std;

const int N = 100010;

int n;
int stk[N], tt;  // 用tt表示栈顶所在的索引 初始时tt为0,表示栈为空

int main()
{
    cin >> n;
    while (n --)
    {
        string op;
        int x;
        
        cin >> op;
        if (op == "push")  //栈顶所在索引往后移动一格,然后放入x
        {
            cin >> x;
            stk[++ tt] = x;
        }
        else if (op == "pop") tt --;
        else if (op == "query") printf("%d\n", stk[tt]);
        else cout << (tt ? "NO" : "YES") << endl;
    }
    
    return 0;
}

 830. 单调栈

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

输入格式

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

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

输出格式

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

数据范围

1≤N≤10^5
1≤数列中元素≤10^9

输入样例:

5
3 4 2 7 5

输出样例:

思路:由于求第i个数左边的第一个比它小的数,因此若当前数小于它左边的数,则该数左边的数一定不会被输出,则移除。所以维护一个单调递增栈。

        用单调递增栈,当该元素可以入栈的时候,栈顶元素就是它左侧第一个比它小的元素。 

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;

int n;
int stk[N], tt;

int main()
{
    cin >> n;
    while (n --)
    {
        int x;
        cin >> x;
        while (tt && stk[tt] >= x) tt --;  //栈顶元素不小于当前元素x,则出栈
        if (! tt) printf("%d ", -1);  //栈空,即没有元素比当前元素x小 输出-1
        else printf("%d ", stk[tt]);  //输出栈顶,即是左边第一个比当前元素x小的数
        
        stk[++ tt] = x;  //更新栈 将当前元素x加入栈中
    }
    
    return 0;
}

 队列

829. 模拟队列

实现一个队列,队列初始为空,支持四种操作:

  1. push x – 向队尾插入一个数 x;
  2. pop – 从队头弹出一个数;
  3. empty – 判断队列是否为空;
  4. query – 查询队头元素。

现在要对队列进行 M 个操作,其中的每个操作 3 和操作 4 都要输出相应的结果。

输入格式

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

接下来 M 行,每行包含一个操作命令,操作命令为 push xpopemptyquery 中的一种。

输出格式

对于每个 empty 和 query 操作都要输出一个查询结果,每个结果占一行。

其中,empty 操作的查询结果为 YES 或 NOquery 操作的查询结果为一个整数,表示队头元素的值。

数据范围

1≤M≤100000
1≤x≤10^9
所有操作保证合法。

输入样例:

10
push 6
empty
query
pop
empty
push 3
push 4
pop
query
push 6

输出样例:

NO
6
YES
4
#include <bits/stdc++.h>

using namespace std;

const int N = 100010;

int n;
// [hh, tt]之间为队列
int q[N], hh, tt = -1;  //hh队头,tt队尾

int main()
{
    cin >> n;
    while (n --)
    {
        int x;
        string op;
        cin >> op;
        
        if (op == "push")
        {
            cin >> x;
            q[++ tt] = x;
        }
        else if (op == "query") printf("%d\n", q[hh]);
        else if (op == "empty") printf("%s\n", (tt >= hh ? "NO" : "YES"));
        else hh ++;
    }
    
    return 0;
}

 154. 滑动窗口

给定一个大小为 n≤10^6 的数组。

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

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

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

以下是一个例子:

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

窗口位置最小值最大值
[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

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

输入格式

输入包含两行。

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

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

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

输出格式

输出包含两个。

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

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

输入样例:

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

输出样例:

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

思路(以求最小值为例)

由于输出的是滑动窗口内的最小值,若滑动窗口中当前的值小于左边的值,则该值左边的数不可能被输出,则移除。所以维护一个单调递增队列。

该队列存储原数组对应值的下标。在队列中,这些下标按照从小到大的顺序被存储,并且它们对应在数组a中的值是严格单调递增的。

当滑动窗口向右移动时,将新的元素放入队列中。

为了维持队列的性质,需要不断地将新的元素与队尾的元素相比较,如果新元素小于等于队尾元素,那么队尾的元素就可以被永久地移除,将其弹出队列。直到队列为空或者新的元素小于队尾的元素。此时将新的元素下标加入队列中。

由于队列中下标对应的元素值是严格单调递增的,因此此时队首下标对应的元素就是滑动窗口中的最小值。

窗口向右移动的时候,需要从队首弹出元素保证队列中的所有元素都是窗口中的,即队头元素在窗口的左边的时候,弹出队头。

#include <bits/stdc++.h>

using namespace std;

const int N = 1000010;

int n, k;
int a[N], q[N];

int main()
{
    cin >> 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 ++)
    {
        if (hh <= tt && q[hh] < i - k + 1) 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 && q[hh] < i - k + 1) hh ++;
        while (hh <= tt && a[q[tt]] <= a[i]) tt --;
        q[++ tt] = i;
        if (i >= k - 1) printf("%d ", a[q[hh]]);
    }
    
    return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值