一、什么是数据结构?
数据结构是计算机存储、组织数据的方式。
线性结构: 线性表(数组、链表、栈、队列、哈希表)
树形结构: 二叉树、AVL树、红黑树、B树、堆、Trie、哈夫曼树、并查集
图形结构: 邻接矩阵、邻接表
在实际应用中,根据使用场景来选择最合适的数据结构。
二、线性表
线性表是具有n个相同类型元素的有限序列(n ≥ 0)。
a1是首节点(首元素),an是尾结点(尾元素),a1是a2的前驱,a2是a1的后继。
常见的线性表:
- 数组
- 链表
- 栈
- 队列
- 哈希表(散列表)
线性表有一个特点就是都有索引的概念。
生活中的线性表:
三、数组
数组(Array)是一种顺序存储的线性表,所有元素的内存地址是连续的。
1 | int array[] = new int[] {11, 22, 33}; |
在很多编程语言中,数组都有个致命的缺点:无法动态修改容量。但实际开发中,我们更希望数组的容量是可以
动态改变的。
3.1. 动态数组(Dynamic Array)
3.1.1. 接口设计
一个动态数组需要满足下面的方法才算动态。
1 | int size(); // 元素的数量 |
在Java中,成员变量会自动初始化,比如int类型自动初始化为 0,对象类型自动初始化为null。
3.1.2. 打印数组
重写toString
方法,在toString
方法中将元素拼接成字符串(字符串拼接建议使用StringBuilder)。
1 |
|
3.1.3. 添加元素
添加元素是添加在数组最后面的,所以元素添加其实就是在索引等于size的位置添加。
1 | elements[size] = element; |
3.1.4. 指定位置添加元素
案例:在size等于5,index等于2的位置插入元素。
插入元素需要注意把index后面的元素重新往后移动一位。
插入元素时,移动元素位置是从最后面的元素开始的。如果从前面的元素开始移动,会把后面的元素值覆盖。错误的覆盖顺序:
1 | public void add(int index, int element) { |
3.1.5. 删除元素
案例:删除size等于7,index等于3的元素。
也就是删除元素值为44的元素:
而且元素被删除后,后面的元素要往前移:
1 | public int remove(int index) { |
思考:最后一个元素如何处理?
不需要处理,因为size已经限制了元素的数量。
3.1.6. 如何扩容?
数组申请的内存是一段连续的内存,是不能继续再申请空间拼接的。唯一的做法就是申请一块更大内存,把原数组的数据复制到新内存中,并且让原来的数组指针指向新的内存地址。
1 | // 添加元素 |
oldCapacity >> 1
是向右位移一位的意思,即oldCapacity / 2
。位运算效率特别高。
3.1.7. 泛型
上面的案例只能操作int类型的元素。使用泛型技术可以让动态数组更加通用,可以存放任何数据类型。
Java中所有的类型都继承自Object。
完整案例:
1 | package com.idbeny; |
可以阅读官方数组源码:
java.util.ArrayList