【从蛋壳到满天飞】JAVA 数据结构剖析和算法完成-栈和行列

《【从蛋壳到满天飞】JAVA 数据结构剖析和算法完成-栈和行列》

媒介

【从蛋壳到满天飞】JAVA 数据组织剖析和算法完成,悉数文章也许的内容以下:
Arrays(数组)、Stacks(栈)、Queues(行列)、LinkedList(链表)、Recursion(递归头脑)、BinarySearchTree(二分搜刮树)、Set(鸠合)、Map(映照)、Heap(堆)、PriorityQueue(优先行列)、SegmentTree(线段树)、Trie(字典树)、UnionFind(并查集)、AVLTree(AVL 均衡树)、RedBlackTree(红黑均衡树)、HashTable(哈希表)

源代码有三个:ES6(单个单个的 class 范例的 js 文件) | JS + HTML(一个 js 合营一个 html)| JAVA (一个一个的工程)

悉数源代码已上传 github,点击我吧,光看文章能够控制两成,着手敲代码、动脑思索、绘图才够控制八成。

本文章合适 对数据组织想相识而且感兴趣的人群,文章作风自始自终云云,就以为手机上看起来比较轻易,如许显得比较有条理,整顿这些笔记加源码,时刻跨度也算快要半年时刻了,愿望对想进修数据组织的人或许正在进修数据组织的人群有协助。

栈 Statck

  1. 栈也是一种线性组织
  2. 比拟数组来讲响应的操纵更少,

    1. 栈对应的操纵是数组的子集,
    2. 因为它的实质就是一个数组,
    3. 而且它有比数组更多的限制。
  3. 栈的实质就是一个数组

    1. 它将数据排开来放的,
    2. 增加元素的时刻只能从栈的一端增加元素,
    3. 掏出元素的时刻也只能栈的一端掏出元素,
    4. 这一端叫做栈顶,当如许的限制了数组,
    5. 从而形成了栈这类数据组织以后,
    6. 它能够在盘算机天下中关于
    7. 组建逻辑发作异常异常主要的作用。
  4. 栈的操纵

    1. 从栈顶增加元素,把元素一个一个的放入到栈中,
    2. 如增加值的时刻为 1、2、3,
    3. 你取值的时刻递次则为 3、2、1,
    4. 因为你增加元素是只能从一端放入,
    5. 掏出元素时也只能从一端掏出,
    6. 而这一段就是栈顶,
    7. 栈的出口和进口都是统一个位置,
    8. 所以你只能依据先进后出、后进先出的递次
    9. 增加数据或许掏出数据,不存在插进去和索引。
  5. 栈是一种后进先出的数据组织

    1. 也就是 Last In First Out(LIFO),
    2. 如许的一种数据组织,在盘算机的天下里,
    3. 它拥有着难以想象的作用,
    4. 无论是典范的算法照样算法设想都接触到
    5. 栈这类看似很简朴但实在运用异常普遍的数据组织,

栈的简朴运用

  1. 无处不在的 Undo 操纵(打消)

    1. 编辑器的打消操纵的道理就是靠一个栈来举行保护的,
    2. 如 将 每次输入的内容顺次放入栈中 我 喜好 你,
    3. 假如 你 字写错,你打消一下,变成 我 喜好,
    4. 再打消一下 变成 我。
  2. 顺序挪用的体系栈

    1. 顺序挪用时经常会涌现在一个逻辑中心
    2. 先住手然后跳到别的一个逻辑去实行,
    3. 所谓的子函数的挪用就是这个历程,
    4. 在这个历程当中盘算机就须要运用一个
    5. 称为体系栈的一个数据组织来纪录顺序的挪用历程。
    6. 比方有三个函数 A、B、C,
    7. 当 A 实行到一半的时刻挪用 B,
    8. 当 B 实行到一半的时刻挪用 C,
    9. C 函数能够实行运转完,
    10. C 函数运转完了以后继承运转未完成的 B 函数,
    11. B 函数运转完了就运转未完成 A 函数,
    12. A 函数运转完了就终了了。
       function A () {
          1 ...;
          2 B();
          3 ...;
       }
    
       function B () {
        1 ...;
        2 C();
        3 ...;
       }
    
       function C () {
        1 ...;
        2 ...;
        3 ...;
       }
  3. 体系栈纪录的历程是:

    1. A 函数实行,在第二行中缀了,因为要去实行函数 B 了,
    2. 这时候刻函数信息A2会被放入体系栈中,体系栈中显现:[A2]
    3. 然后 B 函数实行,在第二行也中缀了,因为要去实行函数 C 了,
    4. 这时候刻函数信息 B2 会被放入体系栈中,体系栈中显现:[A2, B2]
    5. 然后 C 函数实行,C 函数没有子函数可实行,那末实行究竟,函数 C 实行终了,
    6. 从体系栈中掏出函数 B 的信息,体系栈中显现:[A2]
    7. 依据从体系栈中掏出的函数 B 的信息,从函数 B 本来中缀的位置继承最先实行,
    8. B 函数实行终了了,这时候刻会再从体系栈中掏出函数 A 的,体系栈中显现:[]
    9. 依据从体系栈中掏出的函数 A 的信息,从函数 A 本来中缀的位置继承最先实行,
    10. A 函数实行完了,体系栈中已没有函数信息了,好的,顺序终了。
    11. 存入体系栈中的是函数实行时的一些信息,
    12. 所以掏出来后,能够依据这些信息来继承完成
    13. 本来函数未实行终了的那部份代码。
  4. 2 和 3 中诠释的道理 就是体系栈最奇异的处所

    1. 在编程的时刻举行子历程挪用的时刻,
    2. 当一个子历程实行完成以后,
    3. 能够自动的回到上层挪用中缀的位置,
    4. 而且继承实行下去。
    5. 都是靠一个体系栈来纪录每一次挪用历程当中
    6. 中缀的谁人挪用的点来完成的。
  5. 栈虽然是一个异常简朴的数据组织

    1. 然则它能够处理盘算机范畴异常庞杂的一个问题,
    2. 这个问题就是这类子历程子逻辑的挪用,
    3. 在编译器内部它运转完成的道理是什么,
    4. 深切明白这个历程,
    5. 以至能够协助你明白一些更庞杂的逻辑历程,
    6. 比方递归如许的一个历程,你会有越发深入的明白。

