<1>线性表
线性表,顾名思义,其组成元素间具有线性关系的一种结构。
(Ps:两个变量之间存在一次函数关系,就称它们之间存在线性关系。。。更通俗一点讲,如果把这两个变量分别作为点的横坐标与纵坐标,其图象是平面上的一条直线,则这两个变量之间的关系就是线性关系。)
线性表(Linear List)是由n个类型相同的数据元素组成的有限序列,即:
LinearList=(a0,a1,···,an-1)
其接口LList声明如下,描述线性表的取值、置值、插入、删除等操作。
public interface LList<E>{ //线性表接口
boolean isEmpty(); //判断线性表是否为空
int length(); //返回线性表长度
E get(int index); //返回序号为index的对象,index的初值为0
E set(int index,E element);//设置序号为index的对象为element,返回原对象
boolean add(E element); //插入element对象,未指定位置
E remove(int index); //移除序号为index的对象,返回被移除的对象
void clear(); //清空线性表
}
线性表有顺序存储和链式存储两种结构:
public class SeqList<E> implements LList<E> //顺序表类
public class SinglyLinkedList<E> implements LList<E> //单链表类
LList接口中的方法在顺序表类和链表类中表现出多态性
<2>线性表的顺序实现
顺序存储意味着物理存储次序(一组连续内存中的位置)与逻辑次序(直接前驱与直接后继)相同。
设每个元素占用c个字节,a0的存储位置为Loc(a0),则ai的存储位置为
Loc(ai)=Loc(a0)+i*c
计算一个元素地址所需时间是一个常量,因此存取任何一个元素的时间复杂度是O(1),故顺序表示一种随机存取结构。
(数组是顺序存储的随机存取结构,在程序设计语言中,数组已被实现为一种构造数据类型。数组一旦占用一片存储空间,这片存储空间的地址和长度就是确定的,不能更改。数组只能进行赋值、取值两种随机存储操作,不能进行插入、删除操作。)
顺序表类:
public class SeqList<E> implements LList<E>{
private Object[] table; //对象数组,私有成员
private int n; //顺序表长度
public SeqList(int capacity){ //构造方法,创建指定容量的空表
this.table=new Object[Math.abs(capacity)];
this.n=0;
}
public SeqList(){
this(16); //构造方法的重载,指定空表的默认容量
}
@Override
public boolean isEmpty() { //判断顺序表是否为空,若空返回true
return this.n==0;
}
@Override
public int length() {
return this.n; //返回顺序表的长度
}
//返回index(初值为0)位置的对象,若序号无效,然会null
@Override
public E get(int index) {
if(index>=0&&index<this.n)
return (E)this.table[index];
return null;
}
//设置index位置的对象为element,若操作成功,返回原对象,否则返回null
@Override
public E set(int index, E element) {
if(index>=0&&index<this.n&&element!=null){
E old=(E)this.table[index]; //改变引用即可
this.table[index]=element;
return old;
}
return null;
}
//在index位置插入element对象,若操作成功返回true,不能插入null
public boolean add(int index,E element) {
if(element==null) //不能插入null
return false;
if(this.n==this.table.length){ //若数组满,则需要扩充顺序表容量
Object[] temp=this.table;
this.table=new Object[temp.length*2];//重新申请容量更大的一个数组
for(int i=0;i<temp.length;i++){ //复制数组元素,O(n)
this.table[i]=temp[i];
}
}
if(index<0) //下标容错
index=0;
if(index>this.n)
index=this.n;
for(int j=this.n-1;j>=index;j--) //元素后移,平均移动n/2
this.table[j+1]=this.table[j];
this.table[index]=element;
this.n++;
return true;
}
//在顺序表最后插入element对象
@Override
public boolean add(E element){
return add(this.n,element);
}
//移除index位置的对象,若操作成功,则返回被移除去对象,否则返回null
@Override
public E remove(int index) {
if(this.n!=0&&index>=0&&index<this.n){
E old=(E)this.table[index];
for(int j=index;j<this.n-1;j++) //元素前移,平均移动n/2
this.table[j]=this.table[j+1];
this.table[this.n-1]=null;
this.n--;
return old; //操作成功,返回被移动对象
}
return null;
}
@Override
public void clear() { //清空顺序表
if(this.n!=0){
for(int i=0;i<n;i++)
this.table[i]=null;
this.n=0;
}
}
}
顺序表操作效率分析:
随机存取,因此存取任何一个元素的get()、set()方法的时间复杂度都是O(1)
对于插入、删除来说,所花费的时间主要用在移动元素上
设在第i个位置插入元素的概率为pi,则插入一个元素的平均移动次数为
如果在各位置插入元素的概率相同,即p0=p1=····=pn=1/(n+1),则有
换言之,在等概率情况下,插入一个元素平均需要移动一半的元素,时间复杂度为O(n),同理,删除一个元素的时间复杂度亦为O(n)。