数据结构之单向链表

这篇博客详细介绍了链表的基本概念,包括链表的成员变量和接口设计,如size、first等。通过节点类展示了链表节点的结构。接着,文章提供了添加、删除、获取、设置和查找元素等链表操作的实现方法。还涵盖了单链表的删除、反转和环形链表检测等练习题。最后,讨论了带虚拟头节点的单向链表的实现和其对操作的影响。
摘要由CSDN通过智能技术生成

1. 概念

链表是一直链式存储的线性表,所有的元素的内存地址不一定是连续的。

在这里插入图片描述

2. 成员变量和接口设计

JDK中的ArrayListLinekdList都实现了List接口,这里仅仅为了学习数据结构来撰写,并不涉及到设计层面,因此就没过根据JDK源码来设计实现继承等相关问题。

2.1 成员变量

	 /**
     * 链表长度
     */
    private int size;

    /**
     * 头节点
     */
    private Node<E> first;

	private static final int ELEMENT_NOT_FOUND = -1;

2.2 节点类

	private static class Node<E>{
        E element;
        Node<E> next;

        public Node(E element){
            this.element = element;
        }

        public Node(E element, Node<E> next){
            this.element = element;
            this.next = next;
        }
    }

2.2 接口设计

  1. int size():返回链表长度;
  2. boolean isEmpty():判断链表是否为空;
  3. boolean contains(E element):判断链表中是否存在某元素;
  4. void add(E element):添加元素;
  5. E get(int index):返回index位置的元素;
  6. E set(int index, E elemnt):设置index位置的元素为element;
  7. void add(int index, E element):向index位置添加元素element;
  8. E remove(int index):删除index位置的元素;
  9. int indexOf(E element):查看元素的位置;
  10. void clean() :清空链表。

3. 实现方法

3.1 简便方法实现

  • int size()
	/**
     * 获取链表长度
     * @return
     */
    public int size(){
        return size;
    }
  • boolean isEmpty()
	/**
     * 判断链表是否为空
     * @return
     */
    public boolean isEmpty(){
        return size == 0;
    }
  • void clean()
	/**
     * 清空链表
     */
    public void clean(){
        size = 0;
        first = null;
    }

3.2 获取节点方法

针对链表的操作的方法需要根据节点来进行。

	/**
     * 返回index位置的节点
     * @param index
     * @return
     */
    private Node<E> node(int index){
        rangeCheck(index);
        Node<E> node = first;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }

3.3 添加

要添加某个元素到指定的位置,找到它前一个节点,并且修改前一个节点的next指向它,再将新节点的next指向原先的下一个节点即可,如下图。

添加前:
在这里插入图片描述
添加后:
在这里插入图片描述

	/**
     * 添加元素到index位置
     * @param index 位置
     * @param e 元素
     */
    public void add(int index, E e){
   		rangeCheckForAdd(index);
        if (index == 0){
            first = new Node<E>(e,first);
        }else {
            Node<E> prev = node(index - 1);
            prev.next = new Node<E>(e,prev.next);
        }
        size++;
    }

    /**
     * 添加元素到链表尾部
     * @param e
     */
    public void add(E e){
        add(size,e);
    }

3.4 删除

删除时只需要将其前面节点的next指向当前节点的next即可。

在这里插入图片描述

	/**
     * 删除index位置的节点
     * @param index
     * @return
     */
    public E remove(int index){
    	rangeCheck(index);
        Node<E> node = first;
        if (index == 0){
            first = first.next;
        }else {
            Node<E> prev = node(index - 1);
            node = prev.next;
            prev.next = node.next;
        }
        return node.element;
    }

3.5 get set indexOf contains

