Java数据结构与算法之双向链表

一、学习过单链表和单向循环链表之后,对于链表结构特点应该已经相当熟悉。而在链表中还有一种双向链表,来第一步看图说话:

从图中可以看出双链表和单链表相比有如下特点:

1、双链表除了拥有后继结点next外还有一个前驱结点prev;

2、存储同样数据多的数据,双链表要不单链表占用更多的空间(双链表要比单链每个结点要多存储一个前继指针prev);

3、支持双向遍历,比单链表更灵活。

二、具体实现:

1、成员变量及构造方法:

package cn.cast.DoubleLinkedList;
/**
 * 双链表具体实现类
 * @autor Administrator
 * @param <T>
 */
public class MyDoubLinkedList<T> implements DoubleLinkedList<T>{
	
	private DNode<T> head;//不带数据的头结点
	private DNode<T> tail;//指向尾部的指针
	private int length;//链表的长度
	
	public MyDoubLinkedList(){
		this.head=this.tail=new DNode<T>();
	}

2、结点类比单链表多了个前驱指针;

/**
 * 双链表结点类
 * @author Administrator
 *
 */
public class DNode<T> {

	public T data;//存储数据
	public DNode<T> prev,next;//前继指针和后继指针
	
	public DNode(T data,DNode<T> prev,DNode<T> next){
		this.data=data;
		this.next=next;
		this.prev=prev;
	}
	public DNode(T data){
		this(data,null,null);
	}
	public DNode(){
		this(null,null,null);
	}
	
	public String toString(){
		return this.data.toString();
	}
	
}

3、具体实现方法:双链表和单链表的主要差异也主要体现插入、删除、查找、替换四个操作中,下面就从具体分析下这个四个方法

3.1、添加元素:和单链表一样,双链表在插入时同样是有两种方式的操作:一种插入空链表和尾部插入,另一种是从链表的中间插入。如下图:

具体代码实现如下:

        /**指定位置添加元素
	 * @param index
	 * @param data
	 * @return
	 */
	@Override
	public boolean add(int index, T data) {
		//下标合法性校验
		if(index<0){
			throw new IndexOutOfBoundsException();
		}
		//存储数据校验
		if(data==null){
			throw new NullPointerException("data==null");
		}
		int j=0;
		DNode<T> front = this.head;
		//查找要插入结点位置的前一个结点;
		while(front.next!=null && j<index){
			j++;
			front=front.next;
		}
		//创建需要插入的结点,并让其前继指针指向front,后继指针指向front.next;
		DNode<T> q=new DNode<T>(data,front,front.next);
		//空双链表的插入和链表尾部插入,无需此操作
		if(front.next!=null){
			//更改front.next的前继指针
			front.next.prev=q;
		}
		//更改front的后继指针
		front.next=q;
		//在尾部插入时需要注意更新tail指向
		if(front==this.tail){
			this.tail=q;
		}
		return true;
	}

3.2、尾部添加元素:

        /**
	 * 尾部添加元素
	 * @param data
	 * @return
	 */ 
	@Override
	public boolean add(T data) {
		//存储数据校验
		if(data==null){
			throw new NullPointerException("data==null");
		}
		//创建新结点,并让其前继指针指向tail,后继指针指向tail.next
		DNode<T> newDNode =new DNode<T>(data,tail,tail.next);
		//tail的后继指针指向新结点
		tail.next=newDNode;
		//修改tail
		tail=newDNode;
		return true;
	}

3.3 删除元素:双链表删除元素与单链表的场景一致,头结点删除,尾部删除和中间删除,具体操作和分析:头结点和尾部删除一致,需要防止 p.next.prev抛空指针的情况,而对于(c)情况则无需关系 p.next.prev的值。

代码示例如下:

	/**
	 * 根据下标删除结点
	 * @param index
	 * @return
	 */
	@Override
	public T remove(int index) {
		T temp=null;
		//合法性校验
		int size = length();
		if(index<0 || index>size || isEmpty()){
			throw new IndexOutOfBoundsException();
		}
		DNode<T> p=this.head;
		int j=0;
		//头结点、尾结点、中间结点删除都需要查找结点的位置,所有i<=index
		while(p!=null && i<=index){
			p = p.next;
			j++;
		}
		//当双链表只有一个结点时或尾部删除时无需此部
		if(p.next!=null){
			p.next=p.prev;
		}
		p.prev.next=p.next;
		//如果是尾结点
		if(p==this.tail){
			this.tail=p.prev;//更新末结点的指向
		}
		temp=p.data;
		return temp;
	}

获取元素:与单链表一致,只需要找到当前结点并获取值即可,代码实现如下:

        /**
	 * 获取元素
	 * @param index
	 * @return
	 */
	@Override
	public T get(int index) {
		T temp=null;//存储数据临时变量
		int size = length();
		//合法性校验
		if(index<0 || index>size || isEmpty()){
			throw new IndexOutOfBoundsException();
		}
		int j=0;
		DNode<T> p = this.head;
		//获取结点的位置
		while(p!=null && j<=index){
			j++;
			p = p.next;
		}
		if(p!=null){
			temp=p.data;
		}
		return temp;
	}

替换元素:和获取结点类似,首先是获取元素,找到要替换的结点,替换存储元素即可,代码示例如下:

        /**
	 * 替换元素
	 * @param index
	 * @param data
	 * @return
	 */
	@Override
	public T set(int index, T data) {
		T oldData=null;//旧存储数据临时变量
		int size = length();
		//合法性校验
		if(index<0 || index>size || isEmpty()){
			throw new IndexOutOfBoundsException();
		}
		int j=0;
		DNode<T> p = this.head;
		//获取结点的位置
		while(p!=null && j<=index){
			j++;
			p = p.next;
		}
		//用新存储数据替换旧存储数据
		if(p!=null){
			oldData=p.data;
			p.data=data;
			
		}
		return oldData;
	}

如上,双链表的主要方法已经完成了,和单链表相比不是也很简单。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值