C++课堂作业二之反转链表
1问题链接:
2解题想法:
这题原来用数组打过,现在是想保留暂存数据的数组,然后按顺序提取出来到创建的链表里,再写一个传入反转首尾地址的链表反转函数来进行反转。
3GITHUB链接
具体的解决过程长而繁,这里直接给出代码链接,有稍加注释。然后主要想说一下反转的做法(其实是老师课件里的):举个例子来说,反转整个链表就是对数据首节点进行后删,把删掉的节点后插到head之后,也就是数据首节点之前,重复这个操作直到原来的数据首节点变成尾节点,这时反转也就完成了。
4具体过程:
这题这学期的c上机课上打过,当时自己是没有打出来,看着讲题同学的PPT也是折腾了好久才打出来。当初用的是数组,但是这次的主要目的是练习链表,所以我是打算在原来的代码基础上修改成存到链表里,然后在链表里反转后再把链表输出来。
然后毕竟原来没有打出来,所以还是大概说明一下模仿的ppt的想法:先把输入的数据存到一个结构体数组里,输入的时候用另外一个数组来标记题目里的地址(具体的可以看注释),根据标记数组把数据按照题目里的顺序存到另外一个结构体数组里,然后对这个排好序的数组进行反转的操作,最后再把这个数组输出来。
代码如下:
#include<stdio.h>
//定义了个结构体来存储数据
struct Node
{
int Address;
int Date;
int Next;
};
//记录题目里数据地址的标记数组
int mark[100000]={0};
//暂时的数据存储数组和实际操作输出的数组
struct Node node1[100000],node2[100000];
int main()
{
//用来进行反转操作的函数
void reverse(struct Node *p,int k);
int head,N,k;
//输入数据的首地址、数据个数、还有反转时用到的k
scanf("%d %d %d",&head,&N,&k);
int i,start;
//输入乱序的数据
for(i=0;i<N;i++)
{
scanf("%d %d %d",&node1[i].Address,&node1[i].Date,&node1[i].Next);
//标记数组的下标对应题目里的地址,元素则对应该地址数据在存储数组node1里的位置
mark[node1[i].Address]=i;
//记录第一个数据在存储数组中的位置
if(node1[i].Address==head) start=i;
}
//把数据按题目中要求的顺序存到node2数组里
i=0;
//先是start记录的第一个数据
node2[0]=node1[start];
//从第一个数据里获取第二个地址,也就是node[1].Next
//利用标记数组找到该地址的数据在node1数组中的位置,也就是mark[node[1].Next]
//把该数据整体赋值给node2[1],也就是按顺序的第二个数据
//接下来是第三个、第四个...直到出现题目中的结束地址-1
while(node2[i].Next!=-1)
{
i++;
node2[i]=node1[mark[node2[i-1].Next]];
}
int count=i;
i=0;
//对按题目要求排好序的node2数组进行反转操作
while(i+k<=count+1)
{
reverse(&node2[i],k);
i=i+k;
}
//按要求输出操作后的数据
for(i=0;i<count;i++)
{
//不足五位补零
//直接输出下一个数据的地址,反转的时候没有改变数据里的Next
printf("%05d %d %05d\n",node2[i].Address,node2[i].Date,node2[i+1].Address);
}
//结束的地址-1需要特判,它不能补零
printf("%05d %d -1",node2[count].Address,node2[count].Date);
return 0;
}
//用来进行反转的函数
//传入反转的起始地址和反转个数
void reverse(struct Node *p,int k)
{
int i;
struct Node t;
for(i=0;i<k/2;i++)
{
//改变的数组下标指向的数组元素,元素本身在内存里的储存位置没变
t=*(p+i);
*(p+i)=*(p+k-i-1);
*(p+k-i-1)=t;
}
}
然后这是原来的提交结果:
至少原来的是对的,所以修改的前提条件是满足了的,接下来就是怎么改成链表了。
想法是拆分成链表的创建,链表的输出,以及最主要的链表的反转等部分,其中链表的反转按照课件里的想法还用到了后插和后删操作(比如反转整个链表就是对第一个数据节点进行后删,然后把该节点后插到head后面,再对第一个后删再后插到head后面,直到原来的第一个数据节点变成最后一个,这时反转也就完成了),就是现在需要改成要传入反转的首尾地址。
主要的想法就是:保留原来暂时储存数据的结构体数组,然后用链表的创建函数从数组按顺序提取出数据到新建的节点里,并记录链表的长度,然后是把链表分成每K个一组,将每组的首尾地址传入链表反转的函数里,将其整个反转再接回去,最后输出。下面是一些链表的操作函数:
creat
在main函数里直接把head指向了题目要求的第一个节点,第一个节点不再单独申请空间。
(ps:后来发现没有头节点的后删和后插是会有问题的)
void creat(struct Node *head)
{
//定义个尾节点,用来连接新节点
//定义个新节点,用来申请新空间
struct Node *tail,*newp;
//大概是初始化
head->Next=NULL;
tail=head;
//创建链表
while(1)
{
//申请新节点
newp=(struct Node *)malloc(sizeof(struct Node));
//申请空间失败的报错
if(newp==NULL)
{
printf("overflow\n");
//整个程序暂停
exit(1);
}
//利用原来的方法将储存在数组里的数据存到新节点里
newp->Address=node[mark[tail->After]].Address;
newp->Date=node[mark[tail->After]].Date;
newp->After=node[mark[tail->After]].After;
newp->Next=NULL;
//把新节点接到链表里
tail->Next=newp;
//尾节点后移一位
tail=newp;
//输到尾地址-1结束
if(tail->After==-1)break;
}
}
deleteAfter
(ps:后来发现这个函数有错,执行的判断什么的,删除的是尾节点的话会出错)
struct Node *deleteAfter(struct Node *p)
{
//定义个结构体指针指向要删除的节点
struct Node *t=p->Next;
if(t->Next!=NULL)
{
//直接将p与后面的节点的后面的接起来
//达到后删的目的
//因为还要用到删除的节点,故并不释放空间
p->Next=t->Next;
p->After=t->Next->Address;
}
//返回被删除的节点
return t;
}
insertAfter
void insertAfter(struct Node *p,struct Node *newp)
{
//将新节点指向要接入的位置
newp->Next=p->Next;
newp->After=p->Next->Address;
//将接入位置的前一个节点指向新节点
//完成链表的插入
p->Next=newp;
p->After=newp->Address;
}
reverse
(ps:准确的说,首地址是要反转的节点的前一个节点的地址)
void reverse(struct Node *head,struct Node *tail)
{
//调用后删和后插操作
struct Node *deleteAfter(struct Node *p);
void insertAfter(struct Node *p,struct Node *newp);
//记录尾节点指向的节点地址
//用于判断循环的结束条件
int mark=tail->After;
//定义rear指向第一个数据节点
struct Node *rear=head,*del;
//大概是k为1的操作,也就是什么都不做
if(head==tail)
{
return;
}
//通过后删和后插来实现反转
while(rear->After!=mark)
{
del=deleteAfter(rear);
insertAfter(head,del);
}
}
链表的输出操作没有什么好说的,这里就不贴了。然后在尝试整个链表反转时发现了问题:创建链表的时候,head直接指向了数据首节点,然后反转的里面后插操作就会有问题,插到head之后就是插到数据首节点之后,然后就相当于删了首节点之后的一个节点之后又给接回去了,根本没变。。。所以现在是想创建的时候加个不存数据的头节点(大概头节点的存在意义就是这个吧),这样head的后插就相当于数据首节点的前插了(有想过前插,但好像那样链表要双向的,还要指向前一个,所以还是放弃了)。
(ps:后来发现课件有说头节点就是为了方便后删和后插,毕竟单向的链表(指向下一个元素)也是后插和后删好实现)
然后,还在改。。。2016.05.20
后来是把head指向了一个申请的结构体空间,这个结构体(也就是没有数据的头节点)的Next再指向储存第一个数据的结构体数组里的元素,然后又在creat,display,reverse等函数里修改了一下,下面是输出反转前后的链表的运行结果(先是尝试整个反转):
然后就是解决每k个节点反转的问题,主要想法就是创建链表的时候记录一下长度,看要反转几次,也就是调用几次reserve函数,每次改变一下要反转的首尾地址就好。大概代码如下:
(ps:也是有bug的代码,首尾地址的传递没有处理好)
int len,j;
struct Node *head,*ph,*pt,*p;
head=(struct Node *)malloc(sizeof(struct Node));
//头节点指向数据首节点
head->Next=&node[first];
//记录链表长度
len=creat(head);
//初始反转的首地址
ph=head;
//一共执行len/k次反转
for(i=0;i<len/k;i++)
{
p=ph;
for(j=0;j<k;j++)
{
p=p->Next;
}
//获取反转的尾地址
pt=p;
//调用函数进行反转
reverse(ph,pt);
//让下次反转的首地址为前一次的尾地址
ph=pt;
}
测试样例可以倒是可以过:
然后我就直接交了一发:
还是回去多试试几个样例好了。。。
折腾来折腾去(只会用输出来判断哪出错也是够了。。。),发现的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