集合-链表

本文介绍了链表的概念,它是由节点按特定顺序链接的离散存储线性结构。阐述了链表的种类,重点是单链表。对比了链表与数组的优缺点,指出读取数据优先用数组,插入删除频繁时优先用链表。还说明了链表的操作,如插入、删除、查找和遍历。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 什么是链表

链表: 链表是由一组不必相连(可相连可不相连)的内存结构(节点)按特定的顺序链接在一起的抽象数据类型。

链表是离散存储线性结构

离散: 链表的每个节点可能存于不相连的内存块中,即两个或多个节点所处的内存块的内存地址不相连,所以说链表是离散的。

线性: 通过链表的头结点可以找到下一个节点,直至找到尾结点,就像多个有顺序的点可以连成一条线,所以说链表是线性的。

链表由节点组成,链表的节点离散分配,节点彼此通过指针相连,每个节点只有一个前驱结点和一个后续节点,其中,头节点没有前驱结点,尾结点没有后续节点。

链表的结构举例:

/**
 *@ClassName: Link
 *@Description: 单链表
 *
 */
public class Link {

    /**
     *@ClassName: Node
     *@Description: 单链表的单个节点类
     *
     */
    private class Node {
        //  节点数据,类型自定义,数量自定义
        private Object data;
        //  下一个节点指针
        private Node next;
    }

    //  单链表的头结点
    private Node head;
    //  单链表的节点个数
    private int size;
}

2. 链表的种类

链表有三种:

  1. 单链表
  2. 双链表
  3. 循环链表

其中循环链表可分为单向循环链表和双响循环链表。

这里主要介绍单链表。

3. 链表与数组的区别

链表的优点:

  • 存储空间没有限制
  • 插入和删除节点很快

链表的缺点:

  • 读取节点速度很慢

数组是一种连续存储线性结构,数组每个元素的类型相同,大小相同。

数组的优点:

  • 读取元素速度很快

数组的缺点:

  • 存储空间有限制,需要事先知道数组的长度
  • 需要连续的内存块
  • 插入和删除元素很慢

可见数组和链表的优缺点基本相反,当主要对数据进行读取时优先考虑使用数组,当插入删除数据操作频繁时优先考虑使用链表、

4. 链表的使用

链表操作一般为,插入节点,删除节点,查找节点,遍历链表。

节点示例图:

在这里插入图片描述

链表示例图:

在这里插入图片描述

插入节点示例图:

在这里插入图片描述

删除节点示例图:

在这里插入图片描述

使用案例:

package work.java.xzk10301002.link;

/**
 *@ClassName: Link
 *@Description: 单链表
 *
 */
public class Link {

    /**
     *@ClassName: Node
     *@Description: 单链表的单个节点类
     *
     */
    private class Node {
        //  节点数据
        private int data;
        //  下一个节点指针
        private Node next;

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

        public Node(int data) {
            this(data, null);
        }
    }

    //  单链表的头结点
    private Node head;
    //  单链表的节点个数
    private int size;

    public Link() {
    }

    /**
     * @Description //TODO 对单链表进行初始化,方便演示遍历
     * @Param []
     * @return void
     **/
    public void initLink() {
        Node node1 = new Node(1);
        this.head = node1;
        Node node2 = new Node(4);
        node1.next = node2;
        Node node3 = new Node(3);
        node2.next = node3;
        Node node4 = new Node(4);
        node3.next = node4;
        this.size = 4;
    }

    /**
     * @Description //TODO 单链表进行遍历,打印每一个节点的数据
     * @Param []
     * @return void
     **/
    public void printLink() {
        // curNode为当前操作节点,初始为head节点
        Node curNode = head;
        // 当前节点不为空则打印节点数据data
        while (curNode != null) {
            System.out.print(curNode.data);
            System.out.print("  ->  ");
            //打印完当前节点数据则指针后移,使下一个节点为当前节点进入下一次循环
            curNode = curNode.next;
        }
        // 退出循环则表示单链表已经遍历完毕
        System.out.println("null");
    }

    /**
     * @Description //TODO 在单链表指定位置节点后面插入新的节点
     * @Param [data, index] date为新节点的数据,index表示在单链表中第index个节点后插入节点
     * @return void
     **/
    public void addNode(int data, int index) {
        // 检查节点的插入点是否合理,合理则继续执行,不合理则给出提示并停止操作
        if (index < 0 || index > this.size) {
            System.out.println("单链表中不存在第" + index + "个节点,插入节点失败!");
            return;
        }
        // 可以插入新节点,创建新的节点对象并初始化
        Node node = new Node(data);
        // 当index为0时表示要在单链表的头结点前插入新节点
        if (index == 0) {
            // 新节点指向头结点
            node.next = this.head;
            // 单链表头指针指向新节点
            this.head = node;
            // 节点插入完毕后,单链表节点数加1
            this.size++;
            // 给出插入节点的反馈
            System.out.println("成功插入新的头结点!节点数据:" + data);
        } else {
            // 遍历单链表,找到要链接新节点的目标节点curNode
            Node curNode = this.head;
            for (int i = 1; i < index; i++) {
                curNode = curNode.next;
            }
            // 找到后使新节点指向curNode的下一个节点
            node.next = curNode.next;
            // 使curNode节点指向新节点
            curNode.next = node;
            // 节点插入完毕后,单链表节点数加1
            this.size++;
            // 给出插入节点的反馈
            // 这里使用占位符进行格式化输出,printf继承了C语言中printf的一些特性
            System.out.printf("成功在第%d个结点后插入新节点!节点数据:%d%n", index, data);
        }
        // 执行添加节点操作后打印单链表
        printLink();
    }

