PTA basic 1025 反转链表 (25 分) c语言实现(gcc)

给定一个常数 K 以及一个单链表 L,请编写程序将 L 中每 K 个结点反转。例如:给定 L 为 1→2→3→4→5→6,K 为 3,则输出应该为 3→2→1→6→5→4;如果 K 为 4,则输出应该为 4→3→2→1→5→6,即最后不到 K 个元素不反转。

输入格式:

每个输入包含 1 个测试用例。每个测试用例第 1 行给出第 1 个结点的地址、结点总个数正整数 N (≤)、以及正整数 K (≤),即要求反转的子链结点的个数。结点的地址是 5 位非负整数,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

核心思路图解

 

 

 

 

 

 

 

 

 

 

初步思路声明链表,然后按照头插法插入结点,当index+k<=n的时候,原地翻转index到index+k-1的结点,temp指针指向index的指针指向

 

提交后检测点5超时,显然是给出了极限条件,即当数据足够多的时候,遍历算法的效率急速下降,所要用到链表和指针来解决问题

 

遍历的方法会超时,用静态结构数组存储数据,使用下标作为指针模拟链表,进行链表的局部翻转,然后输出翻转后的结果

 

关于局部翻转的算法

核心思想

首先用三个指针来记录翻转前一个结点地址pre,后一个结点地址next以及当前节点地址current,然后就是处理子链首尾的连接

因为翻转后链表的首地址会发生变化,因此需要单独记录链表的首地址,这里用一次单独的循环来记录,结束后产生的子链首地址就是链表的首地址

题目给出的测试用例中,如果剩余子链不够k个,就不需要翻转,所以还要考虑到剩余结点的连接

 

第一次提交测试034通过,百度看别人写的代码 1.所给数据可能存在无效结点(测试点6通过)

 

第二次提交 使用静态链表方式重写了代码,测试点0246通过

 

第三次提交 制作了图解的链表算法过程 并且修正了多个子链表翻转时候的边界条件,检测点023456通过

 

最后一次提交通过

 

检测点1正好有N=m*K个子串需要翻转

 

检测点0 2 4 6 只有有1个子串,且N>K需要翻转的时候

 

检测点 3 5 有m个子串需要翻转且N>m*K

 

测试用例1

00100 12 4

00000 4 99999

00100 1 12309

68237 6 34257

33218 3 00000

99999 5 68237

12309 2 33218

34257 7 12922

12922 8 98122

98122 9 13812

13812 10 23721

23721 11 12371

12371 12 -1

 

测试用例2

00100 12 4

00000 4 99999

00100 1 12309

68237 6 34257

33218 3 00000

99999 5 68237

12309 2 33218

34257 7 12922

12922 8 98122

98122 9 13812

13812 10 23721

23721 11 12371

12371 12 18271

18271 13 -1

 



#include "stdio.h"
#include "stdlib.h"
#include "string.h"

typedef struct protoTypeNode{
    //地址直接用数组下标存储 ,使用的是静态结构体数组,原理和链表一样,但是题目已经给定了数据的地址,没办法使用动态链表来存储固定地址尤其是地址为0的数据,
    int data;
    int next;
} node;

//翻转链表核心算法
int reverse(node *linkListPointer,int head,int n,int k){
    node *lp=linkListPointer;//链表指针
    int pre=0,current=0,next=0,nexthead,j=0,rear=0,repeatTimes,i;

    //pre用于记录需要翻转的结点的前一个结点地址
    //current当前翻转结点的地址
    //next下一个需要翻转结点的地址
    //nexthead下一个子链的首结点
    //rear子链翻转后的尾结点(即未翻转的子链的头结点)
    //repeatTime有多少个子链需要翻转
    //remainList需要翻转的子链全部翻转后是否还剩下结点

    pre=head;//指向未翻转的子链表的头结点
    current=lp[head].next;//指向头结点的下一跳结点,同时也是准备翻转的结点,准备用于翻转指向原局部头结点
    next=lp[current].next;//保存翻转结点的下一跳结点
    for(i=0;i<k-1;i++){//第一个元素的下一跳指针需要单独处理,所以重复k-1次
        lp[current].next=pre;//前一个元素的地址pre存入current指向的结点的下一跳指针中
        pre=current;//pre current next前移一位
        current=next;
        next=lp[next].next;
    }
    rear=head;//原局部头结点成为翻转后的尾结点
    head=pre;//局部翻转后,生成新的链表表头结点
    //翻转剩下的子链
    repeatTimes=n/k;//需要翻转多少个子链
    for(j=0;j<repeatTimes-1;j++){
        nexthead=pre=current;//更新子链头结点 //pre current next前移一位
        current=lp[nexthead].next;
        next=lp[current].next;
        for(i=0;i<k-1;i++){
            lp[current].next=pre;//将前一个结点的地址存入当前翻转节点的next指针中
            pre=current;//pre current next前移一位
            current=next;
            next=lp[next].next;
        }
        lp[rear].next=pre;//使 上一个翻转子链表的尾结点的指针指向当前 翻转子链表的新头结点
        rear=nexthead;//当前子链表的局部头结点成为翻转后的尾结点
    }
    lp[rear].next=current;//翻转后的子链尾结点指向剩余子链的首结点
    return head;
}

//统计有效结点
int countList(node *lp,int head){
    int count=0,current=head;
    while(lp[current].next!=-1){
        current=lp[current].next;
        count++;
    }
    return count+1;
}

//按序打印链表
void printList(node *lp,int head,int count){
    int current=head,i;
    //    printf("\n");
    for(i=0;i<count-1;i++){
        printf("%05d %d %05d\n",current,lp[current].data,lp[current].next);
        current=lp[current].next;
    }
    printf("%05d %d %d\n",current,lp[current].data,lp[current].next);
}

int main(){
    int head=0,i,n=0,k=0,count,address,data,next,newhead;
    scanf("%d %d %d",&head,&n,&k);
    node linkList[100000];//N<=10^5
    for(i=0;i<n;i++){
        scanf("%d %d %d",&address,&data,&next);
        linkList[address].data=data;
        linkList[address].next=next;
    }
    count=countList(linkList,head);
    newhead=reverse(linkList, head, count, k);
    printList(linkList, newhead, count);

    return 0;
}

 



 

 

 

后记.
说实话,以前只是知道链表的逻辑,但是手动写代码出来建立一个链表来实现某种算法一直都不会,花了挺长时间一步步debug才搞懂
其实翻转子链的地方并不难理解, 难点是边界处理子链的链接问题
包括 链表的首地址更新,上一个子链的尾结点和下一个子链的头结点的链接, 更新头尾结点

 

posted @ 2021-04-23 22:02  keiiha  阅读(236)  评论(0编辑  收藏  举报