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
}

測試代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章