链表简单实现

本文详细介绍了链表结构的概念、特点、优势与劣势,并提供了链表的简单实现,包括插入、删除等操作。通过对比顺序表,展示了链表在插入和删除操作上的简便性与扩展能力,同时也指出链表占用更大存储空间及读取速度较慢的缺点。

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

在前面回顾了一下顺序表的简单实现,我们不难发现顺序表的一些缺点/不足:

①顺序表在插入和删除结点时,要进行大量的数据移动操作(当顺序表很大时,无疑会耗费相当大的时间)

②顺序表扩大容量不方便(实际上是先建立一个容量更大的顺序表,然后将数据移动到新的顺序表中)

为了克服顺序表结构的这些缺点/不足,我们可以采用链表结构。

那,何为链表呢?

链表结构是一种动态存储分配的结构形式,可以根据需要动态申请所需的内存单元。


典型的链表结点如上图所示,每个结点都应该包括如下两个部分:

  • 数据部分,保存该结点的实际数据
  • 地址部分,保存的是下一个结点的地址

链表结构就是由许多这些结点构成的。


如上图所示,链表中每个结点的地址部分存放的是下一个结点的地址,若当前结点为链表的最后一个结点,则存放空地址null。而在实际使用过程中,我们会另外定义一个“头引用”的变量,该变量指向链表结构的第一个结点。


链表结构的最大好处是结点之间不需要连续存放,如上图所示,假设每个结点所占存储单元为c,链表中的第一个结点为a1,第二个结点为a2,此后结点以此类推。

a1的存储地址为b,a2的存储地址为b+20c,a3的存储地址为b+5c,而a10的存储地址为b+c,对于链表结构而已,两个结点逻辑上相邻,但物理(内存)上不一定相邻。

对于上图所示的单链表结构,访问链表时只能通过表头逐个查找,即通过头引用找到链表中的第一个结点,然后从第一个结点的地址部分找到第二个结点,……这样逐个比较直到找到所需结点为止,而不能像访问顺序表时可以进行随机访问。

这里简单说一下插入操作和删除操作。


上图所示为链表的一般插入操作,在指定的结点后插入新的结点,对应后文代码中的insertBehind方法。

插入操作顺序如下:

  1. 查找指定的结点,若查找失败,则返回false,否则执行第二步操作。(图中nodeTmpFind为查找的结点)
  2. 申请内存空间,保存新增的结点。(图中为nodeTmpAdd结点,数据部分存放要保存的数据aData)
  3. 使新增结点指向指定结点指向的结点。(图中nodeTmpAdd结点地址部分的红色箭头)
  4. 使指定结点指向新增的结点,返回true。(途中nodeTmpFind结点地址部分的红色箭头)


上图所示为链表的一般删除操作,删除指定的结点,并释放内存,对应后文代码中的 delete 方法。

删除操作顺序如下:

  1. 查找要删除的结点,若查找失败,则返回false,否则执行第二步操作。(图中headTmp为要删除的结点,nodeTmp为要删除的结点的上一个结点)
  2. 使要删除的结点的上一个结点指向要删除的结点的下一个结点。(图中nodeTmp结点的地址部分的红色箭头)
  3. 释放要删除的结点的内存,返回true。

关于链表的更多特性和操作这里就不多说了,稍微总结一下链标结构的优势和劣势。

优势:插入和删除操作简便(在链表大的时候会比较明显),扩展链表方便

劣势:链表占用更大的存储空间(每个结点都有一个地址部分),读取结点慢(只能顺序读取,不能随机读取)


代码如下:

package ds.linkedlist;

/**
 * 链表的简单实现
 * 
 * @author Abyss_CMG
 * 
 */
public class LinkedList<E> {

	private LinkedList<E> nextNode;// 用于指向下一个结点
	Object data = new Object();// 用于保存当前结点的数据

