线性表概述
概念:零个或多个数据元素的有限序列
- 数据是有限的
- 第一个元素无前驱,最后一个元素无后继
- 是有数据序的,每个数据在线性表中都有一个固定的位置
- 在较复杂的线性表中,数据元素可以由若干个数据项组成
星座列表就是一个线性表,白羊座是第一个元素,没有前驱,双鱼座是最后一个元素,没有后继,每个元素都在固定的位置,它们是有序的。
线性表的基本操作
initList(list L) | 初始化操作,建立一个空的线性表 |
ListEmpty(list L) | 若线性表为空,则返回true,否则返回false 当线性表当前长度为 0 的时候,线性表为空。 |
ClearList(list L) | 将线性表清空 将线性表的当前长度设为 0 。 |
GetElem(L, i, e) | 将线性表中第 i 个位置的元素返回给e |
LocateElem(L , e) | 在线性表 L 中查找与给定 e 相等的元素,如果查找成功,返回该元素在线性表的位置,否则返回 0 表示失败。 |
ListInsert(L,i,e) | 在线性表第 L 中的第 i 个位置插入新元素 |
ListDelete(L,i ,e) | 删除线性表 L 中第 i 个位置的元素,并用 e 返回其值 |
ListLength(L) | 返回线性表 L 的元素个数 |
线性表的顺序存储结构
线性表的顺序存储结构是指用一段地址连续的存储单元依次存储线性表的数据元素。
如下图,a1 - a10 有序的放在一段连续的空间里。
注意:数组的长度和线性表的长度是不同的,数组的长度是初始化的时候分配的,线性表的长度是数组里面存储数据的长度。
class List<T>{
/**
* 存储数据的数组
*/
private T[] element;
/**
* 默认数组的长度
*/
private static final int DEFAULT_CAPACITY = 20;
/**
* 线性表的长度
*/
private int size;
/**
* 初始化
*/
@SuppressWarnings("unchecked")
public List(){
element = (T[]) new Object[DEFAULT_CAPACITY];
size = 0;
}
@SuppressWarnings("unchecked")
public List(int capacity){
element = (T[]) new Object[capacity];
size = 0;
}
}
插入元素
线性表的元素是有序的,所以插入元素需要把后面的元素都统一向后移动。
插入 b 到下面的线性表的 4 号下标,将 4 号下标以及后面的元素统一向后移动一下,然后用 b 覆盖即可。
/**
* 将元素e插入到指定位置
*/
public boolean insert(int pos,T e){
if(pos < 0 || pos > this.size){
return false;
}
for(int i = size;i > pos-1;i--){
element[i] = element[i-1];
}
element[pos-1] = e;
size++;
return true;
}
删除元素
删除元素是用后面的元素将前面元素覆盖。
删除 4 号下标的元素:将 4 号下标以及后面的元素统一向前移动一下
/**
* 删除元素
* @param pos
* @return 被删除的元素值
*/
public T remove(int pos){
/**
* 判断参数是否合法
*/
if(pos <= 0 || pos >= this.size){
return null;
}
T e = element[pos-1];
for(int i = pos-1;i < (size-1);i++){
element[i] = element[i+1];
}
size--;
return e;
}
线性表的链式存储结构
为了表示每个元素和后继元素之间的关系,我们用结点来表示一个数据,结点由两部分组成,前半部分为本身存储的元素,后半部分存储后继元素的引用。
头指针和头结点的异同
头指针
- 指向链表第一个结点的指针,若链表有头结点,则是指向链表头结点的指针
- 头指针具有标识作用,所以一般用头指针标识链表的名字
- 不论链表为不为空,头指针均不为空,头指针是链表的必要元素
头结点
- 头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,数据域一般无意义。
- 有了头结点,对在第一元素的结点前插入结点和删除第一结点,其操作与其它结点的操作就统一了。
- 头结点不一定为链表的必要元素
/**
* 结点
*/
class Node<T>{
T data;
Node<T> next;
public Node(){
this.data = null;
this.next = null;
}
public Node(T val){
this.data = val;
this.next = null;
}
}
class Link<T>{
/**
* 头结点
*/
Node<T> head;
/**
* 链表长度
*/
int size;
/**
* 初始化结点
*/
public Link(){
this.head = new Node<T>();
this.size = 0;
}
插入元素
- 新建一个结点
- 将新结点的后继置为其前驱的后继
- 将其前驱的后继置为新建的节点
/**
* 头插
*/
public void insertHead(T data){
Node<T> node = new Node<T>(data);
node.next = head.next;
head.next = node;
size++;
}
/**
* 尾插
*/
public void insertTail(T data){
Node<T> node = new Node<T>(data);
Node<T> p = head;
while(p.next != null){
p = p.next;
}
p.next = node;
size++;
}
删除指定元素
- 遍历链表到指定结点
- 置节点的前驱为节点的后继
/**
* 删除某元素
*/
public boolean remove(T data){
Node<T> p = head.next;
Node<T> pre = head;
while(p != null){
if(p.data == data){
pre.next = p.next;
return true;
}
pre = p;
p = p.next;
}
return false;
}
线性表两种存储方式的优缺点
单链表适合频繁的插入或者删除数据的操作。