链表(LinkedList)---单链表

这篇博客介绍了链表作为一种链式存储结构,与动态数组相比的优势。详细讲解了单链表的结构、设计以及与数组的复杂度比较。在实践部分,讨论了如何删除链表中的非末尾节点,提供了两种方法。接着,通过迭代和递归两种方式解释了如何反转单链表。最后,利用快慢指针的方法阐述了判断链表中是否存在环的逻辑。
摘要由CSDN通过智能技术生成

1. 动态数组的缺点

  • 每次申请指定大小的空间,造成内存空间的大量浪费
  • 能否用到多少就申请使用多少呢?链表

2. 链表

      2.1 什么是链表
  • 链表是一种链式存储结构的线性表,所有元素的内存地址不一定连续
      2.2 单链表的结构

在这里插入图片描述

      2.3 单链表和单链表接口的设计
  • 链表的设计
    在这里插入图片描述

  • 链表接口的设计

    • 链表和前文动态数组(DynamicArray)有相同的功能 但有不同的实现 可以把相同的方法抽取为接口 相同方法的相同实现 抽取为抽象类
      在这里插入图片描述
package com.java.liangfw.linkedList2;

/**
 * @version 1.0
 * @Author: liangfangwei
 * @Date: 2021/4/19 16:22
 */
public interface List<T> {

    static final int ELEMENT_NOT_FOUND = -1;

    void add(int index, T ele);

    T get(int index);

    T set(int index, T ele);

    void remove(int index);

    int indexOf(T ele);

    void clear();

}

package com.java.liangfw.linkedList2;

/**
 * @version 1.0
 * @Author: liangfangwei
 * @Date: 2021/4/19 16:25
 */
public abstract class AbstractList<T> implements List<T> {
    /**
     * protected 只能子类使用
     */
    protected int size;

    protected int size() {
        return size;

    }

    public boolean isEmpty() {
        return size == 0;
    }

    public boolean contains(T ele) {

        return indexOf(ele) != ELEMENT_NOT_FOUND;

    }

    /**
     * 添加元素到末尾
     *
     * @param ele
     */
    public void add(T ele) {
        add(size, ele);
    }

    /**
     * protected 子类或者同包 使用的校验方法
     * @param index
     */
    protected void outOfBounds(int index) {
        throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
    }
    protected void rangeCheck(int index) {
        if (index < 0 || index > size) {
            outOfBounds(index);
        }
    }
}

package com.java.liangfw.linkedList2;
import com.sun.deploy.util.StringUtils;

/**
 * @version 1.0
 * @Author: liangfangwei
 * @Date: 2021/4/19 16:38
 */
public class LinkedList<T> extends AbstractList<T> {

    private Node first;

    static class Node<T> {
        T element;
        Node next;

        public Node(T element, Node next) {
            this.element = element;
            this.next = next;
        }
    }

    /**
     * 给某个索引位置添加元素 索引从0开始
     * 1.判断索引是否合格
     * 2.添加
     * 2.1 给第一个位置添加  first =newNode
     * 2.2 非首尾添加 找到index-1的node pre.next=newNode(ele,index位置的node)
     * 2.3 尾添加  最找到倒数第二个元素 pre.next=newNode
     *
     * @param index
     * @param ele
     */
    @Override
    public void add(int index, T ele) {
        // 头
        if (index == 0) {
            first = new Node(ele, first);
            // 头尾之前+尾
        } else {
            // 获取index前的元素
            Node<T> preNode = getIndeNode(index - 1);
            preNode.next = new Node(ele, getIndeNode(index));
        }
        size++;
    }

    @Override
    public T get(int index) {
        return getIndeNode(index).element;
    }
    /**
     * 1.找到index位置的元素 indexNode.element=ele
     *
     * @param index
     * @param ele
     * @return
     */
    @Override
    public T set(int index, T ele) {
        return getIndeNode(index).element = ele;
    }

    /**
     * 删除某位置的元素
     * 找到index-1 位置的preNode.next=pre.next.next;
     *
     * @param index
     */
    @Override
    public void remove(int index) {
        if (index == 0) first = first.next;
        else {
            Node<T> preNode = getIndeNode(index - 1);
            preNode.next = preNode.next.next;
        }
        size--;
    }

    @Override
    public int indexOf(T ele) {
        Node<T> tempNode = first;
        for (int i = 0; i < size; i++) {
            if (ele.equals(tempNode.element)) return i;
            tempNode = tempNode.next;
        }
        return ELEMENT_NOT_FOUND;
    }

    @Override
    public void clear() {
        size = 0;
        first = null;


    }

    /**
     * 获取某个索引位置的元素
     *
     * @param index
     */
    private Node<T> getIndeNode(int index) {
        rangeCheck(index);
        Node temp = first;
        for (int i = 0; i < index; i++) {
            temp = temp.next;
        }
        return temp;
    }