栈的完成

  1. 栈这类数据组织异常有用

    1. 但现实上是异常简朴的。
  2. MyStack<E>

    1. void push(e):入栈
    2. E pop():出栈
    3. E peek():检察位于栈顶位置的元素
    4. int getSize():猎取栈中现实元素的个数
    5. boolean isEmpty():栈是不是为空
  3. 从用户的角度看

    1. 只需支撑这些操纵就好了,
    2. 用户不论你要怎样 resize,
    3. 他只需晓得你这个数组是一个动态的,
    4. 他能够不断的往里面增加元素,
    5. 而且不会涌现问题就 ok,
    6. 实在关于栈也是如许的,
    7. 关于详细的底层完成,用户不体贴,
    8. 现实底层也有多种完成体式格局,
    9. 所以用户就越发不体贴了。
  4. 为了让代码越发的清楚,

    1. 同时也是为了支撑面向对象的一些特征,
    2. 比方说支撑多态性,
    3. 那末就会如许的去设想,
    4. 定义一个接口叫做 IMyStack,
    5. 接口中有栈默许的一切要领,
    6. 然后再定义一个类叫做 MyStack,
    7. 让它去完成 IMyStack,
    8. 如许就能够在 MyStack 中完成对应的逻辑,
    9. 这个 MyStack 就是自定义的栈。
  5. 会复用到之前自定义数组对象。

栈的庞杂度剖析

  1. MyStack<E>

    1. void push(e):O(1) 均派
    2. E pop():O(1) 均派
    3. E peek():O(1)
    4. int getSize():O(1)
    5. boolean isEmpty():O(1)

代码示例

  1. (interface: IMyStack, class: MyArray, class: MyStack, class: Main)
  2. IMyStack

       public interface IMyStack<E> {
             /**
         */
        void push(E e);

        /**
         * @return 出栈
         */
        E pop();

        /**
         * @return 检察栈顶的元素
         */
        E peek();

        /**
         * @return 猎取栈中现实元素的个数
         */
        int getSize();

        /**
         * @return 推断栈是不是为空
         */
        boolean isEmpty();
  }

3. MyArray
  public class MyArray<E> {
        private E [] data;
        private int size;

        // 组织函数,传入数组的容量capacity组织Array
        public MyArray (int capacity) {
              data = (E[])new Object[capacity];
              size = 0;
        }

        // 无参数的组织函数,默许数组的容量capacity=10
        public MyArray () {
  //        this( capacity: 10);
              this(10);
        }

        // 猎取数组中的元素现实个数
        public int getSize () {
              return size;
        }

        // 猎取数组的总容量
        public int getCapacity () {
              return data.length;
        }

        // 返回数组是不是为空
        public boolean isEmpty () {
              return size == 0;
        }

        // 从新给数组扩容
        private void resize (int newCapacity) {

              E[] newData = (E[])new Object[newCapacity];

              int index = size - 1;
              while (index > -1) {
                    newData[index] = get(index);
                    index --;
              }

              data = newData;
        }

        // 给数组增加一个新元素
        public void add (E e) {

              if (size == data.length) {
  //            throw new IllegalArgumentException("add error. Array is full.");
                    resize(2 * data.length);
              }

              data[size] = e;
              size++;
        }

        // 向一切元素后增加一个新元素 (与 add要领功用一样) push
        public void addLast (E e) {

              // 复用插进去元素的要领
              insert(size, e);
        }

        // 在一切元素前增加一个新元素 unshift
        public void addFirst (E e) {

              insert(0, e);
        }

        // 在index索引的位置插进去一个新元素e
        public void insert (int index, E e) {

              if (index < 0 || index > size) {
                    throw new IllegalArgumentException("insert error. require index < 0 or index > size");
              }

              if (size == data.length) {
  //            throw new IllegalArgumentException("add error. Array is full.");
                    resize(2 * data.length);
              }

              for (int i = size - 1; i >= index; i--) {
                    data[i + 1] = data[i];
              }

              data[index] = e;
              size++;
        }

        // 猎取index索引位置的元素
        public E get (int index) {

              if (index < 0 || index >= size) {
                    throw new IllegalArgumentException("get error. index < 0 or index >= size ");
              }
              return data[index];
        }

        // 猎取数组中第一个元素(纯检察)
        public E getFirst () {
              return get(0);
        }

        // 猎取数组中末了一个元素(纯检察)
        public E getLast () {
              return get(size - 1);
        }

        // 修正index索引位置的元素为e
        public void  set (int index, E e) {

              if (index < 0 || index >= size) {
                    throw new IllegalArgumentException("get error. index < 0 or index >= size ");
              }
              data[index] = e;
        }

        // 查找数组中是不是有元素e
        public boolean contain (E e) {

              for (int i = 0; i < size; i++) {
  //            if (data[i] == e) { // 值比较 用 ==
                    if (data[i].equals(e)) { // 援用比较 用 equals()

                                return true;
                    }
              }
              return false;
        }

        // 查找数组中元素e地点的索引,假如不存在元素e,则返回-1
        public int find (E e) {

              for (int i = 0; i < size; i++) {
                    if (data[i].equals(e)) {
                          return i;
                    }
              }
              return -1;
        }

        // 查找数组中一切元素e地点的索引,末了返回寄存 一切索引值的 自定义数组
        public MyArray findAll (E e) {

              MyArray ma = new MyArray(20);

              for (int i = 0; i < size; i++) {
                    if (data[i].equals(e)) {
                          ma.add(i);
                    }
              }

              return  ma;

  //        int[] result = new int[ma.getSize()];
  //        for (int i = 0; i < ma.getSize(); i++) {
  //            result[i] = ma.get(i);
  //        }
  //
  //        return  result;
        }

        // 从数组中删除第一个元素, 返回删除的元素
        public E removeFirst () {
              return remove(0);
        }

        // 从数组中删除末了一个元素, 返回删除的元素
        public E removeLast () {
              return remove(size - 1);
        }

        // 从数组中删除第一个元素e
        public void removeElement (E e) {
              int index = find(e);
              if (index != -1) {
                    remove(index);
              }
  //        if (contain(e)) {
  //            int index = find(e);
  //            remove(index);
  //        }
        }

        // 从数组中删除一切元素e
        public void removeAllElement (E e) {

              int index = find(e);
              while (index != -1) {
                    remove(index);
                    index = find(e);
              }
  //        while (contain(e)) {
  //            removeElement(e);
  //        }
        }

        // 从数组中删除index位置的元素, 返回删除的元素
        public E remove (int index) {

              if (index < 0 || index >= size) {
                    throw new IllegalArgumentException("get error. index < 0 or index >= size ");
              }

              E temp = data[index];

              for (int i = index; i < size - 1; i++) {
                    data[i] = data[i + 1];
              }

  //        for (int i = index + 1; i < size; i++) {
  //            data[i - 1] = data[i];
  //        }
              size --;
  //        data[size] = 0;
              data[size] = null;

              // 防备庞杂度震动 防备容量为4,size为1时,data.length / 2 为 0
              if(size == data.length / 4 && data.length / 2 != 0) {
                    resize(data.length / 2);
              }

              return temp;
        }

        @Override
        // @Override: 要领名 日期-开发人员
        public String toString () {

              StringBuilder sb = new StringBuilder();
              String arrInfo = "Array: size = %d,capacity = %d\n";
              sb.append(String.format(arrInfo, size, data.length));
              sb.append('[');
              for (int i = 0; i < size - 1; i ++) {
                    sb.append(data[i]);
                    sb.append(',');
              }
              if(!isEmpty()) {
                    sb.append(data[size - 1]);
              }
              sb.append(']');

              return sb.toString();
        }
  }

