保证你会单链表,附完整 Java 实现

49 阅读7分钟

结构

单链表结构如下: 在这里插入图片描述 我们知道数组是连续的存储空间;而链表不需要,因为有「指针」,它通过“指针”将一组零散的内存块串联起来使用 。当系统总容量 >100M;但是不连续,此时我们无法申请容量为 100M 的数组,但是链表可以。

一般我们习惯性的将第一个节点称为头结点,并不存储实际的内容;而最后一个节点叫尾节点,是指向一个空地址NULL。

头结点 VS 头指针

这里,会有一个头结点和头指针的概念:

单链表有带头节点和不带头节点两种:

“链表中第一个节点的存储位置叫做头指针”,如果链表有头结点,那么头指针就是指向头结点的指针。头指针所指的不存放数据元素的第一个节点称为「头结点」(头结点指向首元节点 )。头结点的数据域一般不存放数据(有些情况下也可存放链表的长度,用做监视哨等)存放第一个数据元素的节点称作第一个数据元素节点,或称为首元节点

链表中第一个节点的存储位置叫做头指针,那么整个链表的存取就必须从头指针开始。

“链表中第一个结点的存储位置叫做头指针”,如果链表有头结点,那么头指针就是指向头结点数据域的指针。 在这里插入图片描述 头结点的特性:

  • 头结点不是必须的
  • 对在第一个元素节点前插入节点和删除第一个节点,其操作与对其他节点的操作统一了。
    • 1.带头结点时 删除第1个结点(q指向的是头结点):q->next = a1->next; free(p); 删除第i个结点(i不等于1):q->next = ai->next;free(p); 2.不带头结点时 删除第1个结点时(q为空):L = a1->next; free(p); 删除第i个结点(i不等于1):q->next = p->next;free(p);

指针

在 Java 中没有指针这个说法,理解成引用就好了。

将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。

在实际写代码过程中,可能也是指针最容易出错。

单链表所有操作

了解了上面的基本概念,现在可以来写简单的单链表了,在实战中不断犯错,不断理解。

import java.util.Scanner;

/**
 * 包含链表的基本操作,而且输出比较友好哦
 */
public class TestObject {
    private Node head = new Node();//头节点,初始为一个空节点
    private Node temp;//临时节点

    //定义节点结构
    static class Node {
        private Node next;
        private int data;

        public Node(int data) {
            this.data = data;
        }

        public Node() {
        }
    }

    //辅助:遍历链表输出
    public void print(String str) {
        temp = head.next;//head 是头节点,不保存实际的数据
        System.out.println(str+":");
        while (temp != null) {
            System.out.print(temp.data + "->");
            temp = temp.next;
        }
        //最后一个节点指向 null
        System.out.println("null");
    }

    //辅助:链表长度
    public int length() {
        temp = head.next;//我这直接从第一个真正存储元素的位置开始
        int length = 1;
        while (temp != null) {
            length++;
            temp = temp.next;
        }
        return length;
    }

    //初始化一个链表(每次都在链尾插入)
    public void addNode(int data) {
        Node newNode = new Node(data);//新节点的数据
        temp = head;//每次从头节点开始遍历
        // while 循环使得每次 temp 都会是当前链表的最后一个节点
        while (temp.next != null) {
            temp = temp.next;
        }
        //在链表的尾部增加一个节点
        temp.next = newNode;
    }

    //指定位置插入
    public void insertNode(int index, int data) {
        if (index > length() || index < 0) {
            System.out.println("插入位置错误," + "插入范围应为:" + "[1" + "," + length() + "]");
        }

        Node indexNode = new Node(data);
        temp = head;  //始终从头节点开始遍历
        int length = 1;
       /* while (temp != null) {
            if (length == index)
                break;
            temp = temp.next;
            length++;
        }
        //主要理解下面这两句代码
        indexNode.next = temp.next;
        temp.next = indexNode;*/
        //两种方法都可以
        while(temp != null) {
            //注意 length++ 哦
            if (length++ == index) {
                //主要理解下面这两句代码
                indexNode.next = temp.next;
                temp.next = indexNode;
            }
            temp = temp.next;
        }
    }

