文章目录
1、线性表及其逻辑结构
线性表是最简单也是最常用的一种数据结构。英文字母表(A、B、…、Z)是一个线性表,表中每个英文字母是一个数据元素;成绩单是一个线性表,表中每一行是一个数据元素,每个数据元素又由学号、姓名、成绩等数据项组成。
1.1 线性表定义
线性表是具有相同特性的数据元素的一个有限序列。线性表一般表示为:
L = (a1, a2, …, ai,ai+1 ,…, an)
线性表中元素在位置上是有序的,这种位置上有序性就是一种线性关系,用二元组表示:
L = (D, R)
D = {
ai| 1≤i≤n, n≥0}
R = {
r}
r = {
<ai, ai+1> | 1≤i≤n-1}
1.2 线性表的抽象数据类型描述
将线性表数据结构抽象成为一种数据类型,这个数据类型中包含数据元素、元素之间的关系、操作元素的基本算法。对于基本数据类型(int、float、boolean等等)java已经帮我们实现了用于操作他们的基本运算,我们需要基于这些基本运算,为我们封装的自定义数据类型提供操作它们的算法。比如数组就是一种被抽象出来的线性表数据类型,数组自带很多基本方法用于操作数据元素。
Java中的List我们经常会使用到,但是很少关注其内部实现,List是一个接口,里面定义了一些抽象的方法,其目的就是对线性表的抽象,其中的方法就是线性表的一些常用基本运算。 而对于线性表的不同存储结构其实现方式就有所不同了,比如ArrayList是对线性表顺序存储结构的实现,LinkedList是线性表链式存储结构的实现等。存储结构没有确定我们就不知道数据怎么存储,但是对于线性表这种逻辑结构中数据的基本操作我们可以预知,无非就是获取长度、获取指定位置的数据、插入数据、删除数据等等操作,可以参考List。
对于本系列文章,只是对数据结构和一些常用算法学习,接下来的代码将选择性实现并分析算法。对线性表的抽象数据类型描述如下:
public interface IList<T> {
/**
* 判断线性表是否为空
* @return
*/
boolean isEmpty();
/**
* 获取长度
* @return
*/
int length();
/**
* 将结点添加到指定序列的位置
* @param index
* @param data
* @return
*/
boolean add(int index, T data);
/**
* 将指定的元素追加到列表的末尾
* @param data
* @return
*/
boolean add(T data);
/**
* 根据index移除元素
* @param index
* @return
*/
T remove(int index);
/**
* 移除值为data的第一个结点
* @param data
* @return
*/
boolean remove(T data);
/**
* 移除所有值为data的结点
* @param data
* @return
*/
boolean removeAll(T data);
/**
* 清空表
*/
void clear();
/**
* 设置指定序列元素的值
* @param index
* @param data
* @return
*/
T set(int index, T data);
/**
* 是否包含值为data的结点
* @param data
* @return
*/
boolean contains(T data);
/**
* 根据值查询索引
* @param data
* @return
*/
int indexOf(T data);
/**
* 根据data值查询最后一次出现在表中的索引
* @param data
* @return
*/
int lastIndexOf(T data);
/**
* 获取指定序列的元素
* @param index
* @return
*/
T get(int index);
/**
* 输出格式
* @return
*/
String toString();
}
2、线性表的顺序存储结构
2.1 顺序表
把线性表中的所有元素按照其逻辑顺序依次存储在计算机存储器中指定存储位置开始的一块连续的存储空间中。在Java中创建一个数组对象就是分配了一块可供用户使用的连续的存储空间,该存储空间的起始位置就是由数组名表示的地址常量。线性表的顺序存储结构是利用数组来实现的。
在Java中,我们通常利用下面的方式来使用数组:
int[] array = new int[]{
1,2,3}; //创建一个数组
Array.getInt(array, 0); //获取数组中序列为0的元素
Array.set(array, 0, 1); //设置序列为0的元素值为1
这种方式创建的数组是固定长度的,其容量无法修改,当array被创建出来的时候,系统只为其分配3个存储空间,所以我们无法对其进行添加和删除操作。Array这个类里面提供了很多方法用于操作数组,这些方法都是静态的,所以Array是一个用于操作数组的工具类,这个类提供的方法只有两种:get和set,所以只能获取和设置数组中的元素,然后对于这两种操作,我们通常使用array[i]、array[i] = 0的简化方式,所以Array这个类用的比较少。
另外一种数组ArrayList,其内部维护了一个数组,所以本质上也是数组,其操作都是对数组的操作,与上述数组不同的是,ArrayList是一种可变长度的数组。既然数组创建时就已经分配了存储空间,为什么ArrayList是长度可变的呢?长度可变意味着可以从数组中添加、删除元素,向ArrayList中添加数据时,实际上是创建了一个新的数组,将原数组中元素一个个复制到新数组后,将新元素添加进来。如果ArrayList仅仅做了这么简单的操作,那他就不应该出现了。ArrayList中的数组长度是大于等于其元素个数的,当执行add()操作时首先会检查数组长度是否够用,只有当数组长度不够用时才会创建新的数组,由于创建新数组意味着老数据的搬迁,所以这个机制也算是利用空间换取时间上的效率。但是如果添加操作并不是尾部添加,而是头部或者中间位置插入,也避免不了元素位置移动。
2.2 顺序表基本运算的实现
public class LinearArray<T> implements IList<T>{
private Object[] datas;
/**
* 通过给定的数组 建立顺序表
* @param objs
* @return
*/
public static <T> LinearArray<T> createArray(T[] objs){
LinearArray<T> array = new LinearArray();
array.datas = new Object[objs.length];
for(int i = 0; i<objs.length; i++)
array.datas[i] = objs[i];
return array;
}
private LinearArray(){
}
@Override
public boolean isEmpty() {
return datas.length == 0;
}
@Override
public int length() {
return datas.length;
}
/**
* 获取指定位置的元素
* 分析:时间复杂度O(1)
* 从顺序表中检索值是简单高效的,因为顺序表内部采用数组作为容器,数组可直接通过索引值访问元素
*/
@Override
public T get(int index) {
if (index<0 || index >= datas.length)
throw new IndexOutOfBoundsException();
return (T) datas[index];
}
/**
* 为指定索引的结点设置值
* 分析:时间复杂度O(1)
*/
@Override
public T set(int index, T data) {
if (index<0 || index >= datas.length)
throw new IndexOutOfBoundsException();
T oldValue = (T) datas[index];
datas[index] = data;
return oldValue;
}
/**
* 判断是否包含某值只需要判断该值有没有出现过
* 分析:时间复杂度O(n)
*/
@Override
public boolean contains(T data) {
return indexOf(data) >= 0;
}
/**
* 获取某值第一次出现的索引
* 分析:时间复杂度O(n)
*/
@Override
public int indexOf(T data) {
if (data == null) {
for (int i = 0; i < datas.length; i++)
if (datas[i]==null)
return i;
} else {
for (int i = 0; i < datas.length; i++)
if (data.equals(datas[i]))
return i;
}
return -1;
}
/**
* 获取某值最后一次出现的索引
* 分析:时间复杂度O(n)
*/
@Override
public int lastIndexOf(T data) {
if (data == null) {
for (int i = datas.length-1; i >= 0; i--)
if (datas[i]==null)
return i;
} else {
for (int i = datas.length-1; i >= 0; i--)
if (data.equals(datas[i]))
return i;
}
return -1;
}
/**
* 指定位置插入元素
* 分析:时间复杂度O(n)
* 在数组中插入元素时,需要创建一个比原数组容量大1的新数组,
* 将原数组中(0,index-1)位置的元素拷贝到新数组,指定新数组index位置元素值为新值,
* 继续将原数组(index, length-1)的元素拷贝到新数组
* @param index
* @param data
* @return
*/
@Override
public boolean add(int index, T data) {
if (index > datas.length || index < 0