数据结构与算法——单链表

我们知道线性表的顺序存储结构,最大的缺点就是插入和删除时需要移动大量的元素,这样显然很消耗时间。我们可以想一想,怎么样可以不用移动元素而进行删除和插入呢?为了解决这个问题,我们提出了链式存储结构,它是用一组任意的存储单元去存储线性表的数据元素,这组存储单元可以连续,也可以不连续,也就是说这些数据元素可以在内存中未被占用任意位置,这样也节省了大量的空间。如图所示。

链式存储结构

1.结点

为了表示元素与元素之间的逻辑关系,我们将元素分为数据域指针域,我们称这样的元素为一个结点。如图所示:每个节点用来存储实际数据中的一个数据项,每个节点的指针域指向下一个节点,最后一个指向一个空值。即一个单项链表的一个节点分为两个部分,第一部分保存或者显示关于节点的信息,第二部分存储下一个节点的地址,单链表只能向一个方向进行遍历。

2.头结点与头指针

1)头结点

什么是头结点呢?在单链表的第一个结点前设置一个结点,该结点数据域不存储任何信息,而指针域存储指向第一个结点的地址,这个结点被称为头结点。那我们为什么要设置一个这样的头结点呢?当然是因为这样处理更简单,也更加容易理解,在单链表中如果我们要访问一个元素,必须先找到它的前驱,通过前驱结点中指针域的地址去访问,有了头结点,对在第一个结点前插入结点或删除结点就跟其它结点方式一样,更加方便操作。

2)头指针

头指针,顾名思义就是指向第一个结点的指针。如果链表中有头结点,头指针则指向头结点,如图所示,head为头指针。

尾指针,当然是指向最后一个结点的,也就是图中a4的位置。(画图的时候忘了标,请见谅。)

3.单链表的插入与删除

我们可以先来想一想链表是怎么样实现插入和删除元素的?

①插入元素 

插入元素有三种情况,分别是头插、尾插和一般插入。

我们先来看头插法:头插法也有特殊情况,如图所示,当链表为空时,插入一个元素,我们可以先将头结点的下一跳给A的下一跳,再将A的地址给头结点的下一跳,再将尾指针指向A。

当链表不为空时,前面的步骤也是一样的,唯一不同点就是,尾指针不用移动。

尾插法:当链表为空时,我们发现尾插和头插的步骤是一样的。当链表不为空时,把B的地址给A的下一跳,然后rear后移

一般插入:当我们要在A、B之间插入D时,可以将A看作头,这样就会发现与头插法是一样的。将A的下一跳给D的下一跳,然后让A的下一跳指向D的地址。

②删除元素

同样,删除元素也分为三种情况:头删、尾删和一般删除。

头删:当表中只有一个元素时,删头时,被删元素的下一跳给head的下一跳,最后要让尾指针前移。正常删头时,也一样,唯一不同的是尾指针不需要移动。

尾删:删尾时,一定是要去找尾的前驱(因为这是链表,当需要删尾时,我们不知道尾在哪,所以需要从头开始找),然后将前驱的下一跳置为null,然后再让尾指针指向前驱。

一般删除:当要删哪个元素时,就去找这个元素的前驱。如图,要删B,先找到B的前驱A,然后将B的下一跳给A的下一跳,最后将B的下一跳置null,

4.单链表代码实现(LinkedList)

1)首先我们先定义List接口

package com.openlab.list;
/**
 * List是线性表的最终父接口
 * @author ABC
 * @param <E>
 */
public interface List<E> {
	
	/**
	 * 获取线性表中元素的个数(线性表的长度)
	 * @return 线性表中有效元素的个数
	 */
	public int getSize();
	
	/**
	 * 判断线性表是否为空
	 * @return 是否为空的布尔类型值
	 */
	public boolean isEmpty();
	
	/**
	 * 在线性表中指定的index角标处添加元素e
	 * @param index 指定的角标 0<=index<=size
	 * @param e 要插入的元素
	 */
	public void add(int index,E e);
	
