• 链表


    链表介绍

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

    小结

    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后移

      }

      }

      }

      }

  • 相关阅读:
    第十六周个人作业
    排球比赛积分程序
    本周个人总结
    本周个人总结
    排球积分规则
    我与计算机
    排球计分(实践)
    观后感
    18周 个人作业
    总结
  • 原文地址:https://www.cnblogs.com/shuzhiwei/p/11210112.html
Copyright © 2020-2023  润新知