一、线性结构的定义
如果一个数据元素序列满足:
-
除第一个和最后一个数据元素外,每个数据元素只有一个前驱和一个后继数据元素
-
第一个数据元素没有前驱数据元素
-
最后一个数据元素没有后继数据元素
二、线性表抽象数据类型
-
线性表抽象数据类型主要包括两个方面:即数据集合 和 该数据集合上的操作集合
-
数据集合可以表示为 a0,a1,a2,...an-1,每个数据元素的数据类型可以是任意的类型
-
操作集合包括如下
-
求元素个数
-
插入
-
删除
-
查找
-
判断是否为空
-
三、顺序表中有两种基本的存储:顺序结构、链式结构;
顺序表接口代码
/**
* 线性表接口
*/
public interface List<E> {
/**
* 求元素个数
* @return
*/
int size();
/**
* 插入元素
* param index 插入的位置
* @param e 插入的元素
* @return
*/
boolean add(int index, E e);
/**
* 删除元素
* @param index 删除元素的下标
* @return
*/
boolean remove(int index);
/**
* 获取指定下标的元素
* @param index 元素下标
* @return
*/
E get(int index);
/**
* 判断集合是否为空
* @return
*/
boolean isEmpty();
}
3.1、使用顺序结构实现的线性表 -- 顺序表
/**
* 顺序表实现
*/
public class ArrayList<E> implements List<E> {
// 元素集合
private E[] arrayList;
// 集合大小
int size;
// 集合最大大小
private int MAX_SIZE = 50;
public ArrayList() {
arrayList = (E[]) new Object[MAX_SIZE];
}
@Override
public int size() {
return size;
}
@Override
public boolean add(int index, E o) {
// 下标值是否正确
if(index < 0 || index >= MAX_SIZE){
throw new IndexOutOfBoundsException();
}
// 若集合容量已达最大值,则返回false,插入失败
if(size >= MAX_SIZE - 1){
return false;
}
// 把该下标及之后的元素全部往后移一位
for(int i = size; i > index; i--){
arrayList[i] = arrayList[i - 1];
}
arrayList[index] = o;
size++;
return true;
}
@Override
public boolean remove(int index) {
// 下标值是否正确
if(index < 0 || index >= MAX_SIZE){
throw new IndexOutOfBoundsException();
}
// 将该下标之后的值往前移一位,覆盖掉下标原来的值
for(int i = index; i < size - 1; i++){
arrayList[i] = arrayList[i + 1];
}
size--;
return true;
}
@Override
public E get(int index) {
// 下标值是否正确
if(index < 0 || index >= MAX_SIZE){
throw new IndexOutOfBoundsException();
}
// 返回该下标的值
return arrayList[index];
}
@Override
public boolean isEmpty() {
return size == 0? true:false;
}
}
3.2、使用链式结构实现顺序表 -- 单向链表
-
注:链表类型:单向链表、单向循环链表、双向循环链表
-
链表存储结构是基于指针实现的。我们把一个数据元素和一个指针称为节点。
-
链式存储结构是用指针把相互直接关联的节点(即直接前驱节点或直接后继节点)链接起来。链式存储结构的线性表称为链表。
/**
* 单向链表实现
* @param <E>
*/
public class LinkedList<E> implements List<E> {
// 链表元素
class Node<E>{
// 下一个节点
Node next;
// 节点值
E e;
public Node() {
}
public Node(E e){
this.e = e;
}
}
// 链表长度
int size;
// 头节点
private Node head;
// 尾节点
private Node tail;
@Override
public int size() {
return size;
}
@Override
public boolean add(int index, E e) {
// 若插入位置大于尾节点后两位,则插入失败
if(index > size){
return false;
}
// 创建一个新的节点
Node<E> node = new Node<>(e);
if(head == null){
head = node;
tail = head;
size++;
return true;
}
// 若 index == size,则直接插入到尾节点
if(index == size){
tail.next = node;
tail = node;
size++;
return true;
}
// 把节点加入到第 index 位置下
Node temp = head;
// 找到 index 的前一个位置 temp
for(int i = 1; i < index; i ++){
temp = temp.next;
}
node.next = temp.next;
temp.next = node;
size++;
return true;
}
@Override
public boolean remove(int index) {
// 判断节点位置
if(index > size || index < 0){
throw new IndexOutOfBoundsException();
}
// 删除该下标位置下的节点
if(index == 0){ // 删除头节点
head = head.next;
size--;
return true;
}
Node temp = head;
// 找到下标节点并删除
for(int i = 1; i < size; i ++){
if(i == index){
break;
}
temp = temp.next;
}
temp.next = temp.next.next;
size--;
return true;
}
@Override
public E get(int index) {
// 判断节点位置
if(index > size || index < 0){
throw new IndexOutOfBoundsException();
}
Node temp = head;
// 找到下标节点
for(int i = 1; i < size; i ++){
temp = temp.next;
if(i == index){
break;
}
}
return (E) temp.e;
}
@Override
public boolean isEmpty() {
return size == 0?true:false;
}
}
3.3、顺序表和单链表的比较
-
顺序表的主要优点是支持随机读取,以及内存空间利用效率高;顺序表的主要缺点是需要预先给出数组的最大数据元素个数,而这通常很难准确作到。当实际的数据元素个数超过了预先给出的个数,会发生异常。另外,顺序表插入和删除操作时需要移动较多的数据元素。
-
和顺序表相比,单链表的主要优点是不需要预先给出数据元素的最大个数。另外,单链表插入和删除操作时不需要移动数据元素。
-
单链表的主要缺点是每个结点中要有一个指针,因此单链表的空间利用率略低于顺序表的。另外,单链表不支持随机读取,单链表取数据元素操作的时间复杂度为O(n);而顺序表支持随机读取,顺序表取数据元素操作的时间复杂度为O(1)。