当前位置:首页>科技>数据结构之链表详解数据结构链表
发布时间:2026-07-20阅读(1)
链表数一种线性数据结构。它是动态地进行储存分配的一种结构。
什么是线性结构,什么是非线性结构?线性结构是一个有序数据元素的集合。常用的线性结构有:线性表,栈,队列,双队列,数组,串。非线性结构,是一个结点元素可能有多个直接前趋和多个直接后继。常见的非线性结构有:二维数组,多维数组,广义表,树(二叉树等)。
2.链表的基本结构
链表由一系列节点组成的集合,节点(node)由数据域(date)和指针域(next)组成。
date负责储存数据,next储存其直接后续的地址
3.链表的分类
数组:
优点:查询快(地址是连续的)
缺点:1.增删慢,消耗CPU内存
链表就是 一种可以用多少空间就申请多少空间,并且提高增删速度的线性数据结构,但是它地址不是连续的查询慢。
二、单链表[1. 认识单链表](#1. 认识单链表)
2.引人头结点的作用
3.链表的基本操作
1. 认识单链表(1)头结点:第0 个节点(虚拟出来的)称为头结点(head),它没有数据,存放着第一个节点的首地址(2)首节点:第一个节点称为首节点,它存放着第一个有效的数据(3)中间节点:首节点和接下来的每一个节点都是同一种结构类型:由数据域(date)和指针域(next)组成
public static class Node { //Object类对象可以接收一切数据类型解决了数据统一问题 public Object date; //每个节点的数据 Node next; //每个节点指向下一结点的连接 public Node(Object date) { this.date = date; } }
(1)对链表的删除、插入操作时,第一个节点的操作更方便
如果没有头结点,头指针指向链表的首节点,在首节点前插入一个新的节点时,头指针要相应地指向新插入的节点。把首节点删除时,头结点的指向也要更新。
如果没有头结点,那我们在对首节点进行操作时,要一直维护着头结点指向的更新。

如果引入了头结点,头指针始终指向头结点。头结点的next指针始终指向首节点

(2)统一空表和非空表的处理
引入头指针后,头指针指向头结点,无论链表是否为空,头指针均不为空。

在链表后增加节点

思路:
public Object add(Object obj){ //产生一个新的节点 Node newNode = new Node(obj); //如果没有任何节点存在(第一个节点) if (size == 0){ head = newNode; last = newNode; }else { //如果不是第一个节点 last.next = newNode; last = newNode; } size ; return obj; }

思路:
在指定位置插入新节点 nodeIndex ,新节点的前一个结点 current
public void addIndex(int index,double n){ Node current = head; while (current != null){ if (current.date.equals(index)){ //产生一个新节点 Node nodeIndex = new Node(n); nodeIndex.next = current.next; current.next = nodeIndex; size ; } current = current.next; } }

思路:
public boolean delete(Object value){ //链表为空 if (size == 0){ return false; } Node deleteNode = head; //要删除的结点 Node previous = head; //要删除的结点前一个结点 //没找到要删除的结点 while(deleteNode.date!= value){ if(deleteNode.next == null){ return false; }else{ previous = deleteNode; deleteNode = deleteNode.next; } } //如果要删除的是首节点 if (deleteNode.date == head.date){ head = head.next; size--; }else { //如果要删除的是首节点之后的结点 previous.next = deleteNode.next; size--; } return true; }
思路:
public Node find(Object obj){ Node current = head; int tempSize = size; while (tempSize > 0){ if (obj.equals(current.date)){ return current; }else { current = current.next; } tempSize--; } return null; }
思路:
修改指定节点的数据
public void update(int map , int n){ if (size == 0){ System.out.println("链表为空"); return; } Node current = head; for (int i = 1; i < map; i ) { if (current.next == null){ System.out.println("该节点不存在"); break; } current = current.next; if (i == map -1){ current.date = n; } } }
public class LinkedList { private int size; //链表节点的个数 private Node head; //头结点 private Node last; //当前链表的最后一个节点 public LinkedList(){ size = 0; head = null; } //链表的每个节点类 public static class Node { //Object类对象可以接收一切数据类型解决了数据统一问题 public Object date; //每个节点的数据 Node next; //每个节点指向下一结点的引用 public Node(Object date) { this.date = date; } } //在链表后添加元素 public Object add(Object obj){ //产生一个新的节点 Node newNode = new Node(obj); //如果没有任何节点存在(第一个节点) if (size == 0){ head = newNode; last = newNode; }else { //如果不是第一个节点 last.next = newNode; last = newNode; } size ; return obj; } //插入结点 public void addIndex(int index,double n){ Node current = head; while (current != null){ if (current.date.equals(index)){ //产生一个新节点 Node nodeIndex = new Node(n); nodeIndex.next = current.next; current.next = nodeIndex; size ; } current = current.next; } } //删除(指定元素删除节点) public boolean delete(Object value){ //链表为空 if (size == 0){ return false; } Node deleteNode = head; //要删除的结点 Node previous = head; //要删除的结点前一个结点 //没找到要删除的结点 while(deleteNode.date!= value){ if(deleteNode.next == null){ return false; }else{ previous = deleteNode; deleteNode = deleteNode.next; } } //如果要删除的是首节点 if (deleteNode.date == head.date){ head = head.next; size--; }else { //如果要删除的是首节点之后的结点 previous.next = deleteNode.next; size--; } return true; } //查找指定元素的结点 public Node find(Object obj){ Node current = head; int tempSize = size; while (tempSize > 0){ if (obj.equals(current.date)){ return current; }else { current = current.next; } tempSize--; } return null; } //修改 public void update(int map , int n){ if (size == 0){ System.out.println("链表为空"); return; } Node current = head; for (int i = 1; i < map; i ) { if (current.next == null){ System.out.println("该节点不存在"); break; } current = current.next; if (i == map -1){ current.date = n; } } } //显示节点信息 public void display(){ if (size > 0){ Node node = head; int tempSize = size; while (tempSize > 0){ System.out.print(node.date " "); node = node.next; tempSize--; } }else { System.out.println("链表为空"); } System.out.println(); }}
import javax.xml.soap.Node;public class Application { public static void main(String[] args) { LinkedList list = new LinkedList(); System.out.println("在链表后添加节点:" ); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); list.add(6); list.add(7); list.add(8); list.add(9); list.add(10); list.display(); System.out.println("删除第四个节点:" ); list.delete(4); list.display(); System.out.println("查找数据是3的结点:" ); LinkedList.Node nodefind = list.find(3); System.out.println(nodefind.date); System.out.println("在第三节点后面增加一个节点:" ); list.addIndex(3,4); list.display(); System.out.println("把第四个节点的4.0该成0:"); list.update(4,0); list.display(); }}
运行结果

1.认识双链表
2.双链表结点结构的定义
3.双链表的基本操作
1.认识双链表双链表的每个数据节点中都有两个指针,分别前驱指针域和后继指针域。

双向链表中每个节点包含两个节点的指针引用,和一个数据域
public static class Node{ private Object date; private Node next; //指向下一结点的引用 private Node prev; //指向前一结点的引用 public Node(Object date){ this.date = date; } }


package DLinkendList;public class LinkedList { public static class Node{ private Object date; private Node next; //指向下一结点的引用 private Node prev; //指向前一结点的引用 public Node(Object date){ this.date = date; } } private Node head; //头结点 private Node tail; //尾节点 private Node curr; //临时结点,用作指针节点 private int size; //链表节点数 public void LinkedList(){ head = new Node(null); tail = head; size = 0; } //判断链表是否为空 public boolean isEmpty(){ return size == 0; } //在链表尾部添加节点 public void add(Object obj){ if (isEmpty()){ //链表为空,添加第一个新节点 head = new Node(obj); tail = head; size ; }else { curr = new Node(obj); curr.prev = tail; tail.next = curr; //将新结点与原来的尾部结点连接 tail = curr; //curr变成最后一个节点 size ; } } //插入结点 public void addIndex(int index,int value){ curr = head; while (curr != null){ if (curr.date.equals(index)){ Node nodeIndex = new Node(value); nodeIndex.prev = curr; nodeIndex.next = curr.next; curr.next = nodeIndex; if (nodeIndex.next == null){ tail = nodeIndex; } size ; } curr = curr.next; } } //删除指定元素的结点 public boolean delete(Object value){ curr = head; //链表为空 if (size == 0){ return false; } //没找到要删除的结点 while(curr.date!= value){ if(curr.next == null){ return false; }else{ curr.prev = curr; curr = curr.next; } } //如果要删除的是首节点 if (curr.date == head.date){ head = head.next; size--; }else { //如果要删除的是首节点之后的结点 curr.prev.next = curr.next; size--; } return true; } //打印链表 public void display(){ curr = head; for (int i = 0; i < size; i ) { System.out.print(curr.date " "); curr = curr.next; } System.out.println(); }}
public class Application { public static void main(String[] args) { LinkedList list = new LinkedList(); System.out.println("在链表后添加节点:" ); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); list.add(6); list.add(7); list.add(8); list.add(9); list.add(10); list.display(); System.out.println("在5后面加一个6:" ); list.addIndex(5,6); list.display(); System.out.println("删除元素6" ); list.delet(6); list.display(); }}
运行结果

1.双端链表的实现
2.环形链表
3.判断链表中是否有环
4.相交链表
5.删除倒数第N个节点
对于单链表,如果我们想在尾部添加一个节点,就必须从首节点开始遍历到尾节点,
然后在尾结点后面插入一个节点。为了方便操作,可以在设计链表的时候多一个对尾结点的引用。
双指针和双链表的区别

package DoublePointLinkedList;public class LinkedList { private Node head; //头结点 private Node tail; //尾节点 private int size ; //节点个数 private static class Node{ private Object date; private Node next; public Node(Object date){ this.date = date; } } public LinkedList(){ head = null; tail = null; size = 0; } //增加节点(表尾) public void addTail(Object obj){ Node newNode = new Node(obj); if (size == 0){ head = newNode; tail = newNode; size ; }else { tail.next = newNode; tail = newNode; size ; } } //增加节点(表头) public void addHead(Object obj){ Node node = new Node(obj); if (size == 0){ head = node; tail = node; size ; }else { node.next = head; head = node; size ; } } //删除首结点 public boolean deleteHead(){ if (size == 0){ return false; } if (head.next == null){ head = null; tail = null; }else { head = head.next; } size--; return true; } //显示链表 public void display(){ Node node = head; for (int i = 0; i < size; i ) { System.out.print(node.date " "); node = node.next; } System.out.println(); }}
双端链表测试
package DoublePointLinkedList;public class Application { public static void main(String[] args) { LinkedList list = new LinkedList(); System.out.println("在表尾添加节点:"); list.addTail(1); list.addTail(2); list.addTail(3); list.addTail(4); list.addTail(5); list.addTail(6); list.addTail(7); list.display(); System.out.println("在表头添加一个节点数据为0:"); list.addHead(0); list.display(); System.out.println("删除第一个结点:"); list.deleteHead(); list.display(); }}
运行结果

(1)环形链表就是循环链表的意思。循环链表没有专门的头结点,链表尾结点的指针域不指向null,而是指向链表的其他结点

(2)循环链表的实现
创建一个节点类Node
package CircularLinkendList;public class Node { private int data; private Node next; public Node(int data){ this.data = data; } public int getData() { return data; } public Node getNext() { return next; } public void setData(int data) { this.data = data; } public void setNext(Node next) { this.next = next; }}
写一个循环链表添加节点的方法
思路:
(1)链表为空的时候,插入第一个节点
那插入的这个节点是第一个节点也是最后一个节点
也就是这个节点的next指向自己的地址

(2)插入第二个节点
实例化一个辅助指针 currentNode ,让这个辅助指针指向第一个节点的地址
让辅助指针 currentNode 的 next 指向新的节点 newNode (currentNode.next = newNode)

(3)把链表“环”起来
再实例化一个辅助指针 first ,这个辅助指针也指向第一个节点的地址
让新节点 newNode 的next指向第一个节点,也就是指向first(newNode.next = first)

思路清晰之后上代码
创建一个链表类
package CircularLinkendList;public class LinkendList { private Node first = null; private Node currentNone = null; public void add(int value){ for (int i = 1; i <= value; i ) { Node newNode = new Node(i); if (first == null){ first = newNode; first.setNext(first); currentNone = first; }else { currentNone.setNext(newNode); newNode.setNext(first); currentNone = currentNone.getNext(); } } } //显示链表 public void display(){ Node node = first; if (node == null){ System.out.println("链表为空"); return; }do { System.out.print(node.getData() " "); node = node.getNext(); }while (node != first); System.out.println(); }}
测试
package CircularLinkendList;public class Application { public static void main(String[] args) { LinkendList list = new LinkendList(); list.add(5); list.display(); }}
运行结果

详解参见https://leetcode-cn.com/problems/linked-list-cycle/solution/huan-xing-lian-biao-by-leetcode-solution/
问题描述给定一个链表,判断链表中是否有环
如果存在环,则返回true, 否则返回 false
为了给定链表中的环,用整数 pos 来表示;链表尾连接到链表中的位置(索引从0 开始),如果 pos 是 -1 ,则在该链表中没有环( pos 是为了标识链表的实际情况)。
示例1:

输入:head = [3 , 2 , 0 , 4] , pos = 1;输出:ture解释:链表中有一个环,其尾部连接到第二个节点
示例2:

输入:head = [1 , 2] , pos = 0;输出:ture解释:链表中有一个环,其尾部连接到第一个节点
示例3:

输入:head = [1] , pos = -1;输出:false解释:链表中没有环
代码实现
public boolean hasLoop(Node node){ //定义一个快指针一个慢指针 Node slow = node; Node fast = node.next; while (fast != null){ if (slow.data == fast.data){ //当两个指针重逢时,则存在环,否则不存在 return true; } slow = slow.next; //每次迭代慢指针走一步 fast = fast.next.next; //快指针走二步 if (fast == null){ return false; } } return true; //只有一个元素也存在环 }
测试
public class Application { public static void main(String[] args) { LinkendList list = new LinkendList(); Node node1 = new Node(3); Node node2 = new Node(2); Node node3 = new Node(0); Node node4 = new Node(4); node1.next = node2; node2.next = node3; node3.next = node4; node4.next = node2;//构造一个带环的链表(和 pos = 1 差不多意思) System.out.println(list.hasLoop(node2)); }}
运行结果

问题描述
给两个单链表的头节点 headA 和 headB ,找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例1:

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3输出:Intersected at 8解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例2:

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2输出:null解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。这两个链表不相交,因此返回 null 。
思路:
下面我们来分析示例1:


代码来源https://leetcode-cn.com/problems/intersection-of-two-linked-lists/solution/tu-jie-xiang-jiao-lian-biao-by-user7208t/
public LinkedList check(LinkedList headA, LinkedList headB) { if (headA == null || headB == null) return null; LinkedList nodeA = headA; LinkedList nodeB = headB; while (nodeA != nodeB) { nodeA = nodeA == null ? headB : nodeA.next; nodeB = nodeB == null ? headA : nodeB.next; } return nodeA; }
问题描述
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例1:

输入:head = [1,2,3,4,5], n = 2输出:[1,2,3,5]
示例2:
输入:head = [1], n = 1输出:[]输入:head = [1,2], n = 1输出:[1]
思路:

代码实现
public class deleteNLinkedList { public ListNode removeNthFromEnd(ListNode head,int n){ ListNode pre = new ListNode(0); //pre:虚拟指针 pre.next = head; ListNode start = pre; ListNode end = pre; while (n != 0){ // start 先走 n 步 start = start.next; n--; } while (start.next != null){ //start 和 end 相距 n 时一起移动 start = start.next; end = end.next; } end.next = end.next.next; //删除第倒数第 n 个节点 return pre.next; }}
问题描述
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例
输入:head = [1,2,3,4,5]输出:[5,4,3,2,1]
思路
将当前节点的 \textit{next}next 指针改为指向前一个节点

代码实现
public class LinkendList { public Node reverse(Node head){ Node prev = null; Node curr = head; while (curr != null){ Node tempNode = curr.next; curr.next = prev; prev = curr; curr = tempNode; } return prev; } }
运行结果


Copyright © 2024 有趣生活 All Rights Reserve吉ICP备19000289号-5 TXT地图HTML地图XML地图