	/**
	 * 在线性表的表头位置插入一个元素
	 * @param e 要插入的元素 指定在角标0处
	 */
	public void addFirst(E e);
	
	/**
	 * 在线性表的表尾位置插入一个元素
	 * @param e 要插入的元素 指定在角标size处
	 */
	public void addLast(E e);
	
	/**
	 * 在线性表中获取指定index角标处的元素
	 * @param index 指定的角标0<=index<size
	 * @return 该角标所对应的元素
	 */
	public E get(int index);
	
	/**
	 * 获取线性表中表头的元素
	 * @return 表头元素 index=0
	 */
	public E getFirst();
	
	/**
	 * 获取线性表中表尾的元素
	 * @return 表尾的元素 index=size-1
	 */
	public E getLast();
	
	/**
	 * 修改线性表中指定的index处元素为新元素e
	 * @param index 指定的角标
	 * @param e 新元素
	 */
	public void set(int index,E e);
	
	/**
	 * 判断线性表中是否包含指定元素e 默认从前往后找
	 * @param e 要判断是否存在的元素
	 * @return 元素的存在性布尔类型值
	 */
	public boolean contains(E e);
	
	/**
	 * 在线性表中获取指定元素e的角标,默认从前往后
	 * @param e 要查询的数据
	 * @return 数据在线性表中的角标
	 */
	public int find(E e);
	
	/**
	 * 在线性表中删除指定角标处的元素,并返回
	 * @param index 指定的角标 0<=index<size
	 * @return 删除掉的老元素
	 */
	public E remove(int index);
	
	/**
	 * 删除线性表中的表头元素
	 * @return 表头元素
	 */
	public E removeFirst();
	
	/**
	 * 删除线性表中的表尾元素
	 * @return 表尾元素
	 */
	public E removeLast();
	
	/**
	 * 在线性表中删除指定元素
	 * @param e 指定元素
	 */
	public void removeElement(E e);
	
	/**
	 * 清空线性表
	 */
	public void clear();
}

2)LinkedList实现List接口

链表是由多个结点组成的,我们可以先定义一个结点类(Node),用于保存数据项的各项信息:

由于我们所定义的结点类是不能让外界进行操控,所以将Node类私有化,只允许在本类中被调用。

package com.openlab.链表;

import com.openlab.list.List;

public class LinkedList<E> implements List<E> {

	private Node head; // 指向虚拟头节点的头指针
	private Node rear; // 指向尾结点的尾指针
	private int size; // 记录元素的个数
    
    //无参构造函数
	public LinkedList() {

		head = new Node();
		rear = head;
		size = 0;
	}
    //有参构造函数(传入的参数是数组)
    //将数组拆分开,将元素一个一个加入链表中,组成单链表
	public LinkedList(E[] arr) {
		this();
		for (E e : arr) {
			addLast(e);
		}

	}

	@Override
	public int getSize() {

		return size;
	}

	@Override
	public boolean isEmpty() {

		return size == 0 && head.next == null;
	}
      
	@Override
	public void add(int index, E e) { 

		if (index < 0 || index > size) {
			throw new IllegalArgumentException("插入角标非法!");
		}
		Node n = new Node(e, null);

		if (index == 0) {            // 头插
			n.next = head.next;
			head.next = n;
			if (size == 0) {  //当表中没有元素时,插入元素
				rear = n;
			}

		} else if (index == size) { // 尾插
			rear.next = n;
			rear = rear.next;

		} else { // 一般插入
			Node p = head;
			for (int i = 0; i < index; i++) {
				p = p.next;
			}
			n.next = p.next;
			p.next = n;

		}
		size++;
	}

	@Override
	public void addFirst(E e) {//获取表头元素

		add(0, e);
	}

	@Override
	public void addLast(E e) {//获取表尾元素

		add(size, e);
	}