4. MyStack
  public class MyStack<E> implements IMyStack<E> {
        // 借用自定义个动态数组
        private MyArray<E> ma;

        public MyStack () {
            ma = new MyArray<E>();
        }

        public MyStack (int capacity) {
              ma = new MyArray<E>(capacity);
        }

        /**
         * @param e
         * @return 入栈
         */
        @Override
        public void push(E e) {
              ma.addLast(e);
        }

        /**
         * @return 出栈
         */
        @Override
        public E pop() {
              return ma.removeLast();
        }

        /**
         * @return 检察栈顶的元素
         */
        @Override
        public E peek() {
              return ma.getLast();
        }

        /**
         * @return 猎取栈中现实元素的个数
         */
        @Override
        public int getSize() {
              return ma.getSize();
        }

        /**
         * @return 推断栈是不是为空
         */
        @Override
        public boolean isEmpty() {
              return ma.isEmpty();
        }

        // 返回栈的容量
        public int getCapacity () {
              return ma.getCapacity();
        }

        @Override
        // @Override: 要领名 日期-开发人员
        public String toString () {
              int size = ma.getSize();
  //        int capacity = ma.getCapacity();

              StringBuilder sb = new StringBuilder();
  //        String arrInfo = "Stack: size = %d,capacity = %d\n";
  //        sb.append(String.format(arrInfo, size, capacity));
              sb.append("Stack: [");
              for (int i = 0; i < size - 1; i ++) {
                    sb.append(ma.get(i));
                    sb.append(',');
              }
              if (!ma.isEmpty()) {
                    sb.append(ma.getLast());
              }
              sb.append("] right is stack top !");

              return sb.toString();
        }
  }

5. Main
  public class Main {

        public static void main(String[] args) {

              MyStack ms = new MyStack(10);
              for (int i = 1; i <= 10 ; i++) {
                    ms.push(i);
                    System.out.println(ms);
              }

              System.out.println(ms.peek());

  //        System.out.println(ms.isEmpty());
  //        System.out.println(ms.getSize());
  //        System.out.println(ms.getCapacity());

              while (!ms.isEmpty()) {
                    ms.pop();
                    System.out.println(ms);
              }
        }
  }

## 栈的运用

1. undo 操纵-编辑器
2. 体系挪用栈-操纵体系
3. 括号婚配-编译器

### 以编程的体式格局表现栈的运用

1. 括号婚配-编译器
1. 无论是写表达式,这个表达式中有小括号、中括号、大括号,
2. 自然会涌现括号套括号的状况发作,
3. 在这类状况下就一定会发作一个括号婚配的问题,
4. 假如括号婚配是不胜利的,那末编译器会举行报错。
2. 编译器是怎样搜检括号婚配的问题?
1. 道理是运用了一个栈。
3. 能够经由过程解答 Leetcode 中的一个问题,
1. 同时来看栈在括号婚配这个问题中的运用。
2. Leetcode 是总部在美国硅谷一家
3. 异常有岁首又同时有信誉度的面向 IT 公司
4. 口试如许一个在线的平台,
5. 只须要注册一个 Leetcode 用户后,
6. 就能够看到 Leetcode 上有异常多的问题,
7. 关于每个问题会划定输入和输出以后,
8. 然后就能够编写属于自身的逻辑,
9. 更主要的是能够直接把你编写的这个顺序
10.   提交给这个网站,
11.   这个网站会自动的推断你的逻辑誊写的是不是准确,
12.   英文网址:`leetcode.com`,
13.   2017 中文网址:`leetcode-cn.com`
4. `leetcode.com`与`leetcode-cn.com`的区分
1. `leetcode-cn.com`支撑中文,
2. `leetcode-cn.com`的问题数目没有英文版的多。
3. `leetcode-cn.com`的探究栏目标内容没有英文版的多。
4. `leetcode-cn.com`中的问题没有社区议论功用,但英文版的有。
5. leetcode 中第二十号问题:有用的括号