	/**
	 * 在链表中查找指定的数据
	 * 
	 * @param head
	 *            链表头引用
	 * @param fData
	 *            待查询的数据
	 * @return
	 */
	public LinkedList<E> find(LinkedList<E> head, E fData) {
		LinkedList<E> headTmp = head;
		while (headTmp != null) {
			if (headTmp.data.equals(fData)) {
				return headTmp;
			} else {
				headTmp = headTmp.nextNode;
			}
		}
		return null;
	}

	/**
	 * 向链表末尾添加结点
	 * 
	 * @param head
	 *            链表头引用
	 * @param aData
	 *            待插入的数据
	 */
	public LinkedList<E> addEnd(LinkedList<E> head, E aData) {
		LinkedList<E> nodeTmp = new LinkedList<E>();
		nodeTmp.data = aData;
		nodeTmp.nextNode = null;
		if (head == null) {
			head = nodeTmp;
		} else {
			LinkedList<E> headTmp = head;
			while (headTmp.nextNode != null) {
				headTmp = headTmp.nextNode;
			}
			headTmp.nextNode = nodeTmp;
		}
		return head;
	}

	/**
	 * 向链表头部添加结点
	 * 
	 * @param head
	 *            链表头引用
	 * @param aData
	 *            待插入的数据
	 */
	public LinkedList<E> addBeginning(LinkedList<E> head, E aData) {
		LinkedList<E> nodeTmp = new LinkedList<E>();
		nodeTmp.data = aData;
		nodeTmp.nextNode = head;
		head = nodeTmp;
		return head;
	}

	/**
	 * 在指定结点的后面插入新的结点
	 * 
	 * @param head
	 *            链表头引用
	 * @param fData
	 *            要插入结点的位置,即指定结点
	 * @param aData
	 *            待插入的数据
	 * @return 若指定结点不存在,则添加失败,返回false;插入成功返回true
	 */
	public boolean insertBehind(LinkedList<E> head, E fData, E aData) {
		LinkedList<E> nodeTmpFind, nodeTmpAdd;
		nodeTmpFind = find(head, fData);
		if (nodeTmpFind == null) {
			return false;
		} else {
			nodeTmpAdd = new LinkedList<E>();
			nodeTmpAdd.data = aData;
			nodeTmpAdd.nextNode = nodeTmpFind.nextNode;
			nodeTmpFind.nextNode = nodeTmpAdd;
			return true;
		}
	}

	/**
	 * 在指定结点的前面插入新的结点
	 * 
	 * @param head
	 *            链表头引用
	 * @param fData
	 *            要插入结点的位置,即指定结点
	 * @param aData
	 *            待插入的数据
	 * @return 若指定结点不存在,则添加失败,返回false;插入成功返回true
	 */
	public boolean insertBefore(LinkedList<E> head, E fData, E aData) {
		LinkedList<E> nodeTmpFind, nodeTmpAdd;
		nodeTmpFind = find(head, fData);
		if (nodeTmpFind == null) {
			return false;
		} else {
			nodeTmpAdd = new LinkedList<E>();
			nodeTmpAdd.data = nodeTmpFind.data;
			nodeTmpAdd.nextNode = nodeTmpFind.nextNode;
			nodeTmpFind.data = aData;
			nodeTmpFind.nextNode = nodeTmpAdd;
			return true;
		}
	}

	/**
	 * 删除指定结点
	 * 
	 * @param head
	 *            链表头引用
	 * @param dData
	 *            待删除掉结点的数据
	 * @return
	 */
	public LinkedList<E> delete(LinkedList<E> head, E dData) {
		LinkedList<E> headTmp, nodeTmp;
		if (head != null) {
			headTmp = head;
			nodeTmp = head;
			while (headTmp != null) {
				if (headTmp.data.equals(dData)) {
					if (headTmp == head) {
						head = headTmp.nextNode;
						headTmp = null;
						nodeTmp = null;
						return head;
					}
					nodeTmp.nextNode = headTmp.nextNode;
					headTmp = null;
					return head;
				}
				nodeTmp = headTmp;
				headTmp = headTmp.nextNode;
			}
		}
		return head;
	}

