美文网首页程序员Python数据结构和算法分析
python算法-010对链表进行重新排序

python算法-010对链表进行重新排序

作者: DKider | 来源:发表于2019-03-08 22:35 被阅读30次

    任何坏习惯、不好的行为,只有零次和无数次之分。——不知道谁说的


    今天的题有点难度,我开始写的晚了。今天写爬虫遇到了麻烦,头疼了一天。今天发的晚了,停更只有零次和无数次之分!


    题目描述:
    给定链表L0->L2->L3……->Ln-1->Ln
    把链表重新排序为L0->Ln->L2->Ln-1->L3->Ln-2……
    要求:
    (1)在原来链表的基础上进行排序,即不能申请新的节点;
    (2)只能修改节点的next域,不能修改数据域


    今天的题目的难度是有的,不过还在我们能理解的范围内。下面我们来分析下这道题目:要将链表的最后一位接在第一个节点后,首先很容易就能想到:分两重循环,第一重从链表头开始遍历,第二重就是从尾部开始遍历,然后将尾部节点直接连在外层遍历节点之后即可,直到链表到中间。当然你们会问怎么从尾部开始遍历?很简单,每次都遍历到最后一个,然后将其加到前面;然后下次遍历的时候,还是遍历到最后一个节点,因为最后一个节点就是我们需要处理的节点。那又有人问了,怎么判断是否到中间了呢?这也简单,先遍历链表算出链表的长度,然后去中值,然后再从头遍历到这个中值,这非常麻烦!!!
    上面的方法是我写文章时临时想的,是不是看起来没什么问题,但是!!!实际操作起来,非常费时,费力,容易出错。而且会让人觉得我没有脑子。。。

    下面正确方法:

    观察题目的两个链表:L0->L2->L3……->Ln-1->Ln和L0->Ln->L2->Ln-1->L3->Ln-2……,可以想象一条绳子,上面有一个一个的结,我把绳子首尾对接,将绳子对折,是不是就大概像是题目的那样,节点位置改变了,当然题目要求更高点,要变成一条链表。对于链表,你不能说对折就对折了,自然是要想办法——>先将链表从中间断开,然后将后半条链表逆序,然后合并。注意!这里不是直接将其连到后面,而是将后面的每一个节点都插到两个节点之间。这里就涉及到连个重要的操作——如何判断链表的中间节点是哪个?以及如何合并两个链表?

    先来看寻找中间节点的方法:前面已经介绍了一种简单的方法,但是需要遍历链表两次,不是我们想看到的情况。我们现在来看一个更快的方法:

    1. 用两个指针——jump1和jump2。jump1每次向后遍历一个节点;jump2每次向后遍历两个节点。
    2. 当jump2到达尾部时(这里会有两种情况,奇数个和偶数个,下面分析),jump1刚好是到达链表中间节点。

    这里又分两种情况,链表长度为奇数和偶数,这两个是不一样的情况——数学问题:

    • 奇数:jump2步长为2,最后一次刚好可以遍历到最后一个节点。此时的mid节点取jump1和jump1.next都行。
    • 偶数:jump2步长为2,是无法遍历到最后一个节点的,只能遍历到倒数第二个节点,下一跳就为空了,可以结合下面的代码看一下:
      比如:1->2->3->4
      #在一次循环后jump2指向3,根据循环条件,此时是要再进入循环的,
      #这次循环是没问题的,也就是jump2指向None,但是此时来判断是否进循环就出了问题
      #因为jump2是空的,它没有next属性,程序在运行是会报错
      具体细节我在代码注释里写的非常清楚了!仔细看
        jump1 = head.next  # step为1的指针
        jump1Pre = head.next # 用于指向jump1的前驱节点
        jump2 = head.next  # step为2的指针
        mid = None # 用于指向链表的重点
        while jump2.next is not None:
            #这句是重点!!当链表长度为偶数,比如:1->2->3->4
            #在一次循环后jump2指向3,根据循环条件,此时是要再进入循环的,
            #这次循环是没问题的,也就是jump2指向None,但是此时来判断是否进循环就出了问题
            #因为jump2是空的,它没有next属性,程序在运行是会报错
            if jump2.next.next is None:
                break
            # 指向下一跳
            jump1Pre = jump1
            jump1 = jump1.next
            jump2 = jump2.next.next
        #这里接着上面的问题,无论是哪种情况退出循环
        # 其中间值我都设为jump1后一个,
        # 1)循环条件退出,说明个数为偶数,选jump1和jump1.next是一样的
        # 2)if语句退出,说明链表长度为奇数,但是jump2并没有到最后一个,
        #    所以其中间值应该是jump1的后一个,这是数学问题,大家自己画画图
        mid=jump1.next
        jump1Pre=jump1
        jump1Pre.next = None  # 切断
    

    接下来就是逆序链表了——详情看就地逆序法递归法插入法

        #逆序后面的链表,这里就不多说了,前面都讲了三个方法了 
        cur=mid.next#用来逆序链表,指向第2个节点
        mid.next=None
        while cur is not None :
            next=cur.next
            cur.next=mid
            mid=cur
            cur=next
    

    重点:合并两个链表

    我直接上图了。上面的是前半部分,下面的是逆序后的后半部分:
    链表长度为偶数时,前后两个链表合并时的处理顺序:

    链表长度为偶数
    这里我是将两个操作合在一起,作为一次循环做的工作,还可以将1步作为一次循环方法。很多书上是后者,我觉得对于新书,我还是两步操作看的更清楚!从图上可以看到5这个节点我并没有接上,因为在我的循环里它完不成,需要在结束后再接上。图中若是把5这个节点直接拿掉,就是奇数长度的链表的操作过程。以上,看图细细体会。
    代码:
        # 这里是难点
        # 合并head和mid,mid无头节点
        curHead=head.next # 指向前半条链表的第一个节点
        curmid=mid # 指向后半条链表的第一个节点,从这里也可理解前面的mid节点的选取,
        #因为我要的是后半条,自然得要中间节点的后一个
        while curHead.next is not None:
            #因为取中间节点的原因,前半条链表的长度大于等于后半条
            nexthead = curHead.next #记录head的下一个节点
            #这里你看我前面画的图 
            curHead.next = curmid
            nextmid = curmid.next #记录mid的下一个节点
            curmid.next = nexthead
            curHead = nexthead
            curmid = nextmid
        # 这是前半条链表导等于后半条链表,
        # 也就是原链表产的长度为偶数的情况,因为前面循环只处理的
        # 只是将head连到mid,再从mid连到head,每次处理两个链接,
        # 但是偶数个节点之间应该有奇数个链接,这里是mid的最后一个节点没有处理
        if cur mid is not None:
            curHead.next=curmid
        return head
    

    以上的代码我是写在了一个函数里,上面被我分开了,方便大家看。下面来试一试这个算法是否正确,我试了长度分别为:0,1,2,3,9,10,这几组:

    if __name__ == '__main__':
        head=creatLink(5)
        print("BeforeReverse:")
        cur = head.next
        while cur != None:
            print(cur.data)
            cur = cur.next
        head=xReverese(head)
        print("\nAfterReverse:")
        cur = head.next
        while cur != None:
            print(cur.data)
            cur = cur.next
    

    输出结果:


    0
    1
    3
    9
    10

    上面的这种输入可以检验我们的代码是否强壮,不能因为一次对了,就认为整个程序都对了。
    全部的代码:

    class LNode:
        def __init__(self,arg):
            self.data=arg
            self.next=None
    
    """
    题目描述:
    给定链表L0->L2->L3……->Ln-1->Ln
    把链表重新排序为L0->Ln->L2->Ln-1->L3->Ln-2……
    要求:(1)在原来链表的基础上进行排序,即不能申请新的节点;(2)只能修改节点的next域,不能修改数据域
    方法:
    先找到链表的中间节点,并将原链表拆分成两个子链表,将后面的子链表逆序,在将两个子链表合成最终链表
    """
    def creatLink(x):
        i = 1
        head = LNode(None)
        tmp = None
        cur = head
        while i <= x:
            tmp = LNode(i)
            cur.next = tmp
            cur = tmp
            i += 1
        return head
    def xReverese(head):
        #判断链表是否为空,或者只有一个元素,或者只有两个元素
        if head is None or head.next is None or head.next.next is None:
            return head
        # 寻找链表中间节点
        jump1 = head.next  # step为1的指针
        jump1Pre = head.next # 用于指向jump1的前驱节点
        jump2 = head.next  # step为2的指针
        mid = None # 用于指向链表的重点
        while jump2.next is not None:
            #这句是重点!!当链表长度为偶数,比如:1->2->3->4
            #在一次循环后jump2指向3,根据循环条件,此时是要再进入循环的,
            #这次循环是没问题的,也就是jump2指向None,但是此时来判断是否进循环就出了问题
            #因为jump2是空的,它没有next属性,程序在运行是会报错
            if jump2.next.next is None:
                break
            # 指向下一跳
            jump1Pre = jump1
            jump1 = jump1.next
            jump2 = jump2.next.next
        #这里接着上面的问题,无论是哪种情况退出循环
        # 其中间值我都设为jump1后一个,
        # 1)循环条件退出,说明个数为偶数,选jump1和jump1.next是一样的
        # 2)if语句退出,说明链表长度为奇数,但是jump2并没有到最后一个,
        #    所以其中间值应该是jump1的后一个,这是数学问题,大家自己画画图
        mid=jump1.next
        jump1Pre=jump1
        jump1Pre.next = None  # 切断
    
        #逆序后面的链表,这里就不多说了,前面都讲了三个方法了 
        cur=mid.next#用来逆序链表,指向第2个节点
        mid.next=None
        while cur is not None :
            next=cur.next
            cur.next=mid
            mid=cur
            cur=next
    
        # 这里是难点
        # 合并head和mid,mid无头节点
        curHead=head.next # 指向前半条链表的第一个节点
        curmid=mid # 指向后半条链表的第一个节点,从这里也可理解前面的mid节点的选取,
        #因为我要的是后半条,自然得要中间节点的后一个
        while curHead.next is not None:
            #因为取中间节点的原因,前半条链表的长度大于等于后半条
            nexthead = curHead.next #记录head的下一个节点
            #这里你看我前面画的图 
            curHead.next = curmid
            nextmid = curmid.next #记录mid的下一个节点
            curmid.next = nexthead
            curHead = nexthead
            curmid = nextmid
        # 这是前半条链表导等于后半条链表,
        # 也就是原链表产的长度为偶数的情况,因为前面循环只处理的
        # 只是将head连到mid,再从mid连到head,每次处理两个链接,
        # 但是偶数个节点之间应该有奇数个链接,这里是mid的最后一个节点没有处理
        if cur mid is not None:
            curHead.next=curmid
        return head
    
    if __name__ == '__main__':
        head=creatLink(5)
        print("BeforeReverse:")
        cur = head.next
        while cur != None:
            print(cur.data)
            cur = cur.next
        head=xReverese(head)
        print("\nAfterReverse:")
        cur = head.next
        while cur != None:
            print(cur.data)
            cur = cur.next
    

    今天晚了,因为内容比较多,我也不想糊弄自己,更不想糊弄大家,我是不会停更的!!!
    加油!!!

    还是那样,我一如既往的乐于帮助大家——微信、简书:Dkider。语句千万条,正确第一条。代码不规范,手指两行泪。

    Dkider

    相关文章

      网友评论

        本文标题:python算法-010对链表进行重新排序

        本文链接:https://www.haomeiwen.com/subject/lawdpqtx.html