zoukankan      html  css  js  c++  java
  • C++课堂作业二之反转链表

    1问题链接:

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

    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
    ",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
    ");
                //整个程序暂停
                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

  • 相关阅读:
    Pytorch使用tensorboardX可视化。超详细!!!
    pytorch旧版安装
    Pytorch的网络结构可视化(tensorboardX)(详细)
    PyTorch代码调试利器: 自动print每行代码的Tensor信息
    权重衰减(weight decay)与学习率衰减(learning rate decay)
    pytorch的backward
    PyTorch入门学习(二):Autogard之自动求梯度
    Pytorch
    Python之线程、进程和协程
    Python之logging模块
  • 原文地址:https://www.cnblogs.com/mingyueanyao/p/5511741.html
Copyright © 2011-2022 走看看