引入:
我们在学习数组这种数据结构的时候,可能会发现这种数据结构存在着一定的缺陷。在无序数组中搜素是低效的;而在有序数组中插入效率又很低,不管在哪一种数组中删除的效率都是很低的。而且数组一旦创建之后,它的大小是不可改变的。链表的出现就可以解决上面出现的一些问题。链表的机制灵活、用途广泛、适用于许多通用的数据库、在某种程度上是可以取代数组的。
链结点:
在链表中每个数据项都被包含在 ”链结点“ 中。因为一个链表中有许多类似的链结点,所以有必要用一个不同于链表的类来表达链结点。每个Link对象中都包含着对下一个链结点引用的字段。
其结构如下图:
链结点类的相关定义:
class Link{
public int iData;
public double dData;
... 更多的数据
public Link link;
}
链表基本功能的实现:
链结点类:
class Link{
public int iData; // 数据块
public double dData;
public Link next; // 内部的自引用 作用相当于指针
public Link(int id, double dd) {
this.iData = id;
this.dData = dd;
}
public void displayLink() {
System.out.println("{" + iData + ", " + dData + "}");
}
}
链表实习类:
/* 基本链表功能的实现 */
public class LinkList {
public Link first;
public LinkList() {
first = null;
}
public boolean isEmpty() {
return (first == null);
}
// 在第一个位置上插入数据
public void insertFirst(int id, double dd) {
Link newLink = new Link(id, dd); // 创建一个新的结点
newLink.next = first; // 先让新结点中的 next 指向之前位于第一个位置上的 结点
// 这句话的实际操作 就是断开 first 与 之前第一个位置上的结点的联系,然后让新创建的结点中的 next 指向它
first = newLink; // 使用first指向新创建的结点
}
// 删除第一个位置上的数据
public Link deleteFirst() {
Link temp = first; // 首先对first 目前指向的Link 做一个备份 也就是说备份一下将要删除的 Link
first = first.next; // 将first 与第一个位置上的结点的关系断开 然后让其指向第二个结点
return temp; // 将之前的备份返回
}
public void displayList() {
System.out.println("List (first --> last): ");
Link current = first;
while (current != null) {
current.displayLink();
current = current.next; // 逐条读取数据
}
System.out.println("");
}
public static void main(String[] args) {
LinkList theList = new LinkList();
theList.insertFirst(22, 2.99);
theList.displayList();
while (!theList.isEmpty()) {
Link alink = theList.deleteFirst();
System.out.println("Deleted ");
alink.displayLink();
System.out.println(" ");
}
theList.displayList();
}
}
在基本功能的基础上增加查询和删除功能:
链表的查找和删除功能,都是基于 Link 类的,将数据块中的某个属性都做查询的标记,找到后再进行具体的操作。具体的体会都在注释中!!!
// 根据int属性查询指定的结点
public Link find(int key) {
Link current = first; // 保存当前链表的第一个链结点
while (current.iData != key) { // 如果该节点的int属性值与要查找的数据不匹配 进入循环
if (current.next == null) // 如果该结点后再没有节点 说明在当前链表中不存在要查找的数据
return null; // 返回空
else // 如果在该节点下还有结点存在
current = current.next; // 将查看的结点向后移一个位置
}
return current;
}
// 根据int属性删除指定的结点
public Link deleteLink(int key) {
/**
* 在这里为什么要使用俩个Link属性去操作删除?
* 因为在删除的操作中,假如删除了链表中的其中一个结点,
* 要将这个节点的前一个结点和后一个节点再次连接起来,
* 所以在这个方法内部必须要维护一个遍历时当前节点前面的一个结点
* 也就是之前的节点,因为后一个节点我们可以使用 .next 获取,但是没有对应的方法获取前一个
*/
Link current = first; // 当前的
Link previous = first; // 以前的
while (current.iData != key) { // 如果当前节点的int属性 与要查找的属性 不匹配 怎进入循环
if (current.next == null) // 如果当前结点下没有了其他节点 该链表中不存在要删除的项
return null; // 返回null
else {
previous = current; // 将当前节点备份
current = current.next; // 将当前结点向后移动一次
}
}
if (current == first) // 如果找到的结点为 first
first = first.next; // 删除first结点(将first结点下的第一个结点换位 first 节点)
else
previous.next = current.next; // 删除当前节点(将之前的节点的next属性的指向 变为当前的结点的下一个节点)
return current;
}
双端链表
双端链表与传统的链表十分相似,但是增加了新的特性:对最后一个链结点的引用,就像对第一个链结点的引用一样。对最后一个链结点的引用允许像在表头一样,在表尾直接插入一个链结点。
/* 双端链表 */
public class FirstLastList {
private Link first;
private Link last;
public FirstLastList() {
first = null;
last = null;
}
public boolean isEmpty() { return (first == null); }
// 从头部插入结点
public void insertFirst(double d) {
Link newLink = new Link(d); // 创建要插入的结点
if ( isEmpty() ) // 判断是否为首次插入 如果为首次插入
last = newLink; // 则让 last 指向新建的结点
newLink.next = first; // 让新建的结点的 next 属性指向 first 之前指向的结点
first = newLink; // 然后在让first属性指向新建的结点
}
// 从尾部插入
public void insertLast(double d) {
Link newLink = new Link(d); // 创建要插入的结点
if ( isEmpty() ) // 如果为首次插入
first = newLink; // 则让 first 指向新建的结点
else // 如果不是首次插入
last.next = newLink; // 让以前的last指向新的结点
// 这里的 last.next 中 last就相当于 之前链表中的最后一个结点,然后让那最后一个结点的next属性指向新的结点
// 在这里由于是从尾部插入的结点 这个操作就相当于将 新的结点和之前链表中的最后一个结点连接起来
last = newLink; // 让 last 指向新的结点
}
// 从头部删除结点
public double deleteFirst() {
double temp = first.dData; // 对第一个节点的数据做备份
if (first.next == null) // 如果链表中没有其他的结点
last = null; // 将 last 的引用改为null
first = first.next; // 然后将first的引用改到下一个结点
return temp; // 返回节点的数据
}
public void displayList() {
System.out.println("List (first -> last) : ");
Link current = first;
while (current != null) {
current.displayLink();
current = current.next;
}
System.out.println("");
}
}
心得:双端队列和传统的队列不同的地方在于:传统的队列内部值维护一个头部的引用、而在双端队列中还同时引用了一个 尾部的引用,该尾部的引用实时的指向链表中的最后一个结点。这样维护的好处在于:我们可以同时从俩段操作数据。给操作带来了一定的方便。