链表

链表介绍

链表是有序的列表,但是它在内存中是存储如下

小结

  1. 链表是一个有序列表
  2. 链表的数据,在内存空间不一定是连续分布的.

单链表的介绍

  1. 单链表(带头结点) 逻辑结构示意图如下 //

    所谓带头节点,就是链表的头有一个head节点, 该节点不存放具体的数据,只是为了操作方便而设计的这个节点.

单链表的应用实例

  1. 使用带head头的单向链表实现–水浒英雄排行榜管理

  2. 完成对英雄人物的增删改查操作,: 删除和修改,查找可以考虑学员独立完成

    思路分析:

    代码实现

    package com.atguigu.chapter18.linkedlist

    import util.control.Breaks._

    object SingleLinkedListDemo {

    def main(args: Array[String]): Unit = {

    //测试单向链表的添加和遍历

    val hero1 = new HeroNode(1, "宋江", "及时雨")

    val hero2 = new HeroNode(3, "宋江3", "及时雨3")

    val hero3 = new HeroNode(4, "宋江4", "及时雨4")

    val hero4 = new HeroNode(2, "宋江2", "及时雨2")

    //创建一个单向链表

    val singleLinkedList = new SingleLinkedList

    singleLinkedList.add(hero1)

    singleLinkedList.add(hero2)

    singleLinkedList.add(hero3)

    singleLinkedList.add(hero4)

    // singleLinkedList.add2(hero1)

    // singleLinkedList.add2(hero2)

    // singleLinkedList.add2(hero3)

    // singleLinkedList.add2(hero4)

    singleLinkedList.list()

    val hero5 = new HeroNode(3, "吴用", "智多星")

    singleLinkedList.update(hero5)

    println("==========================")

    singleLinkedList.list()

    println("@@@@@@@@@@测试删除@@@@@@@@@@@")

    singleLinkedList.del(4)

    singleLinkedList.del(2)

    singleLinkedList.del(1)

    singleLinkedList.list()

    }

    }

    //定义我们的单向链表管理Hero

    class SingleLinkedList {

    //先初始化一个头结点, 头结点一般不会动

    val head = new HeroNode(0, "", "")

    //删除节点

    //1. 思路

    // 删除的思路

    // 1). head 不能动

    // 2). 使用temp变量, 我们要删除的应该是temp 的下一个结点., 即,我们在比较时,始终比较的是 temp.next 的节点值

    //2. 代码

    def del(no: Int): Unit = {

    var temp = head

    var flag = false // 标志变量用于确定是否有要删除的节点

    breakable {

    while (true) {

    if (temp.next == null) {

    break()

    }

    if (temp.next.no == no) {

    //找到了

    flag = true

    break()

    }

    temp = temp.next //temp后移

    }

    }

    if (flag) {

    //可以删除

    temp.next = temp.next.next

    } else {

    printf("要删除的no=%d 不存在 " , no)

    }

    }

    //修改节点的值, 根据no编号为前提修改, no不能改

    //课后思考题:如果将这个节点替换,如何实现

    def update(newHeroNode: HeroNode): Unit = {

    if (head.next == null) {

    println("链表为空")

    return

    }

    //先找到节点

    var temp = head.next

    var flag = false

    breakable {

    while (true) {

    if (temp == null) {

    break()

    }

    if (temp.no == newHeroNode.no) {

    //找到.

    flag = true

    break()

    }

    temp = temp.next //

    }

    }

    //判断是否找到

    if (flag) {

    temp.name = newHeroNode.name

    temp.nickname = newHeroNode.nickname

    } else {

    printf("没有找到编号为%d 节点,不能修改 ", newHeroNode.no)

    }

    }

    //编写添加方法

    //第一种方法在添加英雄时,直接添加到链表的尾部

    def add(heroNode: HeroNode): Unit = {

    //因为头结点不能动, 因此我们需要哟有一个临时结点,作为辅助

    var temp = head

    //找到链表的最后

    breakable {

    while (true) {

    if (temp.next == null) { //说明temp已经是链表最后

    break()

    }

    //如果没有到最后

    temp = temp.next

    }

    }

    //当退出while循环后,temp就是链表的最后

    temp.next = heroNode

    }

    //第二种方式在添加英雄时,根据排名将英雄插入到指定位置

    //如果有这个排名,则添加失败,并给出提示

    //编号从小到大排序

    //思路

    def add2(heroNode: HeroNode): Unit = {

    //因为头结点不能动, 因此我们需要哟有一个临时结点,作为辅助

    //注意,我们在找这个添加位置时,是将这个节点加入到temp的后面

    //因此,在比较时,是将当前的heroNode temp.next 比较

    var temp = head

    var flag = false // flag 是用于判断是否该英雄的编号已经存在, 默认为false

    breakable {

    while (true) {

    if (temp.next == null) { //说明temp已经是链表最后

    break()

    }

    if (temp.next.no > heroNode.no) {

    //位置找到,当前这个节点,应当加入到temp

    break()

    } else if (temp.next.no == heroNode.no) {

    //已经有这个节点

    flag = true

    break()

    }

    temp = temp.next // 注意

    }

    }

    if (flag) { // 不可以加入

    printf("待插入的英雄编号 %d 已经有了,不能加入 ", heroNode.no)

    } else {

    //加入,注意顺序

    heroNode.next = temp.next

    temp.next = heroNode

    }

    }

    //遍历单向链表

    def list(): Unit = {

    //判断当前链表是否为空

    if (head.next == null) {

    println("链表为空!!")

    return

    }

    //因为头结点不能动, 因此我们需要哟有一个临时结点,作为辅助

    //因为head 结点数据,我们不关心,因此这里 temp=head.next

    var temp = head.next

    breakable {

    while (true) {

    //判断是否到最后

    if (temp == null) {

    break()

    }

    printf("结点信息 no=%d name=%s nickname=%s ",

    temp.no, temp.name, temp.nickname)

    temp = temp.next

    }

    }

    }

    }

    //先创建HeroNode

    class HeroNode(hNo: Int, hName: String, hNickname: String) {

    var no: Int = hNo

    var name: String = hName

    var nickname: String = hNickname

    var next: HeroNode = null //next 默认为null

    }

