图解java链表基本操作篇一(头插法和尾插法)

整体代码结构定义

public class LinkedListTest <E>{
    
    }

节点数据存储定义

这样子定义只是为了可以方便地创建双向链表,循环链表,不影响单链表的创建,在创建链表的过程中,可以修改前驱指针或者后驱指针的使用或否。

// 创建每个数据的节点
   private static class Node<E>{
    
    
        // 节点元素值
        E item;
        // 前一个节点
        Node<E> pre;
        // 下一个节点
        Node<E> next;

        public Node(){
    
    

        }
        public Node(E item) {
    
    
            this.item = item;
        }
        public Node(E item, Node<E> next) {
    
    
            this.item = item;
            this.next = next;
        }

        public Node(E item, Node<E> pre, Node<E> next) {
    
    
            this.item = item;
            this.pre = pre;
            this.next = next;
        }

        public E getItem() {
    
    
            return item;
        }
        public void setItem(E item) {
    
    
            this.item = item;
        }
        public Node<E> getNext() {
    
    
            return next;
        }
        public void setNext(Node<E> next) {
    
    
            this.next = next;
        }
        public Node<E> getPre() {
    
    
            return pre;
        }
        public void setPre(Node<E> pre) {
    
    
            this.pre = pre;
        }
    }

头插法

实例代码(主讲第一种构造方法):

    // 头插法
    public void createNodeFirst(E e){
    
    
        final Node<E> first = head;
        final Node<E> newNode = new Node<>(e, null, first);
        head = newNode;
        if (first != null) {
    
    
            first.pre = newNode;
        }else {
    
    
            tail = newNode;
        }
        size ++;
    }

实例代码(第二种构造方法):

   // 头插法
    public void createNodeFirst(E e){
    
    
//        final Node<E> first = head;
//        final Node<E> newNode = new Node<>(e, null, first);
//        head = newNode;
//        if (first != null) {
    
    
//            first.pre = newNode;
//        }else {
    
    
//            tail = newNode;
//        }
//        size ++;

        final Node<E> first = head;
        final Node<E> newNode = new Node<>(e, null);
        if (first != null) {
    
    
            newNode.next = head;
        }
        head = newNode;
        size ++;
    }

程序调用:

public static void main(String[] args) {
    
    
        LinkedListTest<Character> listTest = new LinkedListTest<>();
        String test = "abcdefg";
        for (char str : test.toCharArray()) {
    
    
            listTest.createNodeFirst(str);
        }
        System.out.println(listTest.size);
        while (listTest.head != null){
    
    
            System.out.print(listTest.head.item + " ");
            listTest.head = listTest.head.next;
        }
    }

结果:
在这里插入图片描述

代码分析:
(第一次代码循环)第一步代码解读:首先定义一个前置节点 first 指向头结点head,此时的头结点 head 为null值。此时的head节点的构造可以如下所示:
在这里插入图片描述
使用IDEA自带的断点调试可以清楚看到每一个节点的数据变化,(不会打断点的恕我无能为力)如下图:
在这里插入图片描述
(第一次代码循环)第二步代码解读:调用节点的构造函数创建出一个有具体值的节点出来,命名为newNode,传入构造newNode节点的数据有具体的值 e变量,(使用泛型,支持定义为Integer,char等等基本的数据类型的传递)和前置节点 first 。此时的链表构造可以表示为:
在这里插入图片描述
在这里插入图片描述

注意: newNode节点中的next指针应当包含了head节点的全部数据,head节点为 null ,那么newNode 节点中的 next 指针也应当是 null, 示意图为了直观了当才这样子画的。

(第一次代码循环)第三步代码解读:主要的作用是将head节点往前移动,存放字符 a 的节点既是head 节点,同时也是 newNode 节点。这个时候的构造图可以这样子解读:
在这里插入图片描述
在这里插入图片描述

