链表及常见算法(java实现)

链表及常见算法(java实现)

注意:本文中的链表默认都是带头节点的。

1.链表的实现

实现很简单,只有两个值,一个data,一个指向下一个节点的指针。由于java语言的特点,这里的指针其实是引用,但为了方便叙述,下文全部称为指针。

public class LNode {
    int data;
    LNode next;
}

2.链表的逆序

就地逆序

就地逆序需要三个指针,遍历的过程中就像是排着一排的人一个一个转身,cur标识现在转身的节点,pre和next则记录转身前前后的两个人,以保证重建连接。

这个方法只需要对链表进行一次遍历,时间复杂度为O(N)。需要常数个额外变量记录前后节点,空间复杂度为O(1)。

总的来说,是一个中规中矩的算法。

//就地逆转
public static void Reverse_still(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;
    while(cur.next !=null) {
        next = cur.next;
        cur.next = pre;
        //继续移动
        pre = cur;
        cur = next;
    }
    cur.next = pre;
    head.next = cur;
}

递归逆序

因为递归部分只能对没有头结点的链表进行处理,所以分成了两个函数进行处理。

个人以为递归永远都是相对优雅的一种方法,并且是相对难理解一些的。这个方法,我觉得就像是把头节点以后的部分看成一个黑盒,工作已经全部完成,算法里面需要体现的就是完成最后一步。但实际运行中,就像是拆一个俄罗斯套娃,只要内存足够大,且做好判断条件,就可以一层一层拆下去,直到任务的完成。

我理解的时候,遇到的难点就是这个黑盒假设,但想通以后,就会感觉很明了,很优雅。

这个方法和就地逆序差不多,都是对链表进行一次遍历,但因为不断的调用自己,就要不断的进行压栈和弹栈操作,所以性能必然会有所欠缺。(但是代码写的好看啊!!!)

//递归逆序(加上头节点)
public static void RecursiveReverse_head(LNode head) {
    if(head == null) {
        return ;
    }
    LNode firstNode = head.next;
    LNode newhead = RecursiveReverse(firstNode);
    head.next = newhead;
}
//对没有头节点的链表进行处理
private static LNode RecursiveReverse(LNode head) {
    if(head == null || head.next == null) {
        return head;
    }else {
        LNode newhead = RecursiveReverse(head.next);
        head.next.next = head;
        head.next = null;
        return newhead;
    }
}

插入逆序

插入逆序时间复杂度同样为O(N)。但是,和就地逆序相比,它不用保存前驱节点,和递归相比,它不用频繁的调用函数,所以它的效率最高。

我在后面写一些算法的时候发现,插入法只适合处理带头节点的链表,一旦有需求逆序没有头节点的链表时,就地逆序显得更简单易用。因为插入法能省下前驱节点的原因就是,它有效的利用了头节点的作用,每次插入都是以头节点作为标准,不断的在头节点,和head.next()之间进行插入操作。一旦没有头节点,则需要考虑程序结束后对头节点的处理,这样的话和就地逆序对比,就会有点得不偿失。

//插入逆序
public static void Reverse_insert(LNode head) {
    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;//开始下一次循环
    }
}

测试代码

以下为上面代码的执行类,当时有点懒,直接写了个for循环构造链表,没有单独封装出一个方法出来,后续的会逐渐完善。

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.println();
    Reverse_insert(head);
    //Reverse_still(head);
    //RecursiveReverse_head(head);
    System.out.print("逆序后:");
    for(cur = head.next; cur != null; cur = cur.next) {
        System.out.print(cur.data+" ");
    }
}

未完待续…

猜你喜欢

转载自blog.csdn.net/qq_41620800/article/details/85541979