双向链表的应用实例

  1. 使用带head头的双向链表实现–水浒英雄排行榜

    管理单向链表的缺点分析:

  2. 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
  3. 单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除,所以前面我们单链表删除时节点,总是找到temp的下一个节点来删除的(认真体会).
  4. 示意图帮助理解删除

  5. 将前面的单向链表改成双向链表

    package com.atguigu.chapter18.linkedlist

    import scala.util.control.Breaks.{break, breakable}

    object DoubleLinkedListDemo {

    def main(args: Array[String]): Unit = {

    //测试单向链表的添加和遍历

    val hero1 = new HeroNode2(1, "宋江", "及时雨")

    val hero2 = new HeroNode2(3, "宋江3", "及时雨3")

    val hero3 = new HeroNode2(4, "宋江4", "及时雨4")

    val hero4 = new HeroNode2(2, "宋江2", "及时雨2")

    //创建一个单向链表

    val doubleLinkedList = new DoubleLinkedList

    doubleLinkedList.add(hero1)

    doubleLinkedList.add(hero2)

    doubleLinkedList.add(hero3)

    doubleLinkedList.add(hero4)

    doubleLinkedList.list()

    val hero5 = new HeroNode2(2, "卢俊义", "玉麒麟")

    doubleLinkedList.update(hero5)

    println("--------------------------")

    doubleLinkedList.list()

    //删除测试

    doubleLinkedList.del(2)

    doubleLinkedList.del(3)

    doubleLinkedList.del(4)

    println("删除后")

    doubleLinkedList.list()

    //加入

    doubleLinkedList.add(hero2)

    println("~~~~~~~~~~~~")

    doubleLinkedList.list()

    }

    }

    class DoubleLinkedList {

    //先初始化一个头结点, 头结点一般不会动

    val head = new HeroNode2(0, "", "")

    //添加-遍历-修改-删除

    //编写添加方法

    //第一种方法在添加英雄时,直接添加到链表的尾部

    def add(heroNode: HeroNode2): Unit = {

    //因为头结点不能动, 因此我们需要哟有一个临时结点,作为辅助

    var temp = head

    //找到链表的最后

    breakable {

    while (true) {

    if (temp.next == null) { //说明temp已经是链表最后

    break()

    }

    //如果没有到最后

    temp = temp.next

    }

    }

    //当退出while循环后,temp就是链表的最后

    temp.next = heroNode

    heroNode.pre = temp

    }

    //遍历方法一样, 不用

    def list(): Unit = {

    //判断当前链表是否为空

    if (head.next == null) {

    println("链表为空!!")

    return

    }

    //因为头结点不能动, 因此我们需要哟有一个临时结点,作为辅助

    //因为head 结点数据,我们不关心,因此这里 temp=head.next

    var temp = head.next

    breakable {

    while (true) {

    //判断是否到最后

    if (temp == null) {

    break()

    }

    printf("结点信息 no=%d name=%s nickname=%s ",

    temp.no, temp.name, temp.nickname)

    temp = temp.next

    }

    }

    }

    //修改节点的值, 根据no编号为前提修改, no不能改

    //课后思考题:如果将这个节点替换,如何实现

    def update(newHeroNode: HeroNode2): Unit = {

    if (head.next == null) {

    println("链表为空")

    return

    }

    //先找到节点

    var temp = head.next

    var flag = false

    breakable {

    while (true) {

    if (temp == null) {

    break()

    }

    if (temp.no == newHeroNode.no) {

    //找到.

    flag = true

    break()

    }

    temp = temp.next //

    }

    }

    //判断是否找到

    if (flag) {

    temp.name = newHeroNode.name

    temp.nickname = newHeroNode.nickname

    } else {

    printf("没有找到编号为%d 节点,不能修改 ", newHeroNode.no)

    }

    }

    //删除

    //思路,因为双向链表可以实现自我删除

    def del(no: Int): Unit = {

    //判断当前链表是否为空

    if (head.next == null) {

    println("链表空")

    return

    }

    var temp = head.next

    var flag = false // 标志变量用于确定是否有要删除的节点

    breakable {

    while (true) {

    if (temp == null) {

    break()

    }

    if (temp.no == no) {

    //找到了

    flag = true

    break()

    }

    temp = temp.next //temp后移

    }

    }

    if (flag) {

    //可以删除

    //temp.next = temp.next.next

    temp.pre.next = temp.next

    //思考

    if (temp.next != null) {

    temp.next.pre = temp.pre

    }

    } else {

    printf("要删除的no=%d 不存在 " , no)

    }

    }

    }

    //先创建HeroNode

    class HeroNode2(hNo: Int, hName: String, hNickname: String) {

    var no: Int = hNo

    var name: String = hName

    var nickname: String = hNickname

    var pre: HeroNode2 = null // pre 默认为null

    var next: HeroNode2 = null //next 默认为null

    }

