线性表(一)
在说线性表之前,我们先来说一下什么是线性结构,为什么要说线性结构呢?因为线性表是最基本也是最常用的一种线性结构,同时也是其他数据结构的基础;
ps:如果有不正确之处,望各位看官不吝赐教,毕竟我还是菜鸟
什么是线性结构: 线性结构的基本特点是除第一个元素无直接前驱,最后一个元素无直接后继之外,其他没有元素都有一个直接前驱和一个直接后继。
什么是线性表: 由多个数据特性相同的元素构成的有限序列称为线性表;线性表是一个非常灵活的数据结构,其长度可以根据需要增长或缩短,即对线性表的数据元素不仅可以进行访问,而且可以进行插入和删除等操作;
线性结构的特点:
- 存在唯一的一个被称作”第一个“的数据元素,头元素;
- 存在唯一个的一个被称作“最后一个”的数据元素,尾元素;
- 除第一个之外,结构中的每个数据元素均只有一个前驱;
- 除最后一个之外,结构中的每个数据元素均只有一个后继;
线性表的顺序存储表示
这里的标题是线性表的顺序存储表示,那么这个顺序是什么意思呢?
答:线性表的顺序指的是用一组地址连续的存储单元依次存储线性表的数据结构,那么我们通常称这种线性表为顺序表,其特点就是*逻辑上相邻的数据元素,其物理次序也是相邻的*;
顺序表的初始化:
-
目的:动态分配一个长度为10的数组;
- 这里的data数组是用来存储线性表元素的,但是它并不是真正的线性表,因为线性表中的元素不为空,只有在最开始size为0的时候线性表为空,而data数组的前size个元素才是我们真正的线性表,也就是说我们的线性表是寄宿在data数组中的;
/** * 声明一个数组用来存储线性表元素 */ private E[] data; /** * 用来记录线性表中的元素个数 */ private int size; /** * 线性表的初始化操作 * 动态分配一个长度为10的数组 */ public Liner() { data = (E[]) new Object[10]; }
增加元素:
-
在末尾加入:
- 插入之前需要判断我们的线性表还能不能继续完全的存放在data数组中,什么情况下才能够完全的存放呢? 当我们的size + 1 小于数组长度时,可以完全的存放在data数组中; 为什么是size + 1 而不是size呢?因为当我们插入一个元素后,线性表长度加1,如果在插入之前没有存放这个元素的位置,那么就会插入失败;
/** * 在线性表的末尾加入一个元素 * @param element 需要插入的元素值 */ public void add(E element) { if (size + 1 > data.length) { // 扩容 data = grow(); } data[size++] = element; }
-
在指定位置插入:
- 这里用到了索引值,我们都知道索引有越界的问题,索引需要进行提前检查;
- 数组拷贝:将index以及index之后的元素都往后移动一位;
- size++ :表示线性表的长度增加了1;
/** * 在指定的索引位置插入给定的元素 * @param index 元素插入的索引位置 * @param element 所要插入的元素 */ public void insert(int index, E element) { if (index > 0 || index <= size + 1) { if (size + 1 > data.length) { data = grow(); } // 数组元素的拷贝 System.arraycopy(data, index, data, index + 1, size - index); data[index] = element; size ++; } }
获取操作:
-
获取元素值:
- 同样,这里也需要进行索引的检查(checkIndex方法的代码后面会给);
/** * 根据给定的索引来获取元素值 * @param index 索引值 * @return 返回索引对应的元素 */ public E getElement(int index) { // 判断索引是否合法 checkIndex(index); // 获取元素 return data[index]; }
-
获取元素的索引:
/** * 根据给定的元素来获取其对应的索引值 * 如果查找成功,则返回该索引 * 如果查找失败,则返回-1 * @return 返回给定元素对应的位置 */ public int getIndex(E element) { if (element != null) { // 遍历线性表匹配元素 for (int i = 0; i < data.length; i++) { if (element.equals(data[i])) { return i ; } } } return -1; }
修改元素:
/**
* 修改指定索引位置上的元素值
* @param index 需要修改的索引
* @param element 替换的值
*/
public void set(int index, E element) {
checkIndex(index);
if (element != null) {
data[index] = element;
}
}
删除元素:
删除元素,需要将index之后的元素往前移动一位并且size减一;
/**
* 删除指定索引位置的元素
* @param index 要删除元素的索引
*/
public void delete(int index) {
checkIndex(index);
System.arraycopy(data, index + 1, data, index, data.length - index - 1);
size --;
}
辅助方法:
/**
* 数组的动态扩容
* 每次扩容至原来的1.5倍
* @return 返回扩容好的数组
*/
private E[] grow() {
int capacity = data.length + (data.length >> 1);
// 创建新的数组
E[] newArray = (E[]) new Object[capacity];
// 数组拷贝
System.arraycopy(data, 0, newArray, 0, size);
return newArray;
}
/**
* 检查索引是否合法
* @param index 需要判定的索引值
*/
private void checkIndex(int index) {
if (index < 0 || index >= size) {
throw new RuntimeException("索引不合法:" + index);
}
}
@Override
public String toString() {
// 当线性表中没有元素时
if (size == 0) {
return "[]";
}
// 当线性表中有元素时
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < size; i++) {
sb.append(data[i] + ",");
}
sb.setCharAt(sb.length() - 1, ']');
return sb.toString();
}
顺序表优缺点:
- 优点:查找和修改的效率高,它们的时间复杂度都为O(1),只需要根据索引就能找到所对应的元素;
- 缺点:
- 增加和删除的效率低,它们的时间复杂度都为O(n),因为几乎每次都需要移动元素;
- 由于顺序表底层用的是数组,数组的长度是固定的,当我们的线性表没有完全占满数组时,会造成资源的浪费;