一、基本介绍
定义 单链表(Single Linked List)是一种特殊的线性数据结构,由一系列节点(Node)组成,每个节点包含两部分:
- 数据域(Data Field):用于存储具体的数据元素。
- 指针域(Pointer Field),也称为链接或引用,指向列表中的下一个节点。最后一个节点的指针通常设置为
null
以表示链表的结束。
特性
- 动态性:单链表可以在运行时根据需要添加或删除节点,无需预先设定固定的容量大小。
- 空间效率:虽然每个节点需要额外的空间存储指针信息,但在处理大规模数据且不需要连续内存空间的情况下,单链表可以更高效地利用内存资源。
- 插入和删除操作:在非头尾位置插入和删除元素的时间复杂度通常为O(n),因为可能需要遍历找到合适的位置;而在头部或尾部进行相应操作则相对高效,时间复杂度可以达到O(1)。
二、基本操作
-
创建链表:初始化一个空链表,即创建一个不包含任何数据元素的头节点,并将其
next
属性设为null
。 -
插入节点:
- 在尾部插入不根据编号:遍历到当前尾节点,然后修改尾节点的
next
指向新节点。 - 在根据编号顺序插入:找到前驱节点,让新节点成为前驱节点的后继节点,原有节点的顺序随之调整。
- 在尾部插入不根据编号:遍历到当前尾节点,然后修改尾节点的
-
删除节点:
- 删除头部节点:直接将头节点更新为原头节点的下一个节点。
- 删除尾部节点:遍历至倒数第二个节点并修改其
next
为null
。 - 删除指定节点:找到待删节点的前驱节点,将其
next
指向待删节点的后继节点。
-
遍历链表:通过头节点开始,沿着
next
指针逐个访问每个节点。 -
修改节点: 找到待修改点的位置,将其数据域修改不修改编号
三、应用场景
单链表因其特性广泛应用于各种场合:
- 实现堆栈和队列:结合适当的接口,单链表可作为堆栈或队列的基础结构来实现高效的插入和删除操作。
- 文件系统目录结构:文件系统的目录项可以视为单链表节点,便于实现子目录的增删改查操作。
- 图形用户界面组件管理:GUI框架中,控件层级关系可以用单链表来维护,方便快速查找、插入或移除组件。
- 文本编辑器中的撤销/重做功能:使用双链表记录操作序列,以便于实现历史记录的回溯与恢复。
四、实现原理
1.带头部链表在直接尾部插入
- 先创建一个head头节点,作用就是表示单链表的头
- 每添加一个节点就直接加入到链表的最后
- 遍历:通过一个辅助变量遍历,以遍历整个链表
2.带头部链表按照编号顺序插入
- 先创建一个head头节点,作用就是表示单链表的头
- 找到添加新节点的位置,通过辅助变量遍历搞定
- 将新节点.next = temp.next
- 将temp.next = 新节点
3.修改节点
- 先创建一个head头节点,作用就是表示单链表的头
- 通过遍历找到该节点
- temp.name = 新节点.name temp.nickName = 新节点.nickName
3.删除节点
- 先创建一个head头节点,作用就是表示单链表的头
- 通过遍历找到该节点的前一个节点
- temp.next = temp.next.next
- 被删除的节点,将不会有其他引用指向,会被垃圾回收机制回收
五、代码展示
说明:代码展示以水浒传英雄添加按照编号顺序添加和不按照编号顺序添加两种方式进行添加操作,其中先定义HeroNode,每一个HeroNode对象就是一个节点并且是带头部链表。
1.定义HeroNode类
class HeroNode {
public int no;
public String name;
public String nickName;
public HeroNode next;
public HeroNode(int Noe, String Name, String NickName) {
this.no = Noe;
this.name = Name;
this.nickName = NickName;
}
@Override
public String toString() {
return "HeroNode{" + "no=" + no + ", name='" + name + '\'' + ", nickName='" + nickName + '\'' + '}';
}
}
2.初始化头节点
class SingleLinkedList {
// 初始化一个头结点 不存放具体数据
private HeroNode head = new HeroNode(0, "", "");
}
3.添加节点(尾部添加)
// 第一种添加节点到单向链表
// 思路,不考虑编号时
// 1.找到当前链表的最后节点
// 2.将最后这个节点的next指向当前新的节点
public void add(HeroNode heroNode) {
// 头结点不能动,所以用辅助遍历temp
HeroNode temp = head;
while (true) {
// 找到链表的最后了
if (temp.next == null) {
break;
}
temp = temp.next;
}
// 退出while循环时,temp就指向了链表的最后
temp.next = heroNode;
}
4.添加节点(按编号顺序添加)
// 第二种添加节点到单向链表
// 考虑编号顺序时
public void addByOrder(HeroNode heroNode) {
HeroNode temp = head;
boolean flag = false; //标志添加的编号是否存在,默认FALSE
while (true) {
if (temp.next == null) { //说明temp已经在链表的最后
break;
}
if (temp.next.no > heroNode.no) { //位置找到
break;
} else if (temp.next.no == heroNode.no) { //编号存在
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
System.out.printf("准备插入的英雄编号 %d 已存在,不能加入\n", heroNode.no);
} else {
// 添加
heroNode.next = temp.next;
temp.next = heroNode;
}
}
4.修改节点
//修改节点 根据编号修改
public void update(HeroNode heroNode) {
HeroNode temp = head.next;
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
if (temp.no == heroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.name = heroNode.name;
temp.nickName = heroNode.nickName;
} else {
System.out.printf("准备修改的英雄编号 %d 不存在,不能修改\n", heroNode.no);
}
}
5.删除节点
// 删除节点
public void delete(int heroNodeNo) {
HeroNode temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.next.no == heroNodeNo) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.next = temp.next.next;
} else {
System.out.printf("准备删除的英雄编号 %d 不存在,不能删除\n", heroNodeNo);
}
}
6.遍历节点
// 显示链表[遍历]
public void list() {
// 链表为空
if (head.next == null) {
return;
}
HeroNode temp = head.next;
while (true) {
if (temp == null) {
break;
}
System.out.println(temp);
// 将节点后移
temp = temp.next;
}
}
六、总结
单链表是一种灵活、动态的数据结构,理解并熟练运用它对于提升编程能力和解决实际问题具有重要意义。尽管其随机访问性能不及数组,但在许多特定场景下,单链表凭借其独特的优点成为了不可或缺的数据结构选择。
总的来说,单链表适用于频繁进行动态增删操作且对顺序访问有要求的场景,但相比数组,在随机访问方面性能较差。