03链表+栈+队列(D2_栈)

目录

讲解一:栈

一、基本介绍

二、代码示例

------------------------------

讲解二:单调栈

一、基本介绍

二、适用场景

三、情形示例

1. 寻找左边第一个小于它的数

2. 寻找左边第一个小于它的数的下标

3. 寻找右边第一个大于它的数

4. 寻找右边第一个大于它的数的下标

七、知识小结


讲解一:栈

一、基本介绍

栈(stack)是一个有序线性表,只能在表的一端(称为栈顶,top)执行插人和删除操作,后插入的元素

将是第一个被删除。所以,栈也称为后进先出(LastInFirstOut,LIFO)或先进后出(First In Last

Out,FILO)线性表。

  • 一个称为入栈(push),表示在栈中插入一个元素;
  • 另一个称为出栈(pop),表示从栈中删除一个元素。

试图对一个空栈执行出栈操作称为下溢(underflow);

试图对一个满栈执行人栈操作称为溢出(overflow)。

通常,溢出和下溢均认为是异常。

二、代码示例

示意图

实现

  • 使用数组实现的叫静态栈
  • 使用链表实现的叫动态栈

------------------------------

讲解二:单调栈

一、基本介绍

🍐单调栈分为单调递增栈和单调递减栈,通过使用单调栈我们可以访问到最近一个比它大(小)

的元素。

🍊 单调递增栈:单调递增栈就是从栈底到栈顶数据是依次递增,通常是寻找某方向第一个比它小

的元素。

🍊 单调递减栈:单调递减栈就是从栈底到栈顶数据是依次递减,通常是寻找某方向第一个比它大

二、适用场景

什么情况适合用单调栈来解决实际问题呢?

通常是在数组中需要通过比较前后元素的大小关系来找最近的比它大(小)的元素问题时,可以使

用单调栈进行求解。

三、情形示例

1. 寻找左边第一个小于它的数

题目描述: 给定一个长度为 n ≤ 10 ^5 的数组 a,输出每个数左边第一个比它小的数,如果不存在则输出 − 1。

【常规思路】

双重循环来做,第一重循环枚举每个数,第二重循环找出指定区间类第一个满足条件的数。

然而这种做法的复杂度是O(n^2)利用单调栈,我们可以将复杂度降低至O(n)。

  • 在指针 i 从左往右遍历的过程中,我们可以用一个栈来保存 i 左边的所有元素(不包括i指向的元素),下标越大的元素越接近栈顶,
    下标越小的元素越接近栈底。
  • 每次我们访问栈顶,只要栈顶元素大于等于 a [ i ],我们就将栈顶元素弹出,直至栈顶元素小于 a [ i ],此时输出栈
    顶元素并将 a [ i ] 压入栈中。 由于栈中保存了 i 左边的所有元素,所以只要有答案,则答案一定在栈中。
  • 由于每个元素一定会被压入一次且至多弹出一次,因此操作次数至多是2n,故总时间复杂度为O(n)。

Java代码如下:

public class Main {
    static int N = (int) (1e5 + 10);
    static int[] a = new int[N], ans = new int[N];
    static Deque<Integer> stack = new LinkedList<>();
    static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    public static void main(String[] args) throws IOException {
        in.nextToken();
        int n = (int) in.nval;
        for (int i = 0; i < n; i++) {//存数组
            in.nextToken();
            a[i] = (int) in.nval;
        }

        for (int i = 0; i < n; i++) {//单调栈模板(注意是数值)
            while (!stack.isEmpty() && stack.peekFirst() >= a[i]) stack.poll();
            if (!stack.isEmpty()) ans[i] = stack.peekFirst();
            else ans[i] = -1;
            stack.push(a[i]);
        }

        for (int i = 0; i < n; i++) {//输出结果
            System.out.print(ans[i] + " ");
        }
    }
}

下面,我们再来看看其他几种情况,基本上都是大同小异。

2. 寻找左边第一个小于它的数的下标

