zoukankan      html  css  js  c++  java
  • 链表

    链表介绍

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

    小结

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

      }

      }

      }

      }

  • 相关阅读:
    Chapter 03Using SingleRow Functions to Customize Output(03)
    Chapter 03Using SingleRow Functions to Customize Output(01)
    Chapter 04Using Conversion Functions and Conditional ExpressionsNesting Functions
    Chapter 04Using Conversion Functions and Conditional ExpressionsGeneral Functions
    Chapter 11Creating Other Schema Objects Index
    传奇程序员John Carmack 访谈实录 (zz.is2120)
    保持简单纪念丹尼斯里奇(Dennis Ritchie) (zz.is2120.BG57IV3)
    王江民:传奇一生 (zz.is2120)
    2011台湾游日月潭
    2011台湾游星云大师的佛光寺
  • 原文地址:https://www.cnblogs.com/shuzhiwei/p/11210112.html
Copyright © 2011-2022 走看看