1. 如:`{ [ ( ) ] }`,
2. 从左往右,先将左边的括号入栈,
3. 然后碰到右侧的括号时就检察栈顶的左边括号举行婚配,
4. 假如能够婚配示意括号有用,不然括号无效,
5. 括号有用那末就将栈顶的左边括号掏出,
6. 然后继承从左往右,左边括号就入栈,右侧括号就婚配,
7. 婚配胜利就让左边括号出栈,婚配失利就是无效括号。
8. 实在栈顶元素反应了在嵌套的层级关联中,
9. 最新的须要婚配的元素。
10.   这个算法异常的简朴,然则也异常的有用。
11.   许多东西中都有如许的逻辑来搜检括号的婚配。
  import java.util.Stack;

  public class Solution {

        public boolean isValid(String s) {
              Stack<Character> stack = new Stack<Character>();
              for (int i = 0; i < s.length(); i++) {

                    char c = s.charAt(i);
                    switch (c) {
                          case '{':
                          case '[':
                          case '(':
                                stack.push(c);
                                break;
                          default: break;
                    }

                    switch (c) {
                          case '}':
                                if(stack.isEmpty() || stack.pop() != '{' ) {
                                      System.out.println("valid error. not parentheses. in");
                                      return false;
                                }
                                break;
                          case ']':
                                if(stack.isEmpty() || stack.pop() != '[') {
                                      System.out.println("valid error. not parentheses. in");
                                      return false;
                                }
                                break;
                          case ')':
                                if(stack.isEmpty() || stack.pop() != '(') {
                                      System.out.println("valid error. not parentheses. in");
                                      return false;
                                }
                                break;
                          default: break;
                    }
              }

              if (stack.isEmpty()) {
                    System.out.println("valid success. parentheses.");
                    return true;
              } else {
                    System.out.println("valid error. not parentheses. out.");
                    return false;
              }
        }

        public static void main(String[] args) {
           Solution s = new Solution();
           s.isValid("{ [ ( ) ] }");
           s.isValid(" [ ( ] ) ");
        }
  }
  // 7ms的
  import java.util.Stack;

  public class Solution {

        public boolean isValid(String s) {
              Stack<Character> stack = new Stack<Character>();
              int cur = 0;
              for (int i = 0; i < s.length(); i++) {

                    char c = s.charAt(i);
                    switch (c) {
                          case '{':
                          case '[':
                          case '(':
                                cur ++;
                                stack.push(c);
                                break;
                          default: break;
                    }

                    switch (c) {
                          case '}':
                                if(cur-- == 0 || stack.pop() != '{' ) {
                                      return false;
                                }
                                break;
                          case ']':
                                if(cur-- == 0 || stack.pop() != '[') {
                                      return false;
                                }
                                break;
                          case ')':
                                if(cur-- == 0 || stack.pop() != '(') {
                                      return false;
                                }
                                break;
                          default: break;
                    }
              }
              return cur == 0;
        }
  }

6. leetcode 是一个异常好的预备口试的一个平台
1. 同时它也是算法比赛的一个入门的处所。
2. 你能够经由过程题库来举行演习,
3. 题库的右侧有关于这些问题的标签,
4. 你能够挑选性的去演习,
5. 而且能够依据难度来举行排序这些问题,
6. 你不一定要悉数答对,
7. 因为这些问题不单单议只要一个标签。
7. 假如你想运用你自身写的类,
1. 那末你能够你自身写的自定义栈作为内部类来举行运用,
2. 比方 把自定义栈的代码放到 Solution 类中,
3. 那样也是能够运用,
4. 还样就趁便测试了你自身数据组织完成的逻辑是不是准确。

### 进修要领议论

1. 不要圆满主义。控制好“度”。
1. 太过于寻求圆满会把自身逼的太紧,
2. 会发作种种焦炙的心态,. 末了以至会疑心自身,
3. 温故而知新,不要住手不前,
4. 控制好这个度,不存在你把那些你以为完整控制了,
5. 然后就成了某一个范畴的专家,
6. 相反一旦你发作很粘稠的厌恶感,
7. 那末就意味着你即将会摒弃或许已挑选了摒弃,
8. 虽然你之前想把它做到 100 分,
9. 然则因为你的摒弃让它变成 0 分。
2. 进修本着自身的目标去。
1. 不要在学的历程当中偏离了自身的目标。
2. 要分清主次。
3. 难的东西,你能够逐步的转头看一看。
1. 那样才会越发的山穷水尽,
2. 更能提拔自身的收成。

## 行列 Queue

1. 行列也是一种线性的数据组织
1. 依旧就是将数据排成一排。
2. 比拟数组,行列对应的操纵是数组的子集。
1. 与栈只能在统一端增加元素和掏出元素有所差别,
2. 在行列中只能从一端(队尾)增加元素,
3. 只能从另一端(队首)掏出元素。
3. 比方你去银行取钱
1. 你须要列队,入队的人不允许插队,
2. 所以他要从队尾最先列队,
3. 而前面取完钱的会从队首脱离,
4. 然后背面的人再往前挪动一名,
5. 末了反复这个历程,
6. 直到没人再列队取钱了。
4. 行列是一种先进先出的数据组织(先到先得)
1. First In First Out(FIFO) 先进先出

### 行列的完成

