Loading

数据结构(一)线性存储结构--数组(顺序表)、链表、栈、队列

线性存储结构:数组+链表+字符串+队列和栈

作者:写Bug的拉哥 https://www.bilibili.com/read/cv8480862?spm_id_from=333.999.0.0 出处:bilibili

img

1.数组

1. 如何在java中如何定义数组

//定义静态数组;指定数字的元素内容,但是不指定数组的长度
int[] a =new int[]{1,2,3,4,5};
//定义动态数组:指定数组的长度,不指定数组的内容
int[] a =new int[5];
//定义数组的其他语法 不推荐
int arrays[] = new int[]{1,2,3,4,5}
//在java中定义数组的时候,数组的长度和内容只能够指定一个
//在使用动态方式创建数组的时候,虚拟机在为数组开辟空间之后,这个数组中并不是真空的。而是使用元素的默认值进行占位:
	//整形数组:byte[],short[],int[],long[]  ==> 0
	//浮点型数组:double[],float[]   ==>0.0
	//字符型数组:char[]  空格  0(utf-8) 
	//引用(类或对象)数组: String  null
	//布尔数组: boolean false
//不管数组中存储的元素类型是基本数据类型的元素还是引用数据类型的元素,数组类型本身就是一种引用数据类型
//数组变量名当中存储的永远都是一个数组对象

2.数组的长度

a.length 返回数组的元素数量 长度 
  //index 0
  //数组下标的最大值 a.length-1 
//2.数组的内存特性
  //数组的内存特性:定长且连续
  //定长指的是java中,一个数组对象在内存中一旦被创建,其长度将不能修改;如果想要修改一个数组的长度,那么只能重新new一个数组
  //连续指的是在Java中,存在于同一个数组中的所有元素,其内存地址之间是连续有规律的
//3.数组的读写效率分析
  //1.定长导致增删慢
  	//java数组中在内存中一旦创建,其长度是不可变的,也就是说如果需要改变一个数组的长度,那么就需要重新创建一个数组
  	//但是在java中,创建对象是十分消耗时间和内存的一种操作,所以如果在涉及到对数组元素的插入和删除时,则必然涉及到数组对象的重新    		 //创建和数组元素的拷贝。

由此可见,在向数组中插入元素,或者删除数组元素的时候,都涉及到新数组的创建和原始元素的拷贝,所以这种操作是很慢的。

3.连续导致遍历快

//处在同一个数组中的元素之间的内存地址是有规律的,也就是说我们可以这些内存地址是连续存在的
//只要是连续存在的内存地址,那么我们就可以直接通过某种方式计算得到某一位元素的内存地址,进而访问这个数组元素
//也就是说,在通过下标访问数组中的元素,我们并不需要从数组的第一个元素开始,一个一个的向后查询,我们只要根据这些规律计算得到目标元素的内存地址
//当前元素的内存地址=元素的大小+内存地址
int array = new int[]{1,2,3,4,5}
数组类型变量存储的是这个数组的首元素地址
array -> 0x0010
如何快速的查询下标为2的内存地址?
已知条件:数组的首个地址是0x0010 单个元素的大小是3
	· 暴力解法 逐个遍历
  · jvm 计算内存地址
  
总结:Java当中访问任意数组当中下标为n的元素的内存地址的计算公式:
  快速随机访问公式
  数组中下标为n的元素的内存地址=数组的下标为0的首地址+(目标元素的下标n*单个元素的大小)

4.数组的典型算法题

1.数组逆序

题目说明:

        将一个数组中的所有元素以倒置的顺序重新存放在这个数组中。

题目案例:

        给定数组:[1,4,2,7,5,9,6]

        倒序存储:[6,9,5,7,2,4,1]

思路解析:

        使用两个变量i和j,分别指向数组的起点和数组的终点,i变量向后走,j变量向前走;在遍历数组过程中将array[i]和array[j]中的元素值使用一个临时空间进行互换,循环条件是i < j。 
package com.uin.List;

import java.util.Arrays;

/**
 * @author wanglufei
 * 数组的逆序
 * @description: TODO
 * @date 2021/11/26/8:48 上午
 */
public class ArrayMerge {
    public void arrayReverse(int[] a) {
        int tmp = 0;
        for (int i = 0, j = a.length - 1; i < j; i++, j--) {
            tmp = a[i];
            a[i] = a[j];
            a[j] = tmp;
        }
    }

