链表介绍
链表是有序的列表,但是它在内存中是存储如下: 链表在内存中不一定是连续分布. [链表是学习 树,森林,图的基础] // 链表的优点 1. 插入,删除,很快, 缺点是检索速度慢. => 树结构(AVL, 红黑树)

单链表的介绍
单链表(带头结点) 逻辑结构示意图如下

单链表的应用实例
使用带head头的单向链表实现 –水浒英雄排行榜管理
1)完成对英雄人物的增删改查操作, 注: 删除和修改,查找
2)第一种方法在添加英雄时,直接添加到链表的尾部
3)第二种方式在添加英雄时,根据排名将英雄插入到指定位置
(如果有这个排名,则添加失败,并给出提示)

代码
package datastructure
/**
* @author cherry
* @create 2019-10-01-18:50
*/
import util.control.Breaks._
object SingleLinkedListDemo {
def main(args: Array[String]): Unit = {
//测试一把链表的创建和遍历
val singleLinkedList = new SingleLinkedList()
//创建三个英雄
val node1 = new HeroNode(1, "宋江", "及时雨")
val node2 = new HeroNode(2, "卢俊义", "玉麒麟")
val node3 = new HeroNode(3, "吴用", "智多星")
val node4 = new HeroNode(4, "林冲", "豹子头")
singleLinkedList.add(node4)
singleLinkedList.add(node1)
singleLinkedList.add(node3)
singleLinkedList.add(node2)
/*singleLinkedList.addByOrder(node4)
singleLinkedList.addByOrder(node1)
singleLinkedList.addByOrder(node3)
singleLinkedList.addByOrder(node2)*/
//singleLinkedList.addByOrder(node2)
println("链表的情况")
singleLinkedList.list()
//测试对结点的修改
val node5 = new HeroNode(4, "公孙胜", "入云龙")
singleLinkedList.update(node5)
printf("链表修改后的情况\n")
singleLinkedList.list()
/* //测试一把删除结点
println()
println()
println("删除后的链表情况")
singleLinkedList.del(3)
singleLinkedList.del(1)
singleLinkedList.del(4)
singleLinkedList.del(12)
singleLinkedList.list()*/
}
}
//创建单链表的类
class SingleLinkedList {
//创建头结点,指向该链表的头部
val head = new HeroNode(-1, "","")
//删除一个结点
def del(no:Int): Unit = {
//判断是否为空
if(isEmpty()) {
println("链表为空")
return
}
//让temp指向head
var temp = head
var flag = false
//遍历,让temp指向,要删除的结点的前一个结点
breakable {
while (true) {
if (temp.next.no == no) {
//找到
flag = true
break()
}
//判断temp是否指向链表的倒数第二个结点
if (temp.next.next == null) {
break()
}
//让temp后移,实现遍历
temp = temp.next
}
}
//判断flag 的情况
if(flag) {//找到
temp.next = temp.next.next
} else {
printf("你要删除的结点%d 不存在", no)
}
}
//修改结点信息
def update(heroNode: HeroNode): Unit = {
//如果链表为空
if(isEmpty()) {
println("链表为空")
return
}
//辅助指针,帮助我们定位到修改的结点
var temp = head.next
//需要定义一个变量,标识是否找到该节点
var flag = false
breakable {
while (true) {
//比较
if (temp.no == heroNode.no) {
flag = true
break()
}
//判断temp 是不是到最后
if (temp.next == null) { //最后
break()
}
//让temp后移,实现遍历
temp = temp.next
}
}
//判断
if(flag) { //找到
temp.nickname = heroNode.nickname
temp.name = heroNode.name
} else { // 没有找到
printf("你要修改的%d英雄不存在.", heroNode.no)
}
}
//添加英雄到单链表
//默认直接将人加入到链表的最后
def add(heroNode: HeroNode): Unit = {
//思路
//1. 先找到链表的最后结点
//2. 然后让最后结点.next = 新的结点
//因为head 不动,因此我们使用一个辅助指针来完成定位
var temp = head
breakable {
while (true) {
if (temp.next == null) {
//temp已经到链表的最后
break()
}
temp = temp.next // 让temp后移
}
}
//当退出while时,temp指向最后
temp.next = heroNode
}
//按照no编号的从小到大进行插入
def addByOrder(heroNode: HeroNode): Unit = {
//让temp 指向 head
var temp = head
var flag = false // 标识是否已经存在这个编号的结点
breakable {
//将temp 定位要添加的结点前一个位置,即将新的结点,添加到temp 后面
while (true) {
//判断是否是最后一个,如果就是最后一个,也找到位置
if (temp.next == null) {
break()
}
if (temp.next.no == heroNode.no) { //说明no已经存在
flag = true
break()
} else if (temp.next.no > heroNode.no) { //说明heroNode 就应该加入到temp的后面
break()
}
//将temp 后移,实现遍历
temp = temp.next
}
}
//当退出while循环后,
if(flag) { //说明已经有no
printf("已经存在no=%d 人物", heroNode.no)
} else {
heroNode.next = temp.next //
temp.next = heroNode
}
}
//遍历单向链表
//思路
//1. 仍然让temp帮助进行遍历
// 2.判断链表是否空,空退出, 不为空,就遍历,直到最后结点
def list(): Unit = {
if(isEmpty()) {
println("链表为空,无法遍历")
return
}
//记住,head不能动, 为什么指向 head.next,因为我们有效的数据不包括head
var temp = head.next
breakable {
while (true) {
//输出当前这个结点的信息
printf("no=%d name=%s nickname=%s --> \n", temp.no, temp.name, temp.nickname)
//是否是最后结点
if (temp.next == null) { //是最后结点
break()
}
//如果不是最后的结点,让temp 后移
temp = temp.next
}
}
}
def isEmpty(): Boolean = {
head.next == null
}
}
class HeroNode(hNo: Int, hName: String, hNickname: String) {
val no = hNo
var name = hName
var nickname = hNickname
var next: HeroNode = null //默认为null
}
测试添加

