Java手写双向循环链表

1.双向循环链表

1)数组(ArrayList)与链表(LinkedList)的区别:

  1. 数组的优点: 直接通过下标来访问某个元素速度非常快。
  2. 数组的缺点: 插入或者删除某个元素比较慢(因为需要调整整个数组)。另外,需要有连续的地址空间。
  3. 链表的优点: 插入或者删除某个元素非常快(只需要改变指针的指向即可)。不需要连续的地址空间。
  4. 链表的缺点: 不能够通过下标直接访问某个元素(需要遍历)。占用的内存空间要比数组大(因为需要有保存地址的空间)。

结论: 如果事先大体知道数据个数,另外,查询比较多,而删除和插入操作比较少,用数组。反之用链表。

2)双向循环链表

每个节点包含有两部分内容,一部分是数据,另一部分是保存有指向前驱节点和后继节点地址的引用。另外,最后一个节点也保存有指向头节点的引用,头节点也保存有指向最后一个节点的引用。
jdk8.0之前的LinkedList是采用双向循环链表实现的,
jdk8.0之后,采用双向链表实现。

                                                双向循环链表结构示意图
双向循环链表
                                            在尾部添加节点的过程
在这里插入图片描述

                                            在指定下标处添加节点的过程
在指定下标处添加节点                                            删除指定下标处的节点的过程
删除指定下标处的节点

2.实现代码

/**
 * 	双向循环链表:
 * 		双向循环链表是这样一种数据结构:
 * 		每个节点包含有两部分内容,一部分是数据,另一部分是保存有指向前驱节点和后继节点地址的引用。
 * 		另外,最后一个节点也保存有指向头节点的引用,头节点也保存有指向最后一个节点的引用。
 * 		JDK1.8之前的LinkedList是采用双向循环链表实现的,
 * 		JDK1.8之后,采用双向链表实现
 * @author dinghuang
 *
 * @param <E>
 */
public class LinkedList<E> {

	//head保存链表头节点的引用
	private Node head;
	//保存节点(元素)的个数
	private int size;
	
	/**
	 * @return	返回链表中元素的个数
	 */
	public int size() {
		return size;
	}
	
	/*
	 * Node用来描述一个节点,其中,
	 * E用来存放数据,prev存放前驱结点的引用,
	 * next存放后继节点的引用
	 */
	private class Node {
		E data;
		Node prev;
		Node next;
		
		public Node(E e) {
			data = e;
		}
	}
	
	

	/**
	 * 	将元素添加到链表的末尾
	 * @param e	待添加的元素
	 * @return	添加成功,返回true
	 */
	public boolean add(E e) {
		if (head == null) {
			//链表为空,则当前新添加的节点为头节点。
			head = new Node(e);
			//如果只有一个元素,那么前驱节点和后继节点都指向自己
			head.prev = head;
			head.next = head;
			size++;
			return true;
		}
		
		/*
		 * 	如果链表不为空,则先找到尾节点,
		 * 	然后重新建立节点的引用关系
		 */
		//找到尾节点
		Node last = head.prev;
		//重新建立节点的引用关系
		Node node = new Node(e);
		last.next = node;
		node.next = head;
		head.prev = node;
		node.prev = last;
		size++;
		return true;
	}
	
	/**
	 * 	返回链表中各个元素的值,如果链表为空,返回"[]",
	 * 	否则,多个元素使用","分隔。
	 */
	public String toString() {
		if (head == null) {
			return "[]";
		}
		StringBuilder builder = new StringBuilder();
		//先将头节点的元素值添加进来
		builder.append("[" + head.data);
		//找到头节点的下一个节点
		Node next = head.next;
		/*
		 * 	开始进行遍历,直到下一节点是头节点,则循环结束。
		 */
		while (next != head) {
			builder.append(", " + next.data);
			//找到下一个节点
			next = next.next;
		}
		return builder.append("]").toString();
	}
	
	/**
	 * 	返回指定下标的某个元素
	 * @param index	下标
	 * @return	对应下标的某个元素
	 */
	public E get(int index) {
		if (index < 0 || index >= size) {
			//当传入的下标不正确时抛出下标越界异常
			throw new IndexOutOfBoundsException("下标越界");
		}
		Node node = getNode(index);
		return node.data;
	}

	/*
	 * 	返回指定下标对应的节点
	 */
	private Node getNode(int index) {
		Node node = head;
		//当下标小于链表长度的一半时,通过后继节点向后查找元素
		if (index < size / 2) {
			for (int i = 0; i < index; i++) {
				node = node.next;
			}
		} else {
			//当下表大于链表长度的一般时,通过前驱节点向前查找元素
			for (int i = size; i > index; i--) {
				node = node.prev;
			}
		}
		return node;
	}
	
	/**
	 * 	删除指定下标对应的元素
	 * @param index	下标
	 * @return	被删除的元素
	 */
	public E remove(int index) {
		if (index < 0 || index >= size) {
			//当传入的下标不正确时抛出下标越界异常
			throw new IndexOutOfBoundsException("下标越界");
		}
		if (size == 1) {
			E data = head.data;
			head = null;
			size--;
			return data;
		}
		//找到被删除的节点
		Node node = getNode(index);
		//找到该节点的上一个节点和下一个节点
		Node next = node.next;
		Node prev = node.prev;
		//上一个节点和下一个节点建立引用关系
		next.prev = prev;
		prev.next = next;
		//如果要删除的是第一个节点(头节点),应该让第二个节点充当头节点
		if (index == 0) {
			head = node.next;
		}
		size--;
		//返回被删除的元素
		return node.data;
	}
	
	/**
	 * 	将元素添加到指定下标处
	 * @param index	下标
	 * @param data	被添加的元素
	 */
	public void add(int index, E data) {
		if (index < 0 || index > size) {
			//当传入的下标不正确时抛出下标越界异常
			throw new IndexOutOfBoundsException("下标越界");
		}
		if (index == size) {
			//直接在尾部添加
			add(data);
			return;
		}
		Node node = new Node(data);
		//找到对应下标处的节点
		Node next = getNode(index);
		//找到该节点的上一个节点
		Node prev = next.prev;
		//重新建立对应关系
		next.prev = node;
		node.prev = prev;
		prev.next = node;
		node.next = next;
		//如果添加的下标是0, 则这个新节点为头节点
		if (index == 0) {
			head = node;
		}
		size++;
	}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值