    public static void main(String[] args) {
        int[] a = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
        System.out.println(Arrays.toString(a));
        ArrayMerge merge = new ArrayMerge();
        merge.arrayReverse(a);
        System.out.println(Arrays.toString(a));
    }
}

2.有序数组合并

题目说明:

        给定两个升序有序的数组,将这两个数组合并为一个升序有序的数组

题目案例:

        给定两个升序有序的数组:

        arr1 = [1,2,4,4,6,8,9]

        arr2 = [0,1,3,6,7]

        合并后结果为:[0,1,1,2,3,4,4,6,6,7,8,9]

思路解析:

        使用两个变量i和j分别遍历数组arr1和数组arr2,遍历过程中比较arr1[i]和arr2[j]之间的大小关系,并且将较小的一个元素落在结果数组中;哪一个数组中的元素落在结果数组中,哪一个数组的下标向前进1。直到有一个数组先遍历完成,将另一个数组中剩余的元素全部落在结果数组即可。 
package com.uin.sort;

import java.util.Arrays;

/**
 * @author wanglufei
 * 排序并合并 有序数组的合并
 * @description: TODO
 * @date 2021/11/24/10:00 下午
 */
public class SortedArrayMerge {
    public static void main(String[] args) {
        int[] a = new int[]{1, 3, 4, 5, 7};
        int[] b = new int[]{0, 1, 5, 7, 7, 8, 9};
        SortedArrayMerge sam = new SortedArrayMerge();
        int[] result = sam.sortedArrayMerge(a, b);
        System.out.println(Arrays.toString(result));
    }

    public int[] sortedArrayMerge(int[] a, int[] b) {

        //创建一个结果数组,结果数组的长度为两个有序数组的长度之和
        int m = a.length;
        int n = b.length;
        int[] meragee = new int[m + n];
        //创建指针i,j。分别用来遍历A和B
        int i = 0;
        int j = 0;

        //创建指针k,用来控制结果数组的元素的下标
        int k = 0;
        //创建循环变量实现有序数组的合并的操作,在合并的过程中,比较A[i]和B[j]的大小,谁更小,谁就落在结数组
        //中,并且,哪一个数组的元素的落在结果数组中之后,控制这个数组的指针就加+1
        while (i < m && j < n) {
            if (a[i] <= b[j]) {
                meragee[k] = a[i];
                i++;
            } else {
                meragee[k] = b[j];
                j++;
            }
            k++;
        }
        //判断哪一个数组当中还有剩余的元素,就将这些剩余的元素直接 copy到结果数组当中
        if (i < m) {
            while (i < m) {
                meragee[k] = a[i];
                i++;
                k++;
            }
        }
        if (j < n) {
            while (j < n) {
                meragee[k] = b[j];
                j++;
                k++;
            }
        }
        return meragee;
    }
}

2.链表

img

//1.链表的节点一般分为两个部分:data数据域,用来存储要保存的数据,例如一个字符串、一个User对象等等;
//next指针域,用来保存下一个节点的内存地址,串起整个链表
//2.在链表中,链表的第一个节点通常不存储任何数据,他仅用用来引起整个链表,我们将这这个特殊的节点称之为数组的头节点。
//3.在整个链表当中,我们只要知道链表头节点的内存地址,就可以顺着之后的每一个节点的next后继指针域向下,逐个找到后续的所有节点
//4.链表的最后一个节点的后继指针域取值为null,这一特性在遍历整个链表的时候,常用来判断是否还有后继节点

很像火车
火车头 不做乘客
乘客都是从火车头的下一个车厢,开始乘坐
next指针域就好像连接车厢的🪝
如果一节车厢的🪝后面没有挂载其他车厢,说明这个车厢已经是最后一节了
  
//在java中
public class Node {
    Object data;//数据域
    Node next;//next指针 后继指针域,用来保存当前节点下一个节点的内存地址
}

1.链表的基本概念

img

//在java中引用 保存的是内存地址
//next后继指针域 也是内存地址

2.链表的基本操作

package com.uin.linked;

/**
 * @author wanglufei
 * @description: TODO
 * @date 2021/11/27/9:04 上午
 */
public class MyLinked {
    //内部类
    public static class Node {
        Object data;
        Node next;
    }

    //头节点 不存储任何数据
    private Node head = new Node();

