Day29-数据结构与算法-线性表


title: Day29-数据结构与算法-线性表
date: 2020-10-16 10:20:47
tags:


  1. 常用的经典数据结构(例如:二叉树、哈希表、Trie等)
  2. 更高级的数据结构(例如:图、并查集、跳表、布隆过滤器等)与各种算法(例如:排序、KMP、贪心、分治、动态规划等)
  3. 刷题LeetCode和算法真题(海量数据处理、字符串处理)

第一季

常用的经典数据结构

复杂度

  • 求第n个斐波那契数
package com.zimo.复杂度;

import com.zimo.utils.TimeTool;
import com.zimo.utils.TimeTool.Task;

public class Fibonacci {
    
    
    /**
     * 斐波那契数列: 0 1 1 2 3 5 8 13
     * @param args
     */
    public static void main(String[] args) {
    
    
        TimeTool.check("递归",new Task(){
    
    

            @Override
            public void execute() {
    
    
                fib_recursion(45);
            }
        });
        TimeTool.check("公式",new Task(){
    
    

            @Override
            public void execute() {
    
    
                fib(45);
            }
        });
    }
    // 递归 当n过大,导致栈空间溢出
    public static int fib_recursion(int n){
    
    
        if (n <= 1) return n;
        return  fib_recursion(n-1) + fib_recursion(n -2);
    }
    public static int fib(int n){
    
    
        if (n <= 1) return n;
        int first = 0;
        int second = 1;
        for (int i = 0; i < n - 1; i++) {
    
    
            int sum = first + second;
            first = second;
            second = sum;
        }
        return second;
    }
}
  • 斐波那契数的线性代数解法 - 特征方程

    斐波那契数特征方程

public static int fibFunction(int n){
    
    
    double c = Math.sqrt(5);
    return (int)((Math.pow((1 + c) / 2, n) - Math.pow((1-c) / 2, n)) / c);
}
  • 复杂度分析

    • 最好情况复杂度
    • 最坏情况复杂度
    • 平均情况复杂度
  • 评估算法的优劣:

    • 正确性、可读性、健壮性(对不合理输入的反应能力)
    1. 时间复杂度:估算程序指令的执行次数(执行时间)
    2. 空间复杂度:估算所需占用的存储空间

大O表示法

  • 一般用大O表示法来描述复杂度,它表示的是数据规模n对应的复杂度
  • 忽略常数,系数,低阶
  • 对数阶一般省略底数 log2n = log29 * log9n,所以log2n、log9n统称为logn

**注意:**大O表示法仅仅是一种粗略的分析模型,是一种估算,能帮助我们短时间内了解一个算法的执行效率

执行次数 复杂度 非正式术语
12 O(1) 常数阶
2n + 3 O(n) 线性阶
4n² + 2n + 6 O(n²) 平方阶
4log2 + 25 O(logn) 对数阶
3n + 2nlog3n + 15 O(nlogn) nlogn阶
4n³ + 3n² + 22n + 100 O(n³) 立方阶
2n O(2n) 指数阶
  • O1 < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2n) < O(n!) < O(nn)

算法的优化方向:

  • 尽量少的存储空间
  • 尽量少的执行步骤(执行时间)
  • 根据情况,可以:空间换时间,时间换空间

线性表

数据结构是计算机存储、组织数据的方式:

  • 线性结构:数组、链表、栈、队列、哈希表(散列表)等
  • 树形结构:二叉树、AVL树、红黑树、B树、堆、Trie、哈夫曼树、并查集等
  • 图形结构:邻接矩阵、邻接表等

根据实际应用场景选择最合适的数据结构

线性表是具有n个相同类型元素的有限序列(n >= 0)

线性表

数组Array List

数组是一种顺序存储的线性表,所有元素的内存地址是连续的,数组的致命缺陷,一旦确定无法扩容

