链表LinkedList

链表LinkedList


链表是一种动态数据结构,不需要根据添加数据而调整存储空间。

链表

数据存储在节点(Node)中

// Java中关于节点的定义
class Node<E>{
    E data;
    Node next;
}

优点:真正的动态,不需要处理固定容量的问题

缺点:没有随机访问的能力(链表在内存中存储是不是连续,需要根据上一个节点,才能找到下一个节点)

与数组Array对比:数组最好用于索引有语境的情况,支持随机快速查询。链表适合顺序查询,不适合用于索引有语境的情况

链表的实现

public class LinkedList<E>{
    
    //节点类
    private class Node{
        public E e;
        public Node next;
        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }
        public Node(E e) { this(e, null); }
        public Node() { this(null, null);}
        
        @Override
        public String toString() {
            return e.toString();
        }
    }
    
    private Node head;
    int size;
    //链表初始化时,只有头结点且为空,长度size=0
    public LinkedList(){
        head = null;
        size =0;
    }
    
    
    /*一些常用方法*/
    //获取链表长度
    public int getSize(){
        return size;
    }
    
    //判断链表是否为空
    public boolean isEmpty(){
        return size==0;
    }
    
}
(1)添加元素
  • 图解:

添加元素

  • 代码:
    //在链表头添加元素
    public void addHead(E e) {
        Node node = new Node(e);
        node.next = head;
        head = node;
        //添加元素后,size也会发生变化
        size++;
    }
(2)向指定位置插入元素

关键:找到添加位置的前一个节点

  • 图解:
    在这里插入图片描述

  • 代码:

 public void add(int index, E e) {

        //判断插入的位置是否合适
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Add Failed. Illegal index.");
        }

        // index=0即往头部添加元素
        if (index == 0) {
            addHead(e);
        } else {
            //从头结点遍历,找到要插入位置的前一个节点prev
            Node prev = head;
            for (int i = 0; i < index - 1; i++) {
                prev = prev.next;
            }
            //让插入节点node指向prev的下一个节点
            //prev.next = new Node(e,prev.next);
            Node node = new Node(e);
            node.next = prev.next;
            prev.next = node;
            //添加元素后,size也会发生变化
            size++;
        }
(3)设立dummyHead

方便在向指定位置添加元素时,避免对向index=0的位置添加元素时进行特殊处理

  • 图解:

dummyHead

  • 代码:
//加入虚拟节点,对以上代码重新实现
public class LinkedList<E> {

    // 节点类
    private class Node {
        public E e;
        public Node next;

        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        public Node() {
            this(null, null);
        }

        public Node(E e) {
            this(e, null);
        }

        @Override
        public String toString() {
            return e.toString();
        }

    }

    // 链表属性
    public int size;
    public Node dummyHead; // 虚拟头结点

    // 链表构造方法,初始化链表时,size=0;
    public LinkedList() {
        size = 0;
        dummyHead = new Node();
    }

    @Override
    public String toString() {
        Node curNode = dummyHead.next;
        StringBuilder res = new StringBuilder();
        res.append("Head:");
        for (int i = 0; i < size; i++) {
            res.append(curNode.e + "->");
            curNode = curNode.next;
        }
        res.append("null");
        return res.toString();
    }

    // 链表长度
    public int getSize() {
        return size;
    }

    // 判断链表是否为空
    public boolean isEmpty() {
        return size == 0;
    }

    // 向链表中指定位置index(0~size-1)添加元素
    public void add(int index, E e) {
        // 判断index是否合法
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Add failed, illegal index.");
        }

