线性表(三)--- 双向链表

就是给每个节点保留两个引用prev和next,其中prev指向前一个节点,next指向后一个节点。该链表既可以向后依次访问每个节点,也可以向前其次访问每个节点,以这种形式保存的节点称为双向链表。

查找:

既可以从header(头节点),也可以从tail(尾节点)开始寻找指定的节点。一般的是根据搜索的index值来判断跟接近于头节点还是尾节点。即比较index和size(链表的大小)/2的大小。若是index<size/2,则从头节点header开始寻找,若是index>2,则从尾节点tail开始查找。

插入和删除:
都需要同时修改两个引用值。

如图(制作粗糙,请见谅):

参考代码:
public class DuLinkedList<T> {
	//定义一个节点类
	private class Node{
		//保存数据
		private T data;
		//定义指向上一个节点 和 指向下一个节点的引用
		private Node prev;
		private Node next;
		public Node(){

		}
		public Node(T data, Node prev, Node next){
			this.data = data;
			this.prev = prev;
			this.next = next;
		}
	}
	//保存头节点 和 尾节点
	private Node header;
	private Node trailer;
	//链表中的节点数
	private int size;

	//创建空链表
	public DuLinkedList(){
		header = null; 
		trailer = null;
	}

	//根据指定元素创建链表
	public DuLinkedList(T element){
		header = new Node(element, null, null);
		trailer = header;
		size ++;
	}

	//返回链表的长度
	public int length(){
		return size;
	}

	//查找1 回去index处的节点
	public T get(int index){
		return getNodeByIndex(index).data;
	}

	//根据索引获取节点  可以从头 或者 尾开始查找吧  -- 二分法
	private Node getNodeByIndex(int index) {

		if(index < 0 || index > size - 1){
			throw new IndexOutOfBoundsException("线性表越界");
		}
		if(index <= size / 2){
			Node current = header;
			for(int i = 0; i <= size / 2 && current != null; i ++, current = current.next){
				if(i == index){
					return current; 
				}
			}
		}
		else{
			Node current = trailer;
			for(int i = size - 1; i > size / 2 && current != null; i --, current = current.prev){
				if(i == index){
					return current; 
				}
			}	
		}
		return null;
	}

	//查找指定的element
	public int locate(T element){
		Node current = header;
		for(int i = 0; i < size && current != null; i ++, current = current.next){
			if(current.data == element){
				return i; 
			}
		}
		return -1;
	}

	//插入数据
	public void insert(T element, int index){
		if(index < 0 || index > size){
			throw new IndexOutOfBoundsException("线性表越界");
		}
		if(header == null){
			addAtTrailer(element);//尾插法
		}
		else{
			if(index == 0){
				addAtHeader(element);//头插法
			}
			else{
				//找到它的前一个和后一个元素
				Node prevNode = getNodeByIndex(index - 1);
				if(prevNode.next != null){//只有一个元素时
					Node nextNode = prevNode.next;
					//新建这个插入的节点
					Node newNode = new Node(element, prevNode, nextNode);
					prevNode.next = newNode;
					nextNode.prev = newNode;
					size ++;
				}
				else{
					addAtTrailer(element);
				}
			}	
		}	
	}

	//头插法
	public void addAtHeader(T element) {

		//作为一个新的头  新节点的next域指向原来的头节点
		header = new Node(element, null, header);
		if(trailer == null){
			trailer = header;
		}
		size ++;	
	}

	//尾插法  要说明为不改变之后的新尾部
	public void addAtTrailer(T element){

		//判断是不是空链表
		if(header == null){
			header = new Node(element, null, null);
			trailer = header;
		}
		else{
			//原来的尾节点作为插入节点的前一个节点元素
			Node newNode = new Node(element, trailer, null);
			trailer.next = newNode;
			trailer = newNode;
		}
		size ++;
	}

	//删除数据
	public T delete(int index){

		if(index < 0 || index > size -1){
			throw new IndexOutOfBoundsException("线性表越界");
		}
		Node delNode = null;
		//被删除的是头节点
		if(index == 0){
			header = delNode;
			header = header.next;	
			//同时释放新的头节点的prev引用
			header.prev = null;
		}
		else{
			//找到对应的头节点
			Node prevNode = getNodeByIndex(index - 1);
			delNode = prevNode.next;
			//被删除的节点的前一个节点的next指向被删除节点的下一个节点
			prevNode.next = delNode.next;
			if(delNode.next != null){//判删除节点之后是不是空
				//让删除节点的下一额节点的prev指向这个它前一个节点
				delNode.next.prev = prevNode;
			}
			//被删除节点的next引用赋值为null
			delNode.prev = null;
			delNode.next = null;
		}
		size --;
		return delNode.data;
	}

	public T remove(){
		return delete(size - 1);
	}

	//判断是否为空
	public boolean empty(){
		return size == 0;
	}

	//清空列表
	public void clear(){
		header = null; 
		trailer = null;
		size = 0;
	}

	//对于这种双向的数据结构 toString方法一般都要考虑前后关系
	public String toString(){
		if(size == 0){
			return "[]";
		}
		else{
			StringBuffer sb = new StringBuffer("[");
			for(Node current = header; current != null; current = current.next){
				sb.append(current.data.toString() + ", ");
			}
			int len = sb.length();
			return sb.delete(len - 2, len).append("]").toString();
		}
	}

	public String reverseToString(){
		if(size == 0){
			return "[]";
		}
		else{
			StringBuffer sb = new StringBuffer("[");
			for(Node current = trailer; current != null; current = current.prev){
				sb.append(current.data.toString() + ", ");
			}
			int len = sb.length();
			return sb.delete(len - 2, len).append("]").toString();
		}
	}
}

测试部分:
public static void main(String[] args) {
				DuLinkedList<Integer> dlq = new DuLinkedList<Integer>();
				dlq.insert(1, 0);
		//		dlq.addAtTrailer(2);
				dlq.insert(2, 1);
				dlq.addAtTrailer(3);
				System.out.println(dlq);
				System.out.println("倒序输出的结果是:" + dlq.reverseToString());
				System.out.println("序列的长度:" + dlq.length());
				dlq.remove();
				dlq.addAtHeader(4);
				System.out.println("删除一个元素和再添加一个元素的队列:" + dlq);
			}
	



注意:对应插入时可以选择头插法或者尾插法,在输出时,也可以分别从头(header)或者尾(tail)输出数据。

相比较于单链表,它克服了其指针单向性的缺点,其中每个节点既可以向前引用,也可以向后引用,更加方便插入和删除数据元素。


参考:《疯狂java突破程序员的基本功的16课》

以上就是这篇的内容,如果存在错误的地方或有可以优化的地方,请您指出,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值