1问题链接:

https://www.patest.cn/contests/pat-b-practise/1025

2解题想法:

这题原来用数组打过,现在是想保留暂存数据的数组,然后按顺序提取出来到创建的链表里,再写一个传入反转首尾地址的链表反转函数来进行反转。

3GITHUB链接

具体的解决过程长而繁,这里直接给出代码链接,有稍加注释。然后主要想说一下反转的做法(其实是老师课件里的):举个例子来说,反转整个链表就是对数据首节点进行后删,把删掉的节点后插到head之后,也就是数据首节点之前,重复这个操作直到原来的数据首节点变成尾节点,这时反转也就完成了。

4具体过程:

这题这学期的c上机课上打过,当时自己是没有打出来,看着讲题同学的PPT也是折腾了好久才打出来。当初用的是数组,但是这次的主要目的是练习链表,所以我是打算在原来的代码基础上修改成存到链表里,然后在链表里反转后再把链表输出来。

然后毕竟原来没有打出来,所以还是大概说明一下模仿的ppt的想法:先把输入的数据存到一个结构体数组里,输入的时候用另外一个数组来标记题目里的地址(具体的可以看注释),根据标记数组把数据按照题目里的顺序存到另外一个结构体数组里,然后对这个排好序的数组进行反转的操作,最后再把这个数组输出来。

代码如下:

  1. #include<stdio.h>
  2. //定义了个结构体来存储数据
  3. struct Node
  4. {
  5. int Address;
  6. int Date;
  7. int Next;
  8. };
  9. //记录题目里数据地址的标记数组
  10. int mark[100000]={0};
  11. //暂时的数据存储数组和实际操作输出的数组
  12. struct Node node1[100000],node2[100000];
  13. int main()
  14. {
  15. //用来进行反转操作的函数
  16. void reverse(struct Node *p,int k);
  17. int head,N,k;
  18. //输入数据的首地址、数据个数、还有反转时用到的k
  19. scanf("%d %d %d",&head,&N,&k);
  20. int i,start;
  21. //输入乱序的数据
  22. for(i=0;i<N;i++)
  23. {
  24. scanf("%d %d %d",&node1[i].Address,&node1[i].Date,&node1[i].Next);
  25. //标记数组的下标对应题目里的地址,元素则对应该地址数据在存储数组node1里的位置
  26. mark[node1[i].Address]=i;
  27. //记录第一个数据在存储数组中的位置
  28. if(node1[i].Address==head) start=i;
  29. }
  30. //把数据按题目中要求的顺序存到node2数组里
  31. i=0;
  32. //先是start记录的第一个数据
  33. node2[0]=node1[start];
  34. //从第一个数据里获取第二个地址,也就是node[1].Next
  35. //利用标记数组找到该地址的数据在node1数组中的位置,也就是mark[node[1].Next]
  36. //把该数据整体赋值给node2[1],也就是按顺序的第二个数据
  37. //接下来是第三个、第四个...直到出现题目中的结束地址-1
  38. while(node2[i].Next!=-1)
  39. {
  40. i++;
  41. node2[i]=node1[mark[node2[i-1].Next]];
  42. }
  43. int count=i;
  44. i=0;
  45. //对按题目要求排好序的node2数组进行反转操作
  46. while(i+k<=count+1)
  47. {
  48. reverse(&node2[i],k);
  49. i=i+k;
  50. }
  51. //按要求输出操作后的数据
  52. for(i=0;i<count;i++)
  53. {
  54. //不足五位补零
  55. //直接输出下一个数据的地址,反转的时候没有改变数据里的Next
  56. printf("%05d %d %05d\n",node2[i].Address,node2[i].Date,node2[i+1].Address);
  57. }
  58. //结束的地址-1需要特判,它不能补零
  59. printf("%05d %d -1",node2[count].Address,node2[count].Date);
  60. return 0;
  61. }
  62. //用来进行反转的函数
  63. //传入反转的起始地址和反转个数
  64. void reverse(struct Node *p,int k)
  65. {
  66. int i;
  67. struct Node t;
  68. for(i=0;i<k/2;i++)
  69. {
  70. //改变的数组下标指向的数组元素,元素本身在内存里的储存位置没变
  71. t=*(p+i);
  72. *(p+i)=*(p+k-i-1);
  73. *(p+k-i-1)=t;
  74. }
  75. }

