链表重排--双向链表

说明

这题不想清楚还是有很多坑的,改了两次才正确,第一次3、4测试点timeout,因为用了两个循环, O ( n 2 ) O(n^2) O(n2)的算法。第二次多余结点处理错误,最后结束输出-1这个弄了半天。总的来说,这题算细节题,边界的细节处理真的要命。

本程序保证正确:

image.png

题目来源:

7-3 重排链表
分数 25
作者 陈越
单位 浙江大学

题目

给定一个单链表 L 1 L_1 L1 L 2 L_2 L2→⋯→ L n − 1 L_{n-1} Ln1→_L_ n _n n,请编写程序将链表重新排列为 L n → L 1 → L n − 1 → L 2 → ⋯ L_n→L_1→L_{n−1}→L_2→⋯ LnL1Ln1L2。例如:给定_L_为1→2→3→4→5→6,则输出应该为6→1→5→2→4→3。

输入格式:

每个输入包含1个测试用例。每个测试用例第1行给出第1个结点的地址和结点总个数,即正整数 N ≤ 1 0 5 N≤10^5 N105。结点的地址是5位非负整数,NULL地址用−1表示。
接下来有_N_行,每行格式为:
Address Data Next
其中Address是结点地址;Data是该结点保存的数据,为不超过 1 0 5 10^5 105的正整数;Next是下一结点的地址。题目保证给出的链表上至少有两个结点。

输出格式:

对每个测试用例,顺序输出重排后的结果链表,其上每个结点占一行,格式与输入相同。

输入样例:

00100 6
00000 4 99999
00100 1 12309
68237 6 -1
33218 3 00000
99999 5 68237
12309 2 33218

输出样例:

68237 6 00100
00100 1 99999
99999 5 12309
12309 2 00000
00000 4 33218
33218 3 -1

代码长度限制
16 KB
时间限制
500 ms
内存限制
64 MB

思路

  1. 总的思路

思考几个问题:怎么存数据?怎么重排链表?怎么输出结果(即遍历数据)?

  • 因为涉及到头和尾的频繁操作,想到用双向链表存储;
  • 重排链表,em…我想到最简单的就是在读取的时候按照题目要求的顺序读取就好了;我的思路是每次读取两个结点,tail和head,先尾后头,然后每次删除head和tail(就是head后移,tail前移)。具体可以结合代码看看。
  • 那么输出结果的方法就不用太考虑了,主要就是结尾输出-1,就是判断什么时候结束的问题了。
  1. 定义链表结构体 Node:

主要就是添加了一个前驱结点。这里用string来存地址,省的格式化的时候麻烦。
然后就是用unordered_map<string, Node> nodes来模拟内存,存储这些数据。
当前结点地址值就是这个map的key。

struct Node {
    string addr;  // 节点地址
    int data;     // 节点数据
    string next;  // 下一个节点地址
    string pre;   // 前驱指针
};

unordered_map<string, Node> nodes;
  1. 读取链表与构造双向指针:
  2. 先按照输入格式,读取单向链表。
for (int i = 0; i < N; ++i) {
    Node node;
    cin >> node.addr >> node.data >> node.next;
    nodes[node.addr] = node;
}
  1. 找到尾结点,在这过程中设置前驱指针。主要就是用快慢指针的方法,记录前一个结点的地址。
nodes[head].pre = "-1";
// 找到尾结点,并添加前驱指针
while (nodes[cur].next != "-1") {
    tail = cur;
    cur = nodes[cur].next;
    nodes[cur].pre = nodes[tail].addr;
}
  1. 先尾后头输出结果

先说明,cur为本轮的末尾结点,就是待删除结点,tail是删除cur后的尾结点,是下一轮循环的尾结点。head就是当轮循环的头结点。后面要是思绪乱了,把这三个变量内涵再想一想。

这里是最麻烦的。要考虑什么时候结束?(因为可能存在多余结点)结束后最后一个输出的时-1的问题。同时怎么移动head和tail两个指针也是关键。
关键点是理解if (nodes[head].next == "-1")行head的下一个为-1时结束了,因为前面有 nodes[tail].next = "-1";这段代码,tail是本次循环结束后下一循环的尾部,tail如果和head相等了,那就说明全部都遍历过了。因此if (nodes[head].next == "-1")等价于if (head == tail)
但是还没结束,如果链表是奇数个怎么办?所以又要添加下面这段代码,当cur和head

if (nodes[cur].addr == nodes[head].addr) {
    cout << nodes[cur].addr << " " << nodes[cur].data << " " << "-1";
    break;
}

这一步的代码就是:

for (int i = 0; i < N / 2; ++i) {
    // 先尾后头,找到尾结点
    cout << nodes[cur].addr << " " << nodes[cur].data << " " << nodes[head].addr << endl;
    nodes[tail].next = "-1"; // 删除尾结点

    cout << nodes[head].addr << " " << nodes[head].data << " ";

    // 结束后要输出-1
    if (nodes[head].next == "-1") {
        cout << "-1";
        break;
    }
    else {
        cout << nodes[tail].addr << endl;
    }
    head = nodes[head].next; //删除头结点

    // 刷新cur和tail结点
    cur = tail;
    tail = nodes[tail].pre;


    if (nodes[cur].addr == nodes[head].addr) {
        cout << nodes[cur].addr << " " << nodes[cur].data << " " << "-1";
        break;
    }
}

源代码

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;

struct Node {
    string addr;
    int data;
    string next;
    string pre; // 添加前驱指针
};

int main() {
    string head; 
    cin >> head; // 头指针
    int N; 
    cin >> N;
    string tail=head; // 尾指针
    string cur = head; // 移动指针

    unordered_map<string, Node> nodes;

    for (int i = 0; i < N; ++i) {
        Node node;
        cin >> node.addr >> node.data >> node.next;
        nodes[node.addr] = node;
    }

    nodes[head].pre = "-1";
    // 找到尾结点,并添加前驱指针
    while (nodes[cur].next != "-1") {
        tail = cur;
        cur = nodes[cur].next;
        nodes[cur].pre = nodes[tail].addr;
    }


    for (int i = 0; i < N/2; ++i) {
        // 先尾后头,找到尾结点
        cout << nodes[cur].addr << " " << nodes[cur].data << " " << nodes[head].addr << endl;
        nodes[tail].next = "-1"; // 删除尾结点
        
        cout << nodes[head].addr << " " << nodes[head].data << " ";

        // 结束后要输出-1
        if (nodes[head].next == "-1") {
            cout << "-1";
            break;
        }
        else {
            cout << nodes[tail].addr << endl;
        }
        head = nodes[head].next; //删除头结点

        // 刷新cur和tail结点
        cur = tail;
        tail = nodes[tail].pre;


        if (nodes[cur].addr == nodes[head].addr) {
            cout << nodes[cur].addr << " " << nodes[cur].data << " " << "-1";
            break;
        }
    }

    return 0;
}

  • 22
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值