        // 使用prev指向目标位置的前一个元素
        Node prev = dummyHead;
        for (int i = 0; i < index; i++) {
            prev = prev.next;
        }
        // 插入元素
        Node target = new Node(e, prev.next);
        prev.next = target;
        // 注意添加元素之后,size也会发生变化
        size++;
    }

    // 获取指定位置的元素
    public E get(int index) {
        // 判断index是否合法
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Get failed, illegal index.");
        }

        // 使用cur指向目标元素
        Node curNode = dummyHead.next;
        for (int i = 0; i < index; i++) {
            curNode = curNode.next;
        }

        return curNode.e;
    }


    // 修改指定位置的元素
    public void set(int index, E e) {
        // 判断index是否合法
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Set failed, illegal index.");
        }

        // 使用cur指向目标元素
        Node curNode = dummyHead.next;
        for (int i = 0; i < index; i++) {
            curNode = curNode.next;
        }

        curNode.e = e;
    }


    // 判断是否包含指定元素
    public boolean contains(E e) {
        Node curNode = dummyHead.next;
        for (int i = 0; i < size; i++) {

            // null值之间的比较使用==,使用equals发生空指针异常
            // 注意:使用null==.. 而不是 ..==null
            if (e == curNode.e) {
                return true;
            }

            //引用类型比较,使用equals
            if (e.equals(curNode.e)) {
                return true;
            } else {
                curNode = curNode.next;
            }

        }
        return false;
    }

    // 删除指定位置元素,并返回该元素
    public E delete(int index) {
        // 判断index是否合法
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Set failed, illegal index.");
        }

        // 指向目标位置的前一个元素
        Node prev = dummyHead;
        for (int i = 0; i < index; i++) {
            prev = prev.next;
        }

        Node delNode = prev.next;
        prev.next = delNode.next;
        delNode.next = null; // 方便回收,对实际操作无影响
        size--;
        return delNode.e;
    }

    
	//测试方法是否正确
    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();

        // 添加元素测试
        for (int i = 0; i < 5; i++) {
            list.add(list.size,i);//默认添加到链表尾部
            System.out.println(list);
        }
        // 指定位置添加元素
        list.add(2, 222);
        System.out.println(list);

        // 修改元素测试
        list.set(2, 2);
        System.out.println(list);

        // 删除元素测试
        list.delete(2);
        System.out.println(list);

        list.add(0,null);
        System.out.println(list);
        System.out.println(list.contains(1));//true
        System.out.println(list.contains(null));//true
        System.out.println(list.contains(7));//false
    }

}
(4)删除指定位置的元素
  • 图解:

在这里插入图片描述

  • 代码
// 删除指定位置元素,并返回该元素
    public E delete(int index) {
        // 判断index是否合法
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Set failed, illegal index.");
        }

        // 指向目标位置的前一个元素
        Node prev = dummyHead;
        for (int i = 0; i < index; i++) {
            prev = prev.next;
        }

        Node delNode = prev.next;
        prev.next = delNode.next;
        delNode.next = null; // 方便回收,对实际操作无影响
        size--;
        return delNode.e;
    }

时间复杂度分析

  • 添加操作

    方法时间复杂度
    addLast(e) // add(size,e)O(n)
    addHead(e) // add(0,e)O(1)
    add(index ,e)O(n/2)=O(n)
  • 删除操作

    方法时间复杂度
    deleteLast(e) // delete(size,e)O(n)
    deleteHead(e) // delete(0,e)O(1)
    delete(index ,e)O(n/2)=O(n)
  • 修改操作

    方法时间复杂度
    set(index,e) // set(index,e)O(n)
  • 查找操作

    方法时间复杂度
    get(index)O(n)
    contains(e)O(n)

总结:

  • 增加操作: O(n)
  • 删除操作: O(n)
  • 更改操作: O(n)
  • 查询操作: O(n)
    • 需要注意的是,如果只对链表头进行操作,时间复杂度为:O(1)

链表的应用

(1)使用链表实现栈

​ 根据操作链表的时间复杂度分析,在只对链表的头结点进行操作时,时间复杂化度为O(1).根据此特性,据此可以使用链表来实现栈。

  • 代码:

1.栈Stack

Interface Stack<E>{
    void push(E); //入栈
    E pop(E e); //出栈
    E peek(E e); //取出栈顶元素
    int getSize(); //获取栈中的元素个数
    boolean isEmpty(); //判断栈是否为空
}

2. 链表实现

package com.LinkedList;

import com.Stack.Stack;

/**
 * @author Ming
 * @create 2019-06-20 22:01
 */

public class LinkedListStack<E> implements Stack<E> {

    private LinkedList1<E> linkedList;

    public LinkedListStack() {
        this.linkedList = new LinkedList1<E>();
    }

    @Override
    public int getSize() {
        return linkedList.getSize();
    }

    @Override
    public boolean isEmpty() {
        return linkedList.isEmpty();
    }

    //入栈操作
    //对于底层的链表来说,相当于向头结点添加元素
    @Override
    public void push(E e) {
        linkedList.addHead(e);
    }

    //出栈操作,并返回该元素
    //对于底层的链表来说,相当于删除头结点并返回该元素
    @Override
    public E pop() {
        return linkedList.removeHead();
    }

    //取出栈顶元素
    @Override
    public E peek() {
        return linkedList.getHead();
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append("Stack: top");
        res.append(linkedList);
        return res.toString();
    }

    //测试
    public static void main(String[] args) {
        LinkedListStack<Integer> stack = new LinkedListStack<>();
        for (int i = 0; i < 5; i++) {
            stack.push(i);
            System.out.println(stack);
        }
    }

}

(2)使用链表实现队列

​ 链表改进:使用tail标记链表的尾节点,方便对链表尾部的元素进行操作,这样从两端插入元素都是容易的。据此可以使用链表来实现队列。

  • 注意:

    • 在队列中,一般只会对队首以及队尾元素进行操作,不用对队中的元素进行操作,因此在底层实现的链表中,不再使用虚拟头结点dummyHead

    • 由于不存在dummyHead,需要注意链表为空的情况,此时队首head与队尾tail会处于统一位置

  • 图解:

