数据结构学习---基于双向链表实现的链接表

经过前面的学习,我们看到如果使用ListSLink对线性表进行每个元素的访问get(i)则需要n*n的时间,因为在使用链表实现取i号数据元素的操作时,需要将节点的引用给从链表前向后移动i次,而取i+1时又不能在上一次操作---取i好数据元素-----中受益,必须从链表前端开始定位,则访问线性表中每一个元素一次所需要的时间为n*n。这是难以接受的。
链接表可以看成是一组节点序列以及基于节点进行操作的线性结构抽象,或者说对链表的抽象。
链接表接口定义

package taxus.list;


public interface LinkedList {

//查询连接表当前的规模
public int getSize();

//判断是否为空
public boolean isEmpty();

//返回第一个节点
public Node first() throws OutOfBoundaryException;

//返回最后一个节点
public Node last() throws OutOfBoundaryException;

//返回P之前的节点
public Node getPre(Node p) throws InvalidNodeException, OutOfBoundaryException;

//返回P之后的节点
public Node getNext(Node p) throws InvalidNodeException, OutOfBoundaryException;

//将e作为第一个元素插入连接表,并返回e所在的节点
public Node insertFirst(Object e);

//将e作为最后一个节点插入连接表,并返回e所在的节点
public Node insertLast(Object e);

//将e插入到p之后并返回e所在的节点
public Node insertAfter(Node p, Object e) throws InvalidNodeException;

//将e插入到p之前并返回e所在的节点
public Node insertBefore(Node p, Object e) throws InvalidNodeException;

//删除给定位置的元素,并返回
public Object remove(Node p) throws InvalidNodeException;

//删除首元素,并返回
public Object removeFirst() throws OutOfBoundaryException;

//删除末元素,并返回
public Object removeLast() throws OutOfBoundaryException;

//将给定位置的元素替换为新元素,并返回被替换的元素
public Object replace(Node p, Object e) throws InvalidNodeException;

//元素迭代器
public Iterator elements();

}


InvalidNodeException是当作为参数的节点不合法是抛出的异常,节点p在以下情况认为是不合法的:1.p==null 2.p在连接表中不存在 3.在调用getPre(p),p已经是第一个存有数据的节点 3.在调用getNext(p), p已经是最后一个存有数据的节点。定义如下

package taxus.list;

public class InvalidNodeException extends RuntimeException {

public InvalidNodeException(){}

public InvalidNodeException(String err){
super(err);
}

}

为了实现双向链表结构,在之前定义了双向链表节点结构DLNode,由于DLNode实现了Node接口,所以DLNode本身就是一个节点,对链表内部而言就是组成链表的一部分,对外部而言就是一个可以存取数据元素的容器。
在使用双向链表实现链接表时,为了把链表中每个部分(头,中,尾)都当成普通节点进行处理,我们会在双向连表中添加两个哑元节点来实现链接表。分别为头节点,尾节点,它们都不存放数据元素,头节点的pre为空,尾节点的next为空,双向链表结构如图:
[img]http://dl2.iteye.com/upload/attachment/0094/8730/01602c36-973d-3b54-b1ae-daa7c96a4acf.png[/img]
在具有头节点的双向链表中插入和删除节点,无论插入和删除的节点位置在何处,因为首尾节点的存在,都可以转化为双向连表中某个节点的插入和删除;并且因为有首尾节点的存在,整个链表永远不为空。因此在插入和删除节点之后,也不用考虑链表由空变为非空或非空变为空的情况下的head和tail的指向问题,从而简化了操作。
基于双向链表实现的链接表

package taxus.list;