    //向链表的最后一个位置追加元素
    public void append(Object data) {
        //1创建一个变量,用来遍历当前链表,直到找到当前链表当中的最后一个节点,这个临时变量开始的时候指向链表的头节点
        Node cur = head;
        //2通过循环的方式找到当前链表当中最后一个节点,循环条件就是临时变量指向节点的next指针域不是null
        while (cur.next != null) {
            cur = cur.next;
        }
        //3将追加的元素封装在一个Node类型的节点当中
        Node node = new Node();
        node.data = data;

        //4通过步骤2当中的临时变量指向的最后的一个节点,将新建节点追加到链表的最后
        cur.next = node;

    }

    //向链表的指定下标位置插入元素
    public void insert(Object data, int index) {

        //创建一个计步器,初始为0。用来记录cur这个临时变量的跳转次数,作为查找节点插入位置循环的参考
        int step = 0;
        //创建cur临时变量,用来遍历链表中的节点,初始取值还是head
        Node cur = head;
        //创建循环,用来找到新节点的插入位置,循环条件是step<index
        while (step < index) {
            cur = cur.next;
            step++;
        }
        //循环结束的时候,我们这个cur就指向待插入的位置的前一个节点上
        //创建新节点,用来保存插入的数据,新节点的名称定义为node
        Node node = new Node();
        node.data = data;
        //执行新节点的插入操作

        //现将新节点node的next和cur.next产生关系
        //再将cur和cur.next的挂钩断开

        node.next = cur.next;//新节点的后指针域就指向了链表当中插入位置之后原来的节点
        cur.next = node;//插入位置前的节点与原有的节点脱离关系,并且指向新建节点,完成新节点的插入
    }

    //遍历链表
    public void iterate() {
        Node cur = head;
        while (cur.next != null) {
            cur = cur.next;
            System.out.println(cur.data);
        }
    }

    //指定索引 返回链表中的元素
    public Object get(int index) {
        //1.创建计步器变量,用来记录cur变量在链表中跳转的次数,用来控制循环的条件
        int step = 0;
        //2.创建cur变量,初值指向head头节点,用来遍历链表的每一个节点
        Node cur = head;
        //3.创建循环,参考计步器变量取值和目标节点下标取值之间的关系,控制cur变量的跳转
        while (step - 1 < index) {
            cur = cur.next;
            step++;
        }
        //4.返回cur变量指向的目标下标节点中数据域的取值即可

        return cur.data;
    }

    //指定下标 删除链表中的元素
    public void remove(int index) {
        //1.创建计步器变量,用来记录cur变量在链表中跳转的次数,用来控制循环的条件
        int step = 0;
        //2.创建cur变量,初值指向head头节点,用来遍历链表的每一个节点
        Node cur = head;
        //3.创建循环,参考计步器变量取值和目标节点下标取值之间的关系,控制cur变量的跳转
        while (step < index) {
            cur = cur.next;
            step++;
        }
        cur.next=cur.next.next;
    }

    public static void main(String[] args) {
        MyLinked linked = new MyLinked();
        linked.append("aaa");//0
        linked.append("bbb");//1
        linked.append("ccc");//2
        linked.append("ddd");//3
        linked.append("eee");//4
        linked.append("fff");//5

        linked.remove(2);

//        linked.insert("222", 3);
        linked.iterate();
//        System.out.println(linked.get(2));//ccc
    }
}

3.链表的内存特性

②链表的内存特性:不定长且不连续
链表的内存特性,正好和数组是相反的,一句话概括就是:不定长且不连续
不定长指的是,在内存中,链表的节点数量是动态分配的,-个链表结构中存在多少个节点,取
决于我们向这个链表中添加了多少元素
如果我们想要向这个链表中追加元素或者插入元素,那么我们只要新建一个节点保存这个元素,
并且改变几个引用值,就可以完成操作
并不需要重建整个链表,更不需要拷贝原始链表中的任何元素
不连续指的是,在每次添加新元素到链表中的时候,链表的节点都是重新new出来的
正如大家知道的,每次new出来的对象,即使数据类型是一样的,但是他们之间的内存地址也是互
相没有关系的
也就是说,即使是存储在同-一个链表中的不同节点,他们之间的内存地址也是没有规律,不连续
的
这样一来,如果我们想要遍历链表中所有的节点,或者按照下标找到链表中的特定节点,那么不
得不每一-次都重新从链表的头结点出发
一个一个的遍历节点,查找想要的元素

链表连续导致随机访问慢
  

4. 其他链表:双链表和循环链表