**泛型:**使用泛型计数可以让动态数组更加通用,可以存放任何数据类型

  • 数组动态扩容:手撸ArrayList,使用泛型存在内存管理问题

    package com.zimo.线性表.数组;
    import java.util.Objects;
    
    /**
     * 线性表 - 动态数组
     *      接口设计:
     *          1.元素的数量:int size();
     *          2.是否为空:boolean isEmpty();
     *          3.是否包含某个元素:boolean contains(E element);
     *          4.添加元素到最后面:void add(E element);
     *          5.返回index位置对应的元素:E get(int index);
     *          6.设置index位置的元素:E set(int index, E element);
     *          7.往index位置添加元素:void add(int index, E element);
     *          8.删除index位置对应的元素:E remove(int index);
     *          9.查看元素的位置:int indexOf(E element);
     *          10.清楚所有元素:void clear();
     *          11.删除index对应的元素:E remove(E element);
     *
     * @author Liu_zimo
     * @version v0.1 by 2020-10-16 13:59:56
     */
    public class AutoArray <E> {
          
          
        public static void main(String[] args) {
          
          
            AutoArray<Integer> array = new AutoArray<>();
            array.add(10);
            array.add(12);
            array.add(14);
            array.add(15);
            System.out.println(array.toString());
            array.remove(2);
            System.out.println(array.toString());
        }
    
        private int size = 0;       // 元素的数量
        private E[] elements;   // 所有的元素
    
        private static final int DEFAULT_CAPACITY = 10;
        private static final int ELEMENT_NOT_FOUND = -1;
    
        public AutoArray() {
          
          
            this(DEFAULT_CAPACITY);
        }
    
        public AutoArray(int capacity) {
          
          
            capacity = capacity < DEFAULT_CAPACITY ? DEFAULT_CAPACITY : capacity;
            this.elements = (E[]) new Object[capacity];
        }
    
        /**
         * 清楚所有元素
         */
        public void clear(){
          
          
            // 为了支持泛型,需要将数据清空begin
            for (int i = 0; i < elements.length; i++) {
          
          
                elements[i] = null;
            }
            // end
            size = 0;
        }
    
        /**
         * 元素的数量
         * @return 【out】返回元素的数量
         */
        public int size(){
          
          
            return size;
        }
    
        /**
         * 是否为空
         * @return 【out】返回数组是否为空
         */
        public boolean isEmpty(){
          
          
            return size == 0;
        }
    
        /**
         * 是否包含某个元素
         * @param element 【in】要校验的元素
         * @return 【out】校验结果
         */
        public boolean contains(E element){
          
          
            return indexOf(element) != ELEMENT_NOT_FOUND;
        }
    
        /**
         * 添加元素到尾部
         * @param element 【in】要添加的元素
         */
        public void add(E element){
          
          
            add(size, element);
        }
    
        /**
         * 添加元素到index
         * @param index 【in】要插入的位置
         * @param element 【in】要插入的元素
         */
        public void add(int index, E element){
          
          
            //if (element == null) return;    // 是否允许存空值(null)
            // 允许等于size
            rangeCheckForAdd(index);
            ensuerCapacity(size + 1);
            for (int i = size; i > index; i--) {
          
          
                elements[i] = elements[i - 1];
            }
            elements[index] = element;
            size++;
        }
    
        /**
         * 获取index位置的元素
         * @param index 【in】要获取元素的位置
         * @return 【out】返回元素值,如果没有返回null
         */
        public E get(int index){
          
          
            rangeCheck(index);
            return elements[index];
        }
    
        /**
         * 设置index位置的元素
         * @param index 【in】要设置元素的位置
         * @param element 【in】要设置的元素
         * @return 【out】原来的元素
         */
        public E set(int index, E element){
          
          
            rangeCheck(index);
            E old = elements[index];
            elements[index] = element;
            return old;
        }
    
        /**
         * 删除index位置的元素
         * @param index 【in】要删除元素的位置
         * @return 【out】返回被删除的元素
         */
        public E remove(int index){
          
          
            rangeCheck(index);
            E old = elements[index];
            for (int i = index + 1; i < size; i++) {
          
          
                elements[i - 1] = elements[i];
            }
            size--;
            // 为了支持泛型,需要将最后一个引用清空
            elements[size] = null;
            return old;
        }
    
        public E remove(E element){
          
          
            return remove(indexOf(element));
        }
    
        /**
         * 查看元素的索引
         * @param element 【in】要查找的元素
         * @return 【out】元素所在位置
         */
        public int indexOf(E element){
          
          
            // 如果允许null,那么要特殊处理
            if (element == null){
          
          
                for (int i = 0; i < elements.length; i++) {
          
          
                    if (elements[i] == null) return i;
                }
            }else {
          
          
                for (int i = 0; i < elements.length; i++) {
          
          
                    // if (element == elements[i]) return i;
                    // 泛型的改进
                    if (element.equals(elements[i])) return i;
                }
            }
            return ELEMENT_NOT_FOUND;
        }
    
        /**
         * 要保证有capacity的容量
         * @param capacity 【in】至少需要的容量
         */
        private void ensuerCapacity(int capacity) {
          
          
            int oldCapacity = elements.length;      // 旧数组的长度
            if (oldCapacity >= capacity) return;
    
            // 新容量为旧容量的1.5倍
            int newCapacityLength = oldCapacity + (oldCapacity >> 1);  // 等价于 oldCapacity * 1.5
            E[] newElements = (E[]) new Object[newCapacityLength];
            for (int i = 0; i < size; i++){
          
          
                newElements[i] = elements[i];
            }
            elements = newElements;
        }
    
        private void outOfBounds(int index){
          
          
            throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
        }
    
        private void rangeCheck(int index){
          
          
            if (index < 0 || index >= size){
          
          
                outOfBounds(index);
            }
        }
    
        private void rangeCheckForAdd(int index){
          
          
            if (index < 0 || index > size){
          
          
                outOfBounds(index);
            }
        }
    
        @Override
        public String toString() {
          
          
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("size = ").append(size).append(", [");
            for (int i = 0; i < size; i++) {
          
          
                if (i != 0) stringBuilder.append(", ");
                stringBuilder.append(elements[i]);
    //            if (i != size - 1){
          
          
    //                stringBuilder.append(", ");
    //            }
            }
            stringBuilder.append("]");
            return stringBuilder.toString();
        }
    }
    

    优化:增加一个头节点指针,使数组变成环形数组,增强性能,减少增删移动次数

  • 对象数组

    对象数组

动态数组的缩容

如果内存使用比较紧张,动态数组有比较多的剩余空间,可以考虑进行缩容操作
如果扩容倍数、缩容时机设计不得当,有可能会导致复杂度震荡(扩容倍数 x 缩容倍数 = 1)

public E remove(int index) {
    
    
    rangeCheck(index);
    E old = elements[index];
    for (int i = index + 1; i < size; i++) {
    
    
        elements[i - 1] = elements[i];
    }
    size--;
    // 为了支持泛型,需要将最后一个引用清空
    elements[size] = null;
    trim();	// 增加缩容操作
    return old;
}

/**
 * 缩容:剩余空间占总容量的一半时,就进行缩容
 */
