Java算法——1实现单链表的逆序(就地逆序、递归法、头插法)

题目描述

给定一个带头结点的单链表,请将其逆序。即如果单链表原来为head→1→2→3→4→5→6→7,则逆序后变为head→7→6→5→4→3→2→1。

解题思路

由于单链表与数组不同,单链表中每个结点的地址都存储在其前驱结点的指针域中,因此,对单链表中任何一个结点的访问只能从链表的头指针开始进行遍历。在对链表的操作过程中,需要特别注意在修改结点指针域的时候,记录下后继结点的地址,否则会丢失后继节点。

方法一:就地逆序

主要思路:在遍历链表时,修改当前结点的指针域的指向,让其指向它的前驱结点。为此,需要一个指针变量来保存前驱结点的地址。此外,为了在调整当前结点指针域的指向后还能找到后继结点,还需要另外一个指针变量来保存后继结点的地址,在所有的结点都被保存好以后就可以直接完成指针的逆序了。
在这里插入图片描述

class LNode {
    /**
     * 数据域
     */
    int data;
    /**
     * 下一个结点的引用
     */
    LNode next;
}
public class Test1 {
    /**方法功能:对单链表进行逆序输入参数:head:链表头结点*/
    public static void reverse(LNode head){

        //判断链表是否为空
        if(head==null || head.next==null){
            return;
        }
        //前驱结点
        LNode pre = null;
        //当前结点
        LNode cur = null;
        //后继结点
        LNode next = null;
        //把链表首结点变为尾结点
        cur = head.next;
        next = cur.next;
        cur.next = null;
        pre = cur;
        cur = next;
        //使当前遍历到的结点cur指向其前驱结点
        while(cur.next != null){
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        //结点最后一个结点指向倒数第二个结点
        cur.next = pre;
        //链表头结点指向原来链表的尾结点
        head.next = cur;
    }

    public static void main(String[] args){
        //链表头结点
        LNode head = new LNode();
        head.next = null;
        LNode tmp = null;
        LNode cur = head;
        //构造单链表
        for(int i=1;i<8;i++){
            tmp = new LNode();
            tmp.data = i;
            tmp.next = null;
            cur.next = tmp;
            cur = tmp;
        }
        System.out.print("逆序前:");
        for(cur=head.next;cur!=null;cur=cur.next){
            System.out.print(cur.data+" ");
        }
        System.out.print("\n逆序后:");
        reverse(head);
        for(cur=head.next;cur!=null;cur=cur.next){
            System.out.print(cur.data+" ");
        }
    }
}

程序运行结果

逆序前:1 2 3 4 5 6 7 
逆序后:7 6 5 4 3 2 1 

算法性能分析

只需对链表进行一次遍历,时间复杂度为O(N),N为链表长度。需要常数个额外的变量来保存当前结点的前驱结点和后继结点,空间复杂度为O(1)

方法二:递归法

原链表为1→2→3→4→5→6→7,递归主要思想是:先逆序除第一个结点以外的子链表(将1→2→3→4→5→6→7变为1→7→6→5→4→3→2),接着把结点1添加到逆序的子链表的后面(将1→2→3→4→5→6→7变为7→6→5→4→3→2→1)。同理,在逆序链表2→3→4→5→6→7时,也是先逆序子链表3→4→5→6→7(逆序为7→6→5→4→3),接着实现链表的整体逆序(2→7→6→5→4→3转换为7→6→5→4→3→2)

	private static LNode recursiveReverse(LNode head){
        /**
         * @Author: JavaRecord
         * @Description:对不带头结点的链表进行逆序
         * @Param [head]
         * @Return linked.list1.LNode
         * @Date 2020/8/10
         * @Time 14:17
         */
        //如果链表为空或者只有一个元素
        if(head==null || head.next==null){
            return head;
        }else{
            //反转后面的节点
            LNode newhead = recursiveReverse(head.next);
            //把当前遍历的结点加到后面结点逆序后的链表的尾部
            head.next.next = head;
            head.next = null;
            return newhead;
        }
    }

    public static void reverse(LNode head){
        /**
         * @Author: JavaRecord
         * @Description:对带头结点的单链表进行逆序
         * @Param [head]
         * @Return void
         * @Date 2020/8/10
         * @Time 14:36
         */
        if(head==null){
            return;
        }
        //获取链表的第一个结点
        LNode firstNode = head.next;
        //对链表进行逆序
        LNode newhead = recursiveReverse(firstNode);
        //头结点指向逆序后链表的第一个结点
        head.next = newhead;
    }

算法性能分析

递归法也只需要对链表进行一次遍历,算法时间复杂度为O(N),N为链表长度。

优点:思路比较直观,容易理解,而且不需要保存前驱结点的地址

缺点:算法实现难度大。需要不断调用自己,需要额外的压栈和弹栈操作,与方法一相比,性能有所下降

方法三:插入法

主要思路:从链表的第二个结点开始,把遍历到的结点插入到头结点的后面,直到遍历结束。假定原链表为head→1→2→3→4→5→6→7,在遍历到2时,将其插入到头结点后,链表变为head→2→1→3→4→5→6→7,同理将后序遍历到的所有结点都插入到头结点head后,就可以实现逆序。

	public static void reverse3(LNode head){
        /**
         * @Author: JavaRecord
         * @Description:插入法
         * @Param [head]
         * @Return void
         * @Date 2020/8/10
         * @Time 14:48
         */
        //判断链表是否为空
        if(head==null || head.next==null) {
            return;
        }
        LNode cur = null;
        LNode next = null;
        cur = head.next.next;
        //设置链表第一个结点为尾结点
        head.next.next = null;
        //把遍历到的结点插入到头结点的后面
        while (cur!=null){
            next = cur.next;
            cur.next = head.next;
            head.next = cur;
            cur = next;
        }

    }

算法性能分析

对单链表进行一次遍历,时间复杂度O(N)。与方法一相比,这种方法不需要保存前驱结点的地址,与方法二相比,这种方法不需要递归调用,效率更高

逆序输出

(1)就地逆序+顺序输出:改变了链表原来的结构

(2)逆序+顺序输出:申请新的空间,逆序,然后顺序输出,需要申请额外的存储空间

(3)递归输出:

public void reversePrint(LNode firstNode){
    if(firstNode==null){
        return;
    }
    reversePrint(firstNode.next);
    System.out.println(firstNode.data+" ");
}
  • 3
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值