在这里插入图片描述

  • 代码:
package com.LinkedList;

/**
 * @author Ming
 * @create 2019-06-21 10:06
 */

public class LinkedListQueue<E> implements Queue<E> {

    //定义节点
    private class Node {
        public E e;
        public Node next;

        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        public Node(E e) {
            this(e, null);
        }

        public Node() {
            this(null, null);
        }

        @Override
        public String toString() {
            return e.toString();
        }
    }

    private Node head; //头结点
    private Node tail;//尾结点
    private int size;

    public LinkedListQueue() {
        head = null;
        tail = null;
        size = 0;
    }

    @Override
    public int getSize() {
        return size;
    }

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

    //入队(向链表尾部添加元素)
    //需要注意队列为空的情况
    @Override
    public void enQueue(E e) {
        //队列为空
        if (tail == null) {
            tail = new Node(e);
            head = tail;//添加元素后,暂时队列中只一个元素,头尾节点是同一个节点
        } else {
            //队列不为空
            tail.next = new Node(e);
            tail = tail.next;
            tail.next = null;
        }
        size++;
    }

    //出队(移除链表头部元素)
    @Override
    public E deQueue() {
        if (isEmpty()) {
            throw new IllegalArgumentException("DeQueue failed, Empty queue");
        }
        Node resultNode = head;
        head = head.next;//头结点出队,让当前head的下一个节点,作为新的节点
        resultNode.next = null;//方便回收

        //若出队后,队列为空
        if (head == null) {
            //头结点出队后,tail节点仍然指向之前的尾节点
            tail = null;
        }
        size--;
        return resultNode.e;
    }

    //获取头结点元素
    @Override
    public E getFront() {
        if (isEmpty()) {
            throw new IllegalArgumentException("Get failed, Empty queue");
        }
        return head.e;
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append("Queue:front ");
        Node cur = head;
        while (cur != null) {
            res.append(cur.e + "->");
            cur = cur.next;
        }
        res.append("Null tail");
        return res.toString();
    }

    //测试
    public static void main(String[] args) {
        LinkedListQueue<Integer> queue = new LinkedListQueue<>();
        for (int i = 0; i < 6; i++) {
            queue.enQueue(i);
            System.out.println(queue);
            if(i%3==2){
                queue.deQueue();
            }
        }
    }
}

补充:尝试使用含有dummyHead的链表实现队列

链表与递归

举例:leetCode 203 移除链表元素

CategoryDifficultyLikesDislikes
algorithmsEasy (41.09%)247-
Tags

linked-list

删除链表中等于给定值 val 的所有节点。

示例:

输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5

解答:

(1)链表中未包含dummyHead

/*
 * @lc app=leetcode.cn id=203 lang=java
 *
 * [203] 移除链表元素
 */
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */

//链表中的删除元素,理解为:让目标节点delNode的前一个节点pre.next指向delNode.next即可
class Solution {
    public ListNode removeElements(ListNode head, int val) {

        //先处理头结点元素
        //包括删除当前头结点而产生的新的头结点
        while (head != null && head.val == val) {
            head = head.next;
        }

        //处理完成之后,head=null(即链表为空)
        if (head == null) {
            return null;
        }

        //处理中间的元素
        //需要先使用prev指向目标元素的前一个位置
        ListNode prev = head;
        while (prev.next != null) {
            //指定位置的值满足条件,则删除
            if (prev.next.val == val) {
                prev.next = prev.next.next;
            } else {
                prev = prev.next;
            }
        }
        return head;
    }
}

(2)链表中包含dummyHead(避免了对head是否为空的特殊处理)

/*
 * @lc app=leetcode.cn id=203 lang=java
 *
 * [203] 移除链表元素
 */
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {

            //定义虚拟头节点,其next指向真正的头结点
            ListNode dummyHead = new ListNode(-1);
            dummyHead.next = head;
            
            //删除指定位置节点,需要先使用prev指向目标元素的前一个位置
            ListNode prev = dummyHead;
            while(prev.next!=null){
                if(prev.next.val==val){
                    prev.next = prev.next.next;   
                }else{
                    prev = prev.next;
                }
            }
            return dummyHead.next;
    }
}

(3)递归

链表中的递归:

在这里插入图片描述

使用递归解决上述问题:(LeetCode203:删除链表中的相同元素)

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

  • 代码:

/*
 * @lc app=leetcode.cn id=203 lang=java
 *
 * [203] 移除链表元素
 */
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return null;
        }
        
        ListNode res = removeElements(head.next, val);
        if (head.val == val) {
            return res;
        } else {
            head.next = res;
            return head;
        }
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值