本文整理自《大话数据结构》及传智播客视频教程
1.线性表定义
线性表是由零个或多个数据元素组成的有限序列。
根据它的定义,可以得出以下几点
- 序列,说明线性表是有序的,若存在多个元素,第一个元素无前驱,最后一个元素无后继,其他元素都有且只有一个前驱和后继;
- 有限,说明数据元素个数是有限的;
- 最后一个,数据元素的类型必须相同;
- 线性表能够逐项访问和顺序存取。
2.线性表数学定义
线性表是具有相同类型的 n( ≥ 0)个数据元素的有限序列(a0,a1, a2, …, an)
ai是表项,n 是表长度。
其中,
- a0为线性表的第一个元素,只有一个后继;
- an为线性表的最后一个元素,只有一个前驱;
- 除a0和an外的其它元素ai,既有前驱,又有后继。
3. 线性表抽象数据类型
public interface ISequenceList {
void clearList(); //请求线性表
boolean isEmpty(); //判读线性表是否为空
int length(); //线性表长度
T get(int index); //获取线性表
boolean insert(int index, T t); //插入元素
boolean delete(int index); //删除元素
int indexOf(int x); //查找某个元素
void printList(); //输出整个线性表
}
4. 线性表存储结构
线性表有两种存储结构,一个是顺序存储(也就是顺序表),一个是链式存储(链表)。
4.1 顺序存储(顺序表)
4.1.1 基本概念
线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。线性表 (a1,a2,……,an)的顺序存储示意图如下:
4.1.2 顺序表操作插入和删除的逻辑稍微复杂些,通过图例来说明一下;
-
插入元素
- 删除指定元素
4.1.3 实现
public interface ISequenceList {
void clearList(); //请求线性表
boolean isEmpty(); //判读线性表是否为空
int size(); //线性表长度
E get(int index); //获取线性表
boolean insert(int index, E t); //插入元素
boolean delete(int index); //删除元素
int indexOf(E e); //查找某个元素
}
public class SequenceList implements ISequenceList {
private final int DEFAULT_SIZE = 12;
private int size;
private int capacity;
private Object array[];
public SequenceList() {
size = 0;
capacity = DEFAULT_SIZE;
array = new Object[capacity];
}
public SequenceList(int capacity) {
size = 0;
this.capacity = capacity;
array = new Object[capacity];
}
@Override
public void clearList() {
for (int i = 0; i < size; i++) {
array[i] = null;
}
size = 0;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public int size() {
return size;
}
@Override
public E get(int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("索引越界");
}
return (E) array[index];
}
@Override
public boolean insert(int index, E e) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("索引越界");
}
ensureCapacity(size + 1);
for (int i = size; i > index; i--) {
array[i] = array[i - 1];
}
array[index] = e;
size++;
return true;
}
@Override
public boolean delete(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("索引越界");
}
for (int i = index; i < size; i++) {
array[i] = array[i + 1];
array[i + 1] = null;
}
size--;
return true;
}
@Override
public int indexOf(E e) {
int index = -1;
for (int i = 0; i < size; i++) {
if (e.equals(array[i])) {
index = i;
}
}
return index;
}
/**
* 如果数组的长度不足,需要进行扩容
*
* @param capacity 数组所需长度
*/
private void ensureCapacity(int capacity) {
if (capacity > this.capacity) {
this.capacity = capacity + 2;
}
array = Arrays.copyOf(array, this.capacity);
}
}
// 测试代码
public class TestSequenceList {
public static void main(String[] args) {
SequenceList sequencePersons = new SequenceList<>(1);
for (int i = 0; i < 3; i++) {
Person p = new Person();
p.setAge(i);
p.setName("H" + i);
sequencePersons.insert(0, p);
}
//删除
// for (int i = 0; i < sequencePersons.size(); i++) {
// sequencePersons.delete(0);
// }
Person p = new Person();
p.setAge(3);
p.setName("H" + 3);
int index = sequencePersons.indexOf(p);
if (index == -1) {
System.out.println("不存在");
} else {
System.out.println("存在");
}
sequencePersons.insert(0, p);
index = sequencePersons.indexOf(p);
if (index == -1) {
System.out.println("不存在");
} else {
System.out.println("存在");
}
}
static final class Person {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
4.1.4 顺序表优缺点
优点:
无需为线性表中的逻辑关系增加额外的空间;
可以快速的获取表中合法位置的元素;缺点:
从上图中可以看到,插入和删除操作需要移动大量元素;
当线性表长度变化较大时难以确定存储空间的容量。
4.2 线性表链式存储(链表)
4.2.1 基本概念
为了表示每个数据元素与其直接后继元素之间的逻辑关系,每个元素除了存储本身的信息外,还需要存储指示其直接后继的信息。
单链表
链表中,每个节点中只包含一个指针域,这样的链表叫单链表。
通过每个节点的指针域将线性表的数据元素按其逻辑次序链接在一起 。表头节点
链表中的第一个节点,包含指向第一个数据节点的指针以及链表自身的一些信息。一个链表中可以没有头部节点。
- 数据节点
链表中代表数据元素的节点,包含指向下一个数据元素的指针和数据元素信息。
- 尾节点
链表中最后一个节点,其下一个元素的指针为null,表示无后继。
4.2.2 同样,插入和删除通过图例来说明
4.2.3 实现
4.3 静态链表
通过上面的操作可以看出,链表是通过指针功能将各个节点链接起来的,但早期的语言如Basic没有指针,按照前面的方法,就没法实现。于是,有人就想出用数组代替指针来描述单链表。
通过数组描述的单链表叫做静态链表。
4.3.1 基本概念
一个静态链表的节点包含数据域和游标。通常静态链表会将第一个数据元素放到数组下标为1(即a[1])的位置中。因为静态链表的第一个元素和最后一个元素是两个特殊的节点,不存数据,后面会讲到。
- 数据域:用于存储数据元素的值
- 游标:用来存放数组下标,类似链表中的指针,表示直接后继元素所在数组中的位置。注意,游标不是数组的下标。
public class StaticLinkListNode {
public T data; // 数据
public int cursor; // 游标
...
}
- 备用链表
所谓备用链表,就是静态链表中未被使用的数组元素。
作用:回收数组中未使用或者之前使用过(现在不用)的存储空间,留待后期使用。
下面通过两张图来说明一下静态链表中的一些知识点;
下面是示例代码
(删除元素的代码没有给出,可以参考插入元素的代码)
public class StaticListNode {
public T data;
public int currsor;
public StaticListNode(T data, int cur) {
this.data = data;
this.currsor = cur;
}
}
/**
* 静态链表
*/
public class StaticLinkList implements ISequenceList {
private int mSize;
private StaticListNode[] mNodes;
private int DEFAULT_SIZE = 6;
public StaticLinkList(int size) {
DEFAULT_SIZE = size;
mNodes = new StaticListNode[size];
for (int i = 0; i < mNodes.length; i++) {
//这个写法错误,因为mNodes[i]的中的元素为空,
//mNodes[i].data = null;
//mNodes[i].currsor = i + 1;
mNodes[i] = new StaticListNode<>(null, i + 1);
}
mNodes[mNodes.length - 1].currsor = 0;
mSize = 0;
}
public StaticLinkList() {
mNodes = new StaticListNode[DEFAULT_SIZE];
for (int i = 0; i < mNodes.length; i++) {
mNodes[i] = new StaticListNode<>(null, i + 1);
}
mNodes[mNodes.length - 1].currsor = 0;
mSize = 0;
}
@Override
public void clearList() {
for (int i = 0; i < mSize; i++) {
mNodes[i].currsor = i + 1;
mNodes[i].data = null;
}
mNodes[mNodes.length - 1].currsor = 0;
mSize = 0;
}
@Override
public boolean isEmpty() {
return mSize == 0;
}
@Override
public int size() {
return mSize;
}
@Override
public E get(int index) {
if (index < 1 || index >= DEFAULT_SIZE)
return null;
StaticListNode lastNode = mNodes[DEFAULT_SIZE - 1];
int currIndex;
for (int i = 1; i <= index; i++) {
currIndex = lastNode.currsor;
lastNode = mNodes[currIndex];
}
return (E) lastNode.data;
}
@Override
public boolean insert(int index, E t) {
if (index < 1 || index >= mNodes.length) {
throw new IndexOutOfBoundsException("索引越界");
}
int backupIndex = mNodes[0].currsor;//备用链表第一个节点索引
mNodes[0].currsor = mNodes[backupIndex].currsor; //因为原先备用链表的第一个节点准备使用,所以备用链接后移一位。
StaticListNode node = new StaticListNode<>(t, 0);
mNodes[backupIndex] = node; //将插入的节点放到备用链表第一节点上。
int currIndex = mNodes.length - 1;
for (int i = 1; i <= index - 1; i++) { //找到index前一个元素的位置
currIndex = mNodes[currIndex].currsor;
}
mNodes[backupIndex].currsor = mNodes[currIndex].currsor;
mNodes[currIndex].currsor = backupIndex;
mSize++;
return true;
}
@Override
public boolean delete(int index) {
// 删除元素的代码没有给出,可以参考插入元素的代码
return false;
}
@Override
public int indexOf(E e) {
return 0;
}
}