public class LinkedListDLNode implements LinkedList {

private int size;

private DLNode head;

private DLNode tail;

public LinkedListDLNode(){
size = 0;
head = new DLNode();
tail = new DLNode();
head.setNext(tail);
tail.setPre(head);
}

private DLNode checkPosition(Node p) throws InvalidNodeException{
if (p == null) {
throw new InvalidNodeException("错误, p为空");
}
if (p == head) {
throw new InvalidNodeException("错误:p指向头结点,非法");
}
if (p == tail) {
throw new InvalidNodeException("错误:p指向尾节点,非法");
}
DLNode node = (DLNode) p;
return node;
}

public int getSize() {
return size;
}

public boolean isEmpty() {
return size == 0;
}

public Node first() throws OutOfBoundaryException {
if (isEmpty()) {
throw new OutOfBoundaryException("错误:连接表为空");
}
return head.getNext();
}

public Node last() throws OutOfBoundaryException {
if (isEmpty()) {
throw new OutOfBoundaryException("错误:连接表为空");
}
return tail.getPre();
}

public Node getPre(Node p) throws InvalidNodeException,
OutOfBoundaryException {
DLNode node = checkPosition(p);
node = node.getPre();
if (node == head){
throw new OutOfBoundaryException("错误:已经是连接表前端");
}
return node;
}

public Node getNext(Node p) throws InvalidNodeException,
OutOfBoundaryException {
DLNode node = checkPosition(p);
node = node.getNext();
if (node == tail) {
throw new OutOfBoundaryException("错误:已经是连接表尾端。");
}
return node;
}

public Node insertFirst(Object e) {
DLNode node = new DLNode(e, head, head.getNext());
head.getNext().setPre(node);
head.setNext(node);
size++;
return node;
}

public Node insertLast(Object e) {
DLNode node = new DLNode(e, tail.getPre(), tail);
tail.getPre().setNext(node);
tail.setPre(node);
size++;
return node;
}

public Node insertAfter(Node p, Object e) throws InvalidNodeException {
DLNode node = checkPosition(p);
DLNode newNode = new DLNode(e, node, node.getNext());
node.getNext().setPre(newNode);
node.setNext(newNode);
size++;
return newNode;
}

public Node insertBefore(Node p, Object e) throws InvalidNodeException {
DLNode node = checkPosition(p);
DLNode newNode = new DLNode(e, node.getPre(), node);
node.getPre().setNext(newNode);
node.setPre(newNode);
size++;
return newNode;
}

public Object remove(Node p) throws InvalidNodeException {
DLNode node = checkPosition(p);
Object obj = node.getData();
node.getPre().setNext(node.getNext());
node.getNext().setPre(node.getPre());
size--;
return obj;
}

public Object removeFirst() throws OutOfBoundaryException {
return remove(head.getNext());
}

public Object removeLast() throws OutOfBoundaryException {
return remove(tail.getPre());
}

public Object replace(Node p, Object e) throws InvalidNodeException {
DLNode node = checkPosition(p);
Object obj = node.getData();
node.setData(e);
return obj;
}

public Iterator elements() {
return new LinkedListIterator(this);
}

}

LinkListDLNode中共有3个成员变量,其中head和tail分别指向双向链表中控的头节点和尾节点,他们本身不存储数据,size用来表明当前连表中数据元素个数,是该成员变量可以在常数时间内返回链接表规模,而不用从头到尾计算元素个数。除此之外双向链表中的各个方法时间复杂度都为常数,通过上述代码可以看出在使用节点作为参数时,链表实现插入,删除操作的优越性。
[b]
迭代器[/b]
迭代器是程序设计的一种模式,它属于设计模式中的行为模式,功能是提供一种方法顺序访问一个聚集对象中各个元素,而又不需要暴露该对象的内部表示。
多个对象聚在一起形成的总体称之为聚集,聚集对象是能够包容一组对象的容器对象。聚集依赖于聚集结构的抽象化,具有复杂性和多样性。例如数组就是一种基本的聚集。
聚集对象提供一种方法,允许用户按照一定的顺序访问其中的所有元素。而迭代器提供了一个访问聚集对象中各个元素的统一接口,简单的说迭代器就是对遍历操作的抽象。

迭代器接口

package taxus.list;

public interface Iterator {
//移动到第一个元素
public void first();

//移动到下一个元素
public void next() throws OutOfBoundaryException;

//检查迭代器中是否还有剩余的元素
public boolean isDone();

//返回当前元素
public Object currentItem() throws OutOfBoundaryException ;
}

下面给出LinkedList的迭代器

package taxus.list;

public class LinkedListIterator implements Iterator {

private LinkedList list;//连接表

private Node current;//当前节点

public LinkedListIterator(LinkedList list){
this.list = list;
if (list.isEmpty()) {
current = null;
} else {
current = list.first();
}
}

public void first() {
if (list.isEmpty()) {
current = null;
} else {
current = list.first();
}
}

public void next() throws OutOfBoundaryException {
if (isDone()) {
throw new OutOfBoundaryException("错误:已经没有元素");
}
if (current == list.last()) {
current = null;
} else {
current = list.getNext(current);
}
}

public boolean isDone() {
return current == null;
}

public Object currentItem() throws OutOfBoundaryException {
if (isDone()) {
throw new OutOfBoundaryException("错误:已经没有元素");
}
return current.getData();
}

}



由于迭代器是基于链接表聚集对象的,因此在类中有一个成员变量为链接表对象引用;除此之外还有个用于返回当前元素的节点对象引用。 代码中的方法均在常数时间内完成。
有了基于链接表聚集对象的迭代器实现以后,就可以对链接表中的数据使用迭代器接口提供的方法进行完整的遍历了。在双向链表实现的链接表代码中可以对外提供一个访问所有数据元素的迭代器,即elements()方法实现的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值