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

数据结构 No.5 双向链表

2018-02-02 10:36 218 查看

概念

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。 所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。 一般我们都构造双向循环链表。

解释

每个节点有两个连接:一个指向前一个节点,(当此“连接”为第一个“连接”时,指向空值或者空列表);而另一个指向下一个节点,(当此“连接”为最后一个“连接”时,指向空值或者空列表)




一个双向链表有三个整数值: 数值, 向后的节点链接, 向前的节点链接(图1)

在一些低级语言中, XOR-linking 提供一种在双向链表中通过用一个词来表示两个链接(前后),我们通常不提倡这种做法。

双向链表也叫双链表双向链表中不仅有指向后一个节点的指针,还有指向前一个节点的指针。这样可以从任何一个节点访问前一个节点,当然也可以访问后一个节点,以至整个链表。一般是在需要大批量的另外储存数据在链表中的位置的时候用。双向链表也可以配合下面的其他链表的扩展使用。

由于另外储存了指向链表内容的指针,并且可能会修改相邻的节点,有的时候第一个节点可能会被删除或者在之前添加一个新的节点。这时候就要修改指向首个节点的指针。有一种方便的可以消除这种特殊情况的方法是在最后一个节点之后、第一个节点之前储存一个永远不会被删除或者移动的虚拟节点,形成一个下面说的循环链表。这个虚拟节点之后的节点就是真正的第一个节点。这种情况通常可以用这个虚拟节点直接表示这个链表,对于把链表单独的存在数组里的情况,也可以直接用这个数组表示链表并用第0个或者第-1个(如果编译器支持)节点固定的表示这个虚拟节点。

具体实现

因为有了前面一篇(No4 链表)的讲述,这里就不在过多的讲述细则,Node实体类中包含三个属性,一个数据域,存放具体的内容,尾指针域存放下一个结点的地址,头指针域存放上一个结点的地址
/*
* 链结点,相当于是车厢
*/
public class Node {
//数据域
public long data;
//尾指针域(指向下一个结点)
public Node next;
//头指针域(指向前一个结点)
public Node previous;

public Node(long value) {
this.data = value;
}

/**
* 显示方法
*/
public void display() {
System.out.print(data + " ");
}
}

一个双向链表首先要有两个属性,一个头结点,一个尾结点,他们都是属性一个没有具体值的虚拟结点,便于控制整个链表。

从头结点插入:就是把传入的值的这个新链结点,拼接在链表的头部,变成头结点,之前的头结点变成这个新链结点的下一个结点。具体解释一下代码,插入一个具体的数据,这时会新建一个链结点,存入具体数据,判断整个链表目前是否为空(具体方法为判断头结点是否为空,至于为什么再往后看),如果为空,即第一次插入,这是头尾结点均为空,需要建立关系,所以先将这个结点赋值给尾结点。如果不为空,证明此链表已经存在数据,这时候需要将新建的链结点放入当前头结点的头指针域。至此无论之前的链表是否为空,此链表已经存在数据。然后新建链结点的尾指针域中存放的就是之前的头结点,数据域存放的就是插入的值,然后把这个链结点赋值给头结点。完成头结点插入
从尾结点插入:就是把传入的值的这个新链结点,拼接在链表的头部,变成头结点,之前的头结点变成这个新链结点的下一个结点。具体解释一下代码,新建链结点,存入传入的数据,然后判断整个链表是否为空,为空,赋值给头结点。不为空,给当前尾结点的尾指针域赋值为新建链结点,给新建链结点的头指针域赋值为当前为节点。最后一步,将新建结点赋值给尾结点。
从头结点删除:就是把链表的第一个链结点从链表中剔除,下一个结点赋值为头结点。下一个结点可以为空。证明链表目前只存在一个链结点。具体解释一下代码,首先先将头链结点保存下来,赋值给一个临时链结点,判断头结点的下一个结点。为空,把尾结点置为空。不为空,将下一个结点的头指针域赋值为空。最后一步将临时链结点也就是原头结点的下一个结点赋值给头结点。完成删除。
从尾结点删除:就是把链表的最后一个链结点从链表中剔除,上一个结点赋值为尾结点。特殊处理只剩下一个结点的情况。具体解释一下代码,首先判断头结点有没有下一个结点,也就是链表中是否只存在一个结点。如果是,将最后一个结点置为空即可。不是的话,将尾结点的前一个结点的尾指针域置为空,尾结点的上一个结点变为尾结点。
查找方法根据数据域:以下一个结点是否为空临界条件,每次比较当前链结点的数据域是否等于目标数据,不一致则根据尾指针域判断下一个,只到找出目标数据,没有返回null
删除方法,根据数据域:找到链表中第一个匹配目标元素的值,判断特殊情况,即头结点的数据域为目标结点,这时只需要将头结点的下一个结点变成头结点即可。如果不是就需要将目标链结点的上一个链结点的下一个结点指向目标链结点的下一个结点。完成按数据域删除
/*
* 双向链表
*/
public class DoubleLinkList {
//头结点
private Node first;
//尾结点
private Node last;

public DoubleLinkList() {
first = null;
last = null;
}

/**
* 插入一个结点,在头结点后进行插入
*/
public void insertFirst(long value) {
Node node = new Node(value);
if(isEmpty()) {
last = node;
} else {
first.previous = node;
}
node.next = first;
first = node;
}

/**
* 插入一个结点,从尾结点进行插入
*/
public void insertLast(long value) {
Node node = new Node(value);
if(isEmpty()) {
first = node;
} else {
last.next = node;
node.previous = last;
}
last = node;
}

/**
* 删除一个结点,在头结点后进行删除
*/
public Node deleteFirst() {
Node tmp = first;
if(first.next == null) {
last = null;
} else {
first.next.previous = null;
}
first = tmp.next;
return tmp;
}

/**
* 删除结点,从尾部进行删除
*/
public Node deleteLast() {
if(first.next == null) {
first = null;
} else {
last.previous.next = null;
}
last = last.previous;
return last;
}

/**
* 显示方法
*/
public void display() {
Node current = first;
while(current != null) {
current.display();
current = current.next;
}
System.out.println();
}

/**
* 查找方法
*/
public Node find(long value) {
Node current = first;
while(current.data != value) {
if(current.next == null) {
return null;
}
current = current.next;
}
return current;
}

/**
* 删除方法,根据数据域来进行删除
*/
public Node delete(long value) {
Node current = first;
while(current.data != value) {
if(current.next == null) {
return null;
}
current = current.next;
}

if(current == first) {
first = first.next;
} else {
current.previous.next = current.next;
}
return current;

}

/**
* 判断是否为空
*/
public boolean isEmpty() {
return (first == null);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: