数据结构与算法实战(四)动态数据结构基础:链表

数据结构与算法实战(四)动态数据结构基础:链表

链表是真正的动态数据结构

一、什么是链表

链表结构

  • 数据存储在节点(Node)中

    class Node{
    	E e;
    	Node next;//指向下一个结点(最后一个结点为null)
    }
    
  • 优点:真正的动态,不需要处理固定容量问题

  • 缺点:丧失了随机访问的能力

数组和链表的对比

数组
  • 最好用于索引有语意的情况下。
  • 最大的优点:支持快速查询
链表
  • 不适合用于索引有语意的情况。
  • 最大的优点:动态

二、实现链表

1、实现内部Node

采用内部类的方式

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; //记录链表元素个数

    public LinkedList(){
        head = null;
        size = 0;
    }

    //获取链表元素个数
    public int getSize(){
        return size;
    }

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

2、在链表头添加元素

//在链表头添加元素e
    public void addFirst(E e){
//        Node node = new Node(e);
//        node.next = head;
//        head = node;
        head = new Node(e,head);
        
        size ++;
    }

3、在链表中间添加元素

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

node.next = prev.next;
prev.next = node;

顺序很重要,不能改变!

//在链表的index(0 - based)添加新元素e
    public void add(int index, E e){
        if(index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Illegal index");
        if(index == 0){
            addFirst(e);
        }else {
            Node prev = head;
            for (int i = 0; i < index - 1; i++) {
                prev = prev.next;//把当前prev存的当前节点的下一个节点放进prev中,直到待插入节点的前一个位置
            }
//            Node node = new Node(e);
//            node.next = prev.next;
//            prev.next = node;
            prev.next = new Node(e,prev.next);
            size ++;
        }

4、为链表设立虚拟头节点

给链表头部添加元素的逻辑和在其他位置添加元素不一样,所以引入一个虚拟头节点(dummyHead)的概念来使之一样。

private Node dummyHead;
int size; //记录链表元素个数

public LinkedList(){
    dummyHead = new Node();
    size = 0;
}
 //在链表的index(0 - based)添加新元素e
    public void add(int index, E e){
        if(index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Illegal index");

        Node prev = dummyHead;
        for (int i = 0; i < index; i++) {
            prev = prev.next;//把当前prev存的当前节点的下一个节点放进prev中,直到待插入节点的前一个位置
        }
//            Node node = new Node(e);
//            node.next = prev.next;
//            prev.next = node;
        prev.next = new Node(e, prev.next);
        size++;
        }

    //在链表头添加元素e
    public void addFirst(E e){
        add(0,e);
    }

    //在链表末尾添加元素e
    public void addLast(E e){
        add(size,e);
    }
}

5、链表的遍历,查询和修改

//获得链表的第index(0 - based)个位置的元素
    public E get(int index){
        if(index < 0 || index > size)
            throw new IllegalArgumentException("Get failed. Illegal index");

        Node cur = dummyHead.next;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        return cur.e;
    }
    //获得第一个元素
    public E getFirst(){
        return get(0);
    }
    //获得最后一个元素
    public E getLast(){
        return get(size - 1);
    }

    //修改链表的第index(0 - based)个位置的元素为e
    public void set(int index ,E e){
        if(index < 0 || index > size)
            throw new IllegalArgumentException("Set failed. Illegal index");
        
        Node cur = dummyHead.next;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        cur.e = e;
    }

    //查找链表是否有元素e
    public boolean contains(E e){
        
        Node cur = dummyHead.next;
        while (cur != null){
            if(cur.e.equals(e))
                return true;
            cur = cur.next;
        }
        return false;
    }
    
    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();

//        Node cur = dummyHead.next;
//        while (cur != null){
//            res.append(cur + "->");
//            cur = cur.next;
//        }
        for(Node cur = dummyHead.next; cur != null; cur = cur.next)
            res.append(cur + "->");
        res.append("NULL");
        
        return res.toString();
    }

6、链表元素的删除

prev.next = delNode.next;
delNode.next = null;
//从链表删除第index(0 - based)个位置的元素,并返回元素
public E remove(int index){
    if(index < 0 || index > size)
        throw new IllegalArgumentException("remove failed. Illegal index");
    Node prev = dummyHead;
    for (int i = 0; i < index; i++) {
        prev = prev.next;
    }
    Node retNode = prev.next;
    prev.next = retNode.next;
    retNode.next = null;
    size --;

    return retNode.e;
}
//删除第一个元素
public E removeFirst(){
    return remove(0);
}
//删除最后一个元素
public E removeLast(){
    return remove(size - 1);
}

7、链表的复杂度分析

对于增删改查都是O(n)

而如果只对链表头进行操作:O(1)

只查链表头元素:O(1)

三、使用链表实现栈

public class LinkedListStack<E> implements Stack{

    private LinkedList<E> list;

    public LinkedListStack(){
        list = new LinkedList<>();
    }

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

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

    @Override
    public void push(Object o) {
        list.addFirst((E) o);
    }

    @Override
    public E pop() {
        return list.removeFirst();
    }

    @Override
    public E peek() {
        return list.getFirst();
    }

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

        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);
        }

        stack.pop();
        System.out.println(stack);
    }
}