	/**
	 * 修改第一个符合查询条件的结点的数据
	 * 
	 * @param head
	 *            链表头引用
	 * @param fData
	 *            待修改的结点的数据
	 * @param sData
	 *            要修改的数据
	 * @return 若修改成功,返回true,否则返回false
	 */
	public boolean set(LinkedList<E> head, E fData, E sData) {
		LinkedList<E> nodeTmp;
		nodeTmp = find(head, fData);
		if (nodeTmp != null) {
			nodeTmp.data = sData;
			return true;
		} else {
			return false;
		}
	}

	/**
	 * 修改所有符合查询条件的结点的数据
	 * 
	 * @param head
	 *            链表头引用
	 * @param fData
	 *            待修改的结点的数据
	 * @param sData
	 *            要修改的数据
	 * @return 若修改成功,返回true,否则返回false
	 */
	public boolean setAll(LinkedList<E> head, E fData, E sData) {
		LinkedList<E> nodeTmp;
		boolean isSet = false;
		while ((nodeTmp = find(head, fData)) != null) {
			nodeTmp.data = sData;
			isSet = true;
		}
		return isSet;
	}

	/**
	 * 获取链表长度
	 * 
	 * @param head
	 *            链表头引用
	 * @return 返回链表长度
	 */
	public int getSize(LinkedList<E> head) {
		LinkedList<E> headTmp = head;
		int len = 0;
		while (headTmp != null) {
			len++;
			headTmp = headTmp.nextNode;
		}
		return len;
	}

	/**
	 * 获取链表头结点的数据
	 * 
	 * @param head
	 *            链表头引用
	 * @return 头结点数据
	 */
	@SuppressWarnings("unchecked")
	public E getHeadInfo(LinkedList<E> head) {
		Object dataTmp = null;
		if (head != null) {
			dataTmp = head.data;
		}
		return (E) dataTmp;
	}

	/**
	 * 遍历链表,并输出信息
	 * 
	 * @param head
	 *            链表头引用
	 */
	void getLinkedListInfo(LinkedList<E> head) {
		LinkedList<E> headTmp = head;
		int num = 1;
		System.out.printf("链表共有%d个结点:\n", getSize(head));
		while (headTmp != null) {
			System.out.println("第" + num + "结点内容:" + headTmp.data.toString()
					+ ",类型:" + headTmp.data.getClass().getSimpleName());
			headTmp = headTmp.nextNode;
			num++;
		}
	}
}



测试代码:

package ds.linkedlist;

public class Simple {
	public static void main(String[] args) {
		LinkedList<Object> head = null;
		LinkedList<Object> list = new LinkedList<Object>();

		System.out.println("----新建链表----");
		list.getLinkedListInfo(head);

		System.out.println("----初始化数据----");
		String data1 = "34";
		head = list.addEnd(head, data1);
		int data2 = 56;
		head = list.addEnd(head, data2);
		list.getLinkedListInfo(head);

		System.out.println("----测试添加功能----");
		System.out.println("addBeginning方法,插入数值12");
		int data3 = 12;
		head = list.addBeginning(head, data3);
		list.getLinkedListInfo(head);
		System.out.println("addEnd方法,插入字符串78");
		String data4 = "78";
		head = list.addEnd(head, data4);
		list.getLinkedListInfo(head);

		System.out.println("----测试查找功能----");
		System.out.println("当查找的数据的类型与链表中的数据类型相同时:查找字符串34");
		System.out.println("结果为:" + list.find(head, "34"));
		System.out.println("当查找的数据的类型与链表中的数据类型不相同时:查找数值34");
		System.out.println("结果为:" + list.find(head, 34));

		System.out.println("----测试插入功能----");
		System.out.println("insertBefore方法,在数值56前插入数值34");
		int data5 = 34;
		list.insertBefore(head, 56, data5);
		list.getLinkedListInfo(head);
		System.out.println("insertBehind方法,在数值56后插入78");
		String data6 = "78";
		list.insertBehind(head, 56, data6);
		list.getLinkedListInfo(head);

		System.out.println("----测试修改功能----");
		System.out.println("set方法,将数值34改为字符串ok");
		String data7 = "ok";
		System.out.println("修改结果为:" + list.set(head, 34, data7));
		list.getLinkedListInfo(head);
		System.out.println("set方法,将字符串34改为字符串ok");
		System.out.println("修改结果为:" + list.set(head, "34", data7));
		list.getLinkedListInfo(head);
		System.out.println("setAll方法,将所有字符串ok改为数值34");
		System.out.println("修改结果为:" + list.setAll(head, data7, data5));
		list.getLinkedListInfo(head);

		System.out.println("----测试删除功能----");
		System.out.println("删除数值12");
		head = list.delete(head, 12);
		list.getLinkedListInfo(head);
		System.out.println("删除数值34");
		head = list.delete(head, 34);
		list.getLinkedListInfo(head);
		System.out.println("删除字符串78");
		head = list.delete(head, "78");
		list.getLinkedListInfo(head);
	}
}


