双链表

双链表是一种常见的链表数据结构,它具有每个节点除了存储数据外,还保存指向前一个节点和后一个节点的指针。这种结构使得双链表具有灵活的插入和删除操作,能够在 O(1) 时间复杂度内完成。

算法原理:

  1. 节点结构:每个节点包含两个指针,一个指向前一个节点,一个指向后一个节点,以及一个数据域,用来存储节点数据。

  2. 头节点和尾节点:双链表通常有一个头节点和一个尾节点,它们的指针分别指向链表的第一个节点和最后一个节点,这样可以便于在链表两端进行插入和删除操作。

操作步骤:

  1. 初始化:创建一个空的双链表,初始化头节点和尾节点,使它们的指针均为空。

  2. 节点插入

    • 在链表的特定位置插入一个新节点时,需要修改该位置前后节点的指针,将新节点正确连接到链表中。
    • 插入操作包括更新当前节点的前后指针以及更新前后节点的指针,确保链表的连续性。
  3. 节点删除

    • 从双链表中删除一个节点时,需要修改待删除节点前后节点的指针,将它们正确连接起来,从而将待删除节点从链表中移除。
  4. 遍历

    • 从头节点或尾节点开始,根据节点的后继指针或前驱指针依次访问链表中的每个节点,以完成遍历操作。

双链表的特点是可以在任意位置高效地插入和删除节点,同时支持正向和反向遍历。这使得双链表在需要频繁插入和删除操作的场景中非常有用。

问题:

要实现双链表,需支持以下五种操作:

  1. 在双链表的最左侧插入一个数。
  2. 在双链表的最右侧插入一个数。
  3. 删除第 k 个插入的数。
  4. 在第 k 个插入的数左侧插入一个数。
  5. 在第 k 个插入的数右侧插入一个数。

在执行 M 次操作后,从左到右输出整个链表。

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

输入格式为:
  • 第一行包含一个整数 M,表示操作次数。
  • 接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:
    • L x:在链表的最左端插入数 x。
    • R x:在链表的最右端插入数 x。
    • D k:将第 k 个插入的数删除。
    • IL k x:在第 k 个插入的数左侧插入一个数 x。
    • IR k x:在第 k 个插入的数右侧插入一个数 x。
输入样例:
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;

int value[N], l[N], r[N], idx;

// 初始化双链表
void init(){
    r[0] = 1, l[1] = 0; // 设置双链表的初始状态,0是左端哨兵,1是右端哨兵
    idx = 2; // 索引从2开始,因为0和1被用作哨兵
}

// 在第 k 个插入的数右侧插入一个数 x
void insert(int k, int x){
    value[idx] = x;
    l[idx] = k;
    r[idx] = r[k];
    l[r[k]] = idx;  
    r[k] = idx++; // 插入后,k右侧的数就是新的数x
}

// 删除第 k 个插入的数
void remove(int k){
    r[l[k]] = r[k];    
    l[r[k]] = l[k]; // 删除k节点后,调整前后节点的指向
}

int main(){
    int m;
    cin >> m;
    
    init(); // 初始化双链表
    
    while(m--){
        int k, x;
        string op;
        
        cin >> op;
        if(op == "L"){ // 在最左端插入
            cin >> x;
            insert(0, x);
        }
        else if(op == "R"){ // 在最右端插入
            cin >> x;
            insert(l[1], x);
        }
        else if(op == "D"){ // 删除第 k 个插入的数
            cin >> k;
            remove(k + 1); // 第 k 个插入的数实际索引是 k + 1
        }
        else if(op == "IL"){ // 在第 k 个插入的数左侧插入一个数
            cin >> k >> x;
            insert(l[k + 1], x);
        }
        else { // 在第 k 个插入的数右侧插入一个数
            cin >> k >> x;
            insert(k + 1, x);
        }
    }
    
    // 从左到右输出整个链表
    for(int i = r[0]; i != 1; i = r[i]) 
        cout << value[i] << ' ';
    
    return 0;
}

双链表是一种数据结构,相较于单链表,每个节点不仅保存了指向下一个节点的指针,还保存了指向前一个节点的指针。这使得双链表在某些情况下更加灵活,能够更方便地进行前向和后向遍历。以下是双链表的总结:

特点:

  1. 前后指针: 每个节点包含两个指针,分别指向前一个节点和后一个节点。
  2. 哨兵节点: 通常在双链表的两端设置哨兵节点,简化边界条件的处理。

基本操作:

  1. 插入操作: 在指定位置的节点前或后插入新节点,通过调整前后节点的指针完成插入。
  2. 删除操作: 删除指定位置的节点,同样通过调整前后节点的指针完成删除。
  3. 初始化: 设置哨兵节点,初始化前后指针。

应用场景:

  1. 前后遍历: 可以方便地实现从前向后或从后向前遍历链表。
  2. 插入和删除效率: 在涉及频繁插入和删除操作的场景中,双链表比单链表更高效。

代码实现注意事项:

  1. 初始化: 在初始化时设置哨兵节点,确保链表的边界条件被妥善处理。
  2. 索引转换: 在实际应用中,第 k 个插入的数对应的节点索引可能需要加一或减一,具体取决于索引从 0 还是 1 开始。

总体而言,双链表在某些场景下具有优势,但在内存占用上相对于单链表会更多。根据具体应用需求,选择合适的数据结构以提高操作效率。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值