四、使用链表实现队列

增加一个节点tail指向最后一个元素

从head端删除元素,从tail端插入元素(无dummyHead)

package LinkedListImpl;

public class LinkedListQueue<E> implements Queue {

    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,tail;
    int size; //记录链表元素个数

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

    //获取链表元素个数
    public int getSize(){
        return size;
    }

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

    //入队
    @Override
    public void enqueue(Object o) {
        E e = (E) o;
        if(tail == null){
            tail = new Node(e);
            head = tail;       
        }else {
            tail.next = new Node(e);
            tail = tail.next;
        }
        size ++;
    }

    //出队
    @Override
    public E dequeue() {
        if(isEmpty()){
            throw new IllegalArgumentException("Cannot dequeue from an empty queue");
        }
        Node retNode = head;
        head = head.next;
        retNode.next = null;
        if(head == null) 
            tail = null;
        size --;
        return retNode.e;
    }

    @Override
    public E getFront() {
        if(isEmpty()){
            throw new IllegalArgumentException("Queue is empty");
        }
        
        return head.e;
    }

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

五、Leetcode203题

203. 移除链表元素

难度简单473

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

示例:

输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5
//使用头节点
class Solution {
    public ListNode removeElements(ListNode head, int val) {

        while (head != null && head.val == val){
            ListNode delNode = head;
            head = head.next;
            delNode.next = null;
        }

        if(head == null)
            return null;

        ListNode prev = head;
        while (prev.next != null){
            if(prev.next.val == val){
                ListNode delNode = prev.next;
                prev.next = delNode.next;
                delNode.next = null;
            }
            else {
                prev = prev.next;
            }
        }
        return head;
    }
}
//使用虚拟头节点
public class Solution {
    public ListNode removeElements(ListNode head, int val) {

        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;

        ListNode prev = dummyHead;
        while (prev.next != null){
            if(prev.next.val == val){
                ListNode delNode = prev.next;
                prev.next = delNode.next;
                delNode.next = null;
            }else
                prev = prev.next;
        }
        return dummyHead.next;
    }
}

六、链表与递归

1、递归

  • 本质上,将原来的问题,转化为更小的同一问题
  • 例如:数组求和

2、数组求和的实现

public class sun {

    public static int sum(int[] arr){
        return sum(arr,0);
    }

    //计算arr[l ... n)这个区间内所有数字的和
    private static int sum(int[] arr, int l){
        //求解最基本问题
        if(l == arr.length)
            return 0;
        
        //把原问题转化为更小的问题
        return arr[l] + sum(arr , l+1);
    }

    public static void main(String[] args) {

        int[] nums = {1,2,3,4,5,6,7};
        System.out.println(sum(nums));//28
    }
}

3、链表和递归

链表具有天然的递归性

0 -> 1 -> 2 -> 3 ->null 可以看成 0 -> (一个更短的链表,不过少了一个结点)

使用递归解决LeetCode203题
public class Solution3 {
    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;
        }
//        head.next = removeElements(head.next,val);
//        return head.val == val ? head.next : head;
    }
}

4、递归的微观解读

  • 递归函数的调用,本质就是函数的调用
  • 只不过调用的函数是自己而已
对数组求和进行解读

在这里插入图片描述

七、链表经典问题:反转链表

206. 反转链表

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
package LeetCode206;

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
//非递归
class Solution {
    public ListNode reverseList(ListNode head) {

        ListNode prev = null;
        ListNode cur = head;

        while (cur != null){
            ListNode next = cur.next;
            cur.next = prev;
            prev = cur;
            cur = next;
        }
        return prev;
    }
}
package LeetCode206;

//递归
public class Solution2 {
    public ListNode reverseList(ListNode head) {

        if(head == null || head.next == null)
            return head;

        ListNode rev = reverseList(head.next);//反转了除head结点之外的链表,并且第一个元素指向null
        head.next.next = head;//head.next.next指向的是head的下一个元素的反转后链表所指向的null,令它指向head
        head.next = null;
        return rev;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值