表、栈和队列

2023-10-26

表、栈和队列

增强的for循环

List<? extends Integer> l = ...
for (float i : l) {
    ...
}

same as:(编译器使用迭代器进行改写)

for (Iterator<Integer> i = l.iterator(); i.hasNext();) {
    float i0 = (Integer)i.next();
}

这段代码对于任何实现了Iterable接口的对象都可以起作用。

对于数组:

T[] a = Expression;
L1: L2: ... Lm:
for (int i = 0; i < a.length; i++) {
    {VariableModifier} TargetType Identifier = a[i];
    Statement
}

REF http://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.14.2

如果对正在被迭代的集合进行结构上的改变(即对该集合使用add、remove或clear方法),那么迭代器就不再合法(看源码可以发现是通过一个modCount的变量进行判断总共修改次数的),但是,如果迭代器调用自己的remove方法,那么这个迭代器就仍然是合法的

关于ArrayList和LinkedList的选择

ArrayList底层是用数组实现的,LinkedList则是用双向列表。ArrayList的查找和修改修很快(get和set),都是常数时间(直接修改数组对应索引的值即可),但是都说LinkedList的插入和删除很快,的确,如果是知道结点的位置,插入和删除确实很快,常数时间就可以。但是,在插入前,肯定要先遍历找到结点,这是一个跟N有关的操作。源码中是node(inde index)方法,它是首先判断index的与中间位置的关系,在前半段就通过头结点来进行查找,在后半段就通过尾结点进行查找,确实比ArrayList移动数据快,但也不是常数时间的操作。具体可以查看when-to-use-linkedlist-over-arraylist

总结一下:

For LinkedList:

  1. get(int index) is O(n/4) average
  2. add(E element) is O(1)
  3. add(int index, E element) is O(n/4) average, but O(1) when index = 0 <— main benefit of LinkedList
  4. remove(int index) is O(n/4) average
  5. Iterator.remove() is O(1) <— main benefit of LinkedList
  6. ListIterator.add(E element) is O(1) <— main benefit of LinkedList

Note: O(n/4) is average, O(1) best case (e.g. index = 0), O(n/2) worst case (middle of list)

For ArrayList

  1. get(int index) is O(1) <— main benefit of ArrayList
  2. add(E element) is O(1) amortized, but O(n) worst-case since the array must be resized and copied
  3. add(int index, E element) is O(n/2) average
  4. remove(int index) is O(n/2) average
  5. Iterator.remove() is O(n/2) average
  6. ListIterator.add(E element) is O(n/2) average

Note: O(n/2) is average, O(1) best case (end of list), O(n) worst case (start of list)

关于ArrayList和LinkedList,修改list立马抛出异常

AbstactList中有一个int值modCount,对它的注释是:

* The number of times this list has been <i>structurally modified</i>.
* Structural modifications are those that change the size of the
* list, or otherwise perturb it in such a fashion that iterations in
* progress may yield incorrect results.

可以看到这个值的作用就是记录那些对list的尺寸有了改动或者对list内部进行了改动导致iterations不能正确执行的修改次数,而在ArrayList和LinkedList的clear()、add()、remove()、sort()等对这个值都进行了modCount++,在Iterator中的next()方法中,会首先执行checkForComodification()方法:

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

所以只要修改了list,立马就会抛出异常。

ArrayList

transient Object[] elementData; // non-private to simplify nested class access

private int size;

一个是放数据的数组,一个是代表当前数据多少的整型。

再贴几个常用的方法的具体实现:

    //因此是常数级(添加在末端)
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        /* 
        * Copies an array from the specified source array, beginning at the
        * specified position, to the specified position of the destination array.
        * @param      src      the source array.
        * @param      srcPos   starting position in the source array.
        * @param      dest     the destination array.
        * @param      destPos  starting position in the destination data.
        * @param      length   the number of array elements to be copied.
        public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
        */
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);//一个native方法
        elementData[index] = element;
        size++;
    }

    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

    //可以看到是remove掉第一个符合条件的数据
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

LinkedList

    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

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

用到的数据结构,可以看到其实是一个双向链表。

transient int size = 0;
transient Node<E> first;
transient Node<E> last;

分别代表头指针和尾指针。

