数据结构与算法-动态数组

1,401 阅读3分钟

前言:什么是数据结构?

  • 数据结构是计算机存储和组织数据的方式

  • 线性结构:线性表(数组,链表,栈,队列,哈希表)

  • 树形结构:二叉树,AVL树,红黑树,B树,堆,哈夫曼树,并查集

  • 图形结构:邻接矩阵,邻接表

  • 在实际应用中,我们要根据实际的应用场景来选择合适的数据结构

1,数组(Array)

  • 数组是一种顺序存储的线性表,所有元素的内存地址都是连续的

  • array存放在栈空间
  • array里面的元素存放堆空间,每个元素占用4个字节
  • 在堆空间的内存地址都是连续的

2,动态数组

  • 可以通过一个数组来实现动态数组,动态数组的内存是变化的可以实现动态扩容
  • 动态数组可以实现 增删改查 的操作

2.1,动态数组的暴露接口

  • 外部调用实现的数组的常用函数接口

    // 元素的数量 int size(); // 是否为空 boolean isEmpty(); // 是否包含某个元素 boolean contains(E element); // 添加元素到最后面 void add(E element); // 返回index位置对应的元素 E get(int index); // 设置index位置的元素 E set(int index, E element); // 往index位置添加元素 void add(int index, E element); // 删除index位置对应的元素 E remove(int index); // 查看元素的位置 int indexOf(E element); // 清除所有元素 void clear();

2.2,动态数组的设计

  • 创建动态ArrayList首先要包含两个元素:

  • size属性:管理数组中元素的数量

  • elements属性:用来管理数组元素的存取

  • 创建ArrayList

    public class ArrayList { private int size; private E[] elements; }

  • 初始化ArrayList,创建size,elements,设置elements的初始化空间

    public class ArrayList { private int size; private E[] elements; // 设置elements数组默认的初始化空间 private static final int CAPACITY_DEFAULT = 10; public ArrayList(int capacity) { capacity = capacity < CAPACITY_DEFAULT ? CAPACITY_DEFAULT : capacity; elements = (E[]) new Object[capacity]; } // 默认情况 public ArrayList() { this(CAPACITY_DEFAULT); } }

3,动态数组的实现

3.1,元素的数量

  • 我们创建ArrayList,已经初始化size,所以我们只需要返回当前size

    /**     
     * 元素的数量     
     * @return     
     */    
     public int size() {        
        return size;    
     }
    

3.2,查看数组是否为空

  • 可以通过判断size的数量,判断是否为空

    /**     
     * 是否为空     
     * @return     
     */    
     public boolean isEmpty() {         
        return size == 0;    
     }
    

3.3,是否包含某个元素

  • 当元素存在是,只需要判断索引内容是否为ELEMENT_NOT_FOUND = -1

    /**

    • 是否包含某个元素
    • @param element
    • @return
      */
      public boolean contains(E element) {
      return indexOf(element) != ELEMENT_NOT_FOUND;
      }

3.4,添加元素

  • 当我们添加元素时,都是在数组的末尾添加。
  • 这个 新添加的元素的索引 就是 **当前数组的长度,**size就是当前数组的长度,所以将新添加的元素放在size索引的位置,然后size加1

public void add(E element) {
	elements[size] = element;
	size++;
}

3.5,根据index查询元素

  • 查询时,先检查index是否越界,直接取出对应索引的元素

    public E get(int index) {
    rangeCheck(index);
    return elements[index];
    }
    //提示输出错误
    private void outOfBounds(int index) {        
        throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);    
    }    //检查index是否越界    
    
    private void rangeCheck(int index) {        
        if (index < 0 || index >= size) {            
            outOfBounds(index);        
        }    }
    

3.6,设置index位置的元素

  • 获取索引位置元素进行替换

    public E set(int index, E element) { // 判断索引是否越界 rangeCheck(index); // 取出被替换元素 E oldElement = elements[index]; // 替换元素 elements[index] = element; // 返回被替换元素 return oldElement; }

3.7,在index位置添加的元素

  • arrayList本来有5个元素,在index=2出入一个新的元素。需要把原来index=4的55,移动到index=5,依次向右移动

  • 注意:在插入时,最后的元素要先移动,防止元素被覆盖

public void add(int index, E element) {
	// 从后向前的顺序, 将元素向后移动
	for (int i = size - 1; i >= index; i--) {
		elements[i + 1] = elements[i];
	}
	// 插入元素
	elements[index] = element;
	// size+1
	size++;
}

  • 注意:插入的元素也不能越界,插入元素可以插入在最后一个位置,也就是index=size

    public void rangeCheckForAdd(int index) { // 当索引小于0 或 大于 size时 抛出异常 if (index < 0 || index > size) { throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size); } }

3.8,删除index位置对应的元素

  • arrayList的数组有7个元素,删除index=3的元素,索引为4,5,6的元素需要按顺序依次向左移动
  • 去掉最后一个元素,size减1

public E remove(int index) {
	// 判断索引是否越界
	rangeCheck(index);
	// 取出需要删除的元素
	E element = elements[index];
	// 通过循环, 将index后面的所有元素向前移动一位
	for (int i = index; i < size - 1; i++) {
		elements[i] = elements[i + 1];
	}
	// 删除最后一个元素
	elements[--size] = null;
	// 将删除的元素返回
	return element;
}

//
private void rangeCheck(int index) {
	if (index < 0 || index >= size) {
		throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
	}
}
  • 注意:传入index不能越界,不能大于等于size

3.9,查看元素的位置

  • 首先判断传入元素是否为null,不为null,通过遍历元素进行对比,返回对应的索引

    private static final int ELEMENT_ON_FOUND = -1; public int indexOf(E element) { if (element == null) { for (int i = 0; i < size; i++) { if (elements[i] == null) return i; } }else { for (int i = 0; i < size; i++) { if (element.equals(elements[i])) return i; } } return ELEMENT_ON_FOUND; }

3.10,清空数组

  • 通过遍历索引,清空对应元素,size置为0

    public void clear() { // 清空存储的数据 for (int i = 0; i < size; i++) { elements[i] = null; } // 将size置为0 size = 0; }

3.11,数组扩容

  • 由于原来的数组空间固定,当元素存满时,就需要扩容。
  • 数组扩容:原来的oldArray无法扩容,将原来的oldArray数组拷贝一份为newArray,对newArray进行扩容
  • 将oldArray的数据移动到对应的newArray,elements的指针指向newArray

private void ensureCapacity() {
	// 获取数组当前容量
	int oldCapacity = elements.length;
	// 如果 当前存储的元素个数 < 当前数组容量, 直接返回
	if (size < oldCapacity) return;
	// 新数组的容量为原数组容量的1.5倍
	int newCapacity = oldCapacity + (oldCapacity >> 1);
	// 创建新数组
	E[] newElements = (E[]) new Object[newCapacity];
	// 原数组中的元素存储到新数组中
	for (int i = 0; i < size; i++) {
		newElements[i] = elements[i];
	}
	// 引用新数组
	elements = newElements;
}