private void trim() {
    
    
    int capacity = elements.length;
    int newCapacity = capacity >> 1;
    if (size >= newCapacity || capacity <= DEFAULT_CAPACITY) {
    
    
        return;
    }
    // 缩容:剩余空间还很多
    E[] newElements = (E[]) new Object[newCapacity];
    for (int i = 0; i < size; i++) {
    
    
        newElements[i] = elements[i];
    }
    elements = newElements;
}

链表Linked List

  • 动态数组有个明显的缺点:可能造成内存空间的大量浪费
  • 链表是一种链式存储的线性表,所有元素的内存地址不一定是连续的

链表的大部分接口和动态数组是一致,所以可以抽取公共方法:

package com.zimo.线性表;
/**
 * 线性表 - 公共的方法抽取
 *
 * @author Liu_zimo
 * @version v0.1 by 2020-10-19 16:30:00
 */
public interface List<E> {
    
    
    /**
     * 清楚所有元素
     */
    void clear();

    /**
     * 元素的数量
     * @return 【out】返回元素的数量
     */
    int size();

    /**
     * 是否为空
     * @return 【out】返回数组是否为空
     */
    boolean isEmpty();

    /**
     * 是否包含某个元素
     * @param element 【in】要校验的元素
     * @return 【out】校验结果
     */
    boolean contains(E element);

    /**
     * 添加元素到尾部
     * @param element 【in】要添加的元素
     */
    void add(E element);

    /**
     * 添加元素到index
     * @param index 【in】要插入的位置
     * @param element 【in】要插入的元素
     */
    void add(int index, E element);

    /**
     * 获取index位置的元素
     * @param index 【in】要获取元素的位置
     * @return 【out】返回元素值,如果没有返回null
     */
    E get(int index);

    /**
     * 设置index位置的元素
     * @param index 【in】要设置元素的位置
     * @param element 【in】要设置的元素
     * @return 【out】原来的元素
     */
    E set(int index, E element);

    /**
     * 删除index位置的元素
     * @param index 【in】要删除元素的位置
     * @return 【out】返回被删除的元素
     */
    E remove(int index);

    E remove(E element);

    /**
     * 查看元素的索引
     * @param element 【in】要查找的元素
     * @return 【out】元素所在位置
     */
    int indexOf(E element);
}
package com.zimo.线性表;
/**
 * 线性表 - 抽象类
 *
 * @author Liu_zimo
 * @version v0.1 by 2020-10-19 16:40:00
 */
public abstract class AbstractList<E> implements List<E> {
    
    

    protected int size;   // 元素的数量

    public int size() {
    
    
        return size;
    }

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

    public boolean contains(E element) {
    
    
        return indexOf(element) != ELEMENT_NOT_FOUND;
    }

    public void add(E element) {
    
    
        add(size, element);
    }

    public E remove(E element){
    
    
        return remove(indexOf(element));
    }

    protected void outOfBounds(int index){
    
    
        throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
    }

    protected void rangeCheck(int index){
    
    
        if (index < 0 || index >= size){
    
    
            outOfBounds(index);
        }
    }

    protected void rangeCheckForAdd(int index){
    
    
        if (index < 0 || index > size){
    
    
            outOfBounds(index);
        }
    }
}
单向链表

链表接口设计

package com.zimo.线性表.链表;

import com.zimo.线性表.AbstractList;
import com.zimo.线性表.List;
import org.w3c.dom.Node;

/**
 * 线性表 - 单向链表
 *      链表的大部分接口和动态数组是一致
 *
 * @author Liu_zimo
 * @version v0.1 by 2020-10-19 16:30:00
 */
public class LinkList<E> extends AbstractList<E> {
    
    
    // 测试
    public static void main(String[] args) {
    
    
        LinkList<Integer> list = new LinkList<>();
        list.add(20);
        list.add(0, 10);
        list.add(30);
        list.add(list.size(),40);
        list.remove(1);
        System.out.println(list);
    }

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

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

    @Override
    public void add(int index, E element) {
    
    
        if (index == 0){
    
    
            firstNode = new Node<>(element, firstNode);
        }else {
    
    
            Node<E> previous = node(index - 1);
            previous.next = new Node<>(element, previous.next);
        }
        size++;
    }

    @Override
    public E get(int index) {
    
    
        return node(index).element;
    }

    @Override
    public E set(int index, E element) {
    
    
        Node<E> node = node(index);
        E old = node.element;
        node.element = element;
        return old;
    }

    @Override
    public E remove(int index) {
    
    
        Node<E> node = firstNode;
        if (index == 0){
    
    
            firstNode = firstNode.next;
        }else {
    
    
            Node<E> prev = node(index - 1);
            node = prev.next;
            prev.next = prev.next.next;
        }
        size--;
        return node.element;
    }