    /**
     * @Description //TODO 在单链表的末尾插入新节点
     * @Param [data] 新节点的数据
     * @return void
     **/
    public void addNode(int data) {
        // 只有一个参数表示要在单链表的末尾插入节点,实际上可视为插入位置是节点总数后面
        addNode(data, this.size);
    }

    /**
     * @Description //TODO 根据传入的参数删除指定的节点
     * @Param [index] 索引,表示单链表中要删除的第index个节点
     * @return void
     **/
    public void deleteNodeByIndex(int index) {
        // 通过索引指定删除第index个节点,先判断index是否合理
        if (index < 1 || index > this.size) {
            // index不合理给出提示并停止操作
            System.out.println("单链表中不存在第" + index + "个节点,删除失败!");
            return;
        } else if (index == 1) {
            // index=1表示删除头节点,此时将头指针指向头结点的下一个节点即可
            this.head = head.next;
            // 单链表节点总数减1
            this.size--;
        } else {
            // 遍历单链表,找到要删除的节点的前一个节点preNode
            Node preNode = this.head;
            for (int i = 1; i < index - 1; i++) {
                preNode = preNode.next;
            }
            // 找到前一个节点preNode后,判断删除节点是不是尾节点
            if (preNode.next.next == null) {
                // 是尾部节点则直接使preNode指针指向null即可
                preNode.next = null;
            } else {
                // 不是尾部节点则使preNode指针指向删除节点的下一个节点
                preNode.next = preNode.next.next;
            }
            // 单链表节点总数减1
            this.size--;
        }
        // 给出删除反馈
        System.out.println("成功删除第" + index + "个节点!");
        // 执行删除节点操作后打印单链表
        printLink();
    }

    /**
     * @Description //TODO 根据需要删除的数据删除单链表的节点
     * @Param [data] 需要删除的数据
     * @return void
     **/
    public void deleteNodeByData(int data) {
        // 删除一个未知节点需要两个信息
        // 当前节点,用于对比数据data,可能成为要删除的节点,初始为头结点
        Node curNode = this.head;
        // 当前节点curNode的前一个节点,用于在确定要删除的节点后执行删除操作,初始为头结点
        Node preNode = this.head;
        // 记录下遍历单链表前的节点总数,留有后用
        int allNodes = this.size;
        // 当前节点不是null则一直循环,退出循环表示单链表遍历完毕
        while (curNode != null) {
            // 判断当前节点数据是否为data
            if (curNode.data == data) {
                // 判断当前节点是不是头结点
                if (curNode == this.head) {
                    // 是头结点则使当前节点的下一个节点成为头结点即可完成删除
                    this.head = head.next;
                    // 使curNode和preNode回到初始状态
                    curNode = this.head;
                    preNode = this.head;
                } else {
                    // 不是头结点则删除当前节点,使preNode指向curNode指向的节点即可
                    preNode.next = curNode.next;
                    // preNode不用改变,更新一下新的curNode即可
                    curNode = preNode.next;
                }
                // 走到这说明删除了一个节点,不管是不是头结点
                // 单链表节点总数减1
                this.size--;
                // 给出删除反馈
                System.out.println("成功删除一个数据为" + data + "的节点!");
            } else {
                // 当前节点不是要删除的节点
                // 当前节点成为新的preNode
                preNode = curNode;
                // 当前节点后移一个
                curNode = curNode.next;
            }
        }
        // 遍历完毕,根据节点总数的变化给出删除操作的反馈
        if (allNodes == this.size) {
            // 节点总数无变化,说明没有找到数据为data的节点
            System.out.println("单链表中不存在数据为" + data + "的节点,删除失败!");
        } else {
            System.out.printf("删除完毕,一共删除了%d个数据为%d的节点!%n", allNodes - this.size, data);
        }
        // 执行删除节点操作后打印单链表
        printLink();
    }

    /**
     * @Description //TODO 在单链表中查找数据为data的节点并给出节点信息
     * @Param [data] 要找的节点的信息
     * @return void
     **/
    public void searchNode(int data) {
        // 使用curNode遍历单链表
        Node curNode = this.head;
        // 计数,用于记录找到的节点位置
        int count = 0;
        // 总数,找到的数据的data的节点总数
        int sum = 0;
        // 用于存储找到的节点信息
        StringBuffer result = new StringBuffer();
        // 当前节点不为null则一直循环查找
        while (curNode != null) {
            // 计数加1
            count++;
            // 判断当前节点信息是否为data
            if (curNode.data == data) {
                // 找到一个节点,总数加1
                sum++;
                // 迭代信息
                result.append(",第");
                result.append(count);
                result.append("个节点");
            }
            // 当前节点核对完毕,指针后移
            curNode = curNode.next;
        }
        // 查找完毕,给出反馈
        if (sum == 0) {
            System.out.println("没找到数据为" + data + "的节点!");
        } else {
            System.out.printf("一共找到%d个数据为%d的节点,位置为:%s%n", sum, data, result.substring(1));
        }
    }
}
package work.java.xzk10301002.link;

/**
 *@ClassName: LinkTest
 *@Description: 单链表功能演示的启动类
 *
 */
public class LinkTest {
    public static void main(String[] args) {
        Link link = new Link();
        link.initLink();
        link.printLink();
        link.addNode(6, 9);
        link.addNode(6, 0);
        link.addNode(8);
        link.addNode(9, 3);
        link.deleteNodeByIndex(2);
        link.deleteNodeByIndex(0);
        link.deleteNodeByIndex(1);
        link.deleteNodeByData(1);
        link.searchNode(5);
        link.searchNode(4);
        link.deleteNodeByData(4);
    }
}

运行结果:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值