(第一次代码循环)第四步代码解读:首先存放的字符是 a , 这个时候链表只有一个字符,而且前置节点 first 在程序开头指向的是一个没有数据的那头结点 head ,那理所应当此时的 first 节点是一个 null 值的状态,也就是如上面第一步代码解读的结构一样。所以,这一步代码执行的是尾节点 tail 指向新的节点数据newNode。最后一步代码执行的是保存链表中有数据的长度,执行到这一步,链表中具有数据的长度为 1 ,此时的链表结构可以这样子表示:
在这里插入图片描述
在这里插入图片描述

第二次代码循环:当代码执行到第二次循环时,这个时候的前置节点 first 指向头结点 head,其链表结构与第一次代码循环中head的节点结构一样。
在这里插入图片描述

头结点在第一次循环中已经保存有数据了,这个时候创建一个新的节点数据 newNode ,其中保存的字符值为 b 。
在这里插入图片描述

head 节点往前移动,这个时候的整体链表结构可以这样子解读:
在这里插入图片描述
在这里插入图片描述

然后这个时候的 first 节点不为空,那么 first 节点的前节点保存新创建的节点newNode节点。
在这里插入图片描述
在这里插入图片描述

第三次以上的代码循环与第二次代码循环的分析一致,这里就不做多余的补充了。另外可以看出上面的构图,pre 节点均为空,也就是处于没有使用的状态,这个可以看作是单链表的头插法。

尾插法

实例代码(主讲第一种构造方法):

    // 尾插法
    public void createNodeLast(E e){
    
    
        final Node<E> last = tail;
        final Node<E> newNode = new Node<>(e, last, null);
        tail = newNode;
        if (last != null) {
    
    
           last.next = newNode;
        }else {
    
    
            head = newNode;
        }
        size ++;
    }

实例代码(第二种构造方法):

  // 尾插法
    public void createNodeLast(E e){
    
    
//        final Node<E> last = tail;
//        final Node<E> newNode = new Node<>(e, last, null);
//        tail = newNode;
//        if (last != null) {
    
    
//           last.next = newNode;
//        }else {
    
    
//            head = newNode;
//        }
//        size ++;

        final Node<E> last = tail;
        final Node<E> newNode = new Node<>(e, null);
        if(last != null){
    
    
            tail.next = newNode;
            tail = newNode;
        }else {
    
    
            head = newNode;
            tail = head;
        }
        size ++;
    }

程序调用:

public static void main(String[] args) {
    
    
        LinkedListTest<Character> listTest = new LinkedListTest<>();
        String test = "abcdefg";
        for (char str : test.toCharArray()) {
    
    
            listTest.createNodeLast(str);
        }
        System.out.println(listTest.size);
        while (listTest.head != null){
    
    
            System.out.println(listTest.head.item);
            listTest.head = listTest.head.next;
        }
    }

结果:
在这里插入图片描述
代码分析:
(第一次代码循环)第一步代码解读:此时的链表尾节点 tail 指针为空,创建后置节点 last 指向 链表尾节点 tail 。此时的链表结构可以这样解读:
在这里插入图片描述
使用IDEA自带的断点调试可以清楚看到每一个节点的数据变化,(不会打断点的恕我无能为力)如下图:
在这里插入图片描述
在这里插入图片描述

(第一次代码循环)第二步代码解读:调用节点的构造函数创建出一个有具体值的节点出来,命名为newNode,传入构造newNode节点的数据有具体的值 e 变量和 后置节点 last 。此时的链表构造可以表示为:
在这里插入图片描述
由于指向新创建节点 newNode 节点的 last 节点或者是 tail 节点的值均为 null 所以 newNode 节点的前一个节点 pre 的值为 null 。
在这里插入图片描述

(第一次代码循环)第三步代码解读: 此时的 链表的尾节点 tail 指向新创建的节点 newNode ,具体的作用是 tail 节点往后移动,这个时候的链表结构可以这样表示:
在这里插入图片描述
在这里插入图片描述

(第一次代码循环)第四,五步代码解读: 这个时候已经进入到了 if else 语句了,很明显在上图展示的链表结构中last节点的值均为 null 值,这个时候头结点指向新创建的节点 newNode中,同时后面的存放链表有效长度加一(size ++),链表的结构可以这样子解读:
在这里插入图片描述
在这里插入图片描述

