一文搞懂数据结构之 单向链表

本文详细介绍单向链表的基本概念及其核心操作方法,包括添加、删除、插入、修改等,并提供具体实现代码示例。

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

链表:是一种非线性存储的数据结构,相较于线性存储的数据结构, 线性存储结构需要事先关注数据量的大小,而非线性结构不必。非线性链表具有增删快查询慢的特点,线性链表删除,插入慢,查询快。

单向链表

        单向链表的节点需要包含存储其后一个节点的指针变量,是必须的。

        单项链表增、删、插 、改时需关注的地方

        1. 增加时

                在链表中,头节点不要直接使用。应使用头结点的拷贝变量。

                可以在链表中,加入一个,最后增加项的指针,暂且定义为 last,让这个指针始终指向最后一次添加的数据的地址,这样方便下次增加数据时直接加到链表的后方,而不需要从头再遍历寻找链表的最后一个节点,再去加入。

public class SingleLinkedList {
    /**
     * 链表头节点
     */
    private HeroNode head = new HeroNode();
    /**
     * 最后增加的数据的地址指向
     */
    private HeroNode last = head;

    /**
     * 添加节点
     *
     * @param node 节点
     */
    public void add(HeroNode node) {
        last.setNext(node);
        last = last.getNext();
    }

         

        2. 删除时

                在删除单项链表的其中一项元素时,不仅要拷贝头节(current = head)点的指针去遍历整个链表查找待删除节点,还要记录下要删除元素的前一个节点,以便删除节点后和下一个节点连接,可以使用一个临时的变量before,这样,我们就可以专心地关注 current 就行了。

        

 /**
     * 从链表中移除 no 元素
     *
     * @param no 元素编号
     */
    public void remove(int no) {
        HeroNode current = head;
        HeroNode before = current;
        // 别忘记判断空
        if (isEmpty()){
            System.out.println("链表为空,没法删");
            return;
        }
        while ((current = current.getNext()) != null) {
            // 找到了要删除的节点
            if (current.getNo() == no) {
                // 跨越当前节点连接到下一个节点
                before.setNext(current.getNext());
                break;
            }
            before = current;
        }
        // 没找到要删除的节点
        if (current == null) {
            System.out.println("未找到这个节点");
        }
    }

        3. 插入时,倒没什么需要特别注意的点。找到要插入的位置直接连接就行了

        4. 修改时:

                找到要修改的节点将要修改的数据直接写入节点即可。

                

  /**
     * 根据元素编号更新元素
     *
     * @param heroNode 包含编号的待更新元素
     */
    public void updateNode(HeroNode heroNode) {
        HeroNode temp = head;
        while ((temp = temp.getNext()) != null) {
            if (temp.getNo() == heroNode.getNo()) {
                temp.setName(heroNode.getName());
                temp.setNickName(heroNode.getNickName());

                break;
            }
        }
        if (temp == null) {
            System.out.println("未找到要更新的节点");
        }
    }

单项链表的逆序,可以构建一个新的链表,采用头插法,将旧链表的数据依次取出插入即可。

        

 /**
     * 获取反转链表
     *
     * @return 反转链表头
     */
    public HeroNode getReverse() {
        // 判断链表是否为空
        if (isEmpty()) {
            System.out.println("链表为空,无需反转");
            return null;
        }
        // 遍历链表,采用头插法,插入到新的链表中,将新链表的表头,赋值给原链表
        // 创建新链表
        HeroNode newHead = new HeroNode();
        HeroNode newTemp = null;
        // 遍历链表
        HeroNode temp = head;

        // 如果有效节点不为空,则添加到新链表中
        while ((temp = temp.getNext()) != null) {
            // 头插法插入数据
            // temp 取出节点的值
            newTemp = new HeroNode(temp.getNo(), temp.getName(), temp.getNickName());
            if (newHead.getNext() == null) {
                newHead.setNext(newTemp);
            } else {
                HeroNode node = newHead.getNext();
                newTemp.setNext(node);
                newHead.setNext(newTemp);
            }
        }

        return newHead;
    }

附上完整单项链表代码,并不完美。

        

package pers.uxteam.data;

public class SingleLinkedList {
    /**
     * 链表头节点
     */
    private HeroNode head = new HeroNode();
    /**
     * 最后增加的数据的地址指向
     */
    private HeroNode last = head;

