Java之链表(不带头结点,带头结点,迭代实现,递归实现)
创始人
2025-05-31 10:16:34

目录

一.链表

1.什么是链表

2.链表的分类

 二.不带头结点单向链表的非递归实现

1.接口的定义

2. 不带头结点单向链表的结构

3.链表的添加操作(头插法和尾插法)

1.头插法

2.尾插法

4. 链表的插入操作

5.链表的删除操作

1.删除指定索引的结点

2.删除指定值的第一个结点

3.删除指定值的所有结点

6.链表的其他操作

1.将指定索引的结点的值更换为指定值

2.获得指定索引结点的值

3.判断链表是否包含指定值

4.获得指定值的第一个索引

5.toString方法

三.带头结点单向链表的非递归实现

1.带头结点单向链表的结构

2.链表的添加操作(头插法和尾插法)

1.头插法

2.尾插法

3. 链表的插入操作

4.链表的删除操作

1.删除指定索引的结点

2.删除指定值的第一个结点

3.删除指定值的所有结点

5.链表的其他操作

四.不带头结单向点链表的递归实现

1.基本介绍

2. 链表的插入操作

3.递归打印链表结点的值

1.正序打印

2.逆序打印

4.链表的删除操作

1.删除指定值的所有结点

2.删除链表中的重复值(保留)

3.删除链表中的重复值(不保留)

5.链表的其他操作

五.不带头结点双向链表的非递归实现

1.不带头结点双向链表的结构

2.链表的添加操作(头插法和尾插法)

1.头插法

2.尾插法

3. 链表的插入操作

4.链表的删除操作

1.删除指定索引的结点

2.删除指定值的第一个结点

3.删除指定值的所有结点


一.链表

1.什么是链表

链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。

 

2.链表的分类

链表的类型多种多样,具体可以分为:

1.带头结点的链表和不带头结点的链表

2.单向链表和双向链表

3.循环的和非循环的

 二.不带头结点单向链表的非递归实现

1.接口的定义

在实现具体的方法之前,我们先定义一个接口,之后的这些链表都将实现这些接口,实现这些接口内的方法,主要包含了一些常用的CRUD方法.这个接口我们使用了泛型,之后的链表类也将继承这个接口的泛型,元素的类型就是泛型的类型

public interface SeqList {// 在线性表中的最后插入值为element的新元素void addLast(E element);// 在线性表的前边插入新元素,插入后的元素下标为indexvoid addFirst(E element);//在索引为index处插入值为element的结点void add(int index, E element);// 删除当前线性表中索引为index的元素,返回删除的元素值E removeByIndex(int index);// 删除当前线性表中第一个值为element的元素void removeByValue(E element);// 删除当前线性表中所有值为element的元素void removeAllValue(E element);// 将当前线性表中index位置的元素替换为element,返回替换前的元素值E set(int index, E element);// 得到下标为index结点的值E get(int index);// 查询链表内是否包含element的结点boolean contains(E element);// 查询当前线性表中第一个值为element的下标int indexOf(E element);}

2. 不带头结点单向链表的结构

我们这里定义了一个内部类Node,为单向链表的结点类,同时单向链表的属性包含了头结点的引用信息和链表结点个数的定义

public class SingleLinkedList implements SeqList {private Node head;  //第一个结点的位置private int size;  //链表结点的个数private class Node {E val;Node next;public Node(E val) {this.val = val;}}@Overridepublic void addLast(E element) {}@Overridepublic void addFirst(E element) {}@Overridepublic void add(int index, E element) {}@Overridepublic E removeByIndex(int index) {return null;}@Overridepublic void removeByValue(E element) {}@Overridepublic void removeAllValue(E element) {}@Overridepublic E set(int index, E element) {return null;}@Overridepublic E get(int index) {return null;}@Overridepublic boolean contains(E element) {return false;}@Overridepublic int indexOf(E element) {return 0;}
}

3.链表的添加操作(头插法和尾插法)

1.头插法

