数组基础及自定义数组及简单复杂度分析

数组 Array

1 数组基础

  • 数据类型一致

    • 为什么数组只能存储一种类型的数据?

      • 每种数据类型所占空间大小不同,比如char占两个字节,int占4个字节,如果数组里面存了多种数据类型,那就不方便根据索引去计算地址偏移量了,实现不了Random Access[1]

      • 如果你存放了多种该数据类型,那么你必须用其他的数据结构去记录哪个位置是什么类型,比如你数组的第一位放了一个byte,如果你不记录,当你取用的时候,你怎么知道他是byte类型呢,而不是char呢。所以说只存一种数据简单纯粹。

      • 特例:存储不同类型的数据

        Object[] arr  = new Object[3];
        arr[0] = 1;
        arr[1] = "aa";
        arr[2] = true;
        
  • 数组是对象

    • string[] s = {"1","2","3"};中存在4个对象,数组s也是一个对象
    • int[] arr = {1,2,3};中存在1个对象,因为基本数据类型不是对象
  • 索引

    • 索引是从0开始的

    • 索引可以有语意(比如索引代表学号,元素是分数)

      也可以无语意

      并非所有有语意的索引都适用于数组(身份证号不能为索引,浪费太多空间)

  • 优点:
    1、按照索引查询元素速度快 时间复杂度O(1)
    2、能存储大量数据
    3、按照索引遍历数组方便 时间复杂度O(1)

    缺点:
    1、根据内容查找元素速度慢 时间复杂度O(n)
    2、数组的大小一经确定不能改变。
    3、数组只能存储一种类型的数据
    4、增加、删除元素效率慢 时间复杂度O(n)
    5、未封装任何方法,所有操作都需要用户自己定义。

1572507581142.png
  • 随机访问是说你可以随意访问该数据结构中的任意一个节点,假设该数据结构有10个节点,你可以随意访问第1个到第10个节点。 +对于链表而言,如果其存在10个节点,如果你要访问第5个节点,你只能从列表的头或者尾,依次遍历相邻的每一个节点;对于vector而言,你可以直接利用[]操作符,直接访问[4],不需要遍历其他的节点,这就是随机访问。 +*比如first是第一个元素的地址,现在想访问第N个元素。 随机访问:直接first+N,便可以得到第N个元素的地址,因为这些相邻元素是按顺序连续存储的。 **+比如普通数组就是可随机访问的。而链表不支持随机访问,链表存储的元素,它们的存储地址也不是连续的,是随机的。 要想访问第N个元素,只能从second = first->next遍历第2个元素,然后再three = first->next遍历第3个元素… 这样一直到第N个元素。所以这样的访问速度就没有随机访问快。

2 基于java的数组,二次封装我们自己的数组类(增删改查)

  • 使用泛型

    • 让我们的数据结构可以放置“任何”数据类型

    • 不可以是基本数据类型,只能是类对象,可以使用包装类

      基本数据类型 包装类
      boolean Boolean
      char Character
      byte Byte
      short Short
      int Integer
      long Long
      float Float
      double Double
  • 数组类中的变量语意

    • E[] data 代表该数组
    • int capacity 容量:该数组最多可以存储多少元素(可以用data.length表示)
    • int size 索引:数据中实际有多少元素
      • 数组中没有元素时,size为0,可表示为索引
1572507946481.png
public class Array<E> {
    private E[] data;
    private int size;
    //获取数组的大小
    public int getCapacity(){return data.length; }
    
    //构造函数,传入数组容量capacity 构造Array
    public Array(int capacity){
        data = (E[])new Object[capacity];
        size = 0;
    }
    //无参构造函数,默认数组容量是10
    public Array(){ this(10);}
    //获取数组中的元素个数
    public int getSize(){ return size; }
    public boolean isEmpty(){ return size==0; }
    ...
  • 向数组中添加元素

      //向所有元素后添加一个新元素
        public void addLast(E e){
            add(size,e);
        }
    
        //向数组开始处添加一个新元素
        public void addFirst(E e){
            add(0,e);
        }
    
        //在第index个位置插入一个新元素e
        public void add(int index,E e){
            if(index < 0 || index > size)
                throw new IllegalArgumentException("Add failed . Require index>=0 and index <=size");
            if(size == data.length)
                resize(2*data.length);//resize解析在文章之后,为扩容操作
            for(int i= size-1; i>= index; i--){
                data[i+1] = data[i];//将index之后元素向后移一位,index位置依然包含原来的元素
            }
            data[index] = e;
            size++;
        }
    
    
    • 在数组后添加一个元素
Honeycam 2019-10-31 16-14-20.gif

​ 在第index个位置插入一个新元素e

在索引位置添加数据.gif
  • 在数组中查询元素和修改元素

    • 重写toString

    • @Override
          public String toString(){
              StringBuilder res = new StringBuilder();
              res.append(String.format("Array:size= %d , capacity = %d\n",size,data.length));
              res.append('[');
              for (int i = 0; i < size; i++) {
                  res.append(data[i]);
                  if(i != size -1)
                      res.append(",");
              }
              res.append(']');
              return res.toString();
          }
      