    @Override
    public String toString() {
        StringBuilder string = new StringBuilder();
        string.append("size=").append(size).append(", [");
        LinkedList.Node<T> node = first;
        for (int i = 0; i < size; i++) {
            if (i != 0) {
                string.append(", ");
            }

            string.append(node.element);
            node = node.next;
        }
        string.append("]");
        return string.toString();
    }
}

      2.4 链表和数组复杂度比较

在这里插入图片描述

3. 单链表练习

      3.1 删除链表元素
  • 请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点 。
    现有一个链表 – head = [4,5,1,9],它可以表示为:在这里插入图片描述

示例 1:
输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
解释:给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该 链表应变为 4 -> 1 -> 9.
示例 2:
输入:head = [4,5,1,9], node = 1
输出:[4,5,9]
解释:给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.

链表至少包含两个节点。
链表中所有节点的值都是唯一的。
给定的节点为非末尾节点并且一定是链表中的一个有效节点。
不要从你的函数中返回任何结果。

  • 思路分析:
    • 方法一:找到待删除元素之前的元素preNode proNode的下一个节点指向下下个节点即可 preNode.next=preNode.next.next;
    • 方法二: 将待删除元素的值替换为后面元素的值 相当删除了该元素 。找到待删除的元素,node.val=node.next.val node.next=node.next.next;
  • 方法一:
    在这里插入图片描述
public E remove(int index) {
        Node<E> node = first;
        if (index == 0) {
            first = first.nextNode;
        } else {
            Node<E> preNode = getIndexNode(index - 1);
            node = preNode.nextNode;
            preNode.nextNode = node.nextNode;
        }
        size--;
        return node.element;
    }

在这里插入图片描述

public class _237_删除链表中的节点 {
     static class ListNode {
        int val;
        ListNode next;
        ListNode(int x) {
            val = x;
        }
    }
    /**
     *  删除链表的的节点
       思路一: 如果传入index 找到index-1 preNode的值
             preNode.next=preNode.next.next;
       思路二:如果传入node ,待删除节点的值替换为next节点的值 待删除节点的next 替换为 nextNode
           node.val=node.next.val;
           node.next=node.next.next;
     */
    public void deleteNode(ListNode node) {
        node.val=node.next.val;
        node.next=node.next.next;
    }
}

      3.2 反转链表
  • 反转一个单链表。分别使用迭代和递归实现

示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

  • 思路分析:
    • 迭代方法:初始化一前一后两个指针 pre=null,cur=head;改变指针指向,实现局部反转,两指针同时向前移动一位在这里插入图片描述
public ListNode reverseList2(ListNode head) {
         ListNode pre=null;
         ListNode cur=head;
         ListNode curNextpNode;
         while (cur!=null){
             curNextpNode=cur.next;
             //1. 实现局部反转
             cur.next=pre;
             // 2. 指针同时向前移动
             pre=cur;
             cur=curNextpNode;
         }
         return pre;
    }
  • 递归
    • 递归的使用条件:
      • (1)大问题可以拆成两个子问题
      • (2)子问题的求解方式和大问题一样
      • (3)存在最小子问题
    • 递归求解的过程?
      • 求最小子问题的解得出较大问题的解
      • 由规模问题的解不断得出更大问题的解
      • 最后得出原来问题的解
    • 例子:递归求解的过程?1+2+3…+n
int sum(int n){
if(n<=1) return n;
return n+sum(n-1);
}

在这里插入图片描述

  • 递归求解的思路
    • 反转单链表 可以拆解为 头+反转剩余部分的两个子问题
    • 剩余部分可以拆解为头+反转剩余部分的子问题 反复如此
    • 最终只需要反转最后的一个节点

在这里插入图片描述

    public ListNode reverseList(ListNode head) {
        // 最小子问题的解
      if(head==null||head.next==null) return head;
        // 分解问题的过程
        ListNode p = reverseList(head.next);
        // 解合并的过程
        head.next.next=head;
        head.next=null;
        return p;
    }
      3.3 判断是否有环
  • 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
    如果链表中存在环,则返回 true 。 否则,返回 false 。

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

  • 思路分析: 快慢指针 两指针相遇有环 快指针=null 则无环
    • 快指针每次走两步 faster=faster.next.next;
    • 慢指针每次走一步 slow=slow.next;
    • 如果if(faster==slow)有环
    • 循环条件为( faster!=null && faster.next!=null )

 public boolean hasCycle(ListNode head) {
        ListNode fasterPoint = head;
        ListNode slowPoint = head;
        while (fasterPoint != null && fasterPoint.next != null) {
            fasterPoint = fasterPoint.next.next;
            slowPoint = slowPoint.next;
            if (slowPoint == fasterPoint) return true;
        }
        return false;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值