Java基础 - 线性表之链式存储结构-双向链表

如果为每个节点保留两个引用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)));
	}
}


测试结果为:

从上面的程序可以看出,由于双向链表需要同时维护两个方向的指针,因此添加节点、删除节点时的指针维护成本更大;但双向链表具有两个方向的指针,
因此可以向两个方向搜索节点,因此双向链表在搜索节点。删除指定索引处的节点时具有较好的性能。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值