看得见的数据结构——关于数组表,你真的懂吗?

Posted 码个蛋

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了看得见的数据结构——关于数组表,你真的懂吗?相关的知识,希望对你有一定的参考价值。



链接:https://juejin.im/post/5bf626c5e51d450d5441721d


0. 前言


一讲到装东西的容器,你可能习惯于使用ArrayList和数组,你有想过ArrayList和数组的区别吗?


Java的类起名字都不是随便乱起的,一般前面是辅助,后面是实质:

ArrayList = Array + List
Array就是数组,List便是表结构,ArrayList即数组实现的表结构。

问题来了,什么是表结构?


注:不要问我效果图用什么软件画的...因为本来就没用什么软件画。

下一节带你一起自己画!!!

不管别的,先留图镇楼:


看得见的数据结构——关于数组表,你真的懂吗?

表结构的常规操作

看得见的数据结构——关于数组表,你真的懂吗?

数组的扩容与缩容

看得见的数据结构——关于数组表,你真的懂吗?
0.1 在我们生活中都有什么表?
课程表,成绩表,作息时间表、列车行程表、手表(这个算了吧...)
0.2 表有什么用?
看得见的数据结构——关于数组表,你真的懂吗?

可以把同类的对象统一管理,比如成绩表:      高三12班的54为同学的成绩是对象,对象又包括数学、语文、英语...等属性。


把混乱的54个对象放在一起,这么一排,哪个是学霸,哪个是学渣一目了然。


如果其中某个对象的成绩改错了,可以get到对象重新set一下。


如果中间一个人作弊了,分数取消,直接remove掉,后面的人名词往前排。


如果高三12班和高三11班要比较成绩,两张表contact一下,就成一张表,合并也很方便。

0.3 表和数组有什么不同?

打个最恰当的比方就是:

数组相当于打印出来的纸质版表结构像是Excel中可操作版


a. 数组定长:添加新元素,定位添加都很困难

b. 拿删除来说:数组remove掉了,后面的人名次都不变----(我还没个空白名次高,你说气人不气人...)

c. 表是一种抽象数据类型(Abstract Data Type),既然是抽象就是规范或功能,表会有不同的实现形式


[番外]:小和尚问老和尚:"什么是圣人?"    老和尚说:"好好学习,天天向上,乐于助人,诚信友善"这里"圣人"便是一种抽象,"好好学习,天天向上,乐于助人,诚信友善"便是"圣人"的"条件(功能)",小和尚按照这么做了,他就是老和尚眼中的"圣人",即小和尚实现了圣人。

看得见的数据结构——关于数组表,你真的懂吗?



d. 同样,表是一种抽象,也可以定义你眼中的表,并为它附上add(),get(),set(),remove()等功能。

e. 其实Java的ArrayList实现了List这个抽象接口。

0.4 数组表结构:本文要务
看得见的数据结构——关于数组表,你真的懂吗?


1. 定义自己的表结构


由于Java用List,为了不混淆,取了个新名字叫Chart

1.1 定义表的接口

也就是说说你的表能干嘛用(接口方法最好注释非常清晰)

public interface IChart<T> {
//region -------------添加操作------------

   /**
    * 定点添加
    *
    * @param index 索引
    * @param el    数据元素
    */

   void add(int index, T el);

   /**
    * 添加尾
    *
    * @param el 数据元素
    */

   void add(T el);

//endregion

//region -------------删除操作------------

   /**
    * 定位删除
    *
    * @param index 索引
    * @return 删除的元素
    */

   T remove(int index);

   /**
    * 删除尾位
    *
    * @return 删除的元素
    */

   T remove();

   /**
    * 删除指定元素的第一次出现时
    *
    * @param el 数据元素
    * @return 元素位置
    */

   int removeEl(T el);

   /**
    * 删除所有指定元素
    *
    * @param el 数据元素
    */

   boolean removeEls(T el);

   /**
    * 清空集合
    */

   void clear();

//endregion

//region -------------改查操作------------

   /**
    * 设置某位置的元素新值
    *
    * @param index 索引
    * @param el    新值
    * @return 旧值
    */

   T set(int index, T el);

   /**
    * 根据指定位置获取元素
    *
    * @param index 索引
    * @return 数据元素
    */

   T get(int index);

   /**
    * 根据指定元素获取匹配索引
    *
    * @param el 数据元素
    * @return 索引集
    */

   int[] getIndex(T el);
//endregion

//region ------------其他操作-------------

   /**
    * 集合是否包含某元素
    *
    * @param el 数据元素
    * @return 是否包含
    */

   public boolean contains(T el);

   /**
    * 连接两个集合
    *
    * @param iChart 插入集合
    * @return 合并后的集合
    */

   public IChart<T> contact(IChart<T> iChart);

   /**
    * 定点连接两个集合
    *
    * @param index  索引
    * @param iChart 插入集合
    * @return 合并后的集合
    */

   IChart<T> contact(int index, IChart<T> iChart);

   /**
    * 是否为空
    *
    * @return 是否为空
    */

   boolean isEmpty();

   /**
    * 返回集合大小
    *
    * @return 大小
    */