然后这是原来的提交结果:

至少原来的是对的,所以修改的前提条件是满足了的,接下来就是怎么改成链表了。

想法是拆分成链表的创建,链表的输出,以及最主要的链表的反转等部分,其中链表的反转按照课件里的想法还用到了后插和后删操作(比如反转整个链表就是对第一个数据节点进行后删,然后把该节点后插到head后面,再对第一个后删再后插到head后面,直到原来的第一个数据节点变成最后一个,这时反转也就完成了),就是现在需要改成要传入反转的首尾地址。

主要的想法就是:保留原来暂时储存数据的结构体数组,然后用链表的创建函数从数组按顺序提取出数据到新建的节点里,并记录链表的长度,然后是把链表分成每K个一组,将每组的首尾地址传入链表反转的函数里,将其整个反转再接回去,最后输出。下面是一些链表的操作函数:

creat

在main函数里直接把head指向了题目要求的第一个节点,第一个节点不再单独申请空间。

(ps:后来发现没有头节点的后删和后插是会有问题的)

  1. void creat(struct Node *head)
  2. {
  3. //定义个尾节点,用来连接新节点
  4. //定义个新节点,用来申请新空间
  5. struct Node *tail,*newp;
  6. //大概是初始化
  7. head->Next=NULL;
  8. tail=head;
  9. //创建链表
  10. while(1)
  11. {
  12. //申请新节点
  13. newp=(struct Node *)malloc(sizeof(struct Node));
  14. //申请空间失败的报错
  15. if(newp==NULL)
  16. {
  17. printf("overflow\n");
  18. //整个程序暂停
  19. exit(1);
  20. }
  21. //利用原来的方法将储存在数组里的数据存到新节点里
  22. newp->Address=node[mark[tail->After]].Address;
  23. newp->Date=node[mark[tail->After]].Date;
  24. newp->After=node[mark[tail->After]].After;
  25. newp->Next=NULL;
  26. //把新节点接到链表里
  27. tail->Next=newp;
  28. //尾节点后移一位
  29. tail=newp;
  30. //输到尾地址-1结束
  31. if(tail->After==-1)break;
  32. }
  33. }

deleteAfter

(ps:后来发现这个函数有错,执行的判断什么的,删除的是尾节点的话会出错)

  1. struct Node *deleteAfter(struct Node *p)
  2. {
  3. //定义个结构体指针指向要删除的节点
  4. struct Node *t=p->Next;
  5. if(t->Next!=NULL)
  6. {
  7. //直接将p与后面的节点的后面的接起来
  8. //达到后删的目的
  9. //因为还要用到删除的节点,故并不释放空间
  10. p->Next=t->Next;
  11. p->After=t->Next->Address;
  12. }
  13. //返回被删除的节点
  14. return t;
  15. }

insertAfter

  1. void insertAfter(struct Node *p,struct Node *newp)
  2. {
  3. //将新节点指向要接入的位置
  4. newp->Next=p->Next;
  5. newp->After=p->Next->Address;
  6. //将接入位置的前一个节点指向新节点
  7. //完成链表的插入
  8. p->Next=newp;
  9. p->After=newp->Address;
  10. }

reverse

(ps:准确的说,首地址是要反转的节点的前一个节点的地址)

  1. void reverse(struct Node *head,struct Node *tail)
  2. {
  3. //调用后删和后插操作
  4. struct Node *deleteAfter(struct Node *p);
  5. void insertAfter(struct Node *p,struct Node *newp);
  6. //记录尾节点指向的节点地址
  7. //用于判断循环的结束条件
  8. int mark=tail->After;
  9. //定义rear指向第一个数据节点
  10. struct Node *rear=head,*del;
  11. //大概是k为1的操作,也就是什么都不做
  12. if(head==tail)
  13. {
  14. return
  15. }
  16. //通过后删和后插来实现反转
  17. while(rear->After!=mark)
  18. {
  19. del=deleteAfter(rear);
  20. insertAfter(head,del);
  21. }
  22. }