(第二次代码循环)当代码执行到第二次循环时,这个时候的后置节点 last 指向链表尾结点 tail。
在这里插入图片描述
在这里插入图片描述

尾结点在第一次循环中已经保存有数据了,这个时候创建一个新的节点数据 newNode ,其中保存的字符值为 b ,同时将后置节点 last 存放在新创建节点的前置指针区域,这个时候的链表结构可以这样子解读:
在这里插入图片描述
在这里插入图片描述

程序运行,这个时候来到 if else 语句中,这个时候可以从上图中看出 last 节点很明显不为空,将后置节点 last 的 下一个节点指向新创建的 newNode 节点,这个时候的链表结构可以这样子解读:
在这里插入图片描述
区别不大,也就是 next 节点不为空了,可以这样子理解 newNode 节点的 pre 指针存放了 tail 节点 ,last 节点和 head 节点,差不多就是你中有我,我中有你的样子,在下面的计算节点中展示数据会显示出死循环。
在这里插入图片描述
举一个简单的例子你就懂了:春娇与志明各有两颗糖,分别写着春娇吃和志明吃,现在春娇给一颗写着春娇吃的字样的糖给志明,然后志明再给一颗写着志明吃的字样的糖给春娇,问什么时候他们俩才能获得一模一样的糖?显然不可能的是吧?

代码奉上

/**
 * @author: 随风飘的云
 * @describe: 单链表、双链表、循环链表
 * @date 2022/03/21 22:13
 */
public class LinkedListTest <E>{
    
    
    // 创建每个数据的节点
    private static class Node<E>{
    
    
        // 节点元素值
        E item;
        // 前一个节点
        Node<E> pre;
        // 下一个节点
        Node<E> next;

        public Node(){
    
    

        }
        public Node(E item) {
    
    
            this.item = item;
        }
        public Node(E item, Node<E> next) {
    
    
            this.item = item;
            this.next = next;
        }

        public Node(E item, Node<E> pre, Node<E> next) {
    
    
            this.item = item;
            this.pre = pre;
            this.next = next;
        }

        public E getItem() {
    
    
            return item;
        }
        public void setItem(E item) {
    
    
            this.item = item;
        }
        public Node<E> getNext() {
    
    
            return next;
        }
        public void setNext(Node<E> next) {
    
    
            this.next = next;
        }
        public Node<E> getPre() {
    
    
            return pre;
        }
        public void setPre(Node<E> pre) {
    
    
            this.pre = pre;
        }
    }

    private int size;
    private Node<E> head;
    private Node<E> tail;
    
    /**
     * 无参构造函数
     */
    public LinkedListTest() {
    
    
    }
    
    // 头插法
    public void createNodeFirst(E e){
    
    
        final Node<E> first = head;
        final Node<E> newNode = new Node<>(e, null, first);
        head = newNode;
        if (first != null) {
    
    
            first.pre = newNode;
        }else {
    
    
            tail = newNode;
        }
        size ++;
    }

    // 尾插法
    public void createNodeLast(E e){
    
    
        final Node<E> last = tail;
        final Node<E> newNode = new Node<>(e, last, null);
        tail = newNode;
        if (last != null) {
    
    
           last.next = newNode;
        }else {
    
    
            head = newNode;
        }
        size ++;
    }

    public static void main(String[] args) {
    
    
//        LinkedListTest<Character> listTest = new LinkedListTest<>();
//        String test = "abcdefg";
//        for (char str : test.toCharArray()) {
    
    
//            listTest.createNodeFirst(str);
//        }
//        System.out.println(listTest.size);
//        while (listTest.head != null){
    
    
//            System.out.print(listTest.head.item + " ");
//            listTest.head = listTest.head.next;
//        }

        LinkedListTest<Integer> listTest = new LinkedListTest<>();
        for (int i = 0; i < 10; i++) {
    
    
            listTest.createNodeLast(i);
        }
        System.out.println(listTest.size);
        while (listTest.head != null){
    
    
            System.out.print(listTest.head.item + " ");
            listTest.head = listTest.head.next;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/m0_46198325/article/details/123767437