测试修改

测试删除

测试按no排序添加

双向链表的应用实例
使用带head头的双向链表实现 –水浒英雄排行榜
管理单向链表的缺点分析:
1)单向链表,查找的方向只能是一个方向,而双向链
表可以向前或者向后查找。
2)单向链表不能自我删除,需要靠辅助节点 ,而双向
链表,则可以自我删除,所以前面我们单链表删除
时节点,总是找到temp的下一个节点来删除的(认真
体会).
3)示意图帮助理解删除

代码
package datastructure
/**
* @author cherry
* @create 2019-10-01-20:57
*/
import scala.util.control.Breaks.{break, breakable}
object DoubleLinkedListDemo {
def main(args: Array[String]): Unit = {
//测试一下双向遍历的添加和遍历
//测试一把链表的创建和遍历
val doubleLinkedList = new DoubleLinkedList()
//创建三个英雄
val node1 = new HeroNode2(1, "宋江", "及时雨")
val node2 = new HeroNode2(2, "卢俊义", "玉麒麟")
val node3 = new HeroNode2(3, "吴用", "智多星")
val node4 = new HeroNode2(4, "林冲", "豹子头")
doubleLinkedList.add(node1)
doubleLinkedList.add(node2)
doubleLinkedList.add(node3)
doubleLinkedList.add(node4)
println("双向链表的情况")
doubleLinkedList.list()
val node5 = new HeroNode2(4, "公孙胜", "入云龙")
doubleLinkedList.update(node5)
println("双向链表的修改后情况")
doubleLinkedList.list()
//删除的测试
doubleLinkedList.del(1)
doubleLinkedList.del(2)
doubleLinkedList.del(3)
doubleLinkedList.del(4)
println("双向链表的删除后情况")
doubleLinkedList.list()
}
}
//添加,遍历,修改,删除
class DoubleLinkedList {
//创建头结点,指向该链表的头部
val head = new HeroNode2(-1, "","")
//删除结点,因为双向链表可以实现自我删除,因此我们让temp指向要删除的结点即可
def del(no:Int): Unit = {
if(isEmpty()) {
println("链表空")
return
}
var temp = head.next
var flag = false
breakable {
while (true) {
if (temp.no == no) {
flag = true
break()
}
if (temp.next == null) {
break()
}
temp = temp.next //遍历
}
}
//当退出while循环后,如果flag=true,则temp 就指向要删除的结点
if(flag) {
//删除
temp.pre.next = temp.next
if(temp.next != null) {
temp.next.pre = temp.pre
}
}else{
printf("要删除的no=%d,不存在", no)
}
}
//添加英雄到单链表
//默认直接将人加入到链表的最后
def add(heroNode: HeroNode2): Unit = {
//思路
//1. 先找到链表的最后结点
//2. 然后让最后结点.next = 新的结点
//因为head 不动,因此我们使用一个辅助指针来完成定位
var temp = head
breakable {
while (true) {
if (temp.next == null) {
//temp已经到链表的最后
break()
}
temp = temp.next // 让temp后移
}
}
//当退出while时,temp指向最后
temp.next = heroNode
heroNode.pre = temp//必须
}
//遍历单向链表
//思路
//1. 仍然让temp帮助进行遍历
// 2.判断链表是否空,空退出, 不为空,就遍历,直到最后结点
def list(): Unit = {
if(isEmpty()) {
println("链表为空,无法遍历")
return
}
//记住,head不能动, 为什么指向 head.next,因为我们有效的数据不包括head
var temp = head.next
breakable {
while (true) {
//输出当前这个结点的信息
printf("no=%d name=%s nickname=%s --> ", temp.no, temp.name, temp.nickname)
//是否是最后结点
if (temp.next == null) { //是最后结点
break()
}
//如果不是最后的结点,让temp 后移
temp = temp.next
}
}
}
def isEmpty(): Boolean = {
head.next == null
}
//修改结点信息
def update(heroNode: HeroNode2): Unit = {
//如果链表为空
if(isEmpty()) {
println("链表为空")
return
}
//辅助指针,帮助我们定位到修改的结点
var temp = head.next
//需要定义一个变量,标识是否找到该节点
var flag = false
breakable {
while (true) {
//比较
if (temp.no == heroNode.no) {
flag = true
break()
}
//判断temp 是不是到最后
if (temp.next == null) { //最后
break()
}
//让temp后移,实现遍历
temp = temp.next
}
}
//判断
if(flag) { //找到
temp.nickname = heroNode.nickname
temp.name = heroNode.name
} else { // 没有找到
printf("你要修改的%d英雄不存在.", heroNode.no)
}
}
}
class HeroNode2(hNo: Int, hName: String, hNickname: String) {
val no = hNo
var name = hName
var nickname = hNickname
var next: HeroNode2 = null //指向后一个结点 默认为null
var pre: HeroNode2 = null // 指向前一个结点,默认null
}
测试删除