1.前后互找:双链表

如果我们在链表的节点中定义两个指针域,一个指向当前节点的下一个节点,一个指向当前节点的前一个节点,那么我们就可以从前后两个方向来遍历这个链表,由此也就产生了双链表结构。

img

2.首尾衔接:循环链表

如果一个链表的最后一个节点的后继指针域并不是指向null,而是回过头来直接指向第一个存储数据的节点,那么这种结构就形成了环链表结构,也称之为循环链表。循环链表结构在诸如磁盘模拟算法、解决约瑟夫环问题等场景下,有着大量的应用。

img

5.链表的典型题

1.向链表中追加新元素

题目说明:

        向一个单链表结构的最后面添加一个新元素

题目案例:

        原始链表结构:abc -> bcd -> cde -> def

        追加新元素:efg

        得到链表结构:abc -> bcd -> cde -> def -> efg

思路解析:

        在链表的封装类中定义一个Node类型的变量lastNode,用来始终指向最后一个节点。在向链表中追加元素的时候,直接使用lastNode来进行操作更加便捷。

2.向链表插入新元素

题目说明:

        向链表的指定下标位插入新元素

题目案例:

        原始链表结构:abc -> bcd -> cde -> def

        向链表下标为2的位置插入一个新元素efg

        得到链表结构:abc -> bcd -> efg -> cde -> def

思路解析:

        首先遍历链表,找到下标为2的位置。新建一个节点保存数据,并通过修改后继指针域的方式,将新节点加入指定位置。

3.链表的遍历

题目说明:

        从链表中第一个存储数据的节点开始,向后遍历链表中所有的节点

        并将节点数据域中保存的数据取得,进行打印输出

题目案例:

        链表结构:abc -> bcd -> cde -> def

        打印输出:[abc bcd cde def ]

思路解析:

        从链表头结点的下一个节点,也就是链表中真正存储元素的节点开始,向后逐个遍历节点,并取得节点数据域中的数据,进行打印输出。利用链表中最后一个节点的后继指针域的取值为null的特性,控制链表的遍历过程。

3. 数组和链表在Java中的典型应用

1.ArrayList-数组的典型封装

ArrayList 类型内部使用一个Object[] 类型的数组对数据进行存储。从结构上来看,这是一个典型的数组结构的封装,并且为了提升添加元素的效率,ArrayList类型的对象在最初的时候,会给定一个长度为10的Object[]数组来存储元素。

值得一说的是,在jdk1.8 当中,如果使用ArrayList的空构造器创建对象,那么这个数组的默认的长度为0,只有在第一次向ArrayList中添加元素的时候,才会扩展为10。
  
如果这个数组的长度不够了,那么ArrayList会对数组进行扩容,而扩容的方式也并不是简单的添加一个元素空间。
而是将数组的长度扩展为原来的1.5倍,也就是说,ArrayList内部数组长度的默认扩展的方式为:10->15->22->33->49->...
  
扩容机制:
  1.ArrayList() 会使用长度为0的数组
  2.ArrayList(int initialCapacity)会使用指定容量的数组
  3.public ArrayLisy(Collection<? extends E > c)会使用c的大小作为数组的容量
  4.add(Object 0)首次扩容为10,在次扩容为上次的容量的1.5倍
  5.addAll(Collection c)没有元素时,扩容为Math.max(10,实际元素的个数),有元素时为Math.max(原容量1.5倍,实际元素个数)
  6.trimToSize() 防止扩容太多 浪费空间 缩容 elementData=(size==0)?			  EMPTY_ELEMENTDATA:Arrays.copyOf(elementData,size);
  
面试题:
  给你一个空的ArrayList对象,经过多少次扩容之后,能够容纳50个元素?
  elementData.length >= 50
  0->10->15->22->33->49
  解释:15->22
  15*1.5 = 22.5 就扩容到22

2.Iterator的fail-fast fail-safe机制

ArrayList 是fail-fast 的典型代表,遍历的同时不能修改,尽快失败 Vector并且也是线程安全的
CopyOnWriteArrayList是fail-safe的典型代表,遍历的同时可以修改,原因是读写分离
  
