自定义实现LinkedList

本文介绍如何自定义实现LinkedList,通过链表数据结构理解其工作原理。内容包括单向链表与双向链表的概念,以及如何设计数据结构和编写代码来实现双向链表。示例代码展示了基于双向链表的MyLinkedList类及其内部Node类的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇文章自定义实现了ArrayList集合,现在则自定义实现LinkedList。学过集合的读者都知道,ArrayList的底层实现是用数组实现,而LinkedList的实现则是用链表实现。那么什么是链表呢?其实可以把它比喻成一串珠子,珠子通过线来串连起来。要想找到中间的珠子,就只能通过第一个珠子或者最后一个珠子,一个一个的找下去,直到找到所要的珠子。


链表分为单向链表和双向链表,单向链表是指,只能通过前一个珠子找到后一个珠子或者只能通过后一个珠子找到前一个珠子,关联关系是单向的;双向链表是指,既能通过前一个珠子找到后一个珠子,同时也能通过后一个珠子找到前一个珠子,关联关系是双向的。

单向链表示意图:


或者:


双向链表示意图:


现在笔者就以双向链表来实现LinkedList。要想通过链表来存放元素,同时还能通过每一个元素来找到下一个或者前一个对象,那么该怎样设计数据结构呢?首先我们将一个珠子当做成一个节点(Node),该节点要实现存放元素(obj),同时还能找到前一个节点(prev)和下一个节点(next),那么我们可以将一个珠子分成三个小块。如图:


根据上面的设计,那么整个双向链表结构就可以描述成如下的图:


这样基本的数据结构我们就定义完成了,接下来就用实际的代码来实现:

首先我们定义了一个MyList接口(对应Java集合中的List接口),里面包含了集合的基本方法(笔者只是定义了几个比较常用的方法),然后定义了一个MyLinkedList类(对应Java中的LinkedList),该类实现了MyList接口,并且在MyLinkedList中定义了一个内部类Node,其中Node是我们上面设计的数据结构所对应的类。

具体的代码如下:

package com.tiantang.collection;

/**
 * 自定义的List接口
 * @author LiuJinkun
 *
 */
public interface MyList {
	
	/**
	 * 增加元素
	 * @param obj
	 */
	void add(Object obj);
	
	/**
	 * 在指定索引处增加元素
	 * @param index
	 * @param obj
	 */
	void add(int index,Object obj);
	
	/**
	 * 根据对象删除元素
	 * @param obj
	 */
	void remove(Object obj);
	
	/**
	 * 根据索引删除元素
	 * @param index
	 */
	void remove(int index);
	
	/**
	 * 返回指定索引处的元素
	 * @param index
	 * @return
	 */
	Object get(int index);
	
	/**
	 * 修改指定索引处的元素
	 * @param index
	 * @param obj
	 */
	void set(int index,Object obj);
	
	/**
	 * 返回元素在集合中第一次出现索引,不存在就返回-1
	 * @param obj
	 * @return
	 */
	int indexOf(Object obj);
	
	/**
	 * 判断集合是否为空
	 * @return
	 */
	boolean isEmpty();
	
	/**
	 * 清空集合
	 */
	void clear();
	
	int size();

}

package com.tiantang.collection;

/**
 * 自定义的LinkedList类
 * 
 * @author LiuJinkun
 *
 */
public class MyLinkedList implements MyList {
	// 表示集合的第一个元素所在的节点
	private Node first;
	// 表示集合中的最后一个元素所在的节点
	private Node last;

	private int size;

	public MyLinkedList() {
	}

	@Override
	public void add(Object obj) {
		linkLast(obj);
		size++;
	}

	@Override
	public void add(int index, Object obj) {
		// 可以等于size,如果等于size,则将元素加到最后
		if (index < 0 || index > size) {
			throw new IllegalArgumentException();
		}

		if (index == size)
			linkLast(obj);
		else
			linkBefore(obj, node(index));

		size++;
	}