    • 获取index索引位置的元素 O(1)

      //获取index索引位置的元素(隐藏data),通过get可以防止用户访问data中的空位置
          public E get(int index){
              if(index < 0 || index > size)
                  throw new IllegalArgumentException("Add failed . Index is illegal.");
              return data[index];
          }
      
  • 更新index位置的元素 O(1)

     //修改index索引位置的元素为e
        void set(int index,E e){
            if(index < 0 || index > size)
                throw new IllegalArgumentException("Add failed . Index is illegal.");
            data[index] = e;
        }
    
  • 包含,搜索和删除

    • 包含

      //查找数组中是否有元素为e
          public boolean contains(E e){
              for (int i = 0; i < size; i++) {
                  if (data[i].equals(e))
                      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;
          }
      
    • 删除

       //从数组中删除index位置的元素,返回删除的元素
          public E remove(int index){
              if(index < 0 || index > size)
                  throw new IllegalArgumentException("Add failed . Require index>=0 and index <=size");
              E ret = data[index];
              for(int i= index+1; i< size; i++){
                  data[i-1] = data[i];
              }
              size--;
              data[size] = null; //loitering objects 闲逛的对象 != memeory leak 内存泄漏
      
              if(size == data.length/4 && data.length/2 != 0)
                  resize(data.length/2);
              return ret;
          }
          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);
              }
          }
      
删除.gif

3 动态数组

  • 扩容(ArrayList扩容为1.5倍)

     //自动扩容
        private void resize(int newCapacity){
            E[] newData = (E[])new Object[newCapacity];
            for(int i= 0;i < size; i++){
                newData[i] = data[i];
            }
            data = newData;
        }
    
动态数组.gif

4 简单的时间复杂度分析

1572538764694.png
1572539051176.png
1572539302569.png
1572539319218.png
1572539349068.png
1572539440476.png

从中分析可知,数组不适合增删操作

  • 均摊复杂度(amortized time complexity)

    • resize的复杂度分析(基于addLast(O(1)复杂度)方法)
1572568359369.png

​ 平均,每次addLast操作,平均进行2次基本操作

​ 假设capacity = n, n+1次addLast,触发resize,总共进行2n+1次基本操作

平均,每次addLast操作,平均进行2次基本操作

​ 这样均摊,时间复杂度是O(1)的

​ 同理,removeLast操作,均摊复杂度也为O(1)

  • 复杂度震荡

    • 问题:当我们addLast操作和removeLast操作交替运行时,某些时候可能O(n)

      1、此时capacity=n,数组为满状态,进行addLast操作,需要进行一次resize操作(此时扩容为2n)

      ​ 此时addLast O(n) 容量为2n

      2、接下来,数组中有n+1个元素,数组容量为2n,此时进行一次removeLast操作会触发一次resize操作(此时缩容为n)

      ​ 此时removeLast O(n) 容量为n

      3、不断交替运行。。。。。

1572570297273.png

​ -出现问题的原因:removeLast时resize过于着急(Eager(激进))

​ -解决方案:Lazy

​ 当size==capacity/4时,才将capacity减半

//从数组中删除index位置的元素,返回删除的元素
    public E remove(int index){
        if(index < 0 || index > size)
            throw new IllegalArgumentException("Add failed . Require index>=0 and index <=size");
        E ret = data[index];
        for(int i= index+1; i< size; i++){
            data[i-1] = data[i];
        }
        size--;
        data[size] = null; //loitering objects 闲逛的对象 != memeory leak 内存泄漏

        if(size == data.length/4 && data.length/2 != 0)//data.length==1(容量)时/2==0
            resize(data.length/2);
        return ret;
    }
解决复杂度震荡.gif

此文章为学习《玩转数据结构》所总结,如有问题望请指正
尊重版权,这里贴一个此课程链接https://coding.imooc.com/class/207.html


  1. 随机访问是说你可以随意访问该数据结构中的任意一个节点,假设该数据结构有10个节点,你可以随意访问第1个到第10个节点。 +对于链表而言,如果其存在10个节点,如果你要访问第5个节点,你只能从列表的头或者尾,依次遍历相邻的每一个节点;对于vector而言,你可以直接利用[]操作符,直接访问[4],不需要遍历其他的节点,这就是随机访问。 +*比如first是第一个元素的地址,现在想访问第N个元素。 随机访问:直接first+N,便可以得到第N个元素的地址,因为这些相邻元素是按顺序连续存储的。 **+比如普通数组就是可随机访问的。而链表不支持随机访问,链表存储的元素,它们的存储地址也不是连续的,是随机的。 要想访问第N个元素,只能从second = first->next遍历第2个元素,然后再three = first->next遍历第3个元素… 这样一直到第N个元素。所以这样的访问速度就没有随机访问快。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容