    //指定位置删除
    public void deleteNode(int index){
        if (index > length()-1 || index < 0) {
            System.out.println("插入位置错误," + "插入范围应为:" + "[1" + "," + length() + ")");
        }

        temp=head;
        int length=1;
        while(temp!=null){
            if(length++==index){
                //将被删除的节点的 next指针 赋给它的前一个 next指针
                temp.next=temp.next.next;
            }
            temp=temp.next;
        }

    }


    public static void main(String[] args) {
        TestObject test = new TestObject();
        //初始化一个链表
        int data[] = {1, 5, 3, 4, 6};
        for (int i = 0; i < data.length; i++) {
            test.addNode(data[i]);
        }
        //输出
        test.print("初始化的链表");

        Scanner insIn = new Scanner(System.in);
        System.out.println("请输入要插入节点的位置:");
        int index=insIn.nextInt();
        System.out.println("请输入要插入节点的数据:");
        //在指定位置插入节点 + 输出
        test.insertNode(index,insIn.nextInt());
        test.print("插入后的链表");

        Scanner delIn = new Scanner(System.in);
        System.out.println("请输入要删除节点的位置:");
        //在指定位置删除节点 + 输出
        test.deleteNode(delIn.nextInt());
        test.print("删除后的链表");
    }
}

下面示意图是在指定位置插入一个节点的详解;其他比如删除操作同理 在这里插入图片描述

总结

  • 学会用画图辅助自己思考,可能有时候只靠想,脑子会不够用哦。
  • 不要觉得自己看懂了,我要我觉得你应该去自己独立的写出来。
  • 如果觉得自己代码没有问题,但是结果并不是自己想象那样的,那么请 Debug。IDEA Debug 了解一下。

### 文章目录

