深入解析链表:解锁数据结构核心奥秘

一. 链表的定义

链表是一种线性数据结构,由一系列节点组成。每个节点包含两个部分:

  1. 数据域(Data):存储节点的数据。
  2. 指针域(Pointer):存储指向下一个节点的地址。

链表的第一个节点称为头节点(Head),最后一个节点的指针域指向空(NULL),表示链表的结束。

二. 链表的结构

1) 单向 / 双向
2) 带头 / 不带头
3) 循环 / 非循环

链表种类丰富多样 重点掌握 单向不带头非循环 链表 可作为其他数据结构的子结构,如 哈希桶、图的邻接表等 笔试常考

三. 实现链表

1) 节点类

定义链表节点类,每个节点包含数据和指向下一个节点的指针。

java 复制代码
public class Node {
    int data;
    Node next;

    public Node(int data) {
        this.data = data;
        this.next = null;
    }
}

2) 链表类:

用于实现链表的功能

java 复制代码
public class MySingleList {
    private ListNode head;

    static class ListNode {
        int val;
        ListNode next;

        ListNode(int val) {
            this.val = val;
            this.next = null;
        }
    }
    public ListNode cur; // 临时头节点
}
插入节点:

插入节点 图示

java 复制代码
// 在链表的末尾插入一个新节点
    void append(int data) {
        Node newNode = new Node(data);
        if (head == null) {
            head = newNode;
        } else {
            Node current = head;
            while (current.next != null) {
                current = current.next;
            }
            current.next = newNode;
        }
        usedSize++;
    }

    // 在链表的开头插入一个新节点
    void prepend(int data) {
        Node newNode = new Node(data);
        newNode.next = head;
        head = newNode;
        usedSize++;
    }

    // 在指定位置插入一个新节点
    void insertAt(int index, int data) {
        if (index < 0 || index > usedSize) {
            throw new IndexOutOfBoundsException("Index out of bounds");
        }
        Node newNode = new Node(data);
        if (index == 0) {
            newNode.next = head;
            head = newNode;
        } else {
            Node current = head;
            for (int i = 0; i < index - 1; i++) {
                current = current.next;
            }
            newNode.next = current.next;
            current.next = newNode;
        }
        usedSize++;
    }
删除节点:
java 复制代码
// 删除指定位置的节点
    void deleteAt(int index) {
        if (index < 0 || index >= usedSize) {
            throw new IndexOutOfBoundsException("Index out of bounds");
        }
        if (index == 0) {
            head = head.next;
        } else {
            Node current = head;
            for (int i = 0; i < index - 1; i++) {
                current = current.next;
            }
            current.next = current.next.next;
        }
        usedSize--;
    }
查找节点:
java 复制代码
// 查询指定位置的节点
    Node getNodeAt(int index) {
        if (index < 0 || index >= usedSize) {
            throw new IndexOutOfBoundsException("Index out of bounds");
        }
        Node current = head;
        for (int i = 0; i < index; i++) {
            current = current.next;
        }
        return current;
    }

四. 链表OJ 实战 !

1) 开胃小菜 删除所有值域为val的节点

力扣链接: . - 力扣(LeetCode)

java 复制代码
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        while(head!= null && head.val == val){
            head = head.next;
        }
        ListNode cur = head;
        while(cur != null && cur.next != null){
            if(cur.next.val == val){
                cur.next = cur.next.next;
            }else{
            cur = cur.next;
            }
        }
        return head;
    } 
}

2) 反转链表 重要程度 五颗星 !!!

力扣链接: . - 力扣(LeetCode)

java 复制代码
//    反转链表
    public ListNode reverseList(ListNode head) {
        // 定义两个指针,pre初始化为null,用于存储反转后的链表
        ListNode pre = null;
        // cur初始化为head,表示当前处理的节点
        ListNode cur = head;
    
        // 循环直到cur为null,表示已经处理完所有节点
        while(cur != null){
            // temp临时存储cur的下一个节点,因为接下来要修改cur.next
            ListNode temp = cur.next;

            // 将cur的next指针指向pre,实现反转
            cur.next = pre;

            // pre和cur都前进一步
            pre = cur; // pre移动到cur的位置
            cur = temp; // cur移动到下一个待处理的节点
        }

        // 当cur为null时,pre就是新链表的头节点
        return pre;
    }

思路: 在遍历链表时逐步反转每个节点的指针方向

3) 快慢指针算法

快慢指针: 处理环形链表或数组非常有用

其实不单单是环形链表或者是数组,如果我们要研究的问题出现循环往复的情况时,均可考虑使用快慢指针的思想。

力扣链接: 876. 链表的中间结点 - 力扣(LeetCode)

java 复制代码
public ListNode middleNode(ListNode head) {
        // 定义快慢指针,都初始化为头节点head
        ListNode fast = head;
        ListNode slow = head;

        // 循环条件,快指针fast及其next不为null
        while (fast != null && fast.next != null) {
            // 快指针fast每次向前移动两步
            fast = fast.next.next;
            // 慢指针slow每次向前移动一步
            slow = slow.next;
        }

        // 当快指针到达链表末尾时,慢指针正好到达中间节点
        return slow;
    }

原理 : 因为fast的速度是 slow的速度的二倍, 而fast走完,slow 必然在中间位置.

4) 合并两个有序链表:

思路 : 使用 虚拟节点 为了简化头节点的处理逻辑

比较大小 插入新链表

如果其中一个链表的节点全部被插入到新链表中,如果另一个链表还有剩余节点, 直 接将这些剩余节点链接到新链表的末尾。

记得最后返回虚拟节点的下一个节点.

力扣链接: . - 力扣(LeetCode)

java 复制代码
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummy = new ListNode(-1);//虚拟节点

        // 创建一个指针cur,用来遍历并构建新的合并后的链表,初始指向哑节点
        ListNode cur = dummy;
        
        // 遍历链表直到其中一个为空
        while(list1 != null && list2 != null){
            if(list1.val > list2.val){
                cur.next = list2;
                list2 = list2.next;
            }else{
                cur.next = list1;
                list1 = list1.next;
            }
            cur = cur.next;
        }

        // 如果其中一个链表先遍历完,将另一个链表的剩余部分连接到新链表的末尾
        cur.next = (list1 == null) ? list2:list1;
        return dummy.next; //返回虚拟节点的下一个节点
    }

五. ArrayList和LinkedList的区别

  • 数组: 适用于需要快速访问和固定大小的情况。
  • 链表: 适用于频繁插入和删除、且大小动态变化的情况。

六. 总结

链表作为数据结构中的佼佼者,凸显了其在动态数据操作场景下的独特价值,特别是对于需要频繁执行插入和删除操作的应用来说,更是不可或缺。掌握链表的基础操作,包括但不限于节点的创建、插入、删除及遍历,以及进阶技巧如链表的反转、合并操作,以及利用快慢指针解决复杂链表问题,是深化链表理解和应用实践的关键步骤。在链表与数组的权衡选择上,认识到两者各自的强项------数组的快速随机访问与链表的高效动态修改能力,是根据具体需求制定数据结构策略的先决条件。总而言之,链表的灵活高效使其在众多计算领域内占有一席之地,而深入探索其特性和应用,则是对每一位技术探索者的智慧挑战与能力提升之旅。

相关推荐
装不满的克莱因瓶9 分钟前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
n北斗16 分钟前
常用类晨考day15
java
骇客野人19 分钟前
【JAVA】JAVA接口公共返回体ResponseData封装
java·开发语言
axxy20001 小时前
leetcode之hot100---240搜索二维矩阵II(C++)
数据结构·算法
yuanbenshidiaos1 小时前
c++---------数据类型
java·jvm·c++
向宇it1 小时前
【从零开始入门unity游戏开发之——C#篇25】C#面向对象动态多态——virtual、override 和 base 关键字、抽象类和抽象方法
java·开发语言·unity·c#·游戏引擎
Lojarro2 小时前
【Spring】Spring框架之-AOP
java·mysql·spring
莫名其妙小饼干2 小时前
网上球鞋竞拍系统|Java|SSM|VUE| 前后端分离
java·开发语言·maven·mssql
isolusion2 小时前
Springboot的创建方式
java·spring boot·后端
Uu_05kkq2 小时前
【C语言1】C语言常见概念(总结复习篇)——库函数、ASCII码、转义字符
c语言·数据结构·算法