在前面回顾了一下顺序表的简单实现,我们不难发现顺序表的一些缺点/不足:
①顺序表在插入和删除结点时,要进行大量的数据移动操作(当顺序表很大时,无疑会耗费相当大的时间)
②顺序表扩大容量不方便(实际上是先建立一个容量更大的顺序表,然后将数据移动到新的顺序表中)
为了克服顺序表结构的这些缺点/不足,我们可以采用链表结构。
那,何为链表呢?
链表结构是一种动态存储分配的结构形式,可以根据需要动态申请所需的内存单元。
典型的链表结点如上图所示,每个结点都应该包括如下两个部分:
- 数据部分,保存该结点的实际数据
- 地址部分,保存的是下一个结点的地址
链表结构就是由许多这些结点构成的。
如上图所示,链表中每个结点的地址部分存放的是下一个结点的地址,若当前结点为链表的最后一个结点,则存放空地址null。而在实际使用过程中,我们会另外定义一个“头引用”的变量,该变量指向链表结构的第一个结点。
链表结构的最大好处是结点之间不需要连续存放,如上图所示,假设每个结点所占存储单元为c,链表中的第一个结点为a1,第二个结点为a2,此后结点以此类推。
a1的存储地址为b,a2的存储地址为b+20c,a3的存储地址为b+5c,而a10的存储地址为b+c,对于链表结构而已,两个结点逻辑上相邻,但物理(内存)上不一定相邻。
对于上图所示的单链表结构,访问链表时只能通过表头逐个查找,即通过头引用找到链表中的第一个结点,然后从第一个结点的地址部分找到第二个结点,……这样逐个比较直到找到所需结点为止,而不能像访问顺序表时可以进行随机访问。
这里简单说一下插入操作和删除操作。
上图所示为链表的一般插入操作,在指定的结点后插入新的结点,对应后文代码中的insertBehind方法。
插入操作顺序如下:
- 查找指定的结点,若查找失败,则返回false,否则执行第二步操作。(图中nodeTmpFind为查找的结点)
- 申请内存空间,保存新增的结点。(图中为nodeTmpAdd结点,数据部分存放要保存的数据aData)
- 使新增结点指向指定结点指向的结点。(图中nodeTmpAdd结点地址部分的红色箭头)
- 使指定结点指向新增的结点,返回true。(途中nodeTmpFind结点地址部分的红色箭头)
上图所示为链表的一般删除操作,删除指定的结点,并释放内存,对应后文代码中的 delete 方法。
删除操作顺序如下:
- 查找要删除的结点,若查找失败,则返回false,否则执行第二步操作。(图中headTmp为要删除的结点,nodeTmp为要删除的结点的上一个结点)
- 使要删除的结点的上一个结点指向要删除的结点的下一个结点。(图中nodeTmp结点的地址部分的红色箭头)
- 释放要删除的结点的内存,返回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