1. `Queue<E>`
1. `void enqueue(E)`:入队
2. `E dequeue()`:出队
3. `E getFront()`:检察队首的元素
4. `int getSize()`:猎取行列中的现实元素大小
5. `boolean isEmpty()`:猎取行列是不是为空的 bool 值
2. 写一个接口叫做 IMyQueue
1. 让 MyQueue 完成这个接口
2. 如许就相符了面向对象的特征。

### 代码示例

1. `(interface: IMyQueue, class: MyArray, class: MyQueue, class: Main)`
2. IMyQueue
  public interface IMyQueue<E> {
        /**
         * @param e
         *  入队
         */
        void enqueue (E e);

        /**
         * @return e
         *  出队
         */
        E dequeue ();

        /**
         * @return e
         *  检察队首的元素
         */
        E getFront ();

        /**
         * @return number
         *  猎取行列中的现实元素个数
         */
        int getSize ();

        /**
         * @return bool
         *   猎取行列是不是为空的bool值
         */
        boolean isEmpty ();
  }

3. MyArray
  public class MyArray<E> {
        private E [] data;
        private int size;

        // 组织函数,传入数组的容量capacity组织Array
        public MyArray (int capacity) {
              data = (E[])new Object[capacity];
              size = 0;
        }

        // 无参数的组织函数,默许数组的容量capacity=10
        public MyArray () {
  //        this( capacity: 10);
              this(10);
        }

        // 猎取数组中的元素现实个数
        public int getSize () {
              return size;
        }

        // 猎取数组的总容量
        public int getCapacity () {
              return data.length;
        }

        // 返回数组是不是为空
        public boolean isEmpty () {
              return size == 0;
        }

        // 从新给数组扩容
        private void resize (int newCapacity) {

              E[] newData = (E[])new Object[newCapacity];

              int index = size - 1;
              while (index > -1) {
                    newData[index] = get(index);
                    index --;
              }

              data = newData;
        }

        // 给数组增加一个新元素
        public void add (E e) {

              if (size == data.length) {
  //            throw new IllegalArgumentException("add error. Array is full.");
                    resize(2 * data.length);
              }

              data[size] = e;
              size++;
        }

        // 向一切元素后增加一个新元素 (与 add要领功用一样) push
        public void addLast (E e) {

              // 复用插进去元素的要领
              insert(size, e);
        }

        // 在一切元素前增加一个新元素 unshift
        public void addFirst (E e) {

              insert(0, e);
        }

        // 在index索引的位置插进去一个新元素e
        public void insert (int index, E e) {

              if (index < 0 || index > size) {
                    throw new IllegalArgumentException("insert error. require index < 0 or index > size");
              }

              if (size == data.length) {
  //            throw new IllegalArgumentException("add error. Array is full.");
                    resize(2 * data.length);
              }

              for (int i = size - 1; i >= index; i--) {
                    data[i + 1] = data[i];
              }

              data[index] = e;
              size++;
        }

        // 猎取index索引位置的元素
        public E get (int index) {

              if (index < 0 || index >= size) {
                    throw new IllegalArgumentException("get error. index < 0 or index >= size ");
              }
              return data[index];
        }

        // 猎取数组中第一个元素(纯检察)
        public E getFirst () {
              return get(0);
        }

        // 猎取数组中末了一个元素(纯检察)
        public E getLast () {
              return get(size - 1);
        }

        // 修正index索引位置的元素为e
        public void  set (int index, E e) {

              if (index < 0 || index >= size) {
                    throw new IllegalArgumentException("get error. index < 0 or index >= size ");
              }
              data[index] = e;
        }

        // 查找数组中是不是有元素e
        public boolean contain (E e) {

              for (int i = 0; i < size; i++) {
  //            if (data[i] == e) { // 值比较 用 ==
                    if (data[i].equals(e)) { // 援用比较 用 equals()

                                return true;
                    }
              }
              return false;
        }

        // 查找数组中元素e地点的索引,假如不存在元素e,则返回-1
        public int find (E e) {

              for (int i = 0; i < size; i++) {
                    if (data[i].equals(e)) {
                          return i;
                    }
              }
              return -1;
        }

        // 查找数组中一切元素e地点的索引,末了返回寄存 一切索引值的 自定义数组
        public MyArray findAll (E e) {

              MyArray ma = new MyArray(20);

              for (int i = 0; i < size; i++) {
                    if (data[i].equals(e)) {
                          ma.add(i);
                    }
              }

              return  ma;

  //        int[] result = new int[ma.getSize()];
  //        for (int i = 0; i < ma.getSize(); i++) {
  //            result[i] = ma.get(i);
  //        }
  //
  //        return  result;
        }

        // 从数组中删除第一个元素, 返回删除的元素
        public E removeFirst () {
              return remove(0);
        }

        // 从数组中删除末了一个元素, 返回删除的元素
        public E removeLast () {
              return remove(size - 1);
        }

        // 从数组中删除第一个元素e
        public void removeElement (E e) {
              int index = find(e);
              if (index != -1) {
                    remove(index);
              }
  //        if (contain(e)) {
  //            int index = find(e);
  //            remove(index);
  //        }
        }

        // 从数组中删除一切元素e
        public void removeAllElement (E e) {

              int index = find(e);
              while (index != -1) {
                    remove(index);
                    index = find(e);
              }
  //        while (contain(e)) {
  //            removeElement(e);
  //        }
        }

        // 从数组中删除index位置的元素, 返回删除的元素
        public E remove (int index) {

              if (index < 0 || index >= size) {
                    throw new IllegalArgumentException("get error. index < 0 or index >= size ");
              }

              E temp = data[index];

              for (int i = index; i < size - 1; i++) {
                    data[i] = data[i + 1];
              }

  //        for (int i = index + 1; i < size; i++) {
  //            data[i - 1] = data[i];
  //        }
              size --;
  //        data[size] = 0;
              data[size] = null;

              // 防备庞杂度震动 防备容量为4,size为1时,data.length / 2 为 0
              if(size == data.length / 4 && data.length / 2 != 0) {
                    resize(data.length / 2);
              }

              return temp;
        }

        @Override
        // @Override: 要领名 日期-开发人员
        public String toString () {

              StringBuilder sb = new StringBuilder();
              String arrInfo = "Array: size = %d,capacity = %d\n";
              sb.append(String.format(arrInfo, size, data.length));
              sb.append('[');
              for (int i = 0; i < size - 1; i ++) {
                    sb.append(data[i]);
                    sb.append(',');
              }
              sb.append(data[size - 1]);
              sb.append(']');

              return sb.toString();
        }
  }