    /**
     * 添加节点
     *
     * @param node 节点
     */
    public void add(HeroNode node) {
        last.setNext(node);
        last = last.getNext();
    }

    /**
     * 根据元素编号更新元素
     *
     * @param heroNode 包含编号的待更新元素
     */
    public void updateNode(HeroNode heroNode) {
        HeroNode temp = head;
        while ((temp = temp.getNext()) != null) {
            if (temp.getNo() == heroNode.getNo()) {
                temp.setName(heroNode.getName());
                temp.setNickName(heroNode.getNickName());

                break;
            }
        }
        if (temp == null) {
            System.out.println("未找到要更新的节点");
        }
    }

    /**
     * 从链表中移除 no 元素
     *
     * @param no 元素编号
     */
    public void remove(int no) {
        HeroNode current = head;
        HeroNode before = current;
        // 别忘记判断空
        if (isEmpty()){
            System.out.println("链表为空,没法删");
            return;
        }
        while ((current = current.getNext()) != null) {
            // 找到了要删除的节点
            if (current.getNo() == no) {
                // 跨越当前节点连接到下一个节点
                before.setNext(current.getNext());
                break;
            }
            before = current;
        }
        // 没找到要删除的节点
        if (current == null) {
            System.out.println("未找到这个节点");
        }
    }

    /**
     * 获取链表头节点
     *
     * @return 链表头节点
     */
    public HeroNode getHead() {
        return head;
    }

    /**
     * v2.0 有序添加到链表中,并返回链表头
     * v1.0 有序添加到链表中
     *
     * @param newNode 元素
     */
    public void addBySort(HeroNode newNode) {
        HeroNode temp = head;
        HeroNode oldNode;
        boolean insertFlag = false; // 是已经将新的节点否插入链表的标识 ,默认未插入
        while ((oldNode = temp.getNext()) != null) {
            // 要添加的数据跟当前旧的的数据比较 ,
            if (oldNode.getNo() > newNode.getNo()) { //如果旧节点 > 新节点
                newNode.setNext(oldNode); //那么就让新节点的 next = 旧节点
                temp.setNext(newNode);
                insertFlag = true;
                break;
            }
            if (oldNode.getNo() == newNode.getNo()) { // 说明该编号已经存在,不添加
                System.out.println("链表中已经存在该编号:" + newNode.getNo());
                insertFlag = true;
                break;
            }
            temp = temp.getNext();
        }
        if (!insertFlag) { // 如果没有插入,则放到队列的最后
            temp.setNext(newNode);
        }
    }

    /**
     * 显示每个节点的详细信息
     */
    public void showAll() {
        HeroNode temp = head.getNext();
        while (temp != null) {
            System.out.println(temp.toString());
            temp = temp.getNext();
        }
    }
    // todo 判断是否为空链表 -- 完成

    /**
     * 判断是否为空链表
     *
     * @return true / false
     */
    public boolean isEmpty() {
        return head.getNext() == null;
    }
    // todo 链表中有效节点的个数 -- 完成

    /**
     * 链表中有效节点的个数
     *
     * @return
     */
    public int size() {
        int length = 0;
        // 链表是否为空
        if (isEmpty()) {
            return length;
        }
        HeroNode temp = head;

        while ((temp = temp.getNext()) != null) {
            length++;
        }
        return length;
    }
    // todo 链表中倒数第 k 个节点 -- 完成

    /**
     * 获取链表中倒数第 k 个节点
     *
     * @param index 倒数第 index 节点
     * @return 倒数第 index 节点信息
     */
    public HeroNode getLastIndexOf(int index) {
        // 首个有效的节点
        HeroNode temp = head.getNext();
        // 如果链表为空
        if (isEmpty()) {
            System.out.println("链表为空,无法获取倒数第 k 个节点");
            return null;
        }
        // 判断 index 是否属于链表范围内
        if (size() - index < 0) {
            System.out.println("要查找的下标超范围!");
            return null;
        }
        // 循环到倒数第 k 个元素  { 共 n 个元素, 下标从 0 开始,到 n-1  }
        for (int i = 0; i < size() - index; i++) {
            temp = temp.getNext();
        }

        return temp;
    }
    // todo 链表的反转 -- 完成

