<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)。