详解-链表+数组双解法-线性结构3-Reversing Linked List

译文:

给定一个常数 K K K和一个单链表 L L L,你应反转 L L L上每个 K K K元素的链接。例如,假设L为 1 → 2 → 3 → 4 → 5 → 6 1→2→3→4→5→6 123456,如果 K = 3 K = 3 K=3,则须输出 3 → 2 → 1 → 6 → 5 → 4 3→2→1→6→5→4 321654; 如果 K = 4 K = 4 K=4,则须输出 4 → 3 → 2 → 1 → 5 → 6 4→3→2→1→5→6 432156

输入格式:

每个输入文件包含一个测试用例。 对于每种情况,第一行都包含第一节点的地址,正数 N ( ≤ 1 0 5 ) N(≤10^5) N105和正数 K ( ≤ N ) K(≤N) KN,正数 N ( ≤ 1 0 5 ) N(≤10^5) N105是节点总数,正数 K ( ≤ N ) K(≤N) KN是要反转的子列表的长度。 节点的地址是 5 5 5位非负整数, − 1 -1 1表示 N U L L NULL NULL
然后有N行,每行以以下格式描述一个节点:

Address Data Next

其中Address是节点的位置,Data是整数,Next是下一个节点的位置。

输出格式:

对于每种情况,输出排序后的链表。 每个节点占一行,并以与输入中相同的格式打印。

输入例:

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

输出例:

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

解题思路一:链表

本着尽量不使用库函数(使用库函数在刷题比赛的时候确实很使用,但实际上可读性较差,如果实际上一个程序需要多人维护,但是别人没用过你用的库函数的话就不方便维护,还得去学你用的库函数),基于C的去解决问题的原则来审题。

题目说的给出的是链表,因此最开始很自然的考虑使用链表解决问题。
流程大致如下:

  1. 捕获数据
  2. 根据地址构建有序链表
  3. 按照要求反转链表
  4. 输出反转的链表

思路应不难,但是如果实现这几个看似简单的步骤需要注意的地方很多,比如因为数据最大长度为 1 0 5 10^5 105,需要在构建有序链表的时候注意,否则会通不过最大数据的边界测试(别问我怎么知道的,差点因此放弃这个做法);再比如不是所有数据都是有用数据,会有一些数据不属于链表,因此在构建链表的时候也需要注意;再比如如何反转链表等等。

这里只介绍如何反转链表,其他的注意点程序中都有注释,直接看程序即可。
假设现有链表 1 → 2 → 3 1→2→3 123头节点head指向1所在节点,将节点1指向节点2的箭头反转为指向节点1前一个节点(此处为空)的步骤为:

  1. 创建一个节点cur=head,一个空节点pre和一个空节点temp
  2. temp = cur->next
  3. cur->next = pre
  4. pre = cur
  5. cur = temp



    按照这个几个步骤循环遍历即可将需要的箭头反转。
    思路介绍到这,接下来是代码时间。

完整程序如下:

//做法一,链表实现
#include <stdio.h>
#include <stdlib.h>

#define MAX_ADD  100000 //5位地址的上界
typedef struct LNode{
    int add; //节点地址
    int val; //指数值
    int next_add; //下一个节点地址
    struct LNode* next; //下一个节点指针
}Node;
typedef Node *List;

void printList(List L);
List creatList(int TargetAdd, int N, int K);
List reverseList(List input, int length, int K);
void reverseKSubList(List head, int K);

int length = 0; //链表的有效长度

int main(){
    int  InputAdd, N, K;
    List input = NULL, output = NULL;
    scanf("%d %d %d", &InputAdd, &N, &K);
    input = creatList(InputAdd, N, K);
    output = reverseList(input, length, K);
    printList(output);
    return 0;
}

//创建有序链表
List creatList(int TargetAdd, int N, int K){
    List res = (List)malloc(sizeof(Node));
    res->next_add = TargetAdd;
    res->next = NULL;
    List res_temp = res;
    //用于存储地址对应的数据值和下一节点的地址
    int temp_add, data[MAX_ADD], next_add[MAX_ADD];
    for(int i=0; i<N; i++){
        scanf("%d ", &temp_add);
        scanf("%d %d", &data[temp_add], &next_add[temp_add]);
    }
    //创建有序链表
    while(1){
        List temp = (List)malloc(sizeof(List));
        if(res_temp->next_add == -1) //当下一个节点地址为-1时构建结束,其他为垃圾数据
            break;
        temp->add = res_temp->next_add;
        temp->val = data[temp->add];
        temp->next_add = next_add[temp->add];
        res_temp->next = temp;
        res_temp = res_temp->next;
        length++;
    }
    res_temp->next = NULL;
    return res;
}

//反转链表
List reverseList(List input, int length, int K){
    if(K == 1)
        return input;
    int times = length / K; //需要反转的次数
    List res = input;
    for(int i=0; i<times; i++){
        reverseKSubList(input, K); //反转以input为头节点的K个节点
        for(int j=0; j<K; j++)
            input = input->next; //将头节点后移K位
    }
    return res;
}

