线性表的链式存储结构是指:用一组任意的存储单元(可以连续,也可以不连续)存储线性表中的数据单元。即逻辑上相邻的数据在计算机内的存储位置不一定相邻。如图所示:
这里具有一个数据域和多个指针域的存储单元通常称为 结点(node)。
在Java中没有显式的指针类型。然而实际上对象的访问就是使用指针来实现的,即在Java 中是使用对象的引用来替代指针的。
因此在使用 Java 实现该结点结构时,一个结点本身就是一个对象。我们定义一个先结点,为了提高复用性,在接口中定义了所有结点均支持的操作,即对结点中存储数据的存取。
/**
* Created by Troshin on 2018/1/14.
*/
public interface Node {
//获取结点数据域
public Object getData();
//设置结点数据域
public void setData(Object obj);
}
单链表结点定义:
/**
* Created by Troshin on 2018/1/14.
*/
public class SLNode implements Node {
private Object element;
private SLNode next;
public SLNode(){
this(null,null);
}
public SLNode(Object ele,SLNode next){
this.element=ele;
this.next=next;
}
public SLNode getNext() {
return next;
}
public void setNext(SLNode next) {
this.next = next;
}
/**
* 接口实现
* @return
*/
@Override
public Object getData() {
return element;
}
@Override
public void setData(Object obj) {
this.element=obj;
}
}
单链表是通过上述定义的结点使用 next 域依次串联在一起而形成的。
链表的第一个结点和最后一个结点,分别称为链表的 首结点和 尾结点。
尾结点的特征是其 next 引用为空(null),"^"表示"null"的意思。
与数组类似,单链表中的结点也具有一个线性次序,即如果结点 P 的 next 引用指向结点 S,则 P 就是 S 的直接前驱,S 是 P 的直接后续。单链表的一个重要特性:就是只能通过前驱结点找到后续结点,而无法从后续结点找到前驱结点。
我们先看看图解的查找、插入、删除操作:
除了查找操作外,插入和删除都给了三个不一样的方法,因此它们的时间复杂度也不一样,复杂度我们稍后讨论。现在我们看看方法的实现,方法的定义在上一章节,因此这里就不重复写了。
/**
* Created by Troshin on 2018/1/14.
*/
public class ListSLinked implements myList {
private Strategy strategy; //数据元素比较策略
private SLNode head; //单链表首结点引用
private int size; //线性表中的数据元素的个数
/**
* 构造方法
*/
public ListSLinked(){
this(
new DefaultStrategy());
}
public ListSLinked(Strategy strategy){
this.strategy=strategy;
head=new SLNode();
size=0;
}
/**
* 辅助方法:获取数据元素 e 所在结点的前驱结点
*/
private SLNode getPreNode(Object e){
SLNode p=head;
while (p.getNext()!=null)
if(strategy.equal(p.getNext().getData(),e))
return p;
else p=p.getNext();
return null;
}
/**
* 辅助方法:获取序号为 0<=i<size 的元素所在结点的前驱结点
* @return
*/
private SLNode getPreNode(int i){
SLNode p =head;
for (;i>0;i--)
p=p.getNext();
return p;
}
/**
* 获取序号为 0<=i<size 的元素所在结点
* @param i
* @return
*/
private SLNode getNode(int i){
SLNode p=head.getNext();
for (;i>0;i--)
p=p.getNext();
return p;
}
/**
* 返回线性表的大小,即数据元素的个数。
* @return
*/
@Override
public int getSize() {
return size;
}
/**
* 如果线性表为空返回 true,否则返回 false。
* @return
*/
@Override
public boolean isEmpty() {
return size==0;
}
/**
* 判断线性表是否包含数据元素 e
* @param e
* @return
*/
@Override
public boolean contains(Object e) {
SLNode p=head.getNext();
while (p!=null)
if (strategy.equal(p.getData(),e))
return true;
else p=p.getNext();
return false;
}
/**
* 返回数据元素 e 在线性表中的序号
* @param e
* @return
*/
@Override
public int indexOf(Object e) {
SLNode p=head.getNext();
int index=0;
while (p!=null)
if (strategy.equal(p.getData(),e))
return index;
else {
index ++;
p=p.getNext();
}
return -1;
}
/**
* 将数据元素 e 插入到线性表中 i 号位置
* @param i
* @param e
* @throws OutOfBoundaryException
*/
@Override
public void insert(int i, Object e) throws OutOfBoundaryException {
if (i<0||i>size)
throw new OutOfBoundaryException("错误,指定插入的序号越界");
SLNode p=getPreNode(i);
SLNode q=new SLNode(e,p.getNext());
p.setNext(q);
size ++;
return;
}
/**
* 将数据元素 e 插入到元素 obj 之前
* @param obj
* @param e
* @return
*/
@Override
public boolean insertBefore(Object obj, Object e) {
SLNode p =getPreNode(obj);
if (p!=null) {
SLNode q = new SLNode(e,p.getNext());
p.setNext(q);
size++;
return true;
}
return false;
}
/**
* 将数据元素 e 插入到元素 obj 之后
* @param obj
* @param e
* @return
*/
@Override
public boolean insertAfter(Object obj, Object e) {
SLNode p=head.getNext();
while (p!=null)
if (strategy.equal(p.getData(),obj)) {
SLNode q = new SLNode(e, p.getNext());
p.setNext(q);
size ++;
return true;
}
else p=p.getNext();
return false;
}
/**
* 删除线性表中序号为 i 的元素,并返回之
* @param i
* @return
* @throws OutOfBoundaryException
*/
@Override
public Object remove(int i) throws OutOfBoundaryException {
if (i<0||i>size)
throw new OutOfBoundaryException("错误,指定删除的序号越界");
SLNode p=getPreNode(i);
Object obj=p.getNext().getData();
p.setNext(p.getNext().getNext());
size --;
return obj;
}
/**
* /删除线性表中第一个与 e 相同的元素
* @param e
* @return
*/
@Override
public boolean remove(Object e) {
SLNode p=getPreNode(e);
if (p!=null){
p.setNext(p.getNext().getNext());
size --;
return true;
}
return false;
}
/**
* 替换线性表中序号为 i 的数据元素为 e,返回原数据元素
* @param i
* @param e
* @return
* @throws OutOfBoundaryException
*/
@Override
public Object replace(int i, Object e) throws OutOfBoundaryException {
if (i>0 || i>=size)
throw new OutOfBoundaryException("错误,指定的序号越界");
SLNode p=getNode(i);
Object obj=p.getData();
p.setData(e);
return obj;
}
/**
* 返回线性表中序号为 i 的数据元素
* @param i
* @return
* @throws OutOfBoundaryException
*/
@Override
public Object get(int i) throws OutOfBoundaryException {
if (i<0 || i>=size)
throw new OutOfBoundaryException("错误,指定的序号越界");
SLNode p=getNode(i);
return p.getData();
}
}
方法 getSize()、isEmpty()的时间复杂度均为Θ(1)。通过成员变量 size 可以直接判断出线性表中数据元素的个数以及线性表是否为空。
方法 getPreNode(Object e)、getPreNode(int i),其功能是找到数据元素 e 或线性表中 i 号数据元素所在结点的前驱结点。这两个方法的平均运行时间 T(n)≈n/2。
方法 replace(int i, Object e)、get(int i)的平均运行时间 T(n)≈n/2,比使用数组实现相应操作要慢得多。
方法 contains(Object e)、indexOf(Object e)主要是在线性表中查找某个数据元素。方法平均运行时间与使用数组的实现一样,都需要从线性表中 0 号元素出发,依次向后查找,因此方法运行时间 T(n) ≈ n/2。
方法 insert(int i, Object e)、remove(int i)的运行时间 T(n)≈n/2,与使用数组实现的运行时间相同。
方法 insertBefore(Object obj, Object e)、insertAfter(Object obj, Object e)、remove(Object e)的平均运行时间 T(n)≈n/2 < n,要优于使用数组实现的运行时间。
数组,即顺序存储结构(顺序表)。
初入数据结构,以学习总结为主,如果写的不详细或者有什么其它错误的地方希望各位dalao们指点指点。
参考的书籍:《数据结构-JAVA版》《数据结构与算法》