线性表之双链表--DoublyLinkList,类似于类库中的 LinkedList

上一节,笔者已经简单的介绍了单链表,下面笔者将介绍一下双链表,在java类库中也有这样的双链表LinkedList 读者也可以参考java类库中的源代码进行了解。翻看文档,在里面有详细的介绍,也比笔者实现的一个 DoublyLinkList更加详细。在LinkedList  中更是有Iterator 和 ListIterator 迭代器,Iterator 只能对集合只能是向前遍历,而ListIterator 即可以向前遍历也可以向后遍历,并且迭代器对象中还有add方法,非常方便。关于LinkedList,笔者在这里不多多介绍,如果想进一步了解可参见源码。

在这里笔者定义了一个 Link 节点类,为了和上一节中的Node 节点区别。 Link 节点中有数据域:nodeValue) 、前指针(前引用):previous  以及 后指针(后引用): next

previousnodeValuenext   

	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();
	}

}


节点类  Link 
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);
	}

}


测试结果


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值