这四个方法都是基于上方的node方法实现的

	/**
     * 给index位置设置新的值
     * @param index
     * @param e
     * @return
     */
    public E set(int index, E e){
        Node<E> node = node(index);
        E oldE = node.element;
        node.element = e;
        return oldE;
    }

    /**
     * 返回index位置的元素
     * @param index
     * @return
     */
    public E get(int index){
        return node(index).element;
    }
    
    /**
     * 返回 元素 e 的位置
     * @param e
     * @return
     */
    public int indexOf(E e){
        Node<E> node = first;
        if (e == null){
            for (int i = 0; i < size; i++) {
                if (node.element == null){
                    return i;
                }
                node = node.next;
            }
        }else {
            for (int i = 0; i < size; i++) {
                if (e.equals(node.element)){
                    return i;
                }
                node = node.next;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

    /**
     * 判断是否存在该元素
     * @param e
     * @return
     */
    public boolean contains(E e){
        return indexOf(e) != ELEMENT_NOT_FOUND;
    }

4. 练习

4.1 删除链表中的节点

请编写一个函数,用于 删除单链表中某个特定节点 。在设计函数时需要注意,你无法访问链表的头节点 head ,只能直接访问 要被删除的节点 。
https://leetcode-cn.com/problems/delete-node-in-a-linked-list/

既然找不到当前链表的前驱节点,那么就让后面的节点覆盖他。

public static void deleteNode(ListNode node) {
        node.val = node.next.val;
        node.next = node.next.next;
    }

4.2 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
https://leetcode-cn.com/problems/reverse-linked-list/

4.2.1 递归实现
 public static ListNode2 reverseList(ListNode2 head) {
        if (head == null || head.next == null){
            return head;
        }
        ListNode2 newHead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }
4.2.2 迭代实现
public static ListNode2 reverseList2(ListNode2 head) {
        if (head == null || head.next == null){
            return head;
        }
        ListNode2 newHead = null;
        while (head != null){
            ListNode2 tmp = head.next;
            head.next = newHead;
            newHead = head;
            head = tmp;
        }
        return newHead;
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.3 环形链表

给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
https://leetcode-cn.com/problems/linked-list-cycle/

快慢指针

	public static boolean hasCycle(ListNode head) {
        if (head == null || head.next == null){
            return false;
        }
        ListNode slow = head;
        ListNode fast = head.next;
        while (fast != null && fast.next != null){
            if (slow == fast){
                return true;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        return false;
    }

5. 带虚拟头节点的单向链表

为了方便统一的进行处理,可以添加一个不存储数据只作为头节点来进行指向下一个节点的的节点,称为虚拟头节点。

针对上方普通单向链表进行修改:

  • 新增构造函数:因为即使链表中没有真正的节点,也需要一个默认的头节点。
	public LinkedListHasHead(){
        first = new Node<>(null,null);
    }
  • node() & toString:原先是从第一个节点就开始存储数据,但这里第二个才是真正存储数据的节点:
	/**
     * 返回index位置的节点
     * @param index
     * @return
     */
    private Node<E> node(int index){
        rangeCheck(index);
        Node<E> node = first.next;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }
	@Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("LinkedList{list = [");
        Node<E> node = first.next;
        while (node != null){
            stringBuilder.append(node.element);
            if (node.next != null){
                stringBuilder.append(",");
            }
            node = node.next;
        }
        stringBuilder.append("]").append(",size = ").append(size).append("}");
        return stringBuilder.toString();
    }
  • add() & remove():原先针对往头节点插入数据做了特殊处理,这里因为引入了头节点,无论链表是否为空,都存在有节点的情况,因此可以忽略。
    /**
     * 添加元素到index位置
     * @param index 位置
     * @param e 元素
     */
    public void add(int index, E e){
        rangeCheckForAdd(index);
        Node<E> prev = index == 0 ? first : node(index - 1);
        prev.next = new Node<E>(e,prev.next);
        size++;
    }
	/**
     * 删除index位置的节点
     * @param index
     * @return
     */
    public E remove(int index){
        rangeCheck(index);

        Node<E> prev = index == 0 ? first :  node(index - 1);
        Node<E>  node = prev.next;
        prev.next = node.next;

        size--;
        return node.element;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值