    /**
     * 获取反转链表
     *
     * @return 反转链表头
     */
    public HeroNode getReverse() {
        // 判断链表是否为空
        if (isEmpty()) {
            System.out.println("链表为空,无需反转");
            return null;
        }
        // 遍历链表,采用头插法,插入到新的链表中,将新链表的表头,赋值给原链表
        // 创建新链表
        HeroNode newHead = new HeroNode();
        HeroNode newTemp = null;
        // 遍历链表
        HeroNode temp = head;

        // 如果有效节点不为空,则添加到新链表中
        while ((temp = temp.getNext()) != null) {
            // 头插法插入数据
            // temp 取出节点的值
            newTemp = new HeroNode(temp.getNo(), temp.getName(), temp.getNickName());
            if (newHead.getNext() == null) {
                newHead.setNext(newTemp);
            } else {
                HeroNode node = newHead.getNext();
                newTemp.setNext(node);
                newHead.setNext(newTemp);
            }
        }

        return newHead;
    }

    // todo 从尾部到头部打印链表 要求: 反向遍历和栈 OR 递归  -- 完成
    public void showFromRear() {
        // 去除头节点,进行递归打印
        // recursionOutput(head.getNext());
        // 反向遍历
        // 将链表反转
        /*
        HeroNode reverse = getReverse();
        while ((reverse = reverse.getNext()) != null){
            // 打印每一个有效节点
            System.out.println(reverse);
        }
        */
        // 栈方式打印
        // 创建栈空间
        StackBox stackBox = new StackBox(size());
        // 1. 将链表压入栈中
        HeroNode temp = head;
        // 如果为有效元素
        while ((temp = temp.getNext()) != null) {
            // 压入栈
            stackBox.push(temp);
        }
        // 将栈中的每个元素弹出
        while ((temp = stackBox.pop()) != null) {
            System.out.println(temp);
        }
    }
    // todo 合并两个有序单链表,合并之后依然有序 我记得有道 力扣题 好像需要这个算法!去做一下 -- 完成

    /**
     * 合并两个有序单链表,合并之后依然有序
     *
     * @param list 要合并的链表头
     * @return 合并后的链表头
     */
    public HeroNode mergeOtherSingleLinkList(SingleLinkedList list) {
        // 遍历两个链表,连接到新链表,连接过程中,做插入
        // 创建合并后的链表
        SingleLinkedList outListHead = new SingleLinkedList();
        // 首先本链表有序的加入 outListHead
        HeroNode temp = this.head;
        while ((temp = temp.getNext()) != null) {
            // 取出链表每个有效元素,使用 addBySort 方法加入加入 outListHead
            HeroNode node = new HeroNode(temp.getNo(), temp.getName(), temp.getNickName());
            outListHead.addBySort(node);
        }
        // 将用户传进来的链表有序的加入 outListHead
        temp = list.getHead();
        while ((temp = temp.getNext()) != null) {
            // 取出链表每个有效元素,使用 addBySort 方法加入加入 outListHead
            HeroNode node = new HeroNode(temp.getNo(), temp.getName(), temp.getNickName());
            outListHead.addBySort(node);
        }
        return outListHead.getHead();

    }

    /**
     * 递归打印
     *
     * @param firstEle 第一个有效数据
     */
    private void recursionOutput(HeroNode firstEle) {
        // 如果还存在有效节点,继续递归
        if (head.getNext() != null) {
            recursionOutput(head.getNext());
        }
        System.out.println(head);
    }

    /**
     * 显示链表所有有效元素
     */
    public void show() {
        HeroNode temp = head;
        while ((temp = temp.getNext()) != null) {
            System.out.println(temp);
        }
    }
}
package pers.uxteam.data;

/***
 * 水浒英雄类
 */
public class HeroNode {
    /**
     * 英雄排名
     */
    private int no;
    /**
     * 英雄姓名
     */
    private String name;
    /**
     * 英雄称号
     */
    private String nickName;
    private HeroNode next;

    public int getNo() {
        return no;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    public HeroNode getNext() {
        return next;
    }

    public void setNext(HeroNode next) {
        this.next = next;
    }


    public HeroNode() {
    }

    /**
     * 英雄构造方法
     *
     * @param no       英雄排名
     * @param name     英雄姓名
     * @param nickName 英雄称号
     */
    public HeroNode(int no, String name, String nickName) {
        this.no = no;
        this.name = name;
        this.nickName = nickName;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickName='" + nickName + '\'' +
                '}';
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值