链表的输出操作没有什么好说的,这里就不贴了。然后在尝试整个链表反转时发现了问题:创建链表的时候,head直接指向了数据首节点,然后反转的里面后插操作就会有问题,插到head之后就是插到数据首节点之后,然后就相当于删了首节点之后的一个节点之后又给接回去了,根本没变。。。所以现在是想创建的时候加个不存数据的头节点(大概头节点的存在意义就是这个吧),这样head的后插就相当于数据首节点的前插了(有想过前插,但好像那样链表要双向的,还要指向前一个,所以还是放弃了)。

(ps:后来发现课件有说头节点就是为了方便后删和后插,毕竟单向的链表(指向下一个元素)也是后插和后删好实现)

然后,还在改。。。2016.05.20

后来是把head指向了一个申请的结构体空间,这个结构体(也就是没有数据的头节点)的Next再指向储存第一个数据的结构体数组里的元素,然后又在creat,display,reverse等函数里修改了一下,下面是输出反转前后的链表的运行结果(先是尝试整个反转):

然后就是解决每k个节点反转的问题,主要想法就是创建链表的时候记录一下长度,看要反转几次,也就是调用几次reserve函数,每次改变一下要反转的首尾地址就好。大概代码如下:

(ps:也是有bug的代码,首尾地址的传递没有处理好)

  1. int len,j;
  2. struct Node *head,*ph,*pt,*p;
  3. head=(struct Node *)malloc(sizeof(struct Node));
  4. //头节点指向数据首节点
  5. head->Next=&node[first];
  6. //记录链表长度
  7. len=creat(head);
  8. //初始反转的首地址
  9. ph=head;
  10. //一共执行len/k次反转
  11. for(i=0;i<len/k;i++)
  12. {
  13. p=ph;
  14. for(j=0;j<k;j++)
  15. {
  16. p=p->Next;
  17. }
  18. //获取反转的尾地址
  19. pt=p;
  20. //调用函数进行反转
  21. reverse(ph,pt);
  22. //让下次反转的首地址为前一次的尾地址
  23. ph=pt;
  24. }

测试样例可以倒是可以过:

然后我就直接交了一发:

还是回去多试试几个样例好了。。。

折腾来折腾去(只会用输出来判断哪出错也是够了。。。),发现的bug是每组反转不能很好的衔接在一起,原来的首尾指向的数据反转后已经换了位置,然后下一轮的首尾地址按原来的赋值就会乱掉,首地址会是链表的第一个数据节点。举个例子来说:把样例中的k改为3,输出的结果会是3、4、1、2、5、6,第一遍的反转确实把链表变成了3、2、1、4、5、6,但是原来的尾节点3已经跑到了第一位,下一次反转的首地址就会变成3的地址,也会变成反转2、1、4,所以输出就会变成是3、4、1、2、5、6。

上面的bug改完之后,结果是停止运行。。。最后发现是后删操作有问题,就里面的执行判断有错,如果删的是尾节点,会崩溃的。

一番修改之后,这是提交结果:

心情真是复杂。。。我到底又漏掉了什么。看到群里说单节点什么的,随意改了一下都没调试就交上去:

额,起码知道哪里错了。然后又改了一下,终于对了(开心):

最终代码已上传GITHUB,这里就不贴了,这里再来一个传送门

2016.05.21