    @Overridepublic void addFirst(E element) {//产生一个值为element的结点Node newNode = new Node(element);//当前结点的指向头结点newNode.next = head;//更新head的引用,将其指向新节点head = newNode;size++;}

2.尾插法

    @Overridepublic void addLast(E element) {Node temp = head;if (head == null) {head = new Node(element);size++;return;}while (temp.next != null) {temp = temp.next;}temp.next = new Node(element);size++;}

4. 链表的插入操作

记住:插入和删除的关键是找到插入和删除位置的前驱节点

    @Overridepublic void add(int index, E element) {//边界的判断if (!rangeCheck(index)) {throw new IllegalArgumentException("get index illegal!");}//头插if (index == 0) {addFirst(element);return;}//尾插if (index == size) {addLast(element);return;}Node temp = head;//找到插入位置的前驱节点for (int i = 0; i < index - 1; ++i) {temp = temp.next;}Node newNode = new Node(element);newNode.next = temp.next;temp.next = newNode;size++;}//index的检查private boolean rangeCheck(int index) {//如果插入的位置索引不合理,返回falseif (index < 0 || index > size) {return false;}return true;}

5.链表的删除操作

1.删除指定索引的结点

    @Overridepublic E removeByIndex(int index) {//边界的判断if (!rangeCheck(index)) {throw new IllegalArgumentException("get index illegal!");}//判断头结点的删除if (index == 0) {Node node = head;head = head.next;node.next = null;size--;return node.val;}//除头结点的输出,找到要删除位置的前驱节点Node temp = head;for (int i = 0; i < index - 1; ++i) {temp = temp.next;}Node node = temp.next;temp.next = temp.next.next;node.next = null;size--;return node.val;}

2.删除指定值的第一个结点

    @Overridepublic void removeByValue(E element) {if (head == null)return;//删除的恰好是头结点if (head.val.equals(element)) {head = head.next;size--;return;}//删除的是其他节点Node temp = head;while (temp.next != null) {if (temp.next.val.equals(element)) {temp.next = temp.next.next;size--;return;}temp = temp.next;}//没有要删除值的结点System.out.println("当前链表中不存在值为:" + element + "的结点");}

3.删除指定值的所有结点

    @Overridepublic void removeAllValue(E element) {if (head == null) {return;}//删除的恰好是头结点while (head != null && head.val.equals(element)) {head = head.next;size--;}//链表全部删完了if (head == null) {return;}//删除的是其他节点Node temp = head;while (temp.next != null) {if (temp.next.val.equals(element)) {temp.next = temp.next.next;size--;} else {//只有后继节点不是element的时候才能移动temp = temp.next;}}}

6.链表的其他操作

1.将指定索引的结点的值更换为指定值

    @Overridepublic E set(int index, E element) {//边界的判断if (!rangeCheck(index)) {throw new IllegalArgumentException("get index illegal!");}Node temp = head;//找到索引为index的结点for (int i = 0; i < index; ++i) {temp = temp.next;}//保存结点的旧值E oldValue = temp.val;//将索引为index结点的值更换为elementtemp.val = element;return oldValue;}

2.获得指定索引结点的值

    @Overridepublic E get(int index) {//边界的判断if (!rangeCheck(index)) {throw new IllegalArgumentException("get index illegal!");}Node temp = head;//找到索引为index的结点for (int i = 0; i < index; ++i) {temp = temp.next;}return temp.val;}

3.判断链表是否包含指定值

    @Overridepublic boolean contains(E element) {Node temp = head;while (temp != null) {if (temp.val.equals(element)) {return true;}temp = temp.next;}return false;}

4.获得指定值的第一个索引

    @Overridepublic int indexOf(E element) {//头结点为空if (head == null)return -1;Node temp = head;for (int i = 0; i < size; ++i) {if (temp.val.equals(element)) {return i;}temp = temp.next;}//表示没有找到值为element的结点的索引return -1;}

5.toString方法

    @Overridepublic String toString() {Node temp = head;StringBuffer sb = new StringBuffer();while (temp != null) {sb.append(temp.val + "->");temp = temp.next;}sb.append("NULL");return sb.toString();}

三.带头结点单向链表的非递归实现

1.带头结点单向链表的结构

带头结点的单链表有一个虚拟头结点,头结点里面存储的值并不是链表中真实的值,没有实际意义,只是作为一个头结点的存在,同时size的值不会因为虚拟头结点的存在加一.

public class SingleLinkedListWithHead implements SeqList {private class Node {E val;Node next;public Node(E val) {this.val = val;}}private Node dummyHead = new Node(null);int size = 0;@Overridepublic void addFirst(E element) {}@Overridepublic void addLast(E element) {}@Overridepublic void add(int index, E element) {}@Overridepublic E removeByIndex(int index) {return null;}@Overridepublic void removeByValue(E element) {}@Overridepublic void removeAllValue(E element) {}@Overridepublic E set(int index, E element) {return null;}@Overridepublic E get(int index) {return null;}@Overridepublic boolean contains(E element) {return false;}@Overridepublic int indexOf(E element) {return 0;}}

2.链表的添加操作(头插法和尾插法)

1.头插法

    @Overridepublic void addFirst(E element) {Node node = new Node(element);node.next = dummyHead.next;dummyHead.next = node;size++;}

2.尾插法

    @Overridepublic void addLast(E element) {Node temp = dummyHead;for (int i = 0; i < size; ++i) {temp = temp.next;}Node node = new Node(element);temp.next = node;size++;}

3. 链表的插入操作

    @Overridepublic void add(int index, E element) {//边界的判断if (!rangeCheck(index)) {throw new IllegalArgumentException("get index illegal!");}if (index == 0) {addFirst(element);return;}if (index == size) {addLast(element);return;}Node temp = dummyHead;Node node = new Node(element);for (int i = 0; i < index; ++i) {temp = temp.next;}node.next = temp.next;temp.next = node;size++;}

4.链表的删除操作

1.删除指定索引的结点

    @Overridepublic E removeByIndex(int index) {//边界的判断if (!rangeCheck(index)) {throw new IllegalArgumentException("get index illegal!");}Node temp = dummyHead;for (int i = 0; i < index; ++i) {temp = temp.next;}Node node = temp.next;temp.next = temp.next.next;return node.val;}

2.删除指定值的第一个结点

    @Overridepublic void removeByValue(E element) {Node temp = dummyHead;while (temp.next != null) {if (temp.next.val.equals(element)) {temp.next = temp.next.next;size--;return;}temp = temp.next;}System.out.println("当前链表中不存在值为:" + element + "的结点");}

3.删除指定值的所有结点

    @Overridepublic void removeAllValue(E element) {Node temp = dummyHead;while (temp.next != null) {if (temp.next.val.equals(element)) {temp.next = temp.next.next;size--;} else {temp = temp.next;}}}

5.链表的其他操作

这里不对其他操作进行描述,因为大致方法和不带头结点的单链表的操作一致

四.不带头结单向点链表的递归实现

1.基本介绍

递归方式主要实现的是插入操作和删除操作,它的结构和不带头结点的单链表的结构一样

2. 链表的插入操作

    //在索引值为index处插入val的结点public void add(int index, int val) {head = addInternal(head, index, val);}private Node addInternal(Node head, int index, int val) {if (index < 0 || index > size) {throw new IllegalArgumentException("不合法");}//头插if (index == 0) {Node node = new Node(val);node.next = head;head = node;size++;return head;}//index不在头结点插入head.next = addInternal(head.next, index - 1, val);return head;}

3.递归打印链表结点的值

1.正序打印

    //正序打印public void print(Node head) {if (head == null) {System.out.print("NULL");return;}System.out.print(head.val + "->");print(head.next);}

2.逆序打印

    //逆序打印public void printReverse(Node head) {if (head == null) {return;}printReverse(head.next);if (head == this.head) {System.out.print(head.val + "->NULL");} else {System.out.print(head.val + "->");}}

4.链表的删除操作

1.删除指定值的所有结点

力扣:力扣

    public ListNode removeElements(ListNode head, int val) {if (head == null)return null;head.next = removeElements(head.next, val);return head.val == val ? head.next : head;}

2.删除链表中的重复值(保留)

力扣:力扣

    public ListNode deleteDuplicates(ListNode head) {if (head == null || head.next == null) {return head;}head.next = deleteDuplicates(head.next);return head.val == head.next.val ? head.next : head;}

3.删除链表中的重复值(不保留)

力扣:力扣

解法一:

    public ListNode deleteDuplicates(ListNode head) {if (head == null || head.next == null) {return head;}if (head.val != head.next.val) {head.next = deleteDuplicates(head.next);return head;} else {//头结点就是重复的结点while (head.next != null && head.val == head.next.val) {head = head.next;}head = deleteDuplicates(head.next);return head;}}

 解法二:

    public ListNode deleteDuplicates(ListNode head) {if(head==null||head.next==null){return head;}if(head.val!=head.next.val){head.next=deleteDuplicates(head.next);return head;}else{//头结点就是重复的结点ListNode cur=head.next;while(cur!=null&&cur.val==head.val){cur=cur.next;}return deleteDuplicates(cur);}}

5.链表的其他操作

在这里不做细致的介绍

五.不带头结点双向链表的非递归实现

1.不带头结点双向链表的结构

public class DoubleLinkedList implements SeqList {private class DoubleNode {E val;DoubleNode pre;DoubleNode next;public DoubleNode(E val) {this.val = val;}public DoubleNode(E val, DoubleNode pre, DoubleNode next) {this.val = val;this.pre = pre;this.next = next;}}private DoubleNode head;private DoubleNode tail;private int size = 0;//尾插法@Overridepublic void addLast(E element) {}@Overridepublic void addFirst(E element) {}//找到索引为index的位置的结点private DoubleNode node(int index) {return null;}@Overridepublic void add(int index, E element) {}@Overridepublic E removeByIndex(int index) {return null;}@Overridepublic void removeByValue(E element) {}@Overridepublic void removeAllValue(E element) {}@Overridepublic E set(int index, E element) {return null;}@Overridepublic E get(int index) {return null;}@Overridepublic boolean contains(E element) {return false;}@Overridepublic int indexOf(E element) {return 0;}
}

2.链表的添加操作(头插法和尾插法)

1.头插法

    @Overridepublic void addFirst(E element) {DoubleNode node = new DoubleNode(element);if (tail == null) {tail = node;} else {head.pre = node;node.next = head;}head = node;size++;}

2.尾插法

    //尾插法@Overridepublic void addLast(E element) {DoubleNode node = new DoubleNode(element);if (head == null) {head = node;} else {tail.next = node;node.pre = tail;}tail = node;size++;}

3. 链表的插入操作

    //找到索引为index的位置的结点private DoubleNode node(int index) {DoubleNode result = null;if (index < (size >> 1)) {result = head;for (int i = 0; i < index; ++i) {result = result.next;}} else {result = tail;for (int i = size - 1; i > index; --i) {result = result.pre;}}return result;}private boolean rangeCheck(int index) {if (index < 0 || index > size) {return false;}return true;}@Overridepublic void add(int index, E element) {if (!rangeCheck(index)) {throw new IllegalArgumentException("illegal index!!!");}if (index == 0) {addFirst(element);}if (index == size) {addLast(element);}DoubleNode pre = node(index - 1);DoubleNode next = pre.next;DoubleNode node = new DoubleNode(element, pre, next);pre.next = node;next.pre = node;size++;}

4.链表的删除操作

1.删除指定索引的结点

    //在当前链表中删除node结点private DoubleNode unlink(DoubleNode node) {DoubleNode pre = node.pre;DoubleNode next = node.next;// 先处理左半区域if (pre == null) {this.head = next;} else {node.pre = null;pre.next = next;}// 在处理右半区域if (next == null) {this.tail = pre;} else {node.next = null;next.pre = pre;}size--;return node;}@Overridepublic E removeByIndex(int index) {if (!rangeCheck(index)) {throw new IllegalArgumentException("illegal index!!");}DoubleNode node = node(index);return unlink(node).val;}

2.删除指定值的第一个结点

    @Overridepublic void removeByValue(E element) {DoubleNode node = head;for (int i = 0; i < size; i++) {if (node.val.equals(element)) {unlink(node);return;}node = node.next;}}

3.删除指定值的所有结点

    @Overridepublic void removeAllValue(E element) {DoubleNode node = head;// 因为每次unlink之后都会修改size的值,但是删除所有元素,// 要把所有链表节点全部遍历一遍int length = this.size;for (int i = 0; i < length; i++) {DoubleNode next = node.next;if (node.val.equals(element)) {unlink(node);}node = next;}}

相关内容

热门资讯

【开源协议】关于6种开源协议的... 关于开源协议的说明前言开源协议分为 宽松型 和 著作权型。如何选择?开源协议(GPL,...
easymock不能用了?fa... 什么是fast mock 前端常常要等待后面接口写好后,才能集成测试,免...
GIS的一些简单算法(自己作记... 一、线的矢量算法 1、简单的线相交算法 算法1:快速排斥与矢量跨立         快...
催债令来了!国家急了,严禁机关... 作者 | 剑书发现没有,当前国家摆在最高优先级的一件大事,竟然是给中小企业还清欠款。就在6月1日,国...
数据分析思维|思考问题的25个... 逻辑思维:能够理性分析问题,梳理出问题的本质和关键点,建立...
三、Docker:Compos...   Docker-compose 是用于定义和运行多容器 Docker 应用程序的编排工具。使用 d...
理想汽车5月交付40856辆,... 新京报贝壳财经讯 6月1日,理想汽车公布2025年5月交付数据。5月份,理想汽车交付新车40856辆...
新宙邦:公司对高性能电子材料均... 证券日报网讯 新宙邦5月30日在互动平台回答投资者提问时表示,公司对高性能电子材料均做了一些布局。其...
电商酒水类产品打假有哪些操作(... 线上酒水类产品蓬勃发展,但产品假货问题也随之而来,不仅损害了品牌商家的合法权益,还对消费者的健康和安...
今年很火的AI绘画怎么玩 1.前言 2022年绝对可以说是AIGC元年,从google搜索的趋势来看࿰...
canvas 教程 指南 万字... 前端妹子问我为什么官网上面只有一个canvas标签,里面什么都没有… 我脸色一变”完了...
开发一个app需要多少钱 APP应用开发主要分为原生APP和HTML5APP开发,使用HTML5开发的app价格...
vue项目的创建 npm install postcss postcss-pxtorem --save-dev 什么是...
二十六、对象的实例化内存布局与... 一、对象的实例化 1.判断对象对用的类是否加载、链接、初始化。 2.为对象分配内存。 3.处理并发...
助力私募行业高质量发展,华西证...   践行传播行业文化,进一步促进私募行业高质量发展,资本市场这场现象级行...
创建索引,解决mysql数据查... 实战场景 :两个表 T_PLATFORM A left join T_OPER_REC...
5. Python中的异常处理... 1. 说明: 自己写的代码保证万无一失有点难度,代码报出异常后ÿ...
图神经网络(GCN) 一、GCN的起源 曾经深度学习一直都是被几大经典模型给统治着,如CNN、RNN等等&#...
5月汽车销量出炉:理想力压小鹏... 【市场】6月第一天,多家汽车企业公布了5月的销量数据,和去年相比实现了两位数增长,但排名发生了变化。...
Python数据结构与算法(p... 主要内容:什么是列表查找顺序查找二分查找一、什么是查找?(...
状态机DP 状态机DP算法特征子数组类DP问题的定义:以最后一个元素结尾的最大xxx特征1...
mendeley管理文献样式 编辑GB/T 7714—2005参考文件样式,解决:显示大小写ÿ...
多次谋求A股上市!锂电巨头业绩... 知名锂电池生产企业天津力神电池股份有限公司(下称“天津力神”)的经营情况曝光。证券时报·e公司记者在...
前沿技术揭秘:云原生-展望容器... 2023年最火热的就是ChatGPT,当然还有5G技术、AI、机器学习、区块链等技术。另外还有一个现...
WalletConnect的下... 加密行业中每一个项目都有它自己的作用,很多我们意想不到或者认为的事情往往最后都会出现转...
独家 | 大润发佘咸平任永辉C... 交流永辉,实名添加微信lihua759321进群多位市场人士向《商业观察家》确认,大润发M会员店商品...
拼多多管理层多次强调利润不可持... 近日,拼多多一季度财报引起市场热议。财报显示,拼多多一季度营收957亿元,同比增长10%,归母净利润...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
矿泉水瓶装大米,便利店能增收3... 矿泉水瓶装大米,便利店能增收300万 把大米装进矿泉水瓶,一瓶卖到60块...
港股迎来打新热!“散户失权”引... 港股行情提振,新股表现亮眼,打新又热了。Wind显示,2025年港股已迎来27只新股,其中仅7只上市...