4. MyQueue
  public class MyQueue<E> implements IMyQueue<E> {
        private MyArray<E> ma;

        public MyQueue () {
              ma = new MyArray<E>();
        }

        public MyQueue (int capacity) {
              ma = new MyArray<E>(capacity);
        }

        /**
         * @param e
         *  入队
         */
        @Override
        public void enqueue (E e) {
              ma.addLast(e);
        }

        /**
         * @return e
         *  出队
         */
        @Override
        public E dequeue () {
              return ma.removeFirst();
        }

        /**
         * @return e
         *  检察队首的元素
         */
        @Override
        public E getFront () {
              return ma.getFirst();
        }

        /**
         * @return number
         *  猎取行列中的现实元素个数
         */
        @Override
        public int getSize () {
              return ma.getSize();
        }

        /**
         * @return bool
         *  猎取行列是不是为空的bool值
         */
        @Override
        public boolean isEmpty () {
              return ma.isEmpty();
        }

        // 猎取行列容量
        public int getCapacity () {
              return ma.getCapacity();
        }

        @Override
        public String toString () {
              int size = ma.getSize ();
              StringBuilder sb = new StringBuilder();
              sb.append("Queue: head [");
              for (int i = 0; i < size - 1; i ++) {
                    sb.append(ma.get(i));
                    sb.append(',');
              }
              if(!isEmpty()) {
                    sb.append(ma.getLast());
              }
              sb.append("] foot. left is queue top!");

              return sb.toString();
        }
  }

5. Main
  public class Main {

        public static void main(String[] args) {

              MyQueue<Integer> mq = new MyQueue<Integer>(10);
              for (int i = 1; i <= 10 ; i++) {
                    mq.enqueue(i);
                    System.out.println(mq);
              }

              System.out.println(mq.getFront());

              while (!mq.isEmpty()) {
                    System.out.println(mq);
                    mq.dequeue();
              }
        }
  }

### 行列的庞杂度剖析

1. `MyQueue<E>`
1. `void enqueue(E)`: `O(1)` 均派
2. `E dequeue()`:`O(n)` 出队的机能斲丧太大了
3. `E getFront()`:`O(1)`
4. `int getSize()`:`O(1)`
5. `boolean isEmpty()`:`O(1)`
2. 出队的机能斲丧太大了
1. 假如有一百万条数据,每次都要操纵一百万次,
2. 那末须要优化它,要让他出队的时刻时刻庞杂度为`O(1)`,
3. 而且还要让他入队的时刻时刻庞杂度依旧是`O(1)`。
4. 能够运用轮回行列的体式格局来处理这个问题。

## 轮回行列

1. 自定义行列的机能是有局限性的
1. 出队操纵时的时刻庞杂度为`O(n)`,
2. 要把他变成`O(1)`。
2. 当掏出行列的第一个元素后,
1. 第一个元素背面一切的元素位置不动,
2. 如许一来时刻庞杂度就为`O(1)`了,
3. 下一次再取元素的时刻从第二个最先,
4. 取完第二个元素以后,
5. 第二个元素背面一切的元素位置也不动,
6. 入队的话直接往队尾增加元素即可。
3. 轮回行列的运用
1. 你能够先用一个数字变量 front 指向队首,
2. 然后再用一个数字变量 tail 指向队尾,
3. front 指向的是行列中的第一个元素,
4. tail 指向的是行列中末了一个元素的后一个位置,
5. 当行列团体为空的时刻,它们才会指向统一个位置,
6. 所以`front == tail`时行列就为空,
7. 假如有一个元素入队了,
8. front 会指向这个元素,
9. 而 tail 会指向这个元素后一个位置(也就是 tail++),
10.   然后再有一个元素入队了,
11.   front 照样指向第一个元素的位置,
12.   而 tail 会指向第二个元素的后一个位置(照样 tail++),
13.   然后再来四个元素入队了,
14.   front 照样指向第一个元素的位置,
15.   而 tail 会指向第六个元素的后一个位置(tail++四次),
16.   以后 要出队两个元素,
17.   front 会指向第三个元素的位置(也就是 front++两次),
18.   front 从指向第一个元素变成指向第三个元素的位置,
19.   因为前两个已出队了,
20.   这时候刻再入队一个元素,
21.   tail 会指向第七个元素的后一个位置(照样 tail++),
22.   这时候行列的容量已满了,能够须要扩容,
23.   然则因为行列中有两个元素已出队了,
24.   那这两个位置空出来了,这时候就须要应用这两个位置的空间了,
25.   这就是轮回行列了,以轮回的体式格局反复应用空间,
26.   自定义行列运用自定义数组完成的,
27.   实在就是把数组算作一个环,数组中一共能够包容 8 个元素,
28.   索引是 0-7,那末 7 以后的索引应当是 0,tail 应当指向 0,
29.   而不是以为全部数组的空间已满了,
30.   应当运用 tail 对数组的容量举行求余盘算,
31.   tail 为 8,容量也为 8,求余以后为 0,所以 tail 应当指向 0,
32.   这时候再入队一个元素,tail 指向这个元素的后一个位置,即 1,
33.   这时候刻假如再入队一个元素,那末此时 tail 和 front 相称,
34.   然则那并不能证实行列为空,反而是行列满了,
35.   所以须要在行列满之前举行推断,`tail+1==front`,
36.   就示意行列已满,当数组中只剩末了一个空间了,
37.   行列就算是满的,因为再入队就会让 tail 与 front 相称,
38.   而谁人前提是行列已空才建立的,虽然关于全部数组空间来讲,
39.   是有认识地糟蹋了一个空间,然则减少了很大的时刻斲丧,
40.   所以当`(tail+1)%c==front`时就能够扩容了,
41.   将`tail+1==front`变成`(tail+1)%c==front`是因为
42.   tail 从数组的末尾跑到前端是有一个求余的历程,
43.   比方 front 指向的是第一个元素,而 tail 指向的第六个元素以后的位置,
44.   那末此时 front 为 0,tail 为 7,容量为 8,另有一个糟蹋掉的空间,
45.   这时候刻`(tail+1)%c==front`,所以行列满了,
46.   这就是轮回行列一切的详细完成必需恪守的划定规矩,
47.   一切的 front 和 tail 向后挪动的历程都如果这类轮回的挪动,
48.   比方钟表,11 点钟的下一个钟头为 12 点钟,也能够管它叫做 0 点,
49.   以后又会变成 1 点、2 点、3 点、4 点顺次类推,
50.   所以全部轮回行列的索引也是像钟表一样形成了一个环,
51.   只不过不一定有 12 刻度,而刻度的数目是由数组的容量(空间总数)决议的,
52.   这就是轮回行列的道理。
4. 运用轮回行列以后,
1. 出队操纵不再是团体往前挪动一名了
2. 而是经由过程转变 front 的指向,
3. 入队操纵则是转变 tail 的指向,
4. 全部操纵轮回往复,
5. 如许一来出队入队的时刻庞杂度都为`O(1)`了。