C++课堂作业二之反转链表的更多相关文章

  1. 课堂作业二 PAT1025 反转链表

    MyGitHub 终于~奔溃了无数次后,看到这个结果 ,感动得不要不要的::>_<:: 题目在这里 题目简述:该题可大致分为 输入链表 -> 链表节点反转 -> 两个步骤 输入 ...

  2. C++课堂作业_02_PAT1025.反转链表

    The 1st classwork of the C++ program 题目:PAT.1025.反转链表 github链接:Click Here mdzz,做完题目的第一感受= = 这道题的题意就是 ...

  3. 20155213 第十二周课堂作业MySort

    20155213 第十二周课堂作业MySort 作业要求 模拟实现Linux下Sort -t : -k 2的功能 参考 Sort的实现 提交码云链接和代码运行截图 初始代码 1 import java ...

  4. 2、java数据结构和算法:单链表: 反转,逆序打印, 合并二个有序链表,获取倒数第n个节点, 链表的有序插入

    什么也不说, 直接上代码: 功能点有: 1, 获取尾结点 2, 添加(添加节点到链表的最后面) 3, 添加(根据节点的no(排名)的大小, 有序添加) 4, 单向链表的 遍历 5, 链表的长度 6, ...

  5. 剑指Offer面试题:15.反转链表

    一.题目:反转链表 题目:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点. 链表结点定义如下,这里使用的是C#描述: public class Node { public in ...

  6. python中使用递归实现反转链表

    反转链表一般有两种实现方式,一种是循环,另外一种是递归,前几天做了一个作业,用到这东西了. 这里就做个记录,方便以后温习. 递归的方法: class Node: def __init__(self,i ...

  7. 剑指offer 15:反转链表

    题目描述 输入一个链表,反转链表后,输出新链表的表头. 法一:迭代法 /* public class ListNode { int val; ListNode next = null; ListNod ...

  8. 【Java】 剑指offer(24) 反转链表

    本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头 ...

  9. 反转链表 Reverse Linked List

    2018-09-11 22:58:29 一.Reverse Linked List 问题描述: 问题求解: 解法一:Iteratively,不断执行插入操作. public ListNode reve ...

随机推荐

  1. WordNet::Similarity的安装和使用

    简介 WordNet::Similarity是一个Perl实现的软件包,可以用来计算两个概念(或者word sense)之间的语义相似度,它提供了六种计算相似度和三种计算概念之间关联度的方法,所有的这 ...

  2. EntityFrameworkCode 操作MySql 相关问题

    近段时间,由于工作原因,使用到了EntityFrameworkCore 操作MySql数据库,使用中遇到一些问题,特此记录 系统环境 Win10 1805,VS 2017,Framework:Asp. ...

  3. lazy初始化和线程安全的单例模式

    1.双检锁/双重校验锁(DCL,即 double-checked locking) JDK 版本:JDK1.5 起 是否 Lazy 初始化:是 是否多线程安全:是 实现难度:较复杂 描述:这种方式采用 ...

  4. Linux笔记-Linux命令初解2

    在看linux过程中,文件属性管理是一个难点,因而作为初学者的我来说,我直接将其放在后面来慢慢研究,因而我个人觉得先学习后面一些知识点之后,回过头来将一些你所不懂的去解透,这是极好的意见事情.对了,我 ...

  5. c# 旋转图片 无GDI+一般性错误

    using (System.Drawing.Bitmap backgroudImg = System.Drawing.Bitmap.FromFile(DoubleClickPicInfo.FileNa ...

  6. [javaSE] 进制转换(二进制十进制十六进制八进制)

    十进制转二进制,除2运算 十进制6转二进制是 110  (注意从右往左写,使用算式从下往上写) 二进制转十进制,乘2过程 二进制110转十进制  0*2的0次方+1*2的1次方+1*2的2次方=6 对 ...

  7. 三、hdfs的JavaAPI操作

    下文展示Java的API如何操作hdfs,在这之前你需要先安装配置好hdfs https://www.cnblogs.com/lay2017/p/9919905.html 依赖 你需要引入依赖如下 & ...

  8. mac obs直播软件 无法输出音频解决办法

    搜索大量的网页,确没有一个实用的设置教程,也正是speechless. 直接做个教程,方便大家的使用 1.安装 boom 2 到app store 上搜索boom 我安装的是正版的,需要128元. 你 ...

  9. Java - “JUC”原子类

    根据修改的数据类型,可以将JUC包中的原子操作类可以分为4类. 1. 基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;2. 数组类型: AtomicIn ...

  10. git杂记-记录每次更新到仓库

    git status 和 git diff的运用 git status 记录的是关于仓库文件是否有变更,例如是否被修改,是否被添加到暂村区.至于文件更改了什么内容该命令并不关心: git status ...