如果为每个节点保留两个引用prev和next,让prev指向当前节点的上一个节点,让next指向当前节点的下一个节点,此时的链表既可以向后依次访问每个节点,也可以向前依次访问链表的每个节点,这种形式的链表被称为双向链表。示意图如下:
双向链表是一种对称结构,它克服了单链表上指针单向性的缺点,其中每个节点既可以向前引用,也可以向后引用,
这样可以方便地插入、删除数据元素。
与单链表相似的是,如果将链表的header节点与tail节点连在一起就构成了双向循环链表。
1、双向链表的查找
由于双向链表既可以从header节点开始依次向后搜索每个节点,也可以从tail节点依次向前搜索每个节点,因此
当程序试图从双向链表值搜索指定索引处的节点时,既可以从该链表的header节点开始搜索,也可以从该链表的tail
节点开始搜索。至于到底是应该从header节点开始搜索还是从tail节点开始搜索,则取决于被搜索的节点是更靠近header还是tial节点。
一般来说,可以通过被搜索index的值来判断它更靠近header还是更靠近tail。如果index < size/2;则说明它更靠近header;反之,,则可判断
它更靠近tail节点,那就应该从tail节点开始搜索。
2、双向链表的插入
双向链表的插入更加的复杂,向双向链表中插入一个新节点必须同时修改两个方向的指针(即引用)。如下图所示双向链表的插入操作:
3、双向链表的删除
在双向链表中删除一个节点也需要同时修改两个方向的指针,双向链表中的删除操作如下示意图:
下面是代码实现:
/**
* 双向链表
* @author wb
*
*/
public class DuLinkList<T> {
//定义一个内部类,也就是节点
private class Node{
//保存数据元素
private T data;
//向前引用
private Node prev;
//向下引用
private Node next;
@SuppressWarnings("unused")
public Node(){
}
public Node(T data, Node prev, Node next){
this.data = data;
this.prev = prev;
this.next = next;
}
}
private Node header;
private Node tail;
public int size;
//双向链表初始化1
public DuLinkList(){
header = null;
tail = null;
size = 0;
}
//双向链表初始化2
public DuLinkList(T element){
header = new Node(element, null, null);
tail = header;
size ++;
}
//返回该双向链表的长度
public int size(){
return size;
}
//判断双向链表是否为空链表
public boolean isEmpty(){
return size == 0;
}
//清空双向链表
@SuppressWarnings("null")
public void clear(){
Node clearNode = header;
for(int i = 0; i < size && clearNode != null; i ++,clearNode = clearNode.next){
clearNode = null;
}
header = null;
tail = null;
size = 0;
}
//返回索引 index处的数据元素
public T get(int index){
return getNodeByIndex(index).data;
}
//返回索引 index处的节点
private Node getNodeByIndex(int index) {
errMsgForGetNodeByIndexOutOfArraysBounds(index);
//根据索引的值判断是从header开始搜索还是从tail开始搜索
if(index <= size/2){
//从header开始搜索
Node current = header;
for(int i = 0; i <= size/2 && current != null; i ++,current = current.next){
if(i == index){
return current;
}
}
}else{
//从tail开始搜索
Node current = tail;
for(int i = size - 1; i > size/2 && current != null; i --,current = current.prev){
if(i == index){
return current;
}
}
}
return null;
}
//获取指定元素在双向链表中的第一个索引位置
public int indexOf(T element){
if(isEmpty()){
return -1;
}
Node current = header;
for(int i = 0; i < size && current != null; i ++, current = current.next){
if(current.data == element){
return i;
}
}
return -1;
}
//获取指定元素在值相等的情况下的第一个在双向链表中的索引位置
public int locate(T element){
if(isEmpty()){
return -1;
}
Node current = header;
for(int i = 0; i < size && current != null; i ++, current = current.next){
if(current.data.equals(element)){
return i;
}
}
return -1;
}
//向双向链表的index索引位置插入数据元素 element
public void insert(int index, T element){
errMsgForInsertIndexOutOfArraysBounds(index);
//如果还是空链表
if(header == null){
//采用尾插法
addAtTail(element);
}else{
//插入的是双向链表的第一个位置
if(index == 0){
addAtHeader(element);
}else{
//插入位置前一个节点
Node preNode = getNodeByIndex(index - 1);
//要插入的新节点
Node newNode = new Node(element, preNode, preNode.next);
preNode.next = newNode;
newNode.next.prev = newNode;
size ++;
}
}
}
//采用头插法向双向链表插入数据元素 element
public void addAtHeader(T element){
//创建next指向原链表的第一个节点
//并把它重新作为header
header = new Node(element, null, header);
<span style="white-space:pre"> </span>header.next.prev = header;
/**
* Node newNode = new Node(element, null, header);
* header.prev = newNode;
* header = newNode;
*/
//如果插入前为空链表
if(tail == null){
tail = header;
}
size ++;
}
//采用尾插法向双向链表插入数据元素 element
public void addAtTail(T element){
//如果插入前为空链表
if(header == null){
header = new Node(element, null, null);
tail = header;
}else{
//完成新加的节点指向前面的节点
Node newNode = new Node(element, tail, null);
//完成节点指向后面的新添加的节点
tail.next = newNode;
//收尾
tail = newNode;
}
size ++;
}
//删除并返回双向链表中指定索引处的元素
public T delete(int index){
errMsgForDeleteIndexOutOfArraysBounds(index);
Node del;
//要删除双向链表中的第一个元素+节点
if(index == 0){
del = header;
header = header.next;
header.prev = null;
//要删除双向链表中的最后一个元素+节点
}else if(index == size - 1){
del = tail;
tail = tail.prev;
tail.next = null;
}else{
Node preNode = getNodeByIndex(index - 1);
del = preNode.next;
preNode.next = del.next<span style="font-family: Arial, Helvetica, sans-serif;">;</span>
del.next.prev = preNode;
del.next = null;
del.prev = null;
}
return del.data;
}
//删除并返回双向链表中最后的元素
public T remove(){
return delete(size - 1);
}
//该双向链表的toString方法
public String toString(){
if(isEmpty()){
return "[]";
}else{
StringBuffer sb = new StringBuffer("[");
Node current = header;
for(int i = 0; i < size && current != null;current = current.next){
sb.append(current.data.toString() + ",");
}
return sb.toString().substring(0, sb.length() - 1) + "]";
}
}
//该双向链表的headerToString方法
public String headerToString(){
if(isEmpty()){
return "[]";
}else{
StringBuffer sb = new StringBuffer("[");
for(Node current = header; current != null; current = current.next){
sb.append(current.data.toString() + ",");
}
return sb.toString().substring(0, sb.length() - 1) + "]";
}
}
//该双向链表的tailToString方法
public String tailToString(){
if(isEmpty()){
return "[]";
}else{
StringBuffer sb = new StringBuffer("[");
for(Node current = tail; current != null; current = current.prev){
sb.append(current.data.toString() + ",");
}
return sb.toString().substring(0, sb.length() - 1) + "]";
}
}
private void errMsgForGetNodeByIndexOutOfArraysBounds(int index){
if(index < 0 || index > size - 1){
throw new IndexOutOfBoundsException( "双向链表索引越界:" + index );
}
}
private void errMsgForDeleteIndexOutOfArraysBounds(int index){
if(index < 0 || index > size - 1){
throw new IndexOutOfBoundsException( "双向链表索引越界:" + index );
}
}
private void errMsgForInsertIndexOutOfArraysBounds(int index){
if(index < 0 || index > size){
throw new IndexOutOfBoundsException( "双向链表索引越界:" + index );
}
}
}
测试类如下:
import com.yc.list.DuLinkList;
public class DuLinkListTest {
public static void main(String[] args) {
DuLinkList<Student> list = new DuLinkList<Student>();
Student s = new Student("WB","湖南工学院",22);
list.addAtTail( s );
list.addAtTail(new Student("SJX","高铁学院",22));
System.out.println( list.tailToString());
System.out.println( list.headerToString());
System.out.println();
list.addAtHeader(new Student("QYB", "湖南工学院", 23));
System.out.println( list.tailToString());
System.out.println( list.headerToString());
System.out.println();
list.insert(1, new Student());
System.out.println( list.tailToString());
System.out.println( list.headerToString());
System.out.println();
list.remove();
System.out.println( list.tailToString());
System.out.println( list.headerToString());
System.out.println();
list.delete(1);
System.out.println( list.tailToString());
System.out.println( list.headerToString());
System.out.println();
System.out.println(list.indexOf(new Student("WB","湖南工学院",22)));
System.out.println(list.locate(new Student("WB","湖南工学院",22)));
}
}
从上面的程序可以看出,由于双向链表需要同时维护两个方向的指针,因此添加节点、删除节点时的指针维护成本更大;但双向链表具有两个方向的指针,
因此可以向两个方向搜索节点,因此双向链表在搜索节点。删除指定索引处的节点时具有较好的性能。