算法通关村第一关--链表青铜挑战笔记

源码地址:GitHub-算法通关村

一、链表的介绍

1.1 概念

链表(Linked List)是一种基本的数据结构。它是由一系列节点(Node)组成的数据集合,每个节点都包含两个要素:数据(通常称为值或元素)和一个指向下一个节点的引用(指针或链接)。

1.2 特点

链表的特点是数据元素不必在内存中连续存储,而是通过指针相互连接,形成一个动态的数据结构。这使得链表在插入和删除元素时更加高效,因为它不需要像数组一样进行元素的搬移。然而,链表的随机访问效率较低,因为要查找特定位置的元素,必须从头节点开始顺着链表逐个遍历,直到找到目标节点。

1.3 分类

常见的链表类型有单向链表、双向链表和循环链表:

  1. 单向链表(Singly Linked List):每个节点包含数据和指向下一个节点的指针。链表的最后一个节点指向空(NULL),表示链表的末尾。

  2. 双向链表(Doubly Linked List):每个节点包含数据,同时具有指向下一个节点和上一个节点的两个指针。这使得在双向链表中可以更方便地进行双向遍历。

  3. 循环链表(Circular Linked List):在单向或双向链表的基础上,将链表的最后一个节点的指针指向头节点,形成一个循环。这样,链表中没有真正意义上的末尾,可以通过任何节点开始循环遍历。

1.4 应用

链表在许多编程场景中都有广泛的应用。它常用于动态数据结构,如队列、栈以及其他高级数据结构的基础。同时,在一些内存受限或需要频繁插入、删除元素的情况下,链表也是一种优雅的选择。

1.5 缺点

然而,链表也有一些缺点。相对于数组,链表的存储空间消耗更大,因为每个节点都需要额外的指针空间来连接其他节点。而且,由于链表中的元素在内存中不是连续存储的,可能会导致缓存未命中,从而降低访问速度。因此,在特定的问题和需求下,需要权衡链表的优缺点来选择最合适的数据结构。

二、链表的使用

2.1 构造

/**
     * 构造链表
     */
    static class Node {
        int val;
        Node next;

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

2.2 初始化

/**
     * 初始化链表
     * @param arr
     * @return
     */
    private static Node initLinkedList(int[] arr) {
        Node head = null;   // 头结点
        Node cur = null;    // 当前结点

        for (int i = 0; i < arr.length; i++) {
            Node newNode = new Node(arr[i]);
            newNode.next = null;
            if (i == 0) {
                head = newNode;
                cur = newNode;
            } else {
                cur.next = newNode;
                cur = newNode;
            }
        }
        return head;
    }

public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5, 6};
        Node head = initLinkedList(arr);
        System.out.println(head);
    }

2.3 获取链表长度

/**
     * 获取链表长度
     *
     * @param head
     * @return
     */
    public static int getLength(Node head) {
        int length = 0;
        Node node = head;
        while (node != null) {
            length++;
            node = node.next;
        }
        return length;
    }

2.4 向链表中插入结点

/**
     * 向链表中插入结点
     *
     * @param head       链表头结点
     * @param nodeInsert 待插入的结点
     * @param position   插入位置
     * @return 新链表的头结点
     */
    public static Node insertNode(Node head, Node nodeInsert, int position) {
        // 1.先判空,否则报空指针异常
        if (head == null) {
            return nodeInsert;
        }
        // 2.越界判断
        int size = getLength(head);
        if (position < 1 || position > size + 1) {
            System.out.println("插入位置越界");
            return head;
        }

        // 在链表开头插入
        if (position == 1) {
            nodeInsert.next = head;
            return nodeInsert;
        }

        // 遍历寻找待插入结点的前一个结点
        Node preNode = head;
        int count = 1;
        while (count < position - 1) {
            count++;
            preNode = preNode.next;
        }

        // 插入结点
        nodeInsert.next = preNode.next;
        preNode.next = nodeInsert;

        return head;
    }

以下是需要注意的问题:

  1. 头节点为空判断:在插入节点之前,进行头节点为空的判断,可以避免空指针异常。

  2. 越界判断:在插入节点时,通过 getLength() 方法计算链表长度,并进行越界判断。这确保了插入位置的合法性,避免了插入位置超出链表长度的情况。

  3. 插入位置为1的情况:在代码中,处理了插入位置为1的情况,即在链表开头插入节点。

  4. 找到待插入节点的前一个节点:在遍历链表查找待插入节点的前一个节点时,需要保证遍历不会超出链表长度,并且找到正确的位置。

  5. 插入节点操作:nodeInsert.next = preNode.next;  preNode.next = nodeInsert;

2.5 删除链表结点

/**
     * 删除链表结点
     *
     * @param head     待删链表头结点
     * @param position 删除结点位置
     * @return 新链表头结点
     */
    public static Node deleteNode(Node head, int position) {
        // 1.先判空
        if (head == null) {
            return null;
        }
        // 2.判断位置参数
        int size = getLength(head);
        if (position > size || position < 1) {
            System.out.println("删除位置有误");
            return head;
        }

        // 头删法
        if (position == 1) {
            return head.next;
        }

        // 遍历寻找待删除结点的前一个结点
        Node preNode = head;
        int count = 1;
        while (count < position - 1) {
            count++;
            preNode = preNode.next;
        }

        // 删除结点
        preNode.next = preNode.next.next;
        return head;
    }

以下是需要注意的问题:

  1. 空指针检查:函数首先会检查输入的链表头结点"head"是否为空。这是为了避免在对链表进行操作时出现空指针异常。

  2. 位置有效性检查:函数会检查给定的删除位置"position"是否有效。删除位置应该在链表大小的范围内(从1到链表大小)。如果删除位置超出这个范围,函数会打印一条消息指示删除位置无效,并返回原始头结点,不做任何操作。

  3. 头部删除:如果删除位置是链表的头部(position为1),函数会直接返回头结点的下一个结点,从而实现删除头结点的操作。

  4. 寻找待删除结点的前一个结点:对于非头部的删除操作,函数会遍历链表,寻找待删除结点的前一个结点,以便在删除时将前一个结点的next指针指向待删除结点的下一个结点,从而完成删除操作。

2.6 输出链表

/**
     * 输出链表
     *
     * @param head
     * @return
     */
    public static String printLinkedList(Node head) {
        StringBuffer sb = new StringBuffer();
        while (head != null) {
            sb.append(head.val);
            head = head.next;
            if (head != null) {
                sb.append("->");
            }
        }
        return sb.toString();
    }

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值