public class Main {
    static int N = (int) (1e5 + 10);
    static int[] a = new int[N], ans = new int[N];
    static Deque<Integer> stack = new LinkedList<>();
    static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    public static void main(String[] args) throws IOException {
        in.nextToken();
        int n = (int) in.nval;
        for (int i = 0; i < n; i++) {//存数组
            in.nextToken();
            a[i] = (int) in.nval;
        }

        for (int i = 0; i < n; i++) {//单调栈模板(注意是下标)
            while (!stack.isEmpty() && a[stack.peekFirst()] >= a[i]) stack.poll();//注意这里的第二个条件是a[stack.peekFirst()] 而不是stack.peekFirst()
            if (!stack.isEmpty()) ans[i] = stack.peekFirst();
            else ans[i] = -1;
            stack.push(i);//这里也不再是a[i],而是存储对应的下标
        }

        for (int i = 0; i < n; i++) {//输出结果
            System.out.print(ans[i] + " ");
        }
    }
}

3. 寻找右边第一个大于它的数

题目描述: 给定一个长度为 n ≤ 10 ^5 的数组 a,输出每个数右边第一个比它大的数,如果不存在

则输出 − 1。之前我们是在一个数的左边去寻找,所以让栈去保存这个数左边的所有数,类似地,

现在需要让栈去保存这个数右边的所有数考虑将数组翻转(倒序遍历),因此情形三变成了「寻找

一个数左边第一个大于它的数」,属于情形一

Java代码如下:

public class Main {
    static int N = (int) (1e5 + 10);
    static int[] a = new int[N], ans = new int[N];
    static Deque<Integer> stack = new LinkedList<>();
    static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    public static void main(String[] args) throws IOException {
        in.nextToken();
        int n = (int) in.nval;
        for (int i = 0; i < n; i++) {//存数组
            in.nextToken();
            a[i] = (int) in.nval;
        }

        for (int i = n - 1; i >= 0; i--) {//单调栈模板(注意是数值)
            while (!stack.isEmpty() && stack.peekFirst() <= a[i]) stack.poll();
            if (!stack.isEmpty()) ans[i] = stack.peekFirst();
            else ans[i] = -1;
            stack.push(a[i]);
        }

        for (int i = 0; i < n; i++) {//输出结果
            System.out.print(ans[i] + " ");
        }
    }
}

4. 寻找右边第一个大于它的数的下标

题目描述: 给定一个长度为 n ≤ 10 ^5 的数组 a,输出每个数右边第一个比它大的数的下标,如果

不存在则输出− 1。结合情形二和情形三即可写出代码。

Java代码如下:

public class Main {
    static int N = (int) (1e5 + 10);
    static int[] a = new int[N], ans = new int[N];
    static Deque<Integer> stack = new LinkedList<>();
    static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    public static void main(String[] args) throws IOException {
        in.nextToken();
        int n = (int) in.nval;
        for (int i = 0; i < n; i++) {//存数组
            in.nextToken();
            a[i] = (int) in.nval;
        }

        for (int i = n-1; i >= 0; i--) {//单调栈模板(注意是下标)
            while (!stack.isEmpty() && a[stack.peekFirst()] <= a[i]) stack.poll();
            if (!stack.isEmpty()) ans[i] = stack.peekFirst();
            else ans[i] = -1;
            stack.push(i);
        }

        for (int i = 0; i < n; i++) {//输出结果
            System.out.print(ans[i] + " ");
        }
    }
}

