Scala数据结构和算法:链表(Linked List)、单链表与双链表、链表介绍、单向链表、双向链表、环形链表、环形链表解决Josephu问题

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

链表介绍

链表是有序的列表,但是它在内存中是存储如下: 链表在内存中不一定是连续分布. [链表是学习 树,森林,图的基础] // 链表的优点 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
}

测试删除

单链表的常见面试题有如下:

  1. 求单链表中有效节点的个数
  2. 查找单链表中的倒数第k个结点 【新浪面试题】
  3. 单链表的反转【腾讯面试题,有点难度】
  4. 从尾到头打印单链表 【百度,要求方式1:反向遍历 。 方式2:Stack栈】
  5. 合并两个有序的单链表,合并之后的链表依然有序

向环形链表的应用场景

丢手帕问题

环形单向链表介绍

环形单向链表的应用实例

ØJosephu  问题

Josephu  问题为:设编号为12… nn个人围坐一圈,约定编号为k1<=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
}

测试代码

内容概要:本文围绕VSG构网型逆变器跟网型逆变器之间的双向平滑切换控制策略展开深入研究,系统探讨了GFL(跟网型)GFM(构网型)逆变器在并网孤岛运行模式下的切换机制。基于Simulink平台构建仿真模型,详细实现了切换过程中的控制逻辑设计、系统稳定性保障及动态响应性能优化等关键技术环节,重点解决模式切换过程中可能出现的电流冲击、频率电压波动等问题,确保微电网在不同运行工况下的连续稳定运行。研究还融合事件触发机制分布式二次控制策略,实现频率电压的无差调节,并提升通信资源受限或遭受DoS攻击等异常情况下的系统弹性协同控制能力,适用于高比例新能源接入场景下的微电网灵活运行需求。; 适合人群:具备电力电子、自动控制、微电网新能源系统等相关专业背景,从事电力系统仿真、分布式能源控制研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①深入理解构网型跟网型逆变器的运行机理及切换控制原理;②设计并仿真验证GFL/GFM模式间的双向平滑切换策略;③应用于微电网分层控制、孤岛检测无缝切换、二次频率电压恢复等实际工程场景;④支撑IEEE Transactions等顶级期刊论文的复现、科研项目开发及创新性课题研究。; 阅读建议:建议结合提供的Simulink仿真模型同步学习,重点关注模式切换判据、控制架构切换逻辑、事件触发条件设定及控制器参数整定方法,配合网盘中的完整代码模型资源进行仿真调试,以深入掌握其实现细节并开展进一步的优化拓展研究。
内容概要:本文研究基于分布式模型预测控制(DMPC)的多个固定翼无人机一致性控制问题,通过构建固定翼无人机的非线性动力学模型,结合分布式控制架构实现多无人机系统在复杂环境下的状态一致性协同飞行。文中系统阐述了模型预测控制算法的设计流程,引入一致性协议以确保编队稳定性,并充分考虑固定翼无人机的强非线性、姿态耦合特性以及通信延迟、局部信息交互受限等实际约束条件。通过Matlab进行仿真验证,展示了多无人机在不同初始条件下实现轨迹协同、速度同步队形保持的有效性。研究重点在于提升控制系统的鲁棒性、可扩展性实时性,为复杂动力学系统下的多智能体协同控制提供了可行的技术路径。; 适合人群:具备自动控制理论、飞行器动力学、多智能体系统或优化控制基础的研究生、科研人员及从事无人机系统开发的工程技术人员。; 使用场景及目标:①应用于多无人机协同编队飞行、区域覆盖监测、灾害救援军事侦察等实际任务场景;②为分布式控制算法在非线性、受限通信条件下的多智能体系统中提供算法设计仿真实现参考;③推动DMPC理论在航空航天领域中的一致性控制自主协同方向的发展; 阅读建议:建议读者结合提供的Matlab代码深入理解系统建模、代价函数构建、分布式优化求解及一致性协议集成等关键环节,重点关注算法在非线性系统中的收敛性鲁棒性表现,可进一步拓展至异构无人机协同、动态避障或通信拓扑时变等更具挑战性的研究方向。
内容概要:本文提出了一种基于蒙特卡洛模拟拉格朗日松弛法相结合的分散式优化方法,旨在解决电动汽车充电站在分时电价机制下的有序充电调度问题。通过构建综合考虑用户充电需求、电网负荷平衡及电价波动等因素的数学模型,采用蒙特卡洛方法生成大量可能的充电行为场景,并结合拉格朗日松弛算法对复杂约束条件进行分解求解,从而实现全局最优或近似最优的充电调度方案。该方法有效缓解了传统集中式优化带来的高计算复杂度问题,提高了调度系统的灵活性实时响应能力,特别适用于大规模电动汽车接入的场景。; 适合人群:具备一定电力系统基础知识、优化算法理论背景及Matlab编程能力的研究生、科研人员以及从事智能电网、电动汽车调度相关工作的工程技术人员。; 使用场景及目标:①应用于城市电动汽车充电站的运营管理,实现削峰填谷、降低用户充电成本并提升电网运行效率;②为电力调度机构提供高渗透率电动汽车接入背景下负荷调控的技术支持;③作为研究分散式优化算法、需求响应机制智能电网互动策略的教学科研案例,推动相关领域的理论发展实践应用。; 阅读建议:建议读者结合所提供的Matlab代码深入理解算法实现细节,重点关注蒙特卡洛场景生成过程拉格朗日乘子迭代更新机制,并可根据实际应用场景调整模型参数以适配不同规模的充电网络多样化的需求特征。
内容概要:本文研究了一种可切换构网型跟网型的逆变器双向平滑切换控制策略,并通过Simulink进行仿真实现。针对新型电力系统中高比例新能源接入带来的稳定性挑战,传统跟网型逆变器依赖电网支撑、在弱电网条件下易失稳的问题,构网型逆变器凭借其主动构建电压频率的能力成为关键解决方案。然而一运行模式难以满足并网离网等多种工况需求,因此实现两种模式之间的无缝切换至关重要。文章提出一种双向平滑切换控制方法,在模式切换瞬间引入预同步控制机制,确保电压幅值、频率相位的一致性,并设计过渡控制策略以有效抑制暂态冲击,避免电流过冲功率振荡,保障系统稳定运行。该策略显著提升了逆变器在复杂电网环境下的适应性可靠性,适用于微电网、孤岛供电及混合运行系统等应用场景。; 适合人群:电力电子、新能源发电、微电网控制等相关领域的研究人员、工程技术人员及高校研究生。; 使用场景及目标:①应用于需要在并网(跟网)离网(构网)之间灵活切换的分布式能源系统;②解决构网/跟网模式切换过程中可能出现的暂态不稳定问题实现无扰动、高可靠性的平滑过渡;③为高比例可再生能源系统的安全稳定运行提供先进的控制技术支持。; 阅读建议:读者应具备电力电子变换器控制、逆变器建模及Simulink仿真基础,建议结合文中控制框图仿真模型深入理解切换逻辑的设计细节,并可通过复现实验验证不同工况下的切换性能。
内容概要:本文提出了一种基于压缩感知中密钥控制测量矩阵的新型图像压缩加密混合算法,深度融合压缩感知理论现代密码学技术,实现图像在高效压缩的同时完成高强度加密。该算法通过引入密钥动态生成随机测量矩阵,不仅提升了系统的安全性抗破解能力,还有效保障了重构图像的质量压缩效率。算法在Matlab平台上完成了完整的代码实现仿真实验,验证了其在不同攻击模型下的鲁棒性可行性,适用于对数据安全传输效率均有高要求的应用场景,在军事通信、远程医疗、视频监控等敏感领域展现出广阔的应用前景。; 适合人群:具备信号处理、图像处理、压缩感知理论及密码学基础的研究生、科研人员,以及从事信息安全、通信工程、数字图像系统开发等相关领域的专业技术人才。; 使用场景及目标:①应用于需同时满足高压缩比高安全性的图像传输系统,如国防、医疗影像云存储、隐私监控等;②为研究压缩感知信息安全交叉领域的科研工作者提供可复现的技术方案代码参考;③作为高校相关课程的实践教学案例,帮助学生理解测量矩阵设计、稀疏重构密钥机制融合的核心原理。; 阅读建议:建议读者系统掌握压缩感知的基本理论(如稀疏表示、随机投影、L1范数重构等),重点剖析密钥控制测量矩阵的生成机制安全性分析模块,并结合所提供的Matlab代码进行仿真复现参数调优,深入理解算法在不同图像类型攻击条件下的性能表现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值