迭代器遍历的同时 用来防止别人的修改
  fail-fast 一旦发现遍历的同时其他人来修改,则立刻抛出异常 ConcurrentModificationException 并发修改异常 ArrayList
  	增强for循环 底层也是Iterator
  	循环开始时和循环中间的修改次数是否一致,来断定是否抛出异常
  fail-safe 发现遍历的同时其他人来修改,应当能有应对策略,例如牺牲一致性来让整个遍历完成。CopyOnWriteArrayList
		CowIterator 来遍历
  	中途有人添加元素 并没有真正的添加进去
  	添加是一个数组 遍历时另一个数组 

3.LinkedList--链表的典型封装

LinkedList内部使用一个双链表来保存数据
之所以使用双链表,原因正如前面提到的,可以从链表头和链表尾两个方向来遍历链表
这样一来,不管是查找链表中的元素,还是向链表的指定位置新添加元素,都能够提升一定的效率,从而节省时间。
  
高频出现的面试的问题:
  1.LinkedList内部是通过链表实现。使用链表的类型的是什么?===>双链表
  2.Java中单元素的接口有哪些?
  	Collection:
			List:有序可重复
      Set:无序不重复
      Queue:队列接口
        Deque:
          LinkedList 

4.小案例:ArrayList和LinkedList对元素增删遍历的效率比较

package com.uin.List;

import java.util.ArrayList;
import java.util.LinkedList;

/**
 * @author wanglufei
 * @description: TODO
 * @date 2021/12/1/6:50 下午
 */
public class TestSpeed {
    public static void main(String[] args) {
        long star = 0;
        long end = 0;
        ArrayList<Integer> l1 = new ArrayList<>();
        LinkedList<Integer> l2 = new LinkedList<>();

        System.out.println("-----添加效率测试----");

        System.out.println("向ArrayList插入100000个元素");
        star = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            l1.add(0, i);
        }
        end = System.currentTimeMillis();
        System.out.println("添加完成,用时" + (end - star) + "毫秒");


        System.out.println("向LinkedList插入100000个元素");
        star = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            l2.add(0, i);
        }
        end = System.currentTimeMillis();
        System.out.println("添加完成,用时" + (end - star) + "毫秒");

        System.out.println("-----遍历效率测试----");

        System.out.println("对ArrayList执行100000次元素查找");
        star=System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            l1.get(i);
        }
        end=System.currentTimeMillis();

        System.out.println("对LinkedList执行100000次元素查找");
        star=System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            l2.get(i);
        }
        end=System.currentTimeMillis();
    }
}

//测试结果
-----添加效率测试----
向ArrayList插入100000个元素
ArrayList添加完成,用时496毫秒
向LinkedList插入100000个元素
LinkedList添加完成,用时5毫秒
-----遍历效率测试----
对ArrayList执行100000次元素查找
ArrayList,用时1毫秒
对LinkedList执行100000次元素查找
LinkedList,用时3980毫秒

4. 栈和队列

img

img

数组栈和链表栈
数组队列和链表队列
  
       boolean             isEmpty() // 判断当前栈是否为空
synchronized E             peek() //获得当前栈顶元素
synchronized E             pop() //获得当前栈顶元素并删除
             E             push(E object) //将元素加入栈顶
synchronized int           search(Object o)  //查找元素在栈中的位置,由栈低向栈顶方向数

1.栈结构

栈结构的特点:元素先进先出
栈的结构特点是:元素先进先出
也就是说,在我们向栈结构中添加元素后,如果将元素从栈结构中取出,那么元素出来的顺序和元素的放入栈的结构的顺序正好是相反的
我们将元素放入栈结构中的操作称之为元素入栈
将元素从栈结构中取出的操作称之为元素出栈
将最先进入栈结构的元素称之为栈底元素,这个方向同时称之为栈底
将最后进入栈结构的元素称之为栈顶元素,这个方法同时称之为栈顶
    
在Java中的实现
    
Stack<String> stack = new Stack<>();
push(); 将制定元素e加入栈结构中
pop();  返回栈顶元素,并且将这个元素从栈结构中删除;如果再次调用这个方法,返回的将是下一个栈顶
peek(); 看栈顶 返回栈顶元素,但是这个元素并不会栈结构中删除;如果再次调用这个方法,那么两次返回的将栈顶元素。

2.栈结构的例题

//栈结构的数组逆序

package com.uin.stack;

import java.util.Arrays;
import java.util.Stack;

/**
 * @author wanglufei
 * 通过栈结构实现数组逆序
 * @description: TODO
 * @date 2021/12/1/10:10 下午
 */