### 轮回行列的简朴完成剖析

1. 轮回行列 MyLoopQueue
1. 他的完成与 MyQueue 有很大的差别,
2. 所以就不运用 MyArray 自定义动态数组了。
2. 轮回行列要从底层从新最先写起
1. data:一个数组。
2. front: 指向队头有用元素的索引。
3. tail: 指向队尾有用元素的后一个位置的索引。
4. size: 经由过程 front 和 tail 也能够做到轮回。
5. 然则运用 size 能够让逻辑越发的清楚清楚明了。
3. 轮回行列完成终了以后,
1. 你能够不运用 size 来举行轮回行列的保护,
2. 而完完整全的运用 front 和 tail,
3. 如许难度会轻微的难一点,
4. 因为详细逻辑须要迥殊的警惕,
5. 会有一些小圈套。
6. 能够试着增加 resize 数组扩容缩容功用到极致,
7. 能够磨炼逻辑才、顺序编写调试才等等。

## 轮回行列的完成

1. 入队前先推断行列是不是已满了
1. 推断体式格局 `(tail + 1) % data.length == front`
2. 推断剖析 (队尾指向的索引 + 1)余以数组的容量是不是为队首指向的索引,
2. 从用户的角度上来看
1. 行列里就是有这么多元素,
2. 一侧是队首一侧是队尾,
3. 别的的内容包含现实的数组的大小是用户指定的容量大小+1,
4. 这些完成细节,用户是悉数不晓得的,给用户屏障掉了,
5. 这就是封装自定义数据组织的目标地点,
6. 用户在详细运用这些自定义数据组织的时刻,
7. 只须要相识接口中所涉及到的这些要领即可,
8. 至于它的内部细节用户完整能够不必体贴。

### 代码示例 `(class: MyLoopQueue, class: Main)`

1. MyLoopQueue
  public class MyLoopQueue<E> implements IMyQueue<E> {
        private E[] data;
        private int front, tail;
        private int size;

        public MyLoopQueue (int capacity) {
              // 这个数组的容量为 传进来的指定容量+1,
              // 因为会有认识的糟蹋一个空间,只要+1后,
              // 才装下用户希冀传进来的一切数据
              data = (E[])new Object[capacity + 1];

              front = tail = size = 0;
        }

        public MyLoopQueue () {
              this(10);
        }

        public int getCapacity () {
              return data.length - 1;
        }

        private void resize (int newCapacity) {

              E[] newData = (E[]) new Object[newCapacity + 1];
              for (int i = 0; i < size; i++) {
  //            索引能够会越界,因而就要取余一下,
  //            假如越界了,就从队首最先
                    newData[i] = data[(front + i) % data.length];
              }
              data = newData;
              front = 0;
              tail = size;
        }

        /**
         * @param e 入队
         */
        @Override
        public void enqueue(E e) {

              if ((tail + 1) % data.length == front) {
                    resize(getCapacity() * 2);
              }

              data[tail] = e;
  //        tail在行列中轮回
              tail = (tail + 1) % data.length;
              size ++;
        }

        /**
         * @return e
         * 出队
         */
        @Override
        public E dequeue() {

              if(isEmpty()) {
                    throw new IllegalArgumentException("can't dequeue from an empty queue.");
              }

              E e = data[front];
              data[front] = null;
              front = (front + 1) % data.length;
              size -- ;

              if (getCapacity() / 4 == size && getCapacity() / 2 != 0) {
                    resize(getCapacity() / 2);
              }

              return e;
        }

        /**
         * @return e
         * 检察队首的元素
         */
        @Override
        public E getFront() {
              if (isEmpty()) {
                    throw new IllegalArgumentException("queue is empty.");
              }
              return data[front];
        }

        /**
         * @return number
         * 猎取行列中的现实元素个数
         */
        @Override
        public int getSize() {
              return size;
        }

        /**
         * @return bool
         * 猎取行列是不是为空的bool值
         */
        @Override
        public boolean isEmpty() {
              return front == tail;
        }

        @Override
        public String toString () {
              StringBuilder sb = new StringBuilder();
              sb.append(String.format("Queue: size = %d,capacity = %d \n", size, getCapacity()));
              sb.append("Queue: head [");
  //        第一种遍历体式格局
  //        for (int i = 0; i < size - 1; i ++) {
  //            sb.append(data[(front + i) % data.length]);
  //            sb.append(',');
  //        }
  //        if(!isEmpty()) {
  //            sb.append(data[(front + size - 1) % data.length]);
  //        }

              // 第二种遍历体式格局
              for (int i = front; i != tail ; i = (i + 1) % data.length) {
                    sb.append(data[i]);

                    if ((i + 1) % data.length != tail) {
                          sb.append(',');
                    }
              }

              sb.append("] foot. left is queue top!");
              sb.append("\n");

              return sb.toString();
        }

  }

