双向链表结点和单向链表结点的区别在于,多了一个指向前驱的指针。当这个节点是第一个节点时,前驱指针是一个空指针。
单向链表的结构如下图1所示:
图1 单向链表结构图
双向链表的结构如下图2所示:
图2 双向链表结构图
显然通过以上2个图的对比,可以看出链表两种形式的特点。双向链表显然我们在创建的时候需要永久维护的是first和last信息,要是链表不为空,first指向的是初始的链结点,last指向的是表尾的链结点(当只有一个链结点的时候,first和last指向同一个);初始链结点的prev指向null,链尾的链结点next指向null。
对双向链表的基本操作有插入、删除和非空,其中插入有insertFirst()即在首位插入新的链结点、insertLast()即在末尾插入新的链结点、insertAfter()即在某一元素值后插入新的链结点;同理删除有deleteFirst()、deleteLast()和deleteKey()。这几个插入和删除方法,单纯的靠想象是很难的,在敲写代码的时候我们应该结合图,看着图示来写逻辑,要不然绕来绕去一会儿就写懵了。
首先链结点Link应有的属性如下代码所示:
/**
* 链结点
*/
public class Link {
public int data;
public Link next;
public Link previous;
public Link(int data) {
this.data = data;
}
}
对DoubleLinkList初始时有:
/**
* 双向列表
*/
public class DoubleLinkedList {
public Link first = null;
public Link last = null;
/**
* 插入一个链结点
*
* @param data
*/
public void insertFirst(int data) { //伪代码 }
/**
*从队尾插入链结点
*
* @param data
*/
public void insertLast(int data) { //伪代码 }
//等等其他对双向链表的操作方法
}
首先是非空判断,显然当first==null时说明链表中没有链结点,代码:
/**
* 链表是否为空
*
* @return
*/
public boolean isEmpty() {
return first == null;
}
2.insertFirst()从首位插入新的链结点,先画图,图示如下:
图3 表头插入
根据图示敲代码逻辑,具体如下:
/**
* 插入一个链结点
*
* @param data
*/
public void insertFirst(int data) {
Link newLink = new Link(data); //根据数值创建结点
if (isEmpty()) {
last = newLink;
} else {
first.previous = newLink;//如果不为空的话 一开始first指向的初始结点的prev字段指向新的newlink 从这里我们可以看出first只是一个引用
newLink.next = first;
}
first = newLink;
}
3.insertLast()从表尾插入新的链结点,逻辑和表头插入比较相似,代码如下:
/**
* 从队尾插入链结点
*
* @param data
*/
public void insertLast(int data) {
Link newLink = new Link(data);
if (isEmpty()) {
first = newLink;
} else {
last.next = newLink;
newLink.previous = last;
}
last = newLink;
}
4.deleteFirst()删除操作,删除操作就是从双向链表中拿出第一个结点,返回值类型为Link:
/**
* 删除第一个结点
*
* @return
*/
public Link deleteFirst() {
if (!isEmpty()) { //首先判断链表是否为空
Link current = first;
if (current.next == null) {
//如果整个链表中只有一个元素 那就是最后一个 直接让last指向null 就可以了
last = null;
} else {
//当前结点的下一个结点的previous指向是null 表示着下一个结点是第一个链结点
current.next.previous = null;
}
first = current.next;//无论是只有一个结点(只有一个的话first =null) 多个结点当前要删除的结点的下一个都是为第一个
return current;
} else {
System.out.println("the doubleLinkList is null , cannot do delete");
return null;
}
}
5.deleteLast()删除表尾结点,返回值类型也是Link:
/**
* 删除最后一个结点
*
* @return
*/
public Link deleteLast() {
if (!isEmpty()) {
Link current = last;
if (current.previous == null) { //满足条件 说明链表中只有一个结点 直接让first指向null
first = null;
} else {
current.previous.next = null;//末尾结点的前一结点的next指向null
}
last = current.previous;
return current;
} else {
System.out.println("the doubleLinkList is null , cannot do delete");
return null;
}
}
以上操作相对简单,依然是敲代码的时候画图理解。
6.insertAfter(int key,int data)在某一要素结点后插入新的结点,要处理的东西比较多,图4中有比较好的解释:
图4 某结点current后插入新的结点
首先我们要找到要出入的位置,之后处理前后结点的问题,代码如下:
/**
* 在指定结点后面插入新的data元素结点
*
* @param key 根据key找到指定结点
* @param data 要插入的新结点
*/
public String insertAfter(int key, int data) {
Link newLink = new Link(data);
//1.先找到key对应的位置
Link current = first;
while (current != null && current.data != key) {
current = current.next;
if (current == null) {
//说明走到了链表的最后一个结点 依然没有对应的结点 就输出没有此节点
return "cannot find the key,insert failed";
}
}
if (current == last) {
//当前链表的最后一项
newLink.next = null;
last = newLink;
} else {
//2.要操作4个节点 最好是自己画图体会整个过程
newLink.next = current.next;
current.next.previous = newLink;
}
newLink.previous = current;
current.next = newLink;
return "insert succeed";
}
我设定的返回值类型是string,根据链表的情况返回不同的处理信息。
7.deleteKey(int key)根据key值找到对应的结点,然后处理删除结点之后的前后结点关系:
/**
* 根据关键元素值 删除对应的链结点
*
* @param key
* @return
*/
public Link deleteKey(int key) {
//1.先找到对应的链结点 从头开始遍历找对应的值 这样的查找是O(N)的 肯定会有更高效的
Link current = first;
while (current != null && current.data != key) {
current = current.next;
if (current == null) {
//说明遍历到了链表的尾部 依然找不到对应的链结点
return null;
}
}
//2.说明找到key对应的链结点 current 删除之
if (current == first) {
//删除的是第一个链结点位置
first = current.next;
current.next.previous = null;
} else if (current == last) {
//删除的是最后一个链结点
last = current.previous;
current.previous.next = null;
} else {
current.next.previous = current.previous;
current.previous.next = current.next;
}
//todo 以上代码可以优化成以下形式 可能稍微难理解一点 假设要删除的点是首位 current.previous就是null
// if (current == first) {
// //要是删除第一个链结点的话
// first = current.next;
// } else {
// current.previous.next = current.next;
// }
//
// if (current == last) {
// //要是删除最后一个链结点的话
// last = current.previous;
// } else {
// current.next.previous = current.previous;
// }
return current;
}
/**
* 从头往后遍历整个链表
*/
public void showAllFromFirst() {
if (!isEmpty()) {
Link current = first;
while (current != null) {
System.out.print(current.data + "\t");
current = current.next;
}
} else {
System.out.println("当前双向链表是空");
}
System.out.println("\n");
}
/**
* 从尾到头遍历整个链表
*/
public void showAllFromLast() {
if (!isEmpty()) {
Link current = last;
while (current != null) {
System.out.print(current.data + "\t");
current = current.previous;
}
} else {
System.out.println("当前双向链表是空");
}
System.out.println("\n");
}
测试类:
/**
* 双向链表测试
*/
public class DoubleLinkedListTest {
public static void main(String[] args) {
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
doubleLinkedList.insertFirst(4);
doubleLinkedList.insertFirst(2);
doubleLinkedList.insertFirst(5);
doubleLinkedList.insertLast(7);
doubleLinkedList.insertLast(1);
doubleLinkedList.insertLast(6); //5 2 4 7 1 6
doubleLinkedList.deleteFirst(); //2 4 7 1 6
System.out.println("取出链表末尾的元素"+doubleLinkedList.deleteLast().data); //取出6后为2 4 7 1
String insertAfteResult = doubleLinkedList.insertAfter(6, 9);//"cannot find the key,insert failed"
System.out.println(insertAfteResult);
doubleLinkedList.showAllFromFirst(); // 2 4 7 1
System.out.println("删除后输出结果");
doubleLinkedList.deleteKey(4); // 2 7 1
doubleLinkedList.showAllFromFirst();
System.out.println("从后往前输出"); // 1 7 2
doubleLinkedList.showAllFromLast();
}
}
输出结果:
以上代码,如有问题请大家及时指出。关于单向链表请在此链接中查看数据结构之链表1_单链表及基本概念。