	/**
	 * 将元素增加到指定节点处
	 * 
	 * @param obj
	 * @param node
	 */
	private void linkBefore(Object obj, Node node) {
		Node temp = new Node(null, obj, node);

		Node up = node.prev;
		if (up != null) {
			up.next = temp;
			temp.prev = up;
			node.prev = temp;
		} else {
			first = temp;
		}

	}

	/**
	 * 将元素添加到链表的最后
	 * 
	 * @param obj
	 */
	private void linkLast(Object obj) {
		Node temp = new Node(last, obj, null);

		if (first == null)
			first = last = temp;
		else {
			last.next = temp;
			last = temp;
		}

	}

	/**
	 * 返回指定索引处的节点
	 * 
	 * @param index
	 * @return
	 */
	private Node node(int index) {
		Node node = first;
		if (node != null) {
			for (int i = 0; i < index; i++) {
				node = node.next;
			}
		}
		return node;
	}

	@Override
	public void remove(Object obj) {
		int index = 0;
		for (Node temp = first; temp != null;) {
			if (temp.obj.equals(obj)) {
				temp = temp.next;
				remove(index);
			} else {
				temp = temp.next;
				index++;
			}
		}
	}

	@Override
	public void remove(int index) {
		rangCheck(index);
		// 获得索引为index的节点
		Node temp = node(index);

		// 获得改节点的上一个节点和下一个节点
		Node up = temp.prev;
		Node down = temp.next;

		// 如果删除的是第一个节点
		if (up == null) {
			down.prev = null;
			first = down;
			size--;
			return;
		}

		// 如果删除的是最后一个节点
		if (down == null) {
			up.next = null;
			last = up;
			size--;
			return;
		}
		// 重新设置连接关系
		up.next = down;
		down.prev = up;

		size--;
	}

	@Override
	public Object get(int index) {
		rangCheck(index);
		Node temp = node(index);
		return temp == null ? null : temp.obj;
	}

	/**
	 * 判断索引是否合法
	 * 
	 * @param index
	 */
	private void rangCheck(int index) {
		if (index < 0 || index >= size) {
			throw new IllegalArgumentException();
		}
	}

	@Override
	public void set(int index, Object obj) {
		rangCheck(index);
		Node temp = node(index);
		temp.obj = obj;
	}

	@Override
	public int indexOf(Object obj) {
		// 用来记录索引
		int index = 0;

		for (Node temp = first; temp != null;) {
			if (temp.obj.equals(obj)) {
				return index;
			}
			temp = temp.next;
			index++;
		}
		// 集合中不存在该对象,就返回-1
		return -1;
	}

	@Override
	public boolean isEmpty() {
		return first == null ? true : false;
	}

	@Override
	public void clear() {
		for (Node temp = first; temp != null;) {
			Node next = temp.next;

			temp.prev = null;
			temp.obj = null;
			temp.next = null;

			temp = next;
		}

		size = 0;
		first = last = null;
	}

	@Override
	public int size() {
		return size;
	}

	/**
	 * 用来存储元素,同时也是存储了每个元素的相邻元素的信息
	 * 
	 * @author LiuJinkun
	 *
	 */
	private class Node {
		// 上一个节点
		Node prev;
		// 本节点中存储的对象
		Object obj;
		// 下一个节点
		Node next;

		Node(Node prev, Object obj, Node next) {
			this.prev = prev;
			this.obj = obj;
			this.next = next;
		}
	}

}

上述代码就实现了LinkedList中的部分方法,实现原理很简单,只要弄清楚底层的数据结构是怎么回事,实现起来就比较简单了。但相比ArrayList( 自定义实现ArrayList)而言,LinkedList实现起来稍微有点复杂。笔者建议读者动手去实践一下,因为笔者在敲代码时发现LinkedList实现起来思路虽然很清晰,但真正写代码的时候需要考虑到各种情况,非常容易出现空指针异常,容易在细节处出错。

经过笔者自己测试,没有发现上述代码存在BUG,但代码上还可以在做优化,笔者在这里只是简单的实现了LinkedList,代码的优化程度肯定没法和jdk的源码相比,主要目的是为了了解LinkedList的结构及常见用法。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值