总结以上情形:

  • 遍历顺序(以怎样的顺序遍历数组 a );
  • 比较方式(如何比较当前元素和栈顶元素);
  • 栈中存储的是什么(是元素本身还是元素的

七、知识小结

初学一门技术时,总有些许的疑惑,别怕,它们是我们学习路上的点点繁星,帮助我们不断成长。

文章粗浅,希望对大家有帮助!

<think>好的,用户想了解链表ADT的定义、实现和使用方法。首先,我需要回忆链表的基本概念。链表是一种线性数据结构,由节点组成,每个节点包含数据和指向下一个节点的指针。ADT即抽象数据类型,它定义了数据结构的逻辑行为,而不涉及具体实现。 用户提供的引用中有几个C语言和C++的例子,比如list.h头文件的结构体定义,还有替换节点的函数。这说明用户可能对链表的实现细节感兴趣,特别是如何用C语言来实现ADT。我需要先解释链表ADT的组成部分,比如节点结构体、链表结构体,以及常见的操作函数。 然后,根据引用内容,用户提到了链表ADT的设计模板,比如初始化链表、添加数据、删除节点等操作。需要确保覆盖这些基本操作,并给出示例代码。比如,引用1中的list.h定义了链表结构体和各种函数原型,应该参考这个结构来说明如何实现每个函数。 另外,用户的问题可能涉及ADT的使用方法,比如如何创建链表、插入元素、遍历等。需要分步骤解释,并可能提供代码示例。例如,newList函数用于创建新链表,而newData用于添加数据。同时要注意错误处理,比如函数返回成功或失败的标志。 还需要注意用户提到的不同引用中的实现差异,比如C和C++的区别。例如,引用2提到C++版的实现,而引用1和3是C语言版本。可能需要指出这些差异,但用户主要关注的是C语言实现,所以应侧重C的示例。 最后,生成相关问题时,要考虑用户可能进一步想了解的内容,比如链表与数组的比较、内存管理、时间复杂度或常见错误等。确保问题覆盖实现细节和应用场景,帮助用户深入理解。 总结步骤:先定义链表ADT的组成,再分步讲解实现中的关键点,如节点结构、操作函数,最后说明使用方法并给出示例。同时结合引用中的代码结构,确保回答准确。</think>### 链表ADT的实现与使用详解 #### 一、链表ADT的定义 链表ADT(Abstract Data Type)定义了以下核心组件: 1. **数据节点**:包含数据域和指针域的结构体,例如: ```c typedef struct NODE { LISTDATA data; // 数据域(可自定义数据类型) struct NODE* next; // 指针域 } node; ``` 2. **链表结构体**:记录链表头尾指针和长度信息,例如引用[1]中的定义: ```c typedef struct LISTSTR { node* head; // 头节点指针 node* end; // 尾节点指针 int len; // 链表长度 } listStruct; ``` #### 二、关键操作的实现步骤 1. **初始化链表** ```c list newList(void) { list newL = (list)malloc(sizeof(listStruct)); if (newL) { newL->head = NULL; newL->end = NULL; newL->len = 0; } return newL; // 返回初始化后的链表指针[^1] } ``` 2. **插入数据** ```c int newData(list L, LISTDATA data) { node* newNode = (node*)malloc(sizeof(node)); if (!newNode) return -1; newNode->data = data; newNode->next = NULL; if (L->len == 0) { // 空链表插入 L->head = newNode; L->end = newNode; } else { // 非空链表尾部插入 L->end->next = newNode; L->end = newNode; } L->len++; return 0; } ``` 3. **删除节点** ```c int delementList(list L, int index) { if (index < 0 || index >= L->len) return -1; node *prev = NULL, *curr = L->head; for (int i = 0; i < index; i++) { // 定位目标节点 prev = curr; curr = curr->next; } if (prev) prev->next = curr->next; // 处理中间节点 else L->head = curr->next; // 处理头节点 if (curr == L->end) L->end = prev; // 更新尾指针 free(curr); L->len--; return 0; } ``` #### 三、典型使用场景 1. **动态数据存储**:适用于频繁插入/删除的场景,例如实时日志记录系统[^2] 2. **内存敏感场景**:不需要连续内存空间分配,可减少内存碎片 3. **实现高级数据结构**:如队列的基础实现 #### 四、代码使用示例 ```c // 创建链表并插入数据 list myList = newList(); LISTDATA d1 = {'A'}, d2 = {'B'}; newData(myList, d1); newData(myList, d2); // 遍历链表 node* current = myList->head; while (current != NULL) { printf("%c ", current->data.c); current = current->next; } // 输出:A B ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodingW丨编程之路

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值