scau18925双向链表-试卷排序

Description

老师要将N张试卷重新排序,每张试卷都有编号为1∼N,采取如下的方法:
先将编号1的试卷放进队列,剩下从第2张到第N张依次放入队列,
放入的时候老师会把编号i的试卷插入到编号为x试卷之前或之后(x<i),
在老师完成这N-1次操作之后,请输出试卷序列的最终编号。



 

输入格式

第1行为一个正整数N,表示了有N张试卷。

第2-N行,第i行包含两个整数x,p,其中x为小于i的正整数,p为0或者1。
若p为0,则表示将编号为i的试卷放入编号为x试卷的左边,为1则放入x试卷的右边。


 

输出格式

输出N个整数,表示完成插入后试卷系列中试卷的编号(1<=N<=100000)。


 

输入样例

4
1 0
2 1
1 0


 

输出样例

2 3 4 1


 

提示

4张试卷。第二行1,0,将编号2的试卷放入编号1的左边,则序列为2  1;
第三行2,1,将编号3试卷放入编号2试卷的右边,则序列为2 3 1;
第四行1 0,将编号4的试卷放入编号1的左边,则序列为2 3 4 1。

#include <iostream>
#include <vector>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int N;
    cin >> N;
    
    vector<int> prev(N + 1), next(N + 1); // prev[i] 和 next[i] 分别表示节点i的前驱和后继
    
    // 初始化第一个节点(编号为1)
    prev[1] = 0;
    next[1] = 0;
    int head = 1, tail = 1; // 链表头和尾的编号
    
    for (int i = 2; i <= N; ++i) {
        int x, p;
        cin >> x >> p;
        
        if (p == 0) { // 插入到x的左边
            prev[i] = prev[x];    // i的前驱是x的原前驱
            next[i] = x;          // i的后继是x
            if (prev[x] != 0) {   // 如果x不是头节点
                next[prev[x]] = i; // 原前驱的后继更新为i
            } else {
                head = i;          // 更新头节点为i
            }
            prev[x] = i;           // x的前驱更新为i
        } else { // 插入到x的右边
            next[i] = next[x];    // i的后继是x的原后继
            prev[i] = x;          // i的前驱是x
            if (next[x] != 0) {   // 如果x不是尾节点
                prev[next[x]] = i; // 原后继的前驱更新为i
            } else {
                tail = i;          // 更新尾节点为i
            }
            next[x] = i;           // x的后继更新为i
        }
    }
    
    // 从头节点开始遍历输出
    int curr = head;
    while (curr != 0) {
        cout << curr << " ";
        curr = next[curr];
    }
    
    return 0;
}

prev 数组的存在意义

是为了支持 双向链表的操作,它在某些场景下非常有用。以下是 prev 数组的意义和具体作用:

1. 支持双向遍历

  • 单向链表

    • 只能从头节点开始,沿着 next 指针单向遍历。

    • 无法从尾节点反向遍历。

  • 双向链表

    • 既可以从头节点开始,沿着 next 指针正向遍历。

    • 也可以从尾节点开始,沿着 prev 指针反向遍历。

示例

假设链表结构如下:

复制

prev[1] = 0, next[1] = 2
prev[2] = 1, next[2] = 3
prev[3] = 2, next[3] = 0
  • 正向遍历(使用 next):

    复制

    1 -> 2 -> 3
  • 反向遍历(使用 prev):

    复制

    3 -> 2 -> 1

2. 支持高效的插入和删除操作

在双向链表中,prev 数组使得插入和删除操作更加高效和灵活。

插入操作
  • 插入到目标节点的左侧

    1. 新节点的 prev 指向目标节点的原前驱。

    2. 新节点的 next 指向目标节点。

    3. 目标节点的原前驱的 next 指向新节点。

    4. 目标节点的 prev 指向新节点。

  • 插入到目标节点的右侧

    1. 新节点的 prev 指向目标节点。

    2. 新节点的 next 指向目标节点的原后继。

    3. 目标节点的原后继的 prev 指向新节点。

    4. 目标节点的 next 指向新节点。

如果没有 prev 数组,插入到左侧时需要遍历链表找到目标节点的前驱,时间复杂度为 O(N)。而有了 prev 数组,插入操作可以在 O(1) 时间内完成。

删除操作
  • 删除目标节点

    1. 目标节点的前驱的 next 指向目标节点的后继。

    2. 目标节点的后继的 prev 指向目标节点的前驱。

如果没有 prev 数组,删除操作需要遍历链表找到目标节点的前驱,时间复杂度为 O(N)。而有了 prev 数组,删除操作可以在 O(1) 时间内完成。


3. 维护链表完整性

  • 在双向链表中,prev 和 next 数组共同维护链表的完整性。

  • 每次插入或删除节点时,都需要同时更新 prev 和 next 数组,确保链表的前后关系正确。


4. 应用场景

  • 需要反向遍历的场景

    • 例如,从链表尾部开始查找或输出数据。

  • 频繁插入和删除的场景

    • 例如,实现 LRU 缓存(最近最少使用缓存)时,需要快速移动节点到链表头部或删除尾部节点。

  • 需要快速查找前驱节点的场景

    • 例如,某些算法需要同时访问当前节点的前驱和后继。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值