2. Main
  public class Main {

        public static void main(String[] args) {

  //        MyQueue<Integer> mq = new MyQueue<Integer>(10);
              MyLoopQueue<Integer> mq = new MyLoopQueue<Integer>(10);

              for (int i = 1; i <= 11 ; i++) {
                    mq.enqueue(i);
                    System.out.println(mq);
              }

              System.out.println(mq.getFront());

              while (!mq.isEmpty()) {
                    System.out.println(mq);
                    mq.dequeue();
              }
        }
  }

## 自定义行列两种体式格局的对照

1. 本来自定行列的出队时,时刻庞杂度为`O(n)`,
1. 运用轮回行列的体式格局后,
2. 出队时时刻庞杂度为`O(1)`,
3. 庞杂度的剖析只是一个笼统上的理论效果,
4. 详细这个变化在机能上意味着会有一个质的奔腾,
5. 行列中元素越多,机能就更能够表现出来。

### 自定义行列的时刻庞杂度对照

1. `MyQueue<E>`:数组行列,运用了自定义数组
1. `void enqueue(E)`:`O(1)` 均派
2. `E dequeue()`:`O(n)` 出队的机能斲丧太大了
3. `E getFront()`:`O(1)`
4. `int getSize()`:`O(1)`
5. `boolean isEmpty()`:`O(1)`
2. `MyLoopQueue<E>`:轮回行列,没有运用自定义数组
1. `void enqueue(E)`:`O(1)` 均派
2. `E dequeue()`:`O(1)` 均派
3. `E getFront()`:`O(1)`
4. `int getSize()`:`O(1)`
5. `boolean isEmpty()`:`O(1)`

### 轮回行列的庞杂度剖析

1. 经由过程设置轮回行列底层的机制
1. 虽然轻微比数组行列要庞杂一些,
2. 然则这些庞杂的事情是值得的,
3. 因为他使得在数组行列中,
4. 出队本该有`O(n)`的庞杂度变成了`O(1)`的庞杂度,
5. 然则这个`O(1)`为均派的时刻庞杂度,
6. 因为出队照样会涉及到缩容的操纵,
7. 在缩容的历程当中照样免不了对行列中一切的元素举行一次遍历,
8. 然则因为不能够每一次操纵都邑触发缩容操纵来遍历一切的元素,
9. 所以应当运用均派庞杂度的剖析体式格局,那样才越发合理。
2. 轮回行列中一切的操纵都是`O(1)`的时刻庞杂度。
3. `O(n)`的庞杂度要比`O(1)`要慢,
1. 然则详细会慢若干能够经由过程顺序来举行测试,
2. 如许就能够晓得在算法范畴和数据组织范畴
3. 要费这么大的劲去研讨越发优化的操纵
4. 这背地现实的意义究竟在那里。
4. 让这两个行列举行入队和出队操纵,
1. 操纵的次数为 100000 次,
2. 经由过程在统一台机械上的耗时状况,
3. 就能够晓得机能有什么差别。
5. 数据行列与轮回行列十万次入队出队操纵后的效果是:
1. `MyQueue,time:15.463472711s`,
2. `MyLoopQueue,time:0.009602136s`,
3. 轮回行列就算操纵一亿次,
4. 时刻也才`MyLoopQueue,time:2.663835877s`,
5. 这个差别主如果在出队的操纵中表现出来的,
6. 这个机能差别是上千倍,所以这也是机能优化的意义。
6. 测试机能时,不要只测试一次,你能够测试 100 次
1. 取平均值即可,因为这不光和你的顺序相干,
2. 还会和你当前盘算机的状况有关,
3. 迥殊是在两个算法的时刻庞杂度一致时,
4. 测试机能时能够出入会迥殊大,
5. 因为这有多方面缘由、如语法、言语、编译器、诠释器等等,
6. 这些都邑致使你代码真正运转的逻辑机制
7. 和你理论剖析的是不一样的,
8. 然则当两个算法的时刻庞杂度不一致时,
9. 这时候刻测试机能的效果一定会有庞大的差别,
10.   如`O(1)`对`O(n)`、`O(n)`对`O(n^2)`、`O(n)`对`O(logn)`。

### 行列的运用

1. 行列的观点在生涯中随处可见
1. 所以运用盘算机来模仿生涯中行列,
2. 如在营业方面你须要列队,
3. 或许越发专业的一些范畴,
4. 比方 收集数据包的列队、
5. 操纵体系中实行任务的列队等,
6. 都能够运用行列。
2. 行列自身是一个很庞杂的问题
1. 关于列队来讲,队首究竟怎样定义,
2. 是有多样的定义体式格局的,也正因为云云,
3. 所以存在广义行列这个观点,
4. 这两种自定义行列
5. 在组建盘算机天下的别的算法逻辑的时刻
6. 也是有主要的运用的,最典范的运用是广度优先遍历。
    原文作者:哎哟迪奥
    原文地址: https://segmentfault.com/a/1190000018597050
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