单向环形链表的应用场景

  1. Josephu 问题

    Josephu 问题为:设编号为12,… nn个人围坐一圈,约定编号为k1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。

    提示:用一个不带头结点的循环链表来处理Josephu 问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。

环形单向链表的应用实例

  1. 使用环形单向链表来解决, Josephu问题

    代码和分析

    代码实现:

    package com.atguigu.chapter18.linkedlist

    import util.control.Breaks._

    object Josephu {

    def main(args: Array[String]): Unit = {

    //创建BoyGame

    val boyGame = new BoyGame

    boyGame.addBoy(70)

    boyGame.showBoy()

    //测试countBoy方法

    boyGame.countBoy(41, 30, 70)

    }

    }

    //定义Boy

    class Boy(bNo: Int) {

    val no: Int = bNo

    var next: Boy = null

    }

    //编写核心类BoyGame

    class BoyGame {

    //定义一个初始的头结点,

    var first: Boy = new Boy(-1)

    //添加小孩【形成一个单向环形的链表】

    //nums 表示共有几个小孩

    def addBoy(nums: Int): Unit = {

    if (nums < 1) {

    println("nums的值不正确")

    return

    }

    //在形成环形链表时,需要一个辅助指针

    var curBoy: Boy = null

    for (no <- 1 to nums) {

    //更加编号创建小孩对象

    val boy = new Boy(no)

    //如果是第一个小孩

    if (no == 1) {

    first = boy

    first.next = first // 形成一个环形的链表

    curBoy = first

    } else {

    curBoy.next = boy

    boy.next = first

    curBoy = boy

    }

    }

    }

    //编写方法countBoy, 完成游戏

    //startNo 从第几个人开始数

    //countNum 数几下

    //nums: 一共多少人

    def countBoy(startNo: Int, countNum: Int, nums: Int): Unit = {

    //对参数进行判断

    if (first.next == null || startNo < 1 || startNo > nums) {

    println("参数有误,重新输入!")

    return

    }

    //完成游戏的思路

    /*

    完成游戏的思路分析->实现代码

    1) first 前面设计一个辅助指针(helper , 即将helper 指针定位到 first 前面

    2) first 指针移动到 startNo 这个小孩(helper 对应移动)

    3) 开始数 countNum 个数[first helper 会对应的移动]

    4) 删除first 指向的这个小孩节点

    5) 思路

    */

    var helper = first

    //1)即将helper 指针定位到 first 前面

    breakable {

    while (true) {

    if (helper.next == first) {

    break()

    }

    helper = helper.next

    }

    }

    //2)first 指针移动到 startNo 这个小孩(helper 对应移动)

    for (i <- 0 until startNo - 1) {

    first = first.next

    helper = helper.next

    }

    // 开始数数,按照给定的值,每数到一个小孩就出圈, 直到环形链表只有一个节点

    breakable {

    while (true) {

    if (helper == first) {

    //只有一个人

    break()

    }

    //3) 开始数 countNum 个数[first helper 会对应的移动]

    for (i <- 0 until countNum - 1) { // 3

    first = first.next

    helper = helper.next

    }

    //输出出圈的人的信息

    printf("小孩%d出圈 ", first.no)

    //first 指向的节点删除

    first = first.next

    helper.next = first

    }

    }

    //while结束后, 只有一个人

    printf("最后留在圈的人是小孩编号为 %d ", first.no)

    }

    //遍历单向的环形链表

    def showBoy(): Unit = {

    if (first.next == null) {

    println("没有任何小孩~")

    return

    }

    //因为first不能动,还是借助一个辅助指针完成遍历

    var curBoy = first

    breakable {

    while (true) {

    printf("小孩编号 %d ", curBoy.no)

    if (curBoy.next == first) {

    break()

    }

    curBoy = curBoy.next //curBoy后移

    }

    }

    }

    }

原文地址:https://www.cnblogs.com/shuzhiwei/p/11210112.html