您的位置:首页 > 理论基础 > 数据结构算法

数据结构专题——线性表之双链表及其Java实现

2017-10-31 22:18 555 查看
上一篇博客中,博主和大家一起实现了线性表的链式存储,由于链表中每个节点都只有一个指针域指向着后继节点,因此这种链表也叫单链表

这种链表的缺点显而易见,因为只能够单向遍历,即使我们想要访问最后一个元素也不得不从第一个元素开始遍历到最后一个元素。

为了解决这个问题,双链表应运而生。和单链表十分相似,唯一的区别在于每个节点当中包含两个指针域,分别指向着这个节点的前驱节点和后继节点。

因此,对于每一个数据元素ai来说,除了存储其本身的数据信息之外,还需要存储一个指示其直接后继元素的内存位置和一个其直接前驱元素的内存位置。我们把存储数据元素信息的域称为数据域,把存储直接后继元素位置的域称为后继指针域,把存储直接前驱元素位置的域称为前驱指针域。这三部分信息组成数据元素ai的存储映像,称为节点(Node)。n个节点连接成一个链表(a1,a2, … ,an),因为这个链表的每个节点中包含两个指针域(指向它的直接后继元素和前驱元素),所以这种链表被称为双链表。

与单链表相比,如果我们需要访问较为靠后的元素数据,只需要从尾节点向前遍历即可。

下面我们就来一起用Java实现一个双链表MyDoublyLinkedList

//双链表中的首标记节点,此节点不存放元素,前驱结点指向null,后继节点指向链表中的第一个元素节点
private Node firstFlagNode;
//双链表中的尾标记节点,此节点不存放元素,后继结点指向null,前驱节点指向链表中的最后一个元素节点
private Node lastFlagNode;
//双链表中元素的数量
private int size;

public MyDoublyLinkedList() {
firstFlagNode = new Node (null,null,null);
//首标记节点的后继指向尾标记节点,尾标记节点的前驱指向首标记节点
lastFlagNode = new Node (null,firstFlagNode,null);
firstFlagNode.nextNode = lastFlagNode;
size = 0;
}

//内部类Node,用来表示双链表中每一个数据元素
class Node{
//数据域
E data;
//前驱指针域,指向该节点的前一个节点
Node prevNode;
//后继指针域,指向该节点的后一个节点
Node nextNode;
public Node(E data, Node prevNode, Node nextNode) {
this.data = data;
this.prevNode = prevNode;
this.nextNode = nextNode;
}

}


接下来我们实现获取元素个数和查看是否为空的方法,很简单单行代码即可实现

@Override
public int size() {
// TODO Auto-generated method stub
return this.size;
}

@Override
public boolean isEmpty() {
// TODO Auto-generated method stub
return size()==0;
}


然后是两种不同的插入方法

@Override
public boolean add(E e) {
// TODO Auto-generated method stub
add(size(),e);
return true;
}

@Override
public void add(int index, E element) {
// TODO Auto-generated method stub
//当index=0时,相当于在第一个位置插入元素,当index=size时相当于在链表末尾插入一个元素
if(index<0 || index>size())
throw new IndexOutOfBoundsException();
Node newNode = new Node(element,null,null);
Node n = getPrevNodeByIndex(index);
//在它之后插入新的节点
n.nextNode.prevNode = newNode;
newNode.prevNode = n;
newNode.nextNode = n.nextNode;
n.nextNode = newNode;
this.size++;
return;
}
//私有方法获取当前index位置的前一个节点
private Node getPrevNodeByIndex(int index) {
//双链表能够从前后两个方向遍历,根据index的大小选择方向遍历
Node n = null;
if(index<size()/2) {
n = firstFlagNode;
for(int i=0; i<index; i++) {
n = n.nextNode;
}
}else {
n = lastFlagNode;
for(int i = size(); i>=index; i--) {
n = n.prevNode;
}
}

return n;
}


紧接着是两个不同的删除方法

@Overri
4000
de
public boolean remove(Object o) {
// TODO Auto-generated method stub
if(o == null) {
for(Node n = firstFlagNode.nextNode; n!=null&&n!=lastFlagNode; n = n.nextNode) {
if(n.data == null) {
//删除本节点
n.prevNode.nextNode = n.nextNode;
n.nextNode.prevNode = n.prevNode;
//清空旧节点的数据域与指针域,方便GC工作
n.data = null;
n.nextNode = null;
n.prevNode = null;
n = null;
this.size--;
return true;
}
}
return false;
}else {
for(Node n = firstFlagNode.nextNode; n!=null&&n!=lastFlagNode; n = n.nextNode) {
if(o.equals(n.data)) {
//删除本节点
n.prevNode.nextNode = n.nextNode;
n.nextNode.prevNode = n.prevNode;
//清空旧节点的数据域与指针域,方便GC工作
n.data = null;
n.nextNode = null;
n.prevNode = null;
n = null;
this.size--;
return true;
}
}
return false;
}
}

@Override
public E remove(int index) {
// TODO Auto-generated method stub
//检查index的边界
checkIndexValidation(index);
//获取index位置的节点
Node targetNode = getPrevNodeByIndex(index).nextNode;
E oldElement = targetNode.data;
//旧节点的前驱节点指向旧节点的后继节点,旧节点的后继节点指向旧节点的前驱节点
targetNode.prevNode.nextNode = targetNode.nextNode;
targetNode.nextNode.prevNode = targetNode.prevNode;
//清空旧节点的数据域与指针域,方便GC工作
targetNode.data = null;
targetNode.nextNode = null;
targetNode.prevNode = null;
targetNode = null;
this.size--;
return oldElement;
}

private void checkIndexValidation(int index) {
if(index<0 || index>=size())
throw new ArrayIndexOutOfBoundsException();
}


之后是获取,修改,查看是否包含,以及清空整个列表这几个简单的方法了:

@Override
public void clear() {
// TODO Auto-generated method stub
for(Node n = firstFlagNode.nextNode; n!=null&&n!=lastFlagNode; ) {
Node next = n.nextNode;
n.data = null;
n.nextNode = null;
n.prevNode = null;
n = next;
}
firstFlagNode.nextNode = lastFlagNode;
lastFlagNode.prevNode = firstFlagNode;
this.size = 0;
}

@Override
public E get(int index) {
// TODO Auto-generated method stub
checkIndexValidation(index);
return getPrevNodeByIndex(index).nextNode.data;
}

@Override
public E set(int index, E element) {
// TODO Auto-generated method stub
checkIndexValidation(index);
Node targetNode = getPrevNodeByIndex(index).nextNode;
E data = targetNode.data;
targetNode.data = element;
return data;
}
@Override
public boolean contains(Object o) {
// TODO Auto-generated method stub
if(o == null) {
for(Node n = firstFlagNode.nextNode; n!=null&&n!=lastFlagNode; n = n.nextNode) {
if(n.data == null) {
return true;
}
}
return false;
}else {
for(Node n = firstFlagNode.nextNode; n!=null&&n!=lastFlagNode; n = n.nextNode) {
if(o.equals(n.data)) {
return true;
}
}
return false;
}
}


双链表相比单链表来说耗费了更多的空间去存储前驱节点的位置,以此来获得某些操作更好的时间性能。这种以少量空间换取更优的性能做法,在很多地方也都可以见到。

本节博客的源码全部放在这里,欢迎大家下载,下载后记得修改源码中的package名字哦。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息