   int size();
   
   /**
    * 获取数组容量
    * @return 数组容量
    */

   int capacity();
   
//endregion
}
1.2 使用数组实现表结构:ArrayChart

实现接口,并实现接口里的所有方法

public class ArrayChart<T> implements IChart<T> {
//空实现---略
}
1.3 成员变量和构造初始化
private int size;//表中数据的个数
private T[] data;//数据核心承载体
private static final int DEFAULT_CAPACITY = 10;//默认数组容量
private static final float GROW_RATE = 1.5f;//扩容增长率

public ArrayChart() {
  this(DEFAULT_CAPACITY);//无参构造默认创建10个容量的数组
}
public ArrayChart(int capacity) {
   data = (T[]) new Object[capacity];//新创建[数组表]时初始化数组
}
1.4 简单接口的实现
@Override
public boolean isEmpty() {
      return size == 0;
}

@Override
public int size() {    
       return size;
}

@Override
public int capacity() {
       return data.length;
}


2. 主要方法的实现(CRUD)


2.1 定点添加元素

看一下操作图(将在下一篇:视图篇完成):默认添加到尾部。
思路:定点后的所有元素后移一位,空出顶点位,让待添加元素入驻。


紫色框代表空的数组位,中间填充的是表中的实际元素。
可见定点添加是在选中索引的前一位添加,所以添加到尾部是add(size,data)来添加。

看得见的数据结构——关于数组表,你真的懂吗?
@Override
public void add(T el) {
  add(size , el);//这里size---是因为在size之前一位添加
}

@Override
public void add(int index, T el) {
  if (index < 0 || index > size) {
      throw new IllegalArgumentException("Add failed! Make sure index < 0 || index > size");
  }
  //从最后一个元素开始,到定点位置元素,元素都后移一位
  for (int i = size - 1; i >= index; i--) {
      data[i + 1] = data[i];
  }
  data[index] = el;
  size++;
}


看得见的数据结构——关于数组表,你真的懂吗?
2.2 查找与设置值:get(), set()


看得见的数据结构——关于数组表,你真的懂吗?


@Override
public T set(int index, T el) {
    if (index < 0 || index >= size) {
        throw new IllegalArgumentException("Set failed! Make sure index < 0 || index > size");
    }
    T oldEl = get(index);
    data[index] = el;//设置一下,很简单
    return oldEl;
}

@Override
public T get(int index) {
    if (index < 0 || index >= size) {
        throw new IllegalArgumentException("Get failed! Make sure index < 0 || index > size");
    }
    return data[index];//查询数组的对应索引处
}


定值查询获取索引

看得见的数据结构——关于数组表,你真的懂吗?


@Override
public int[] getIndex(T el)
{
   int[] tempArray = new int[size];//临时数组
   int count = 0;//重复个数
   for (int i = 0; i < size; i++) {//遍历集合,获取该元素重复个数,及位置数组
       if (data[i].equals(el)) {
           tempArray[count] = i;
           count++;
       }
   }
   //将临时数组压缩---排除空位
   int[] indexArray = new int[count];
   for (int i = 0; i < count; i++) {
       indexArray[i] = tempArray[i];
   }
   return indexArray;//返回查找元素的索引数组(相当于成绩表看数学80分的都有哪些人)
}
2.3 删除元素


看得见的数据结构——关于数组表,你真的懂吗?


@Override
public T remove()
{
   return remove(size - 1);
}

@Override
public T remove(int index)
{
   if (index < 0 || index >= size) {
       throw new IllegalArgumentException("Remove failed! Make sure index < 0 || index > size");
   }
   T temp = get(index);
   //从删除元素索引的下一位开始到结尾,依次左移
   // 可简写: System.arraycopy(data, index + 1, data, index + 1 - 1, size - index + 1);
   for (int i = index + 1; i < size; i++) {
       data[i - 1] = data[i];
   }
   size--;
   //置空--游荡的对象
   data[size] = null;
   return temp;
}

其他删除:

定元素删除单个和定元素删除所有相当于前面的组合操作,就不做操作演示了。

@Override
public int removeEl(T el) {
  int[] indexes = getIndex(el);//查找元素的索引集合,删除首个
  int index = -1;    if (indexes.length > 0) {
      index = indexes[0];
      remove(indexes[0]);
  }    return index;
}

@Override
public boolean removeEls(T el) { //查找元素的索引集合,删除所有
  int[] indexArray = getIndex(el);
  if (indexArray.length != 0) {
      for (int i = 0; i < indexArray.length; i++) {
        remove(indexArray[i] - i); // 注意-i
     }
     return true;
  }
  return false;
}



3. 动态扩容与缩容的实现


也没有什么高大上的,就是一个篮子装不下了,装个更大的篮子装而已

看得见的数据结构——关于数组表,你真的懂吗?

3.1 扩容与缩容方法的实现
/**
* 扩容/缩容
*
* @param newCapacity 新容量
*/

private void grow(int newCapacity) {
   T[] newData = (T[]) new Object[newCapacity];//创建个大篮子
   for (int i = 0; i < size; i++) {//把原来的元素放到大篮子里
       newData[i] = data[i];
   }
   data = newData;
}
3.2 扩容和缩容的调用契机