测试结果:

----新建链表----
链表共有0个结点:
----初始化数据----
链表共有2个结点:
第1结点内容:34,类型:String
第2结点内容:56,类型:Integer
----测试添加功能----
addBeginning方法,插入数值12
链表共有3个结点:
第1结点内容:12,类型:Integer
第2结点内容:34,类型:String
第3结点内容:56,类型:Integer
addEnd方法,插入字符串78
链表共有4个结点:
第1结点内容:12,类型:Integer
第2结点内容:34,类型:String
第3结点内容:56,类型:Integer
第4结点内容:78,类型:String
----测试查找功能----
当查找的数据的类型与链表中的数据类型相同时:查找字符串34
结果为:ds.linkedlist.LinkedList@57c39a2d
当查找的数据的类型与链表中的数据类型不相同时:查找数值34
结果为:null
----测试插入功能----
insertBefore方法,在数值56前插入数值34
链表共有5个结点:
第1结点内容:12,类型:Integer
第2结点内容:34,类型:String
第3结点内容:34,类型:Integer
第4结点内容:56,类型:Integer
第5结点内容:78,类型:String
insertBehind方法,在数值56后插入78
链表共有6个结点:
第1结点内容:12,类型:Integer
第2结点内容:34,类型:String
第3结点内容:34,类型:Integer
第4结点内容:56,类型:Integer
第5结点内容:78,类型:String
第6结点内容:78,类型:String
----测试修改功能----
set方法,将数值34改为字符串ok
修改结果为:true
链表共有6个结点:
第1结点内容:12,类型:Integer
第2结点内容:34,类型:String
第3结点内容:ok,类型:String
第4结点内容:56,类型:Integer
第5结点内容:78,类型:String
第6结点内容:78,类型:String
set方法,将字符串34改为字符串ok
修改结果为:true
链表共有6个结点:
第1结点内容:12,类型:Integer
第2结点内容:ok,类型:String
第3结点内容:ok,类型:String
第4结点内容:56,类型:Integer
第5结点内容:78,类型:String
第6结点内容:78,类型:String
setAll方法,将所有字符串ok改为数值34
修改结果为:true
链表共有6个结点:
第1结点内容:12,类型:Integer
第2结点内容:34,类型:Integer
第3结点内容:34,类型:Integer
第4结点内容:56,类型:Integer
第5结点内容:78,类型:String
第6结点内容:78,类型:String
----测试删除功能----
删除数值12
链表共有5个结点:
第1结点内容:34,类型:Integer
第2结点内容:34,类型:Integer
第3结点内容:56,类型:Integer
第4结点内容:78,类型:String
第5结点内容:78,类型:String
删除数值34
链表共有4个结点:
第1结点内容:34,类型:Integer
第2结点内容:56,类型:Integer
第3结点内容:78,类型:String
第4结点内容:78,类型:String
删除字符串78
链表共有3个结点:
第1结点内容:34,类型:Integer
第2结点内容:56,类型:Integer
第3结点内容:78,类型:String



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值