-   [头结点 VS 头指针](https://editor.csdn.net/md/?articleId=103518330#_VS__8)
-   [指针](https://editor.csdn.net/md/?articleId=103518330#_31)
-   [单链表所有操作](https://editor.csdn.net/md/?articleId=103518330#_38)
-   [总结](https://editor.csdn.net/md/?articleId=103518330#_176)

单链表结构如下:  
![在这里插入图片描述](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9ad8057e76c14279a375e64cde8761c5~tplv-k3u1fbpfcp-zoom-1.image)  
我们知道数组是连续的存储空间;而链表不需要,因为有「指针」,它通过“指针”将一组零散的内存块串联起来使用 。当系统总容量 >100M;但是不连续,此时我们无法申请容量为 100M 的数组,但是链表可以。

一般我们习惯性的将第一个节点称为**头结点**,并不存储实际的内容;而最后一个节点叫**尾节点**,是指向一个空地址NULL。

# []()头结点 VS 头指针

这里,会有一个头结点和头指针的概念:

单链表有带头节点和不带头节点两种:

> “链表中第一个节点的存储位置叫做头指针”,**如果链表有头结点,那么头指针就是指向头结点的指针**。头指针所指的不存放数据元素的第一个节点称为「头结点」(头结点指向首元节点 )。头结点的数据域一般不存放数据(有些情况下也可存放链表的长度,用做监视哨等)存放第一个数据元素的节点称作第一个数据元素节点,或称为**首元节点**

链表中第一个节点的存储位置叫做头指针,那么整个链表的存取就必须从头指针开始。

**“链表中第一个结点的存储位置叫做头指针”,如果链表有头结点,那么头指针就是指向头结点数据域的指针。**  
![在这里插入图片描述](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a96c2dea7b994d2b851b49dd5e79b43b~tplv-k3u1fbpfcp-zoom-1.image)  
**头结点的特性:**

-   头结点不是必须的

-   对在第一个元素节点前插入节点和删除第一个节点,其操作与对其他节点的操作统一了。

    -   1.带头结点时  
        删除第1个结点(q指向的是头结点):q->next = a1->next; free§;  
        删除第i个结点(i不等于1):q->next = ai->next;free§;  
        2.不带头结点时  
        删除第1个结点时(q为空):L = a1->next; free§;  
        删除第i个结点(i不等于1):q->next = p->next;free§;

# []()指针

在 Java 中没有指针这个说法,理解成引用就好了。

**将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。**

在实际写代码过程中,可能也是指针最容易出错。

# []()单链表所有操作

了解了上面的基本概念,现在可以来写简单的单链表了,在实战中不断犯错,不断理解。

import java.util.Scanner;

/**

  • 包含链表的基本操作,而且输出比较友好哦 */ public class TestObject { private Node head = new Node();//头节点,初始为一个空节点 private Node temp;//临时节点

    //定义节点结构 static class Node { private Node next; private int data;

     public Node(int data) {
         this.data = data;
     }
    
     public Node() {
     }
    

    }

    //辅助:遍历链表输出 public void print(String str) { temp = head.next;//head 是头节点,不保存实际的数据 System.out.println(str+":"); while (temp != null) { System.out.print(temp.data + "->"); temp = temp.next; } //最后一个节点指向 null System.out.println("null"); }

    //辅助:链表长度 public int length() { temp = head.next;//我这直接从第一个真正存储元素的位置开始 int length = 1; while (temp != null) { length++; temp = temp.next; } return length; }

    //初始化一个链表(每次都在链尾插入) public void addNode(int data) { Node newNode = new Node(data);//新节点的数据 temp = head;//每次从头节点开始遍历 // while 循环使得每次 temp 都会是当前链表的最后一个节点 while (temp.next != null) { temp = temp.next; } //在链表的尾部增加一个节点 temp.next = newNode; }

    //指定位置插入 public void insertNode(int index, int data) { if (index > length() || index < 0) { System.out.println("插入位置错误," + "插入范围应为:" + "[1" + "," + length() + "]"); }

     Node indexNode = new Node(data);
     temp = head;  //始终从头节点开始遍历
     int length = 1;
    /* while (temp != null) {
         if (length == index)
             break;
         temp = temp.next;
         length++;
     }
     //主要理解下面这两句代码
     indexNode.next = temp.next;
     temp.next = indexNode;*/
     //两种方法都可以
     while(temp != null) {
         //注意 length++ 哦
         if (length++ == index) {
             //主要理解下面这两句代码
             indexNode.next = temp.next;
             temp.next = indexNode;
         }
         temp = temp.next;
     }
    

    }

    //指定位置删除 public void deleteNode(int index){ if (index > length()-1 || index < 0) { System.out.println("插入位置错误," + "插入范围应为:" + "[1" + "," + length() + ")"); }

     temp=head;
     int length=1;
     while(temp!=null){
         if(length++==index){
             //将被删除的节点的 next指针 赋给它的前一个 next指针
             temp.next=temp.next.next;
         }
         temp=temp.next;
     }
    

    }

    public static void main(String[] args) { TestObject test = new TestObject(); //初始化一个链表 int data[] = {1, 5, 3, 4, 6}; for (int i = 0; i < data.length; i++) { test.addNode(data[i]); } //输出 test.print("初始化的链表");

     Scanner insIn = new Scanner(System.in);
     System.out.println("请输入要插入节点的位置:");
     int index=insIn.nextInt();
     System.out.println("请输入要插入节点的数据:");
     //在指定位置插入节点 + 输出
     test.insertNode(index,insIn.nextInt());
     test.print("插入后的链表");
    
     Scanner delIn = new Scanner(System.in);
     System.out.println("请输入要删除节点的位置:");
     //在指定位置删除节点 + 输出
     test.deleteNode(delIn.nextInt());
     test.print("删除后的链表");
    

    } }


> 下面示意图是在指定位置插入一个节点的详解;其他比如删除操作同理  
> ![在这里插入图片描述](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cf291e57ecdd4a09a6751be642009718~tplv-k3u1fbpfcp-zoom-1.image)

# []()总结

-   学会用画图辅助自己思考,可能有时候只靠想,脑子会不够用哦。
-   不要觉得自己看懂了,我要我觉得你应该去自己独立的写出来。
-   如果觉得自己代码没有问题,但是结果并不是自己想象那样的,那么请 Debug。[IDEA Debug](https://www.cnblogs.com/jajian/p/9410844.html) 了解一下。

Markdown 3600 字数 181 行数 当前行 1, 当前列 0

HTML 3023 字数 145 段落