贴一下常用方法的实现:

    /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

    /**
     * Inserts element e before non-null Node succ.
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }
    /**
     * Returns the (non-null) Node at the specified element index.
     */
    Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
    /**
     * Unlinks non-null node x.
     */
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

    public boolean add(E e) {
        linkLast(e);
        return true;
    }

    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

LinkedList还提供了快速获取头尾结点,或者是remove头尾结点的方法。removeFirst、removeLast、getFirst、getLast、addFirst、addLast。。。等等,源码不难,理解了这几点,都可以看懂。

所谓栈,就是一种LIFO(last-in-first-out)表,在Java中是通过继承Vector实现的,主要添加了几个操作让它成为一个栈:

    /**
     * Pushes an item onto the top of this stack. This has exactly
     * the same effect as:
     * <blockquote><pre>
     * addElement(item)</pre></blockquote>
     *
     * @param   item   the item to be pushed onto this stack.
     * @return  the <code>item</code> argument.
     * @see     java.util.Vector#addElement
     */
    public E push(E item) {
        addElement(item);

        return item;
    }

    /**
     * Removes the object at the top of this stack and returns that
     * object as the value of this function.
     *
     * @return  The object at the top of this stack (the last item
     *          of the <tt>Vector</tt> object).
     * @throws  EmptyStackException  if this stack is empty.
     */
    public synchronized E pop() {
        E       obj;
        int     len = size();

        obj = peek();
        removeElementAt(len - 1);

        return obj;
    }

    /**
     * Looks at the object at the top of this stack without removing it
     * from the stack.
     *
     * @return  the object at the top of this stack (the last item
     *          of the <tt>Vector</tt> object).
     * @throws  EmptyStackException  if this stack is empty.
     */
    public synchronized E peek() {
        int     len = size();

        if (len == 0)
            throw new EmptyStackException();
        return elementAt(len - 1);
    }

可以看到所有方法都是同步的(addElement()是同步的),方法的实现也都是用的Vector里的方法(好像Vector是是JDK1.0的遗留产物了,是不是不推荐用了?而且继承Vector,那不是很多跟栈不相关的方法也都可以访问到?)。

栈的应用

平衡符号

就是判断左右括号是否可以闭合:

做一个空栈。读入字符直到文件结尾。如果字符是一个开放符号(左边),则将其推入栈中。如果字符是一个封闭符号,则当栈空是报错。否则,将栈元素弹出。如果弹出的符号不是对应的开放符号,则报错。在文件结尾,如果栈非空则报错。

后缀表达式

即逆波兰表示法,所有操作符置于操作数的后面,因此也被称为后缀表示法。逆波兰记法不需要括号来标识操作符的优先级。

解算方法:

当见到一个数时就把它推入栈中,在遇到一个运算符时该运算符就作用于从该栈弹出的两个数上,再将所得到的结果推入栈中。

中缀到后缀的转换

所谓中缀,就是我们正常时候的表达式,转换方式也可以借助栈来实现:

