大家好,欢迎来到IT知识分享网。
1.链表是什么
链表数一种线性数据结构。它是动态地进行储存分配的一种结构。
什么是线性结构,什么是非线性结构?
线性结构是一个有序数据元素的集合。常用的线性结构有:线性表,栈,队列,双队列,数组,串。
非线性结构,是一个结点元素可能有多个直接前趋和多个直接后继。常见的非线性结构有:二维数组,多维数组,广义表,树(二叉树等)。
2.链表的基本结构
链表由一系列节点组成的集合,节点(Node)由数据域(date)和指针域(next)组成。
date负责储存数据,next储存其直接后续的地址
3.链表的分类
- 单链表(特点:连接方向都是单向的,对链表的访问要通过顺序读取从头部开始)
- 双链表
- 循环链表单向循环链表双向循环链表
4.链表和数组的比较
数组:
优点:查询快(地址是连续的)
缺点:1.增删慢,消耗CPU内存
链表就是 一种可以用多少空间就申请多少空间,并且提高增删速度的线性数据结构,但是它地址不是连续的查询慢。
二、单链表
[1. 认识单链表](#1. 认识单链表)
2.引人头结点的作用
3.链表的基本操作
1. 认识单链表
(1)头结点:第0 个节点(虚拟出来的)称为头结点(head),它没有数据,存放着第一个节点的首地址
(2)首节点:第一个节点称为首节点,它存放着第一个有效的数据
(3)中间节点:首节点和接下来的每一个节点都是同一种结构类型:由数据域(date)和指针域(next)组成
- 数据域(date)存放着实际的数据,如学号(id)、姓名(name)、性别(sex)、年龄(age)、成绩(score)等
- 指针域(next)存放着下一个节点的首地址
(4)尾节点:最后一个节点称为尾节点,它存放着最后一个有效的数据
(5)头指针:指向头结点的指针
(6)尾指针:指向尾节点的指针
(7)单链表节点的定义
public static class Node { //Object类对象可以接收一切数据类型解决了数据统一问题 public Object date; //每个节点的数据 Node next; //每个节点指向下一结点的连接 public Node(Object date) { this.date = date; } }
2.引人头结点的作用
- 概念
- 头结点:虚拟出来的一个节点,不保存数据。头结点的next指针指向首节点。头结点不是链表所必须的。
- 头指针:指向链表第一个节点的指针。头指针是链表所必须的
- 注:头指针始终指向链表的第一个节点。 对于引入头结点的链表:头指针指向头结点;对于没有引入头结点的链表:头指针指向首节点。
- 为什么要引入头结点
(1)对链表的删除、插入操作时,第一个节点的操作更方便
如果没有头结点,头指针指向链表的首节点,在首节点前插入一个新的节点时,头指针要相应地指向新插入的节点。把首节点删除时,头结点的指向也要更新。
如果没有头结点,那我们在对首节点进行操作时,要一直维护着头结点指向的更新。
如果引入了头结点,头指针始终指向头结点。头结点的next指针始终指向首节点
(2)统一空表和非空表的处理
引入头指针后,头指针指向头结点,无论链表是否为空,头指针均不为空。
3.链表的基本操作
(1)增加节点
在链表后增加节点
思路:
- 产生一个新的节点 newNode
- 对链表进行遍历操作,找到当前链表的最后一个节点 last
- 当前链表的最后一个节点的下一个节点 = 新的节点 last.next = newNode
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; }
(2)插入结点
思路:
在指定位置插入新节点 nodeIndex ,新节点的前一个结点 current
- 遍历到需要插入新节点 nodeIndex 的位置
- 当找到该位置时,新插入的结点下一结点 = 前一个结点的下一结点 nodeIndex.next = current.next
- 前一个结点的下一结点 = 新插入的结点 current.next = nodeIndex
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; } }
(3)删除结点
思路:
- 定义一个需要删除的结点 deleteNode
- 找到需要删除的结点的前一个结点 previous
- 前一个结点 的下一个节点 = 需要删除的结点 的下一个节点 previous.next = deleteNode.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; }
(4)查找结点
思路:
- 因为头结点不能动,定义一个当前节点 current,从头结点开始遍历
- 找到该节点返回 current ,找不到返回 null
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; }
(5)修改结点
思路:
修改指定节点的数据
- 遍历到需要修改的结点
- 将节点数据进行替换
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; } } }
5.设计链表:源代码(含测试用例)
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.认识双链表
双链表的每个数据节点中都有两个指针,分别前驱指针域和后继指针域。
2.双链表结点结构的定义
双向链表中每个节点包含两个节点的指针引用,和一个数据域
public static class Node{ private Object date; private Node next; //指向下一结点的引用 private Node prev; //指向前一结点的引用 public Node(Object date){ this.date = date; } }
3.双链表的基本操作
插入结点图解
代码实现
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个节点
对于单链表,如果我们想在尾部添加一个节点,就必须从首节点开始遍历到尾节点,
然后在尾结点后面插入一个节点。为了方便操作,可以在设计链表的时候多一个对尾结点的引用。
双指针和双链表的区别
1.双端链表的实现
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(); } }
运行结果
2.环形链表
(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(); } }
运行结果
3.判断链表中是否有环
详解参见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)); } }
运行结果
4.相交链表
问题描述
给两个单链表的头节点 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:
- 指针 NodeA 指向链表 ListA ,指针 Node 指向链表 ListA , 依次往后遍历
- NodeA 遍历到 ListA 的末尾,则 NodeA = headB 继续遍历
- NodeB 遍历到 ListB 的末尾,则 NodeB = headA 继续遍历
- 此时两链表的长度差就没有了
- 继续往下遍历就能得到结果了
代码来源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; }
5.删除倒数第N个节点
问题描述
给你一个链表,删除链表的倒数第 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]
思路:
- 设前指针 start ,后指针 end ,两个指针都指向 head
- 移动 start ,使start 和 end 相距 n
- start 和 end 同时先前移动,直到 start 指向 null,此时 end 的位置恰好是倒数第 n 个节点的前一个结点
- end 的 next 指向 下一个节点的 next的 next (end.next = end.next.next)
代码实现
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; } }
运行结果
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/123326.html