上一节,笔者已经简单的介绍了单链表,下面笔者将介绍一下双链表,在java类库中也有这样的双链表LinkedList 读者也可以参考java类库中的源代码进行了解。翻看文档,在里面有详细的介绍,也比笔者实现的一个 DoublyLinkList更加详细。在LinkedList 中更是有Iterator 和 ListIterator 迭代器,Iterator 只能对集合只能是向前遍历,而ListIterator 即可以向前遍历也可以向后遍历,并且迭代器对象中还有add方法,非常方便。关于LinkedList,笔者在这里不多多介绍,如果想进一步了解可参见源码。
在这里笔者定义了一个 Link 节点类,为了和上一节中的Node 节点区别。 Link 节点中有数据域:nodeValue) 、前指针(前引用):previous 以及 后指针(后引用): next
previous | nodeValue | next |
T nodeValue;
Link<T> next; // 指针域保存着下一节点的引用
Link<T> previous; // 指针域保存着上一个节点的引用
Link(T nodeValue, Link<T> next, Link<T> previous) {
this.nodeValue = nodeValue;
this.next = next;
this.previous = previous;
}
Link(T nodeValue) {
this(nodeValue, null, null);
}
接下来笔者定义了一个
双链表 DoublyLinkList这是一个带头结点head 和尾节点 end 的 双链表。
private Link<T> head; // 双链表的 表头
private Link<T> end; // 双链表的表尾
public DoublyLinkList() {
head = null;
end = null;
}
插入: 由于双链表有两个指针头指针以及尾指针,所以在表头和表尾插入效率非常高,直接插入即可,时间复杂度为常数。但是在制定元素后面插入需要先找到插入位置,然后再进行插入。时间复杂度为 O(n)。
/**
* 在表头插入一个节点.
* @param t 需要插入的元素
*/
public void insertFirst(T t) {
Link<T> newLink = new Link<T>(t);
if (isEmpty()) {
end = newLink; // 空链表,直接将新节点赋给 end 指针
} else {
head.previous = newLink; // 首先将新节点的引用赋给,目前头节点的 previous域
}
newLink.next = head;// 此时新节点为头节点,原来头节点变成第二个节点,所以要将head(原来头结点引用)给新头节点的next域
head = newLink; // 新头节点引用赋给head
}
/**
* 在表尾插入一个节点
* @param t 需要插入的元素
*/
public void insertLast(T t) {
Link<T> newLink = new Link<T>(t);
if (isEmpty()) { // 空链表,直接将 新节点引用赋给 head指针
head = newLink;
} else {
end.next = newLink; // 首先将新节点的引用赋给,目前尾节点的 next 域
newLink.previous = end; // 此时新节点为尾节点,原来头节点变成第二个节点,所以要将end(原来尾结点引用)给新尾节点的previous域
}
end = newLink; // 新尾节点引用赋给end
}
/**
* 在链表中指定数据项后插入元素
*
* @param known 指定数据项,这个数据项需要在链表中存在才能正确插入
* @param t 需要插入的数据项
*/
public boolean insertAfter(T known, T t) {
Link<T> current = head;
while (!known.equals(current.nodeValue)) { // 从链表表头依次查找,直到找到数据项。
current = current.next;
if (current == null) {
return false; // 没有找到,返回false
}
}
删除: 与插入一样,在表头和表尾删除效率非常高,时间复杂度为常数,而在链表中间删除,则需要先找到删除的元素才能删除,所以时间复杂度为O(n)。
/**
* 删除头节点
*/
public Link<T> removeFirst() {
if (isEmpty()) {
throw new RuntimeException("链表为空,没有删除项!");
}
Link<T> temp = head; // 保存head 并返回
if (head.next == null) { // 链表只有一个节点
end = null;
} else {
head.next.previous = null; // 第一个节点的 previous 始终为空 这里head.next.previous指向原链表的第二个
} // 第一个节点删除后,第二个节点就变为了首节点,所以设置它的 previous为空
head = head.next; // 新头节点引用赋给head
return temp;
}
/**
* 删除尾节点
*/
public Link<T> removeLast() {
if (isEmpty()) {
throw new RuntimeException("链表为空,没有删除项!");
}
Link<T> temp = end; // 保存head 并返回
if (head.next == null) { // 链表只有一个节点
head = null;
} else {
end.previous.next = null; // 最后一个节点的 next 始终为空; 这里end.previous.next指向元链表的倒数第二个
} // 最后一个节点删除后,倒数第二个节点就变成了尾节点,所以设置它的 next 为空
end = end.previous; // 新的尾节点给 end
return temp;
}
/**
* @param t 需要删除的元素
*/
public Link<T> remove(T t) {
Link<T> current = head;
while (!t.equals(current.nodeValue)) { // 没有找到节点
current = current.next;
if (current == null) {
return null;
}
}
if (current == head) { // 节点在表头
head = current.next;
} else {
current.previous.next = current.next;
}
if (current == end) { // 节点在表尾
end = current.previous;
} else {
current.next.previous = current.previous;
}
return current;
}
获取表头表尾的元素以及判断是否为空: 效率非常高,直接返回即可。
/**
* 取得链表的表头存储的元素
*/
public Link<T> getFirst() {
if (isEmpty()) {
throw new RuntimeException("链表为空!");
}
return head;
}
/**
* 取得链表的表尾存储的元素
*/
public Link<T> getLast(){
if (isEmpty()) {
throw new RuntimeException("链表为空!");
}
return end;
}
/**判断是否为空
*/
public boolean isEmpty() {
return head == null;
}
获取链表的大小以及遍历链表(从前向后、从后向前)。
/** 获取链表的大小
*/
public int size(){
int count = 0;
if (isEmpty()) {
return count;
}
Link<T> current = head;
while (current != null) {
current = current.next;
count++;
}
return count;
}
/**
* 从链表的前面向后面遍历
*/
public void disPlayForward() {
if (isEmpty()) {
throw new RuntimeException("链表为空!");
}
Link<T> current = head;
while (current != null) {
System.out.print(current.nodeValue + " ");
current = current.next;
}
}
/**
* 从链表的后面往前面遍历
*/
public void disPlayBackward() {
if (isEmpty()) {
throw new RuntimeException("链表为空!");
}
Link<T> current = end;
while (current != null) {
System.out.print(current.nodeValue + " ");
current = current.previous;
}
}
从上面可以看出,在双链表中由于加入了前指针和后指针,访问链表比单链表更加便捷,可以从不同方向访问,但是双链表还是不支持随机访问,若要查看某个元素必须从头或者从尾开始访问,并没有捷径可走。即使是在类库中的LinkedList.get(n) 方法效率也并不高,即使通过 索引n 和 size()/2 进行比较后再选择从前还是从后遍历,都要进行遍历搜索。
对于 顺序表和链表的选择对于每个程序猿来说,在程序中是十分重要的,若需要随机访问而不需大量中间的插入删除,可以选择ArrayList ; 但是若需大量的插入和删除则需考虑用链表LinkedLis。 对于线性表的介绍就暂时到这里,对于线性表的学习读者仍需借助java类库,以及深入了解java 的集合框架,里面提供了大量的接口(interface) 和实现类,读者和笔者需要大量的学习及使用才能深入掌握就java 提供给我们的便利。
全部代码及测试
DoublyLinkist 类
package org.TT.LinkedList;
public class DoublyLinkList<T> {
private Link<T> head; // 双链表的 表头
private Link<T> end; // 双链表的表尾
public DoublyLinkList() {
head = null;
end = null;
}
/**
* 在表头插入一个节点.
* @param t 需要插入的元素
*/
public void insertFirst(T t) {
Link<T> newLink = new Link<T>(t);
if (isEmpty()) {
end = newLink; // 空链表,直接将新节点赋给 end 指针
} else {
head.previous = newLink; // 首先将新节点的引用赋给,目前头节点的 previous域
}
newLink.next = head;// 此时新节点为头节点,原来头节点变成第二个节点,所以要将head(原来头结点引用)给新头节点的next域
head = newLink; // 新头节点引用赋给head
}
/**
* 在表尾插入一个节点
* @param t 需要插入的元素
*/
public void insertLast(T t) {
Link<T> newLink = new Link<T>(t);
if (isEmpty()) { // 空链表,直接将 新节点引用赋给 head指针
head = newLink;
} else {
end.next = newLink; // 首先将新节点的引用赋给,目前尾节点的 next 域
newLink.previous = end; // 此时新节点为尾节点,原来头节点变成第二个节点,所以要将end(原来尾结点引用)给新尾节点的previous域
}
end = newLink; // 新尾节点引用赋给end
}
/**
* 在链表中指定数据项后插入元素
*
* @param known 指定数据项,这个数据项需要在链表中存在才能正确插入
* @param t 需要插入的数据项
*/
public boolean insertAfter(T known, T t) {
Link<T> current = head;
while (!known.equals(current.nodeValue)) { // 从链表表头依次查找,直到找到数据项。
current = current.next;
if (current == null) {
return false; // 没有找到,返回false
}
}
Link<T> newLink = new Link<>(t);
if (current == end) { // 指定数据项在表尾
newLink.next = null; // 新节点在表尾,所有 next 域为空
end = newLink; // end 指针指向 新的尾节点
} else {
newLink.next = current.next; // 不在表尾 一 将current节点的后一节点的引用给 newLink.next 域
current.next.previous = newLink;// 二 将新节点的引用 给 current节点的后一节点的previous域 (current.next.previous)
}
newLink.previous = current; // 三 current节点的引用 给新节点的previous域(newLink.previous)
current.next = newLink; // 四 新节点的引用赋给 current节点的 next域 (current.next)
return true;
}
/**
* 删除头节点
*/
public Link<T> removeFirst() {
if (isEmpty()) {
throw new RuntimeException("链表为空,没有删除项!");
}
Link<T> temp = head; // 保存head 并返回
if (head.next == null) { // 链表只有一个节点
end = null;
} else {
head.next.previous = null; // 第一个节点的 previous 始终为空 这里head.next.previous指向原链表的第二个
} // 第一个节点删除后,第二个节点就变为了首节点,所以设置它的 previous为空
head = head.next; // 新头节点引用赋给head
return temp;
}
/**
* 删除尾节点
*/
public Link<T> removeLast() {
if (isEmpty()) {
throw new RuntimeException("链表为空,没有删除项!");
}
Link<T> temp = end; // 保存head 并返回
if (head.next == null) { // 链表只有一个节点
head = null;
} else {
end.previous.next = null; // 最后一个节点的 next 始终为空; 这里end.previous.next指向元链表的倒数第二个
} // 最后一个节点删除后,倒数第二个节点就变成了尾节点,所以设置它的 next 为空
end = end.previous; // 新的尾节点给 end
return temp;
}
/**
* @param t 需要删除的元素
*/
public Link<T> remove(T t) {
Link<T> current = head;
while (!t.equals(current.nodeValue)) { // 没有找到节点
current = current.next;
if (current == null) {
return null;
}
}
if (current == head) { // 节点在表头
head = current.next;
} else {
current.previous.next = current.next;
}
if (current == end) { // 节点在表尾
end = current.previous;
} else {
current.next.previous = current.previous;
}
return current;
}
/**
* 取得链表的表头存储的元素
*/
public Link<T> getFirst() {
if (isEmpty()) {
throw new RuntimeException("链表为空!");
}
return head;
}
/**
* 取得链表的表尾存储的元素
*/
public Link<T> getLast(){
if (isEmpty()) {
throw new RuntimeException("链表为空!");
}
return end;
}
/**判断是否为空
*/
public boolean isEmpty() {
return head == null;
}
/** 获取链表的大小
*/
public int size(){
int count = 0;
if (isEmpty()) {
return count;
}
Link<T> current = head;
while (current != null) {
current = current.next;
count++;
}
return count;
}
/**
* 从链表的前面向后面遍历
*/
public void disPlayForward() {
if (isEmpty()) {
throw new RuntimeException("链表为空!");
}
Link<T> current = head;
while (current != null) {
System.out.print(current.nodeValue + " ");
current = current.next;
}
}
/**
* 从链表的后面往前面遍历
*/
public void disPlayBackward() {
if (isEmpty()) {
throw new RuntimeException("链表为空!");
}
Link<T> current = end;
while (current != null) {
System.out.print(current.nodeValue + " ");
current = current.previous;
}
}
public static void main(String[] args) {
DoublyLinkList<String> list = new DoublyLinkList<>();
list.insertFirst("C");
list.insertFirst("B");
list.insertFirst("A");
list.insertLast("D");
list.insertLast("E");
list.insertLast("F");
System.out.println("数组大小: " + list.size());
System.out.println("从前向后遍历:");
list.disPlayForward();
System.out.println();
System.out.println("从后向前遍历:");
list.disPlayBackward();
System.out.println();
System.out.println("表头: " + list.getFirst().nodeValue + " 表尾: " + list.getLast().nodeValue);
System.out.println();
list.removeFirst();
System.out.println("删除第一个后,从前往后遍历:");
list.disPlayForward();
System.out.println();
list.removeLast();
System.out.println("再删除最后一个后,从后往前遍历:");
list.disPlayBackward();
System.out.println();
System.out.println("删除指定节点(D):" + list.remove("D").nodeValue);
System.out.println("在指定节点(C)后面添加节点,若添加成功返回(true):"
+ list.insertAfter("C", "TT"));
System.out.println("从前向后遍历:");
list.disPlayForward();
}
}
package org.TT.LinkedList;
/**
* 双向链表时, Link 存储三个变量 :nodeValue next previous
*
* 这里没有设置Link 的属性为 private 主要是为了其他类访问方便,若要进行保护,可以提供相关的get set方法进行访问
*
*/
public class Link<T> {
T nodeValue;
Link<T> next; // 指针域保存着下一节点的引用
Link<T> previous; // 指针域保存着上一个节点的引用
Link(T nodeValue, Link<T> next, Link<T> previous) {
this.nodeValue = nodeValue;
this.next = next;
this.previous = previous;
}
Link(T nodeValue) {
this(nodeValue, null, null);
}
}