从左到右遍历中缀表达式的每个操作数和操作符。当读到操作数时,立即把它输出,即成为后缀表达式的一部分;若读到操作符,判断该符号与栈顶符号的优先级,若该符号优先级高于栈顶元素,则将该操作符入栈,否则把栈中运算符弹出并加到后缀表达式尾端,直到遇到优先级低于该操作符的栈元素,然后把该操作符压入栈中。如果遇到”(”,直接压入栈中,除非正在处理“)”否则“(”不会从栈中弹出,如果遇到一个”)”,那么就将直到“(”的所有栈元素弹出并加到后缀表达式尾端,但左右括号并不输出。最后,如果读到中缀表达式的尾端,将栈元素依次完全弹出并加到后缀表达式尾端。

例如a+b*c+(d*e+f)*g 可以转成 abc*+de*f+g*+1 + (( 2 + 3)* 4 ) – 5可以转成123+4*+5-

方法调用

递归就是不停的将方法压到方法栈中,到达结束条件再一个个弹出来。

队列

所谓队列就是FIFO(first-in-first-out)的表,Java中的LinkedList实现了Queue,可以用Queue接口来窄化LinkedList的方法,Queue queue = new LinkedList(),这样就只能访问Queue中的方法。主要有以下几个方法:

public interface Queue<E> extends Collection<E> {
    /**
     * Inserts the specified element into this queue if it is possible to do so
     * immediately without violating capacity restrictions, returning
     * {@code true} upon success and throwing an {@code IllegalStateException}
     * if no space is currently available.
     *
     * @param e the element to add
     * @return {@code true} (as specified by {@link Collection#add})
     * @throws IllegalStateException if the element cannot be added at this
     *         time due to capacity restrictions
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this queue
     * @throws NullPointerException if the specified element is null and
     *         this queue does not permit null elements
     * @throws IllegalArgumentException if some property of this element
     *         prevents it from being added to this queue
     */
    boolean add(E e);

    /**
     * Inserts the specified element into this queue if it is possible to do
     * so immediately without violating capacity restrictions.
     * When using a capacity-restricted queue, this method is generally
     * preferable to {@link #add}, which can fail to insert an element only
     * by throwing an exception.
     *
     * @param e the element to add
     * @return {@code true} if the element was added to this queue, else
     *         {@code false}
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this queue
     * @throws NullPointerException if the specified element is null and
     *         this queue does not permit null elements
     * @throws IllegalArgumentException if some property of this element
     *         prevents it from being added to this queue
     */
    boolean offer(E e);

    /**
     * Retrieves and removes the head of this queue.  This method differs
     * from {@link #poll poll} only in that it throws an exception if this
     * queue is empty.
     *
     * @return the head of this queue
     * @throws NoSuchElementException if this queue is empty
     */
    E remove();

    /**
     * Retrieves and removes the head of this queue,
     * or returns {@code null} if this queue is empty.
     *
     * @return the head of this queue, or {@code null} if this queue is empty
     */
    E poll();

    /**
     * Retrieves, but does not remove, the head of this queue.  This method
     * differs from {@link #peek peek} only in that it throws an exception
     * if this queue is empty.
     *
     * @return the head of this queue
     * @throws NoSuchElementException if this queue is empty
     */
    E element();

    /**
     * Retrieves, but does not remove, the head of this queue,
     * or returns {@code null} if this queue is empty.
     *
     * @return the head of this queue, or {@code null} if this queue is empty
     */
    E peek();
}

队列的应用

操作系统的各种数据缓冲区的先进先出管理,应用系统中的各种事件排队管理等等

判断回文

所谓回文就是一个字符序列以中间字符基准两边字符完全相同。解决方法可以将字符逐个分别放入队列和栈中,依次弹出,看是否相等,如果全部相等则是回文。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

表、栈和队列 的相关文章

随机推荐

  • IDEA教程之Activiti插件

    本文作者 Spring ZYL 文章来源 人生就是一个不断学习的过程 码农StayUp CSDN博客 SpringBoot全家桶 Java数据结构与算法分析 设计模式领域博主 版权声明 本文版权归作者所有 转载请注明出处 一 安装Activ
  • 《软件测试》第十四章 网站测试

    软件测试 第十四章 网站测试 14 0 前言 14 1 网页基础 14 2 黑盒测试 14 2 1 文本 14 2 2 超级链接 14 2 3 图片 14 2 4 表单 14 2 5 对象和其他各种简单的功能 14 3 灰盒测试 14 4
  • QQ和MSN 在线代码

    QQ在线聊天代码 a href img src images qq交谈 bmp alt qq交谈 width 68 height 29 border 0 a MSN在线聊天代码 a href target blank img src ima
  • Callable 和 Future

    Callable 和 Future 是 Java 并发编程中用于处理多线程任务的两个关键接口 它们通常与线程池一起使用 以实现异步任务执行和获取结果的功能 Callable Callable 是一个泛型接口 它定义了一个带有返回值的任务 与
  • 多线程爬虫的实现----threading库的使用

    1 作爬虫的时候为了提升抓取的速度 这个时候就需要开启多个线程同时抓取数据 今天就分享一下如何使用Python中的threading库实现多线程抓取数据 from shop import ShopSpider import threadin
  • 微服务框架相关 OpenFeign 源码

    目录 一 基础 二 初始化注册 三 FeignClient 自动配置 四 FeignClient 创建 五 网络请求的发出 六 负载均衡 SpringCloud Loadbalancer 一 基础 使用 OpenFeign 流程 项目中引入
  • 2如何识别操作系统_信创产业成为风口,如何“迁移”值得研究(二)

    在上一讲 信创产业成为风口 如何 迁移 值得研究 中 我们分析了什么是 信创 以及数据迁移在信创过程中的重要意义及其基本要求 本次文章中我们将继续分析 信创实践过程中数据迁移的难点及其解决之道 1难点1 迁移场景复杂 在信创实践过程中 随着
  • html搜索栏热搜效果,CSS3实战开发:百度新闻热搜词特效实战开发_html/css_WEB-ITnose...

    各位网友 今天这篇文章 我将手把手带领大家开发百度新闻首页的 新闻热搜词 特效 在这个特效中应用的知识点都很基础 如果你对这些还不清楚 可以关注我以前写的详细教程 今天讲这个案例 也是希望告诉大家 在开发一个特效的时候 请不要将问题复杂化
  • valn的基础配置

    vlan作业 1 交换机进行vlan配置 lsw1 lsw2 2 进行单臂路由的配置 3 DHCP配置 地址池的配置 端口启动
  • LR-ASPP论文

    论文地址 https arxiv org abs 1905 02244 摘要 我们提出了基于互补搜索技术的组合以及一个新颖的架构设计的下一代移动网络 MobileNetV3通过结合NetAdapt算法补充的硬件网络架构搜索 NAS 调整到移
  • 配置JAVA环境变量

    一 自行安装JDK 位置默认C盘 JDK全称是Java Development Kit 是整个Java的核心 包括了Java运行环境 Java工具和Java基础类库 JDK 是整个Java的核心 包括了Java运行环境 Java工具和Jav
  • 一文读懂 QUIC 协议:更快、更稳、更高效的网络通信

    作者 李龙彦 来源 infoQ 你是否也有这样的困扰 打开 APP 巨耗时 刷剧一直在缓冲 追热搜打不开页面 信号稍微差点就直接加载失败 如果有一个协议能让你的上网速度 在不需要任何修改的情况下就能提升 20 特别是网络差的环境下能够提升
  • 万得Wind量化与东方财富Choice量化接口使用

    接口需要付费 这里接口的付费和配置就不展开了 wind相对容易配置 直接用软件就可以点击并配置 东财请参考 Mac使用Python接入东方财富量化接口Choice 调试与获取数据 但有一点需要注意 wind使用量化接口的时候wind终端需要
  • 王炸功能ChatGPT 联网插件功能放开,视频文章一键变思维导图

    就在上周5月13日 Open AI 发文称 我们将在下周向所有ChatGPT Plus 用户开放联网功能和众多插件 这意味着什么 首先联网功能将使得ChatGPT不再局限于回答2021年9月之前的信息 能直接联网查询最新消息 而插件功能就可
  • BIOS启动过程详解

    BIOS 工作原理 最近几天在看 UNIX 操作系统设计 突然想到计算机是如何启动的呢 那就得从 BIOS 说起 其实这个冬冬早已是 n 多人写过的了 今天就以自己的理解来写写 权当一个学习笔记 一 预备知识 很多人将 BIOS 与 CMO
  • 19.3剪裁

    1 在固定管线中 裁剪是在世界坐标系中 2 在可编程管线中 裁剪是在规格化坐标系中 步骤 1 按照法向量和空间点定义裁剪平面 并归一化 2 根据世界观察投影变换矩阵相乘 求逆转置 即为需要的变换矩阵 3 变换矩阵与裁剪平面变换后就是需要的裁
  • numpy模块(2)

    1 利用布尔值来取元素 import numpy as np mask np array 1 0 1 dtype bool 1表示取对应的元素 0表示不取 arr np array 1 2 3 4 5 6 7 8 9 print arr m
  • Hadoop学习心得---二

    大数据运算解决方案MapReduce Hadoop的分布式计算模型MapReduce 最早是Google提出的 主要用于搜索领域 解决海量数据的计算问题 MapReduce有两个阶段组成 Map和Reduce 用户只需实现map 和redu
  • Three.js(学习)

    在vue项目中使用Three js的流程 1 首先利用npm安装Q three js 具体操作代码如下 npm install three 2 接下来利用npm安装轨道控件插件 npm install three orbit control
  • 表、栈和队列

    表 栈和队列 表 增强的for循环 List