void reverseKSubList(List head, int K){
    List cur = head->next;
    List pre = NULL;
    for(int i=0; i<K; i++){
        List temp = cur->next;
        cur->next = pre;
        if(!pre)
            cur->next_add = -1;
        else
            cur->next_add = pre->add;
        pre = cur;
        cur = temp;
    }
    head->next->next = cur;
    if(cur) //判断是否有后续节点
        head->next->next_add = cur->add;
    else
        head->next->next_add = -1;
    head->next = pre; //将反转后的链表与剩余的链表连接起来
    head->next_add = pre->add;
}

//打印链表
void printList(List L)
{
   List p=L->next;
   if(p)
   {
       List r;
       r = L;
       while(r->next)
       {
           r = r->next;
           if(r->next_add == -1)
               printf("%05d %d %d\n",r->add, r->val, r->next_add);
           else
               printf("%05d %d %05d\n",r->add, r->val, r->next_add);
       }
   }
   else
   {
    printf("NULL");
   }
}

结果如下:

可以发现因为该做法创建了两个很大的数组,因此当做最大N的边界测试时暂用的内存不是很客观,因此考虑其他做法。

解题思路二:数组

虽然题目给出的要求是反转链表,但链表说白了就是线性表,对于PTA这种黑盒测试,只要程序输出结果对了即可,因此被输出的对象也可以不是链表,因此考虑用其他的线性结构的数组来实现。具体做法如下:

  1. 捕获数据存储到数组中
  2. 根据地址序将数组中的数据交换位置排好序
  3. 根据要求反转数组
  4. 输出数据

这种思路对我而言确实不是那么容易想到,我第一反应还是做法一中的创建有序链表,但是一旦想到之后实现难度还是较做法一简单很多的,我觉得直接上程序就OK了,关键的部分程序中都有注释。

完整程序如下:

//做法二,数组
#include <stdlib.h>
#include <stdio.h>

typedef struct Lnode {
    int add; //节点地址
    int val; //节点值
    int next_add; //下一个节点的地址
}List;

int arrangeList(List data[], int N, int head_add);
void exchange(List data[], int i, int j);
void reverseList(List data[], int eff, int K, int last);
void printList(List data[], int last);

int main(){
    int K, N, head_add, last;
    scanf("%d %d %d", &head_add, &N, &K); //捕获输入的K,N,入口地址
    List data[N];
    for (int i = 0; i<N; i++) { //捕获输入的列表数据
        scanf("%d %d %d", &data[i].add, &data[i].val, &data[i].next_add);
    }
    last = arrangeList(data, N, head_add); //将无序数据排序,返回末尾节点的下标
    reverseList(data, last, K, last); //反转列表
    printList(data, last); //输出列表

    return 0;
}

int arrangeList(List data[], int N, int head_add) {
    int last;
    for (int i = 0; i<N; i++) { //遍历数据,找到头节点并置入列表首位
        if (data[i].add == head_add) {
            exchange(data, i, 0);
            break;
        }
    }
    for (int i = 0; i<N; i++) { //遍历数据,按地址顺排序
        for (int j = i + 1; j<N; j++) {
            if (data[i].next_add == data[j].add) {
                exchange(data, i+1, j);
                break;
            }
        }
    }
    //遍历数据,因为测试集中存在无用数据,因此需找到尾节点下标
    for (int i = 0; i<N; i++) {
        if (data[i].next_add == -1) {
            last = i + 1;
        }
    }
    return last;
}

void exchange(List data[], int i, int j) {
    List temp;
    temp.add = data[j].add;
    temp.val = data[j].val;
    temp.next_add = data[j].next_add;
    data[j].add = data[i].add;
    data[j].val = data[i].val;
    data[j].next_add = data[i].next_add;
    data[i].add = temp.add;
    data[i].val = temp.val;
    data[i].next_add = temp.next_add;
}

void reverseList(List data[], int N, int K, int last) {
    int times = N / K; //反转次数
    if (times) {
        //数据首尾两两交换
        for (int i = 1; i <= times; i++) {
            for (int j = 1; j <= K; j++) {
                if ((i - 1)*K + j - 1 <= i * K - j - 1) {
                    exchange(data, (i - 1) * K + j - 1, i * K - j);
                }
            }
        }
    }
    //更新逆转完的列表各个节点的下一节点地址
    for (int i = 0; i<last; i++){
        data[i].next_add = data[i+1].add;
        if (i == last - 1) {
            data[i].next_add = -1;
        }
    }
}

void printList(List data[], int last){
    //因为测试集存在无用数据,因此尾节点下标之后的数据不需要输出
    for (int i = 0; i<last - 1; i++) {
        printf("%05d %d %05d\n", data[i].add, data[i].val, data[i].next_add);
    }
    printf("%05d %d %d\n", data[last - 1].add, data[last - 1].val, -1);
}

如果有哪里理解错的地方欢迎大家留言交流,如需转载请标明出处。

手工码字码图码代码,如果有帮助到你的话留个赞吧,谢谢。

以上。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值