	@Override
	public E get(int index) {//获取指定index的元素

		if (index < 0 || index >= size) {
			throw new IllegalArgumentException("查找角标非法!");
		}
		if (index == 0) {//表头元素
			return head.next.data;
		} else if (index == size - 1) {//表尾元素
			return rear.data;
		} else {
			Node p = head;
            //找指定index的元素,p就移动index次
			for (int i = 0; i <= index; i++) {
				p = p.next;
			}
			return p.data;
		}

	}

	@Override
	public E getFirst() { //获取表头元素

		return get(0);
	}

	@Override
	public E getLast() { //获取表尾元素

		return get(size - 1);
	}
    
	@Override
	public void set(int index, E e) { //修改指定的index处元素为新元素e
		// TODO Auto-generated method stub
		if (index < 0 || index >= size) {
			throw new IllegalArgumentException("查找角标非法!");
		}
		if (index == 0) { //修改表头元素
			head.next.data = e;
		} else if (index == size - 1) { //修改表尾元素
			rear.data = e;
		} else {
			Node p = head;
			for (int i = 0; i <= index; i++) {
				p = p.next;
			}
			p.data = e;
		}
	}

	@Override
	public boolean contains(E e) {
		// TODO Auto-generated method stub
		return find(e) != -1; 
	}

	@Override
	public int find(E e) {
		int index = -1;

		if (isEmpty()) {//判空
			return index;
		}
		Node p = head;
		while (p.next != null) {
			p = p.next;
			index++;
			if (p.data == e) {
				return index;
			}
		}
		return -1;
	}

	@Override
	public E remove(int index) {

		if (index < 0 || index >= size) {
			throw new IllegalArgumentException("删除角标非法!");
		}
		E res = null;
		if (index == 0) { // 头删
			Node p = head.next;
			res = p.data;
			head.next = p.next;
			p.next = null;
			p = null;
			if (size == 1) {  //当删除表中最后一个元素时
				rear = head;
			}

		} else if (index == size - 1) { // 尾删
			Node p = head;
			res = p.data;
			while (p.next != rear) {
				p = p.next;
			}
			p.next = null;
			rear = p;

		} else {//一般删除

			Node p = head;
			for (int i = 0; i < index; i++) {
				p = p.next;
			}
			Node del = p.next;
			res = p.next.data;
			p.next = del.next;
			del.next = null;

		}
		size--;
		return res;
	}

	@Override
	public E removeFirst() {//移除表头元素

		return remove(0);
	}

	@Override
	public E removeLast() {//移除表尾元素

		return remove(size - 1);
	}

	@Override
	public void removeElement(E e) {
		//先用find方法找个该结点,返回结点的下标
		int index = find(e);
		if (index == -1) {
			throw new IllegalArgumentException("元素不存在!");
		}
		remove(index);
	}

	@Override
	public void clear() {//清空表

		head.next = null;
		rear = head;
		size = 0;
	}

	@Override
	public String toString() {

		StringBuilder sb = new StringBuilder();
		sb.append("LinkedList:size=" + getSize() + "\n");
		if (isEmpty()) {
			sb.append("[]");
		} else {
			sb.append('[');
			Node p = head;
			while (p.next != null) {
				p = p.next;
				if (p == rear) {
					sb.append(p.data + "]");
				} else {
					sb.append(p.data + ",");
				}
			}
		}
		return sb.toString();
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}
		if (obj == this) {
			return true;
		}
		if (obj instanceof LinkedList) {
			LinkedList list = (LinkedList) obj;
			if (getSize() == list.getSize()) {
				for (int i = 0; i < getSize(); i++) {
					if (get(i) != list.get(i)) {
						return false;
					}
				}
				return true;
			}
		}
		return false;
	}

	/**
	 * 单项列表的节点
	 */
	private class Node {
		E data; // 数据域
		Node next; // 指针域

		public Node() {
			this(null, null);
		}

		public Node(E data, Node next) {
			this.data = data;
			this.next = next;
		}

		@Override
		public String toString() {

			return data.toString();
		}

	}

}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值