单链表的常见面试题有如下:
- 求单链表中有效节点的个数
- 查找单链表中的倒数第k个结点 【新浪面试题】
- 单链表的反转【腾讯面试题,有点难度】
- 从尾到头打印单链表 【百度,要求方式1:反向遍历 。 方式2:Stack栈】
- 合并两个有序的单链表,合并之后的链表依然有序
单向环形链表的应用场景

丢手帕问题
环形单向链表介绍

环形单向链表的应用实例
ØJosephu 问题
Josephu 问题为:设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
Ø提示
用一个不带头结点的循环链表来处理Josephu 问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。
Ø思路分析示意图
- 约瑟夫问题解决的思路

代码实现
package datastructure
/**
* @author cherry
* @create 2019-10-01-21:21
*/
import util.control.Breaks._
object JosephDemo {
def main(args: Array[String]): Unit = {
//测试一把
val josephu = new Joseph()
josephu.addBoy(5)
josephu.list()
//测试一下游戏
//josephu.countBoy(2,2,5) // 3 - 5 - 2 - 1 - 4
josephu.countBoy2(2,2,5) //3 - 5 - 2 - 1 - 4
}
}
class Joseph {
//定义一个first,初始为null
var first: Boy = null
//分析:因为在出圈过程中,first 实际上是没有什么用处,因此可以去掉
def countBoy2(startNO:Int, countNum:Int, boyNums:Int): Unit = {
//做下数据的验证
if(first == null || startNO > boyNums || startNO <= 0) {
println("输出参数有误,不能玩游戏")
return
}
//1创建辅助指针
var helper = first
//2让 helper 移动到 first 的上一个
breakable {
while (true) {
if (helper.next == first) {
break()
}
helper = helper.next
}
}
//3. 让helper 移动 k - 1
for(i <- 0 until startNO - 1) {
helper = helper.next
}
breakable {
while (true) {
//4. 让first 和 helper 再移动 m - 1 个位置
for (i <- 0 until countNum - 1) { //for循环结束后,helper.next就指向了要删除小孩
helper = helper.next
}
//5. helper.next就指向了要删除的小孩
printf("小孩no=%d出圈\n", helper.next.no)
helper.next = helper.next.next
//判断是否已经只有一个小孩了
if (helper == helper.next) {
break()
}
}
}
printf("最后留在圈中的小孩是no=%d\n",helper.no)
}
//小孩出圈
def countBoy(startNO:Int, countNum:Int, boyNums:Int): Unit = {
//做下数据的验证
if(first == null || startNO > boyNums || startNO <= 0) {
println("输出参数有误,不能玩游戏")
return
}
//1创建辅助指针
var helper = first
//2让 helper 移动到 first 的上一个
breakable {
while (true) {
if (helper.next == first) {
break()
}
helper = helper.next
}
}
//3.让 first 移动 k - 1 个位置, 同时让helper 也做相应的移动
for(i <- 0 until startNO - 1) {
first = first.next
helper = helper.next
}
breakable {
while (true) {
//4. 让first 和 helper 再移动 m - 1 个位置
for (i <- 0 until countNum - 1) { //for循环结束后,first就指向了要删除小孩
first = first.next
helper = helper.next
}
//5. first 就指向了要删除的小孩
printf("小孩no=%d出圈\n", first.no)
first = first.next
helper.next = first
//判断是否已经只有一个小孩了
if (first == helper) {
break()
}
}
}
printf("最后留在圈中的小孩是no=%d\n",first.no)
}
//添加小孩,形成环形链表
def addBoy(boyNums: Int): Unit = {
var curBoy: Boy = null
for (i <- 1 to boyNums) {
//创建小孩对象
val boy = new Boy(i)
//处理第一个小孩, 就形成环形
if (i == 1) {
first = boy
curBoy = boy
first.next = first
} else {
curBoy.next = boy
boy.next = first
curBoy = boy
}
}
}
//遍历环形链表
def list(): Unit = {
//判断链表是否为空
if(first == null) {
println("没有小孩")
return
}
var curBoy = first
breakable {
while (true) {
printf("no=%d \n", curBoy.no)
if (curBoy.next == first) {
//表示curBoy已经是最后这个
break()
}
//curBoy后移
curBoy = curBoy.next
}
}
}
}
//小孩类
class Boy(bNo: Int) {
val no = bNo
var next: Boy = null
}
测试代码

本文介绍了链表的基本概念及特点,通过单链表、双链表和环形单向链表的应用实例详细阐述了链表的操作方法,包括增删改查等基本操作,并提供了完整的代码实现。


被折叠的 条评论
为什么被折叠?



