VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > Java教程 >
  • 数据结构—链表

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.引人头结点的作用

  1. 概念

    头结点:虚拟出来的一个节点,不保存数据。头结点的next指针指向首节点。头结点不是链表所必须的。

    头指针:指向链表第一个节点的指针。头指针是链表所必须的

    注:头指针始终指向链表的第一个节点。 对于引入头结点的链表:头指针指向头结点;对于没有引入头结点的链表:头指针指向首节点。

  2. 为什么要引入头结点

​ (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:

  1. 指针 NodeA 指向链表 ListA ,指针 Node 指向链表 ListA , 依次往后遍历

在这里插入图片描述

  1. NodeA 遍历到 ListA 的末尾,则 NodeA = headB 继续遍历
    在这里插入图片描述
    在这里插入图片描述

  2. NodeB 遍历到 ListB 的末尾,则 NodeB = headA 继续遍历

在这里插入图片描述

  1. 此时两链表的长度差就没有了
  2. 继续往下遍历就能得到结果了

代码来源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://www.cnblogs.com/l574/p/15110517.html

 


相关教程