什么时候扩容----篮子不够装了呗---add
什么时候需要缩容----1000个容量的篮子装1个鸡蛋想想也浪费---remove

1) add检测扩容时机:满了
@Override
public void add(int index, T el) {
   if (size == data.length) {//篮子装不下了---
       grow((int) (GROW_RATE * data.length));//换个1.5倍的篮子
   }
   if (index < 0 || index > size) {
       throw new IllegalArgumentException("Add failed! Make sure index < 0 || index > size");
   }
   //从最后一个元素开始,到定点位置元素,元素都后移一位
   //可简写:System.arraycopy(data, index, data, index + 1, size - index);
   for (int i = size - 1; i >= index; i--) {
       data[i + 1] = data[i];
   }
   data[index] = el;
   size++;
}

2) remove检测缩容时机

这里的判断标志是留多点备用,不然有可能插入移除频繁而导致重复扩容或缩容,
篮子可能会说:“你到底缩还是放,你是不是在玩老子....老子给你多留点空行了吧!”

@Override
public T remove(int index)
{
   if (index < 0 || index >= size) {
       throw new IllegalArgumentException("Remove failed! Make sure index < 0 || index > size");
   }
   T temp = get(index);
   //从删除元素索引的下一位开始到结尾,依次左移
   // 可简写: System.arraycopy(data, index + 1, data, index + 1 - 1, size - index + 1);
   for (int i = index + 1; i < size; i++) {
       data[i - 1] = data[i];
   }
   size--;
   //置空--游荡的对象
   data[size] = null;
   //缩容----此处限定是为了避免反复出现扩容缩容---可自定义
   if (size == data.length / 4 && data.length / 2 != 0 && data.length > 5) {
       grow(data.length / 2);
   }
   return temp;
}

3.3 清空时,数组缩放到初始值
@Override
public void clear() {
  size = 0;
  grow(DEFAULT_CAPACITY);
}


4. 其他操作


4.1 是否包含某元素
@Override
public boolean contains(T el) {
   return getIndex(el).length != 0;//按值查询有数据
}
4.2 contact连接数组
看得见的数据结构——关于数组表,你真的懂吗?
@Override
public IChart<T> contact(IChart<T> iChart) {
   return contact(size - 1, iChart);
}


@Override
public IChart<T> contact(int index, IChart<T> iChart) {
   if (!(iChart instanceof ArrayChart)) {//必须是数组才能联合
       return null;
   }
   //从index处遍历本数组,将待插入数据一个一个插入
   for (int i = index; i < index + iChart.size(); i++) {
       add(i + 1, iChart.get(i - index));
   }
   return this;
}


作为一个表结构,基本上就演示这么多,还有其他操作可以自定义接口,自己实现。

不过不管多么复杂的操作,都是以上操作的组合而已。

看得见的数据结构——关于数组表,你真的懂吗?


5. 小结


关于复杂度的分析,等到所有表结构讲完再整体比较一下。

这里先粗略感觉一下

耗时测试

times

             1000        10000        10w            100w


addHead

             0.0063s    0.2706s    19.5379s    ---

addTail

             0.0004s    0.0025s    0.0141s      0.0687s


removeHead

             0.0063s    0.2771s    19.7902s    ---

removeTail

             0.0005s    0.0036s    0.0091s    0.02301s


可以看出往开始添加/删除会很困难,从代码中可以感觉到,毕竟要让后面所有人挪一挪。
想一下如果30000人排一起,第一个人走了,后面所有人往前挪一下,是不是工程量挺大的?

要是你决定插到第一个,让后面的人都往后移一下.....(大哥,活着难道不好吗....)


所以
频繁对第一个元素进行操作的,还是不要作死,数组表结构(ArrayList)不适合你


这篇数据结构之数组表讲解完啦,

后续还有数据结构系列的其它内容讲解,敬请期待!


看得见的数据结构——关于数组表,你真的懂吗?



近期文章:





看得见的数据结构——关于数组表,你真的懂吗?


等等,先别走!「码个蛋」又有活动了!参与活动不但可以培养自己的好习惯,还能拿到「码个蛋」IP系列专属奖品,速度要快...

看得见的数据结构——关于数组表,你真的懂吗?    


看得见的数据结构——关于数组表,你真的懂吗?


看得见的数据结构——关于数组表,你真的懂吗?


今日问题:

还记得数据结构多少内容?


留言格式:

打卡 x 天,答:xxx


看得见的数据结构——关于数组表,你真的懂吗?


告诉你一个小技巧:

只需3步,你将不会错过任何一篇文章!





以上是关于看得见的数据结构——关于数组表,你真的懂吗?的主要内容,如果未能解决你的问题,请参考以下文章

为什么现在很小的孩子都会玩游戏,他们真的看得懂吗?

面试官常问的Event Loop,你真的懂吗?

关于程序员的笑话,你都看得懂吗?

inline-block,真的懂吗

Java中的boolean类型,你真的懂吗

可以用于云原生中Skywalking框架原理你真的懂吗