    @Override
    public int indexOf(E element) {
    
    
        Node<E> node = this.firstNode;
        if(element == 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 (element.equals(node.element)) return i;
                node = node.next;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

    /**
     * 根据index脚标获取索引对象
     * @param index
     * @return Node
     */
    private Node<E> node(int index){
    
    
        rangeCheck(index);
        Node<E> node = this.firstNode;
        for (int i = 0; i < index; i++) {
    
    
            node = node.next;
        }
        return node;
    }

    @Override
    public String toString() {
    
    
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("size = ").append(size).append(", [");
        Node<E> node = this.firstNode;
        for (int i = 0; i < size; i++) {
    
    
            if (i != 0) stringBuilder.append(", ");
            stringBuilder.append(node.element);
            node = node.next;
        }
        stringBuilder.append("]");
        return stringBuilder.toString();
    }
}

练习:反转链表(递归、非递归)、链表是否有环(快慢指针:[slow:1,fast:2])

虚拟头节点

链表虚拟头节点

package com.zimo.线性表.链表;
import com.zimo.线性表.AbstractList;
/**
 * 线性表 - 链表 - 虚拟头节点
 *
 * @author Liu_zimo
 * @version v0.1 by 2020-10-20 10:20:30
 */
public class LinkListVirtualHead<E> extends AbstractList<E> {
    
    
    private Node<E> firstNode;
    // 创建就要有一个虚拟空节点
    public LinkListVirtualHead() {
    
    
        this.firstNode = new Node<>(null, null);
    }
    private static class Node<E> {
    
    
        E element;
        Node<E> next;
        public Node(E element, Node<E> next) {
    
    
            this.element = element;
            this.next = next;
        }
    }
    @Override
    public void clear() {
    
    
        size = 0;
        firstNode = null;
    }
    @Override
    public void add(int index, E element) {
    
    
        rangeCheckForAdd(index);
        Node<E> previous = index == 0 ? firstNode : node(index - 1);
        previous.next = new Node<>(element, previous.next);
        size++;
    }
    @Override
    public E get(int index) {
    
    
        return node(index).element;
    }
    @Override
    public E set(int index, E element) {
    
    
        Node<E> node = node(index);
        E old = node.element;
        node.element = element;
        return old;
    }
    @Override
    public E remove(int index) {
    
    
        rangeCheck(index);
        Node<E> prev = index == 0 ? firstNode : node(index - 1);
        Node<E> node = prev.next;
        prev.next = prev.next.next;
        size--;
        return node.element;
    }
    @Override
    public int indexOf(E element) {
    
    
        Node<E> node = this.firstNode;
        if (element == 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 (element.equals(node.element)) return i;
                node = node.next;
            }
        }
        return ELEMENT_NOT_FOUND;
    }
    private Node<E> node(int index) {
    
    
        rangeCheck(index);
        Node<E> node = this.firstNode.next;
        for (int i = 0; i < index; i++) {
    
    
            node = node.next;
        }
        return node;
    }
    @Override
    public String toString() {
    
    
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("size = ").append(size).append(", [");
        Node<E> node = this.firstNode.next;
        for (int i = 0; i < size; i++) {
    
    
            if (i != 0) stringBuilder.append(", ");
            stringBuilder.append(node.element);
            node = node.next;
        }
        stringBuilder.append("]");
        return stringBuilder.toString();
    }
}

动态数组、链表复杂度分析
均摊复杂度

双向链表

双向链表

package com.zimo.线性表.链表;

import com.zimo.线性表.AbstractList;

/**
 * 线性表 - 双向链表
 *
 * @author Liu_zimo
 * @version v0.1 by 2020-10-20 13:51:00
 */
public class DupLinkList<E> extends AbstractList<E> {
    
    
    public static void main(String[] args) {
    
    
        DupLinkList<Integer> list = new DupLinkList<>();
        list.add(11);
        list.add(22);
        list.add(33);
        list.add(44);
        list.add(0,55);
        list.add(2,66);
        list.add(list.size(), 77);
        list.remove(0);
        list.remove(2);
        list.remove(list.size() -1);
        System.out.println(list);
    }

    private Node<E> firstNode;      // 头节点
    private Node<E> lastNode;       // 尾节点

    private static class Node<E> {
    
    
        E element;          // 元素
        Node<E> prev;       // 前节点
        Node<E> next;       // 后节点

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

    @Override
    public void clear() {
    
    
        size = 0;
        firstNode = null;
        lastNode = null;
    }

    @Override
    public void add(int index, E element) {
    
    
        rangeCheckForAdd(index);
        if (index == size) {
    
    
            // 往最后添加
            Node<E> oldLast = lastNode;
            lastNode = new Node<>(element, oldLast, null);
            if (oldLast == null) {
    
    
                firstNode = lastNode;
            } else {
    
    
                oldLast.next = lastNode;
            }
        } else {
    
    
            Node<E> next = node(index);
            Node<E> prev = next.prev;
            Node<E> newNode = new Node<>(element, prev, next);
            next.prev = newNode;
            if (prev == null) {
    
    
                firstNode = newNode;
            } else {
    
    
                prev.next = newNode;
            }
        }
        size++;
    }

    @Override
    public E get(int index) {
    
    
        return node(index).element;
    }

    @Override
    public E set(int index, E element) {
    
    
        Node<E> node = node(index);
        E old = node.element;
        node.element = element;
        return old;
    }

    @Override
    public E remove(int index) {
    
    
        rangeCheck(index);
        Node<E> node = node(index);
        Node<E> prev = node.prev;
        Node<E> next = node.next;
        if (prev == null) {
    
    
            firstNode = next;
        } else {
    
    
            prev.next = next;
        }
        if (next == null) {
    
    
            lastNode = prev;
        } else {
    
    
            next.prev = prev;
        }
        size--;
        return node.element;
    }