public class ArrayReverseByStack {
    public void reverse(int[] array) {
        //1.创建一个栈结构
        Stack<Integer> stack = new Stack<>();
        //2。将数组中所有的元素入栈
        for (int i = 0; i < array.length; i++) {
            stack.push(array[i]);
        }
        //3.将栈结构中的所有的元素出栈,返回到数组中
        for (int i = 0; i < array.length; i++) {
            array[i] = stack.pop();
        }
    }

    public static void main(String[] args) {
        int[] array = new int[]{1, 3, 5, 7, 9};
        ArrayReverseByStack rsb = new ArrayReverseByStack();
        rsb.reverse(array);
        System.out.println(Arrays.toString(array));
    }
}
//栈结构 十进制转二进制数
//整除法 到排余数
package com.uin.stack;

import java.util.Stack;

/**
 * @author wanglufei
 * 十进制转二进制数
 * @description: TODO
 * @date 2021/12/2/8:44 上午
 */
public class DecimalToBinary {

    public String toBinary(int num) {
        //1.创建一个栈结构用来存储整除法过程当中产生的余数
        Stack<Integer> stack = new Stack<>();
        //2.通过循环执行整除法,将余数一个一个的入栈
        int tmp = 0;
        while (num > 0) {
            tmp = num % 2;
            stack.push(tmp);
            num = num / 2;
        }
        //出栈
        String result = "0b";
        while (!stack.isEmpty()) {
            result = result + stack.pop();
        }
        return result;
    }

    public static void main(String[] args) {
        DecimalToBinary binary = new DecimalToBinary();
        int num = 12;
        String s = binary.toBinary(num);
        System.out.println(s);
    }
}

3.队列

队列结构的定义:元素先进先出
队列的结构的特征就是和现实生活的中的排队买东西一样,先来排队,一且都是先来后到,元素按照进入队列的方式顺序出队列
我们将元素键入队列的操作,称之为元素入队列
将元素从队列中取出的操作,称之为元素出队列
将元素出队列的一端称之为队头(front)
将元素入队列的一端称之为队尾(rear)

  
队列就好比一根管子,然后我们挡住其中的一端,并且从另一端向管子中放入玻璃球,而管子的粗细正好能够容纳一个玻璃球当我们放开被挡住的一端的时候,最先放入管子的玻璃球将会最先滚动出来,最后放入管子中的玻璃球,将会最后滚动出来。

4.队列在java中的实现

Java中的队列结构:LinkedList
  在Java当中同样也存在队列的结构的实现类,这个实现类是我们比较熟悉的LinkeList类型
  实际上,Collection接口下,不仅有List和Set两大子接口,实际还存在着名为Queue的接口
  这个接口从名字上来看,本身就是队列的含义,并且这个接口还有一个子接口名为Deque(双端队列)
  而LinkedList 类,一方面实现了List接口,一方面也实现了Deque接口,所以我们可以认为LinkedList本身就是一个通过链表实现的双端队列结构。
  说明:双端队列是一种两端都可以同时元素入队列、出队列的结构,也就是说,两端同为队头和队尾
  同样的,在LinkedList类型中,也提供了一些方法,用来支持队列的操作:
img
package com.uin.queue;

import java.util.LinkedList;

/**
 * @author wanglufei
 * @description: TODO
 * @date 2021/12/2/10:05 上午
 */
public class Test {
    public static void main(String[] args) {
        LinkedList<String> queue = new LinkedList<>();

        //入队列
        queue.offer("aaa");
        queue.offer("bbb");
        queue.offer("ccc");
        queue.offer("ddd");
        queue.offer("eee");

        //看队列头
        System.out.println(queue.peek());
        System.out.println(queue.size());

        System.out.println("============");
        //出队列
        queue.poll();
        System.out.println(queue.peek());
        System.out.println(queue.size());
        
        //从队列尾退出元素的操作
        System.out.println(queue.pollLast());
    }
}

//用两个栈实现一个队列
package com.uin.queue;

import java.util.Stack;

/**
 * @author wanglufei
 * 用两个栈实现一个队列
 * @description: TODO
 * @date 2021/12/2/11:33 上午
 */
public class StackQueue<E> {

    private Stack<E> s1 = new Stack<E>();
    private Stack<E> s2 = new Stack<E>();

    public void offer(String e) {
        //直接将进入队列的元素加入s1栈结构当中即可
        s1.push((E) e);
    }

