链表结构
什么是链表结构
如图所示,链表中的每个阶段都应包括如下部分:
- 数据部分:保存的是该结点的实际数据
- 地址部分:保存的是下一个结点的地址
由于采用了引用来指示下一个数据的地址,因此在链表结构中。逻辑上相邻的节点在内存中并不一定相邻,逻辑相邻关系通过地址部分的引用变量来实现
使用链表的优缺点
优点:
- 结点之间不要求连续存放,因此在保存大量数据时,不需要分配一块连续的存储空间。用户可以用new函数动态分配结点的存储空间,当删除某个结点时,给该节点赋值null,释放其占用的内存空间。
缺点:
-
那就是浪费存储空间。因此,对于每个节点数据,都要额外保存一个引用变量。但是,在某些场合,链表结构所带来的好处还是大于其缺点的。
-
对于链表的访问只能从表头逐个查找,即通过head头引用找到第一个结点, 再从第一个结点找到第二个节点……这样逐个比较一直到找到需要的结点为止,而不能像顺序表那样进行随机访问。
链式存储是最常用的存储方式之一 ,它不仅可用来表示线性表,而且可用来表示各种非线性数据结构。
链表结构还可以细分为如下几类:
-
单链表:同上面的链式结构-一样,每个结点中只包含一个引用。
-
双向链表:若每个结点包含两个引用,一个指向下一个结点, 另一个指向上一个结点,说是双向链表。
-
单循环链表:在单链表中,将终端节点的引用域null改为指向表头节点或开始节点即可构成单循环链表
-
多重链的循环链表:如果将表中结点链在多个环上,将构成多重链的循环链表。
1.准备数据
有了前面的理论知识后,开始链表结构的程序设计。准备链表操作中需要用到的变量及类等。示例代码如下:
class DATA2 {
//节点的关键字
String key;
String name;
int age;
}
/**
* 定义链表结构
*/
class CLType {
DATA2 nodeData = new DATA2();
CLType nextNode;
}
上述代码定义了链表数据元素的类DATA2及链表的类CLType.结点的具体数据保存在一个类DATA2中,而引用nextNode用来指向下一个结点。
其实,可以认为该链表是一个班级学生的记录,其中, key为学号,name 为学生的名称,age 为年龄。和顺序表所完成的工作类似。
2.追加结点
追加结点即在链表末尾增加一个结点。表尾结点的地址部分原来保存的是空地址null, 此时需将其设置为新增结点的地址(即原表尾结点指向新增结点),然后将新增结点的地址部分设置为空地址null,即新增结点成为表尾。
由于般情况下,链表只有个头引用head,要在末尾添加结点就需要从头引用head开始逐个检查,直到找到最后一个结点(即表尾)。
典型的追加结点的过程如图2-4所示。追加结点的操作步骤如下:
(1)首先分配内存空间,保存新增的结点。
(2)从头引用head开始逐个检查,直到找到最后一个结点(即表尾)。
(3)将表尾结点的地址部分设置为新增结点的地址。
(4)将新增结点的地址部分设置为空地址null,即新增结点成为表尾。
实例代码如下:
CLType CLAddEnd(CLType head, DATA2 nodeData) {
CLType node, htemp;
node = new CLType();
if (node == null) {
System.out.println("申请内存失败!");
return null;
} else {
node.nodeData = nodeData;
node.nextNode = null;
if (head == null) {
head = node;
return head;
}
htemp = head;
//查找表的末尾
while (htemp.nextNode != null) {
htemp = htemp.nextNode;
}
htemp.nextNode = node;
return head;
}
3.插入头节点
(1)首先分配内存空间,保存新增的结点。
(2)使新增节点指向头引用head所指向的节点
(3)使用头引用head指向新增节点
代码实例如下:
CLType CLAddFirst(CLType head, DATA2 nodeData) {
CLType node;
node = new CLType();
if (node == null) {
System.out.println("申请内存失败!");
return null;
} else {
node.nodeData = nodeData;
node.nextNode = null;
head = node;
return head;
}
}
4.查找节点
CLType CLFindNode(CLType head, String key) {
CLType htemp;
htemp = head;
while (htemp != null) {
if (htemp.nodeData.key.compareTo(key) == 0) {
return htemp;
}
htemp = htemp.nextNode;
}
return null;
}
5.插入节点
插入节点就是在链表中间部分的指定位置增加入结点。插入节点的操作步骤如下:
(1)分配内存空间,保存新增的结点。
(2)找到要插入的逻辑位置,也就是位于哪两个结点之间。
(3)修改插入位置结点的引用,使其指向新增结点,而使新增节点指向原插入位置所指向的节点。
CLType CLInsertNode(CLType head, String findkey, DATA2 nodeData) {
CLType node, nodetemp;
node = new CLType();
if (node == null) {
System.out.println("申请内存失败!");
return null;
}
node.nodeData = nodeData;
nodetemp = CLFindNode(head, findkey);
if (nodetemp != null) {
node.nextNode = nodetemp.nextNode;
nodetemp.nextNode = node;
} else {
System.out.println("未找到正确的位置!");
}
return head;
}
6/删除节点
删除节点的操作步骤如下:
(1)查找需要删除的节点。
(2)使前一节点指向当前节点的下一节点
(3)删除节点
int CLDeleteNode(CLType head, String key) {
//node保存删除节点的前一节点
CLType node, htemp;
htemp = head;
node = head;
while (htemp != null) {
if (head.nodeData.key.compareTo(key) == 0) {
node.nextNode = htemp.nextNode;
//释放内存
htemp = null;
return 1;
} else {
node = htemp;
htemp = htemp.nextNode;
}
}
return 0;
}
7.计算链表长度
int CLLength(CLType head) {
CLType htemp;
int Len = 0;
htemp = head;
while (htemp != null) {
Len++;
htemp = htemp.nextNode;
}
return Len;
}
8.显示所有节点
void CLAllNode(CLType head) {
CLType htemp;
DATA2 nodeData;
htemp = head;
System.out.println("当前链表共有" + CLLength(head) + "个节点。链表所有的数据如下:");
while (htemp != null) {
nodeData = htemp.nodeData;
System.out.println("节点:" + nodeData.key + " " + nodeData.name + " " + nodeData.age);
htemp = htemp.nextNode;
}
}
链表操作实例
import java.util.Scanner;
class DATA2 {
//节点的关键字
String key;
String name;
int age;
}
/**
* 定义链表结构
*/
class CLType {
DATA2 nodeData = new DATA2();
CLType nextNode;
CLType CLAddEnd(CLType head, DATA2 nodeData) {
CLType node, htemp;
node = new CLType();
if (node == null) {
System.out.println("申请内存失败!");
return null;
} else {
node.nodeData = nodeData;
node.nextNode = null;
if (head == null) {
head = node;
return head;
}
htemp = head;
//查找表的末尾
while (htemp.nextNode != null) {
htemp = htemp.nextNode;
}
htemp.nextNode = node;
return head;
}
}
CLType CLAddFirst(CLType head, DATA2 nodeData) {
CLType node;
node = new CLType();
if (node == null) {
System.out.println("申请内存失败!");
return null;
} else {
node.nodeData = nodeData;
node.nextNode = null;
head = node;
return head;
}
}
CLType CLFindNode(CLType head, String key) {
CLType htemp;
htemp = head;
while (htemp != null) {
if (htemp.nodeData.key.compareTo(key) == 0) {
return htemp;
}
htemp = htemp.nextNode;
}
return null;
}
CLType CLInsertNode(CLType head, String findkey, DATA2 nodeData) {
CLType node, nodetemp;
node = new CLType();
if (node == null) {
System.out.println("申请内存失败!");
return null;
}
node.nodeData = nodeData;
nodetemp = CLFindNode(head, findkey);
if (nodetemp != null) {
node.nextNode = nodetemp.nextNode;
nodetemp.nextNode = node;
} else {
System.out.println("未找到正确的位置!");
}
return head;
}
int CLDeleteNode(CLType head, String key) {
//node保存删除节点的前一节点
CLType node, htemp;
htemp = head;
node = head;
while (htemp != null) {
if (htemp.nodeData.key.compareTo(key) == 0) {
node.nextNode = htemp.nextNode;
htemp = null;
return 1;
} else {
node = htemp;
htemp = htemp.nextNode;
}
}
return 0;
}
int CLLength(CLType head) {
CLType htemp;
int Len = 0;
htemp = head;
while (htemp != null) {
Len++;
htemp = htemp.nextNode;
}
return Len;
}
void CLAllNode(CLType head) {
CLType htemp;
DATA2 nodeData;
htemp = head;
System.out.println("当前链表共有" + CLLength(head) + "个节点。链表所有的数据如下:");
while (htemp != null) {
nodeData = htemp.nodeData;
System.out.println("节点:" + nodeData.key + " " + nodeData.name + " " + nodeData.age);
htemp = htemp.nextNode;
}
}
}
public class LinkedList {
public static void main(String[] args) {
CLType node, head = null;
CLType CL = new CLType();
String key, findkey;
Scanner input = new Scanner(System.in);
System.out.println("链表测试。先输入链表中的数据,格式为:关键字 姓名 年龄");
do {
DATA2 nodeData = new DATA2();
nodeData.key = input.next();
if (nodeData.key.equals("0")) {
break;
} else {
nodeData.name = input.next();
nodeData.age = input.nextInt();
//在链表尾部添加节点
head = CL.CLAddEnd(head, nodeData);
}
} while (true);
CL.CLAllNode(head);
System.out.println("演示插入节点,输入插入位置的关键字:");
findkey = input.next();
System.out.println("输入插入节点的数据(关键字 姓名 年龄):");
DATA2 nodeData = new DATA2();
nodeData.key = input.next();
nodeData.name = input.next();
nodeData.age = input.nextInt();
head = CL.CLInsertNode(head, findkey, nodeData);
CL.CLAllNode(head);
System.out.println("演示删除节点,输入删除位置的关键字:");
key = input.next();
CL.CLDeleteNode(head, key);
CL.CLAllNode(head);
System.out.println("演示在链表中查找,输入查找关键字:");
key = input.next();
node = CL.CLFindNode(head, key);
if (node != null) {
nodeData = node.nodeData;
System.out.println("关键字" + key + "对应的节点为" + nodeData.key + " " + nodeData.name + " " + nodeData.age);
} else {
System.out.println("在链表中未找到关键字为" + key + "的节点");
}
}
}
执行结果