    @Override
    public int indexOf(E element) {
    
    
        Node<E> node = this.firstNode;
        if (element == 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 (element.equals(node.element)) return i;
                node = node.next;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

    /**
     * 根据index脚标获取索引对象
     *
     * @param index
     * @return Node
     */
    private Node<E> node(int index) {
    
    
        rangeCheck(index);
        if (index < (size >> 1)) {
    
    
            // 如果index 小于容量的一半,从左查找
            Node<E> node = this.firstNode;
            for (int i = 0; i < index; i++) {
    
    
                node = node.next;
            }
            return node;
        } else {
    
    
            // 如果index 大于容量的一半,从右查找
            Node<E> node = this.lastNode;
            for (int i = size - 1; i > index; i--) {
    
    
                node = node.prev;
            }
            return node;
        }
    }

    @Override
    public String toString() {
    
    
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("size = ").append(size).append(", [");
        Node<E> node = this.firstNode;
        for (int i = 0; i < size; i++) {
    
    
            if (i != 0) stringBuilder.append(", ");
            stringBuilder.append(node.element);
            node = node.next;
        }
        stringBuilder.append("]");
        return stringBuilder.toString();
    }
}
单向链表VS双向链表
  • 粗略对比一下删除的操作数量
    • 单向链表: 1 + 2 + 3 + … + n = (1 + n) * n / 2 = (n / 2) + (n² / 2),除以n平均一下是1/2 + n/2
    • 双向链表:(1 + 2 + 3 + … + n/2) * 2 = [ (1 + n/2) * n/2 ] / 2 * 2 = n/2 + n²/4
    • 操作数量缩减了近一半
动态数组VS双向链表

动态数组:开辟、销毁内存空间的次数相对较少,但可能造成内存空间浪费(可以通过缩容解决)
双向链表:开辟、销毁内存空间的次数相对较多,但不会造成内存空间浪费

  • 如果频繁在尾部进行添加、删除操作,动态数组、双向链表均可选择
  • 如果频繁在头部进行添加、删除操作,建议选择使用双向链表
  • 如果有频繁的(在任意位置)进行添加、删除操作,建议选择使用双向链表
  • 如果有频繁的查询操作(随机访问操作),建议选择使用动态数组

有了双向链表,单向链表是否就没有任何用处了?

并非如此,在哈希表的设计中就用到了单链表

单向循环链表
// 单项链表改进
@Override
public void add(int index, E element) {
    
    
    rangeCheckForAdd(index);
    if (index == 0){
    
    
        Node<E> newFirst = new Node<>(element, firstNode);
        // 拿到最后节点
        Node<E> last = size == 0 ? newFirst : node(size - 1);
        last.next = newFirst;
        firstNode = newFirst;
    }else {
    
    
        Node<E> previous = node(index - 1);
        previous.next = new Node<>(element, previous.next);
    }
    size++;
}
@Override
public E remove(int index) {
    
    
    rangeCheck(index);
    Node<E> node = firstNode;
    if (index == 0){
    
    
        if(size == 1) {
    
    
            firstNode = null;
        } else {
    
    
            Node<E> last = node(size - 1);  // 在改变之前拿到最后一个节点
            firstNode = firstNode.next;
            last.next = firstNode;
        }
    }else {
    
    
        Node<E> prev = node(index - 1);
        node = prev.next;
        prev.next = prev.next.next;
    }
    size--;
    return node.element;
}
双向循环链表

将循环链表发挥最大威力:增设一个成员变量,3个方法

  • current:用于指向某个节点
  • void reset():让current指向头节点first
  • E next():让current往后走一步,也就是current = current.next
  • E remove():删除current指向的节点,删除成功后让current指向下一个节点
package com.zimo.线性表.链表;

import com.zimo.线性表.AbstractList;

/**
 * 线性表 - 双向循环链表
 *
 * @author Liu_zimo
 * @version v0.1 by 2020-10-20 16:15:00
 */
public class DupLoopLinkList<E> extends AbstractList<E> {
    
    
    public static void main(String[] args) {
    
    
        // 练习:解决约瑟夫问题
        DupLoopLinkList<Integer> list = new DupLoopLinkList<>();
        for (int i = 1; i < 9; i++) {
    
    
            list.add(i);
        }
        // 指向头节点
        list.reset();
        while (!list.isEmpty()){
    
    
            list.next();
            list.next();
            System.out.println(list.remove());
        }
    }

    private Node<E> firstNode;      // 头节点
    private Node<E> lastNode;       // 尾节点
    private Node<E> current;        // 用于指向某个节点

    private static class Node<E> {
    
    
        E element;          // 元素
        Node<E> prev;       // 前节点
        Node<E> next;       // 后节点

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

    @Override
    public void clear() {
    
    
        size = 0;
        firstNode = null;
        lastNode = null;
    }

    @Override
    public void add(int index, E element) {
    
    
        rangeCheckForAdd(index);
        if (index == size) {
    
    
            // 往最后添加
            Node<E> oldLast = lastNode;
            lastNode = new Node<>(element, oldLast, firstNode);
            if (oldLast == null) {
    
      // 链表的第一个元素
                firstNode = lastNode;
                firstNode.next = firstNode;
                firstNode.prev = firstNode;
            } else {
    
    
                oldLast.next = lastNode;
                firstNode.prev = lastNode;
            }
        } else {
    
    
            Node<E> next = node(index);
            Node<E> prev = next.prev;
            Node<E> newNode = new Node<>(element, prev, next);
            next.prev = newNode;
            prev.next = newNode;
            if (index == 0) {
    
     // 往0插入节点 next == firstNode条件也可以
                firstNode = newNode;
            }
        }
        size++;
    }

    @Override
    public E get(int index) {
    
    
        return node(index).element;
    }

    @Override
    public E set(int index, E element) {
    
    
        Node<E> node = node(index);
        E old = node.element;
        node.element = element;
        return old;
    }

    @Override
    public E remove(int index) {
    
    
        rangeCheck(index);
        return remove(node(index));
    }

    private E remove(Node<E> node) {
    
    
        if (size == 1){
    
    
            firstNode = null;
            lastNode = null;
        }else {
    
    
            Node<E> prev = node.prev;
            Node<E> next = node.next;
            prev.next = next;
            next.prev = prev;
            if (node == firstNode) {
    
     // index == 0  或 node == firstNode
                firstNode = next;
            }
            if (node == lastNode) {
    
     // index == size - 1
                lastNode = prev;
            }
        }
        size--;
        return node.element;
    }

    @Override
    public int indexOf(E element) {
    
    
        Node<E> node = this.firstNode;
        if (element == 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 (element.equals(node.element)) return i;
                node = node.next;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

    /**
     * 让current指向头节点first
     */
    public void reset(){
    
    
        current = firstNode;
    }

    /**
     * 让current往后走一步,也就是current = current.next
     * @return 当前current所指向的元素
     */
    public E next() {
    
    
        if (current == null) return null;
        this.current = current.next;
        return current.element;
    }

    /**
     * 删除current指向的节点,删除成功后让current指向下一个节点
     * @return 删除节点的元素
     */
    public E remove(){
    
    
        if (current == null) return null;
        Node<E> next = current.next;
        E remove = remove(current);
        current = size == 0 ? null : next;
        return remove;
    }

    /**
     * 根据index脚标获取索引对象
     *
     * @param index
     * @return Node
     */
    private Node<E> node(int index) {
    
    
        rangeCheck(index);
        if (index < (size >> 1)) {
    
    
            // 如果index 小于容量的一半,从左查找
            Node<E> node = this.firstNode;
            for (int i = 0; i < index; i++) {
    
    
                node = node.next;
            }
            return node;
        } else {
    
    
            // 如果index 大于容量的一半,从右查找
            Node<E> node = this.lastNode;
            for (int i = size - 1; i > index; i--) {
    
    
                node = node.prev;
            }
            return node;
        }
    }

    @Override
    public String toString() {
    
    
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("size = ").append(size).append(", [");
        Node<E> node = this.firstNode;
        for (int i = 0; i < size; i++) {
    
    
            if (i != 0) stringBuilder.append(", ");
            stringBuilder.append(node.element);
            node = node.next;
        }
        stringBuilder.append("]");
        return stringBuilder.toString();
    }
}
静态链表
  • 之前学习的链表,是依赖指针(引用)实现的
  • 有些语言没有指针,如早期的BASIC、FORTRAN语言
  • 没有指针情况下如何实现链表
    • 可以通过数组来模拟链表,称为静态链表
    • 数组的每个元素存放两个数据:值、下个元素的索引

栈(stack)

  • 是一种特殊的线性表,只能在一端进行操作
    • 往栈中添加元素的操作,一般叫做push,入栈
    • 从栈中移除元素的操作,一般叫做pop,出栈(只能移除栈顶元素,也叫做:弹出栈顶元素)
    • 后进先出的原则,Last In First Out,LIFO
  • 注意:这里说的“栈”与内存中的“栈空间”是两个不同的概念
  • 接口设计:
    • int size();元素的数量
    • boolean isEmpty();是否为空
    • void push(E element);入栈
    • E pop();出栈
    • E top();获取栈顶元素
package com.zimo.线性表.;

import com.zimo.线性表.List;
import com.zimo.线性表.数组.AutoArray;
import com.zimo.线性表.链表.DupLoopLinkList;

/**
 * 线性表 - 栈
 *
 * @author Liu_zimo
 * @version v0.1 by 2020-10-20 17:36:50
 */
// 可以使用动态数组或者链表实现,如果使用继承,暴露父类接口,选择组合方式比较合理
public class MyStack<E> {
    
    
    public static void main(String[] args) {
    
    
        MyStack<Integer> stack = new MyStack<>();
        for (int i = 1; i <= 5; i++) {
    
    
            stack.push(i);
            System.out.println("入栈元素" + i);
        }
        while (!stack.isEmpty()){
    
    
            System.out.println("弹出元素为:" + stack.pop() + "剩余元素:" + stack.size());
        }
    }
    private List<E> list = new DupLoopLinkList<>(); // 组合双向环形链表实现
    // private AutoArray<E> list = new AutoArray<>(); // 可以改为动态数组

    /**
     * 获取栈元素数量
     * @return 栈元素数量
     */
    public int size(){
    
    
        return list.size();
    }

    /**
     * 判断栈是否为空
     * @return 是否为空结果
     */
    public boolean isEmpty(){
    
    
        return list.isEmpty();
    }

    /**
     * 往栈中添加元素
     * @param element 要添加的元素
     */
    public void push(E element){
    
    
        list.add(element);
    }

    /**
     * 从栈顶弹出元素
     * @return 弹出的元素
     */
    public E pop(){
    
    
        return list.remove(list.size() - 1);
    }

    /**
     * 获取栈顶元素
     * @return 栈顶元素
     */
    public E top(){
    
    
        return list.get(list.size() - 1);
    }
}

应用:浏览器的前进和后退、撤销和恢复(两个栈实现),括号匹配

队列(Queue)

  • 队列是一种特殊的线性表,只能在头尾两端进行操作
    • 队尾(rear):只能从队尾添加元素,一般叫做enQueue,入队
    • 队头(front):只能从队头移除元素,一般叫做deQueue,出队
    • 先进先出原则,First In First Out,FIFO
  • 接口设计
    • int size();元素的数量
    • boolean isEmpty();是否为空
    • void enQueue(E element);入队
    • E deQueue();出队
    • E front();获取队列的头元素
    • void clear();清空
package com.zimo.线性表.队列;

import com.zimo.线性表.List;
import com.zimo.线性表.链表.DupLoopLinkList;

/**
 * 线性表 - 队列
 *
 * @author Liu_zimo
 * @version v0.1 by 2020-10-20 10:22:12
 */
public class MyQueue<E> {
    
    
    public static void main(String[] args) {
    
    
        MyQueue<Integer> queue = new MyQueue<>();
        for (int i = 1; i <= 5; i++) {
    
    
            queue.enQueue(i);
            System.out.println("入队元素" + i);
        }
        while (!queue.isEmpty()){
    
    
            System.out.println("弹出元素为:" + queue.deQueue() + "剩余元素:" + queue.size());
        }
    }
    private List<E> list = new DupLoopLinkList<>(); // 组合双向环形链表实现

    /**
     * 获取队列元素数量
     * @return 队列元素数量
     */
    public int size(){
    
    
        return list.size();
    }

    /**
     * 判断队列是否为空
     * @return 是否为空结果
     */
    public boolean isEmpty(){
    
    
        return list.isEmpty();
    }

    /**
     * 往队列中添加元素
     * @param element 要添加的元素
     */
    public void enQueue(E element){
    
    
        list.add(0, element);
    }

    /**
     * 从队头弹出元素
     * @return 弹出的元素
     */
    public E deQueue(){
    
    
        return list.remove(list.size() - 1);
    }

    /**
     * 获取队头元素
     * @return 队头元素
     */
    public E front(){
    
    
        return list.get(list.size() - 1);
    }

    /**
     * 清空队列元素
     */
    public void clear(){
    
    
        list.clear();
    }
}

练习:用栈模拟队列(inStack,outStack)

双端队列(Deque)
  • 双端队列是能在头尾两端添加、删除的队列,英文deque是double ended queue的简称
  • 接口设计:
    • int size();元素的数量
    • boolean isEmpty();是否为空
    • void enQueueRear(E element);队尾入队
    • void enQueueFront(E element);队头入队
    • E deQueueRear();队尾出队
    • E deQueueFront();队头出队
    • E front();获取队列的头元素
    • E rear();获取队列的尾元素
    • void clear();清空
package com.zimo.线性表.队列;

import com.zimo.线性表.List;
import com.zimo.线性表.链表.DupLoopLinkList;

/**
 * 线性表 - 双端队列
 *
 * @author Liu_zimo
 * @version v0.1 by 2020-10-20 10:22:12
 */
public class MyDeque<E> {
    
    
    public static void main(String[] args) {
    
    
        MyDeque<Integer> deque = new MyDeque<>();
        deque.enQueueFront(11);
        deque.enQueueFront(22);
        deque.enQueueRear(33);
        deque.enQueueRear(44);

        while (!deque.isEmpty()){
    
    
            System.out.println("弹出元素为:" + deque.deQueueFront() + "剩余元素:" + deque.size());
        }
    }
    private List<E> list = new DupLoopLinkList<>(); // 组合双向环形链表实现

    /**
     * 获取队列元素数量
     * @return 队列元素数量
     */
    public int size(){
    
    
        return list.size();
    }

    /**
     * 判断队列是否为空
     * @return 是否为空结果
     */
    public boolean isEmpty(){
    
    
        return list.isEmpty();
    }

    /**
     * 往队列头/尾添加元素
     * @param element 要添加的元素
     */
    public void enQueueFront(E element){
    
     list.add(0, element); }
    public void enQueueRear(E element){
    
     list.add(element); }

    /**
     * 从队尾/头弹出元素
     * @return 弹出的元素
     */
    public E deQueueRear(){
    
     return list.remove(list.size() - 1); }
    public E deQueueFront(){
    
     return list.remove(0); }

    /**
     * 获取队头/尾元素
     * @return 队头元素
     */
    public E front(){
    
     return list.get(0); }
    public E rear(){
    
     return list.get(list.size() - 1); }

    /**
     * 清空队列元素
     */
    public void clear(){
    
    
        list.clear();
    }
}
循环队列(环形队列)
  • 循环队列底层使用数组实现
package com.zimo.线性表.队列.循环队列;

/**
 * 线性表 - 环形队列(数组实现)
 *
 * @author Liu_zimo
 * @version v0.1 by 2020/10/21 11:13:00
 */
public class CircleQueue<E> {
    
    
    public static void main(String[] args) {
    
    
        CircleQueue<Integer> queue = new CircleQueue<Integer>();
        for (int i = 0; i < 10; i++) {
    
    
            queue.enQueue(i);
        }
        for (int i = 0; i< 5; i++) {
    
    
            queue.deQueue();
        }
        System.out.println(queue);
        for (int i = 15; i < 20; i++){
    
    
            queue.enQueue(i);
        }
        System.out.println(queue);
        while (!queue.isEmpty()) {
    
    
            System.out.println(queue.deQueue());
        }
    }

    private int size;       // 元素大小
    private E[] elements;    // 元素
    private int front;      // 对头游标
    private static final int DEFAULT_CAPACITY = 10;

    public CircleQueue() {
    
    
        elements = (E[]) new Object[DEFAULT_CAPACITY];
    }

    public int size(){
    
    
        return size;
    }

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

    public void enQueue(E element){
    
    
        ensuerCapacity(size + 1);
        int index = index(size);
        elements[index] = element;
        size ++;
    }

    public E deQueue(){
    
    
        E frontElement = elements[front];
        elements[front] = null;
        front = index(1);
        size--;
        return frontElement;
    }

    public E front(){
    
    
        return elements[front];
    }

    public void clear(){
    
    
        for (int i = 0; i < size; i++) {
    
    
            elements[index(i)] = null;
        }
        size = 0;
        front = 0;
    }

    // 环形脚标映射
    private int index(int index){
    
    
        // return (front + index) % elements.length;
        // 优化
        index += front;
        return index - (index >= elements.length ? elements.length : 0);
    }

    // 动态扩容
    private void ensuerCapacity(int capacity) {
    
    
        int oldCapacity = elements.length;      // 旧数组的长度
        if (oldCapacity >= capacity) return;

        // 新容量为旧容量的1.5倍
        int newCapacityLength = oldCapacity + (oldCapacity >> 1);  // 等价于 oldCapacity * 1.5
        E[] newElements = (E[]) new Object[newCapacityLength];
        for (int i = 0; i < size; i++){
    
    
            newElements[i] = elements[index(i)];
        }
        elements = newElements;
        // 重置front
        front = 0;
    }

    @Override
    public String toString() {
    
    
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("capacity = ").append(elements.length).append(", front = ").append(front)
                     .append(", size = ").append(size).append(", array:[");
        for (int i = 0; i < elements.length; i++) {
    
    
            if (i != 0) stringBuilder.append(", ");
            stringBuilder.append(elements[i]);
        }
        stringBuilder.append("]");
        return stringBuilder.toString();
    }
}
循环双端队列

可以进行两端添加、删除操作的循环队列

package com.zimo.线性表.队列.循环队列;

import com.zimo.线性表.List;
import com.zimo.线性表.链表.DupLoopLinkList;

/**
 * 线性表 - 循环双端队列
 *
 * @author Liu_zimo
 * @version v0.1 by 2020-10-21 11:55:29
 */
public class CircleDeque<E> {
    
    
    public static void main(String[] args) {
    
    
        CircleDeque<Integer> deque = new CircleDeque<>();
        for (int i = 0; i < 10; i++){
    
    
            deque.enQueueFront(i + 1);
            deque.enQueueRear(i + 100);
        }
        System.out.println(deque);
        for (int i = 0; i < 3; i++){
    
    
            deque.deQueueFront();
            deque.deQueueRear( );
        }
        deque.enQueueFront(11);
        deque.enQueueFront(12);
        System.out.println(deque);
        while (!deque.isEmpty()){
    
    
            System.out.println(deque.deQueueFront());
        }
    }
    private int size;       // 元素大小
    private E[] elements;   // 元素
    private int front;      // 对头游标
    private static final int DEFAULT_CAPACITY = 10;

    public CircleDeque() {
    
    
        elements = (E[]) new Object[DEFAULT_CAPACITY];
    }

    public int size(){
    
    
        return size;
    }

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

    /**
     * 头/尾入队
     * @param element
     */
    public void enQueueFront(E element){
    
    
        ensuerCapacity(size + 1);
        front = index(-1);
        elements[front] = element;
        size++;
    }
    public void enQueueRear(E element){
    
    
        ensuerCapacity(size + 1);
        int index = index(size);
        elements[index] = element;
        size ++;
    }

    /**
     * 头/尾出队
     * @return
     */
    public E deQueueFront(){
    
    
        E frontElement = elements[front];
        elements[front] = null;
        front = index(1);
        size--;
        return frontElement;
    }
    public E deQueueRear(){
    
    
        int rearIndex = index(size - 1);
        E rear = elements[rearIndex];
        elements[rearIndex] = null;
        size--;
        return rear;
    }

    /**
     * 获取头/尾元素
     * @return
     */
    public E front(){
    
     return elements[front]; }
    public E rear(){
    
     return elements[index(size - 1)]; }

    /**
     * 清空队列
     */

    public void clear(){
    
    
        for (int i = 0; i < size; i++) {
    
    
            elements[index(i)] = null;
        }
        size = 0;
        front = 0;
    }

    // 环形脚标映射
    private int index(int index){
    
    
        index += front;
        if (index < 0){
    
    
            return index + elements.length;
        }
        // 优化前 return index % elements.length;
        // 优化
        return index - (index >= elements.length ? elements.length : 0);
    }

    // 动态扩容
    private void ensuerCapacity(int capacity) {
    
    
        int oldCapacity = elements.length;      // 旧数组的长度
        if (oldCapacity >= capacity) return;

        // 新容量为旧容量的1.5倍
        int newCapacityLength = oldCapacity + (oldCapacity >> 1);  // 等价于 oldCapacity * 1.5
        E[] newElements = (E[]) new Object[newCapacityLength];
        for (int i = 0; i < size; i++){
    
    
            newElements[i] = elements[index(i)];
        }
        elements = newElements;
        // 重置front
        front = 0;
    }

    @Override
    public String toString() {
    
    
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("capacity = ").append(elements.length).append(", front = ").append(front)
                .append(", size = ").append(size).append(", array:[");
        for (int i = 0; i < elements.length; i++) {
    
    
            if (i != 0) stringBuilder.append(", ");
            stringBuilder.append(elements[i]);
        }
        stringBuilder.append("]");
        return stringBuilder.toString();
    }
}
  • 已知 n >= 0, m > 0 n % m 等价于 n - (m > n ? 0 : m)的前提条件:n < 2m

猜你喜欢

转载自blog.csdn.net/qq_38205875/article/details/109204748
今日推荐