    public E poll() {
        //创建一个用来保存返回值的变量
        E result = null;
        //1.将s1当中除了栈底的元素之外,其他的元素出栈,并且直接入栈
        while (s1.size() > 1) {
            s2.push(s1.pop());
        }
        //2.将s1当中暴露出来的栈底元素进行出栈,即为方法的返回值
        if (!s1.isEmpty()) {
            result = s1.pop();
        }
        //3.将s2中所有的元素到回到s1中,保证以后加入队列当中的元素的顺序
        if (!s2.isEmpty()) {
            while (s2.size() > 0) {
                s1.push(s2.pop());
            }
        }
        return result;
    }


    public static void main(String[] args) {
        StackQueue<Character> sq = new StackQueue<>();
        sq.offer("A");
        sq.offer("B");
        sq.offer("c");
        sq.offer("d");
        sq.offer("e");
        sq.offer("f");

        System.out.println(sq.poll());
        System.out.println(sq.poll());
        System.out.println(sq.poll());

        sq.offer("f");
        sq.offer("g");
        sq.offer("h");

        System.out.println(sq.poll());
        System.out.println(sq.poll());
        System.out.println(sq.poll());
        System.out.println(sq.poll());
        System.out.println(sq.poll());
    }
}

//用两个队列实现栈结构
package com.uin.queue;

import java.util.LinkedList;

/**
 * @author wanglufei
 * 用两个队列实现栈
 * @description: TODO
 * @date 2021/12/2/5:44 下午
 */
public class QueueStack<E> {
    /**
     * 实现思路:
     * 1.将全部元素加入结构的元素加入队列q1
     * 2.当元素e要退出的时候,将q1中除了e元素之外的元素全部出队列,并同时加入队列q2
     * 3.将队列q1中的元素e出队列,此时队列q1为空
     * 4.将队列q2中的全部元素出队列,送回到空队列q1当中
     * 。。。。。。
     */
    private LinkedList<E> q1 = new LinkedList<>();
    private LinkedList<E> q2 = new LinkedList<>();

    /**
     * 元素入栈的方法
     */
    private void push(E e) {
        //直接将元素入队列
        q1.offer(e);
    }

    /**
     * 元素出栈的方法
     */
    private E pop() {
        //创建一个来保存结果的变量
        E result = null;
        //1.将队列除了队列尾的元素的所有元素全部出队列,加入到q2
        while (q1.size() > 1) {
            q2.offer(q1.poll());
        }
        //当循环结束的时候q1当中就剩e元素
        //2.将队列q1当中剩余的队列尾元素就是出栈元素,即方法的返回值
        if (!q1.isEmpty()) {
            result = q1.poll();
        }
        //3.将q2当中所有的元素出队列,加入q1当中,保证后来元素出栈顺序的正确
        if (!q2.isEmpty()) {
            while (q2.size() > 0) {
                q1.offer(q2.poll());
            }
        }
        return result;
    }


    public static void main(String[] args) {
        QueueStack<Object> queueStack = new QueueStack<>();
        queueStack.push("A");
        queueStack.push("B");
        queueStack.push("C");
        queueStack.push("D");
        queueStack.push("E");

        //退出栈结构的3个元素
        System.out.println(queueStack.pop());
        System.out.println(queueStack.pop());
        System.out.println(queueStack.pop());

        //再次追加3个元素
        queueStack.push("F");
        queueStack.push("G");
        queueStack.push("H");

        //全部元素出结构
        System.out.println(queueStack.pop());
        System.out.println(queueStack.pop());
        System.out.println(queueStack.pop());
        System.out.println(queueStack.pop());
        System.out.println(queueStack.pop());
    }
}

5.栈和队列的总结

常用的API
栈
  push() 元素入栈
  pop()  元素出栈
  peek() 看栈顶
队列
  add() 入队列
  offfer() 入队列
  poll() 出队头队列

5. 线性存储结构的总结

ArrayList和LinkedList的比较
  1.ArrayList
  	基于数组,需要连续内存
  	随机访问很快(根据下标访问)
  	尾部插入、删除性能可以,其他部分插入、删除都会移动数据,因此性能降低
  	可以利用cpu缓存,局部性原理
  			
 	2.LinkedList
  	基于双向链表,无需连续内存
  	随机访问很慢(要沿着链表遍历)
  	头尾插入删除性能高
  	占用内存高
  所以在常见的开发中ArrayList比较常用
posted @ 2022-01-08 11:45  BearBrick0  阅读(146)  评论(0编辑  收藏  举报