数据结构| 稀疏数组| 队列| 链表| 栈|

  数据结构的介绍node

  1)  数据结构是一门研究算法的学科,只从有了编程语言也就有了数据结构.学好数据结构能够编写出更加漂亮,更加有效率的代码。算法

  2)  要学习好数据结构就要多多考虑如何将生活中遇到的问题,用程序去实现解决.编程

  3)  程序 = 数据结构 + 算法小程序

1. 稀疏sparsearray数组

  • 先看一个实际的需求

编写的五子棋程序中,有存盘退出和续上盘的功能。后端

当一个数组中大部分元素为0,或者为同一个值的数组时,可使用稀疏数组来保存该数组。数组

稀疏数组的处理方法是:数据结构

  1)  记录数组一共有几行几列,有多少个不一样的值(有效值的个数)app

  2)   把具备不一样值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模编程语言

      

 

  

代码实现:ide

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

    //1. 先把原始的地图建立(二维数组)
    val rows = 11
    val cols = 11
    val chessMap1 = Array.ofDim[Int](rows, cols)
    chessMap1(1)(2) = 1 //表示黑子
    chessMap1(2)(3) = 2 //表示蓝子
    chessMap1(4)(5) = 2
    chessMap1(5)(6) = 1

    //输出原始的地图
    println("原始的地图以下..")
    for (item1 <- chessMap1) {
      for (item2 <- item1) {
        printf("%d\t", item2)
      }
      println()
    }

    //使用稀疏数组来对chessMap1进行压缩
    //思路
    //1. 使用ArrayBuffer 来保存有效数据
    //2. 每个数据使用Node 对象来表示
    //3. Node 的结构 class Node(val row:Int, val col:Int,val value:Int)

    val sparseArray = ArrayBuffer[Node]()
    val node1 = new Node(rows, cols, 0)
    sparseArray.append(node1)

    //遍历chessMap1, 每发现一个非0的值,就建立给Node ,并加入到sparseArray
    for (i <- 0 until chessMap1.length) { //
      for (j <- 0 until chessMap1(i).length) {
        //
        if (chessMap1(i)(j) != 0) {
          //说明是一个须要保存的数据
          //Node 对象
          val node2 = new Node(i, j, chessMap1(i)(j))
          sparseArray.append(node2)
        }
      }
    }

    //输出稀疏数组
    println("稀疏数组的状况")
    for (node <- sparseArray) {
      printf("%d\t%d\t%d\t\n", node.row, node.col, node.value)
    }

    //将稀疏数组,恢复成原始的地图
    //1. 先从sparseArray 读取第一个node ,并建立新的地图
    val firstNode = sparseArray(0)
    val chessMap2 = Array.ofDim[Int](firstNode.row, firstNode.col)
    //2. 从sparseArray第二个数据开始遍历,并将数组恢复到chessMap2
    for (i <- 1 until sparseArray.length) {
      val node = sparseArray(i)
      chessMap2(node.row)(node.col) = node.value
    }

    //再次输出恢复后的原始地图
    println("恢复后原始的地图以下..")
    for (item1 <- chessMap2) {
      for (item2 <- item1) {
        printf("%d\t", item2)
      }
      println()
    }


  }
}

//每个数据使用Node 对象来表示
class Node(val row: Int, val col: Int, val value: Int)
View Code

 

 2. 队列

  1)  队列是一个有序列表,能够用数组或是链表来实现。数据结构就是研究数据组织形式,而且为算法打基础。

  2)  遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出

  3) 示意图:(使用数组模拟队列示意图)

  4)  线性结构,一对一关系

  • 队列自己是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明以下 其中 maxSize 是该队列的最大容量。
  • 由于队列的输出、输入是分别从先后端来处理,所以须要两个变量 front及 rear分别记录队列先后端的下标,front 会随着数据输出而改变,而 rear则是随着数据输入而改变,如图所示:

          

数组模拟队列

rear 表示队列尾,rear 指向队列的最后一个元素

front 表示队列的头,表示指向队列的第一个元素的前一个位置

  • 当咱们将数据存入队列时称为”addQueue”,addQueue 的处理须要有两个步骤:思路分析

1) 将尾指针日后移:rear+1 , front == rear 【队列空】

2) 若尾指引 rear 小于队列的最大下标 MaxSize-1,则将数据存入 rear所指的数组元素中,不然没法存入数据。 rear  == MaxSize - 1[队列满]

 代码实现:

object ArrayQueueDemo {
  def main(args: Array[String]): Unit = {
    //建立一个队列对象
    val queue = new ArrayQueue(3)
    var key = "" //接收用户的输入
    //简单写个菜单
    while (true) {
      println("show : 显示队列")
      println("add: 添加数据到队列")
      println("get: 从队列头取出元素")
      println("peek: 查看队列头元素")
      key = StdIn.readLine()
      key match {
        case "show" => queue.showQueue()
        case "add" =>
          println("请输入一个数吧")
          val value = StdIn.readInt()
          queue.addQueue(value)
        case "get" =>
          val res = queue.getQueue()//取出
          //判断res 的类型
          if(res.isInstanceOf[Exception]) {
            //输出异常信息
            println(res.asInstanceOf[Exception].getMessage)
          }else {
            printf("取出的队列头元素是%d", res)
          }
        case "peek" =>
          val res = queue.peek()//查看
          //判断res 的类型
          if(res.isInstanceOf[Exception]) {
            //输出异常信息
            println(res.asInstanceOf[Exception].getMessage)
          }else {
            printf("队列头元素是%d", res)
          }
      }
    }
  }
}

//定义一个类ArrayQueue 表队列
//该类会实现队列的相关方法
//数据结构 建立-遍历-测试-修改-删除
class ArrayQueue(arrMaxSize: Int) {
  val maxSize = arrMaxSize
  val arr = new Array[Int](maxSize) //队列的数据存放的数组
  var front = -1 // 表示指向队列的第一个元素的前一个位置
  var rear = -1 //表示指向队列的最后个元素

  //查看队列的头元素,可是不取出
  def peek(): Any = {
    if(isEmpty()) {
      return new Exception("队列空,没有数据返回");
    }
    return arr(front + 1)
  }

  //判断队列是否满
  def isFull(): Boolean = {
    rear == maxSize - 1
  }

  //判断队列是空
  def isEmpty(): Boolean = {
    rear == front
  }

  //从队列中取出数据
  //异常时能够加入业务逻辑
  def getQueue(): Any = {
    if(isEmpty()) {
      return new Exception("队列空,没有数据返回");
    }
    //将front 后移一位
    front += 1
    return arr(front)
  }

  //给队列添加数据
  def addQueue(num: Int): Unit = {
    if (isFull()) {
      println("队列满,不能加入!!")
      return
    }
    //添加时调整rear
    //1. 先将rear 后移
    rear += 1
    arr(rear) = num
  }

  //遍历队列
  def showQueue(): Unit = {
    if (isEmpty()) {
      println("队列空!!")
      return
    }

    for (i <- (front + 1) to rear) {
      printf("arr(%d)=%d\t", i, arr(i))
    }
    println()
  }

}
View Code

 

 数组模拟环形队列

  • 对前面的数组模拟队列的优化,充分利用数组. 所以将数组看作是一个环形的。(经过取模的方
    式来实现便可)
  • 分析说明(思路):

  1) 尾索引的下一个为头索引时表示队列满,即将队列容量空出一个做为约定,这个在作判断队列满的时候须要注意 (rear + 1) % maxSize == front 满] rear == front [空]

  2) 测试示意图:

  3) 代码实现

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

    //建立一个队列对象
    val queue = new CircleArrayQueue(4)
    var key = "" //接收用户的输入
    //简单写个菜单
    while (true) {
      println("show : 显示队列")
      println("add: 添加数据到队列")
      println("get: 从队列头取出元素")
      println("peek: 查看队列头元素")
      key = StdIn.readLine()
      key match {
        case "show" => queue.showQueue()
        case "add" =>
          println("请输入一个数吧")
          val value = StdIn.readInt()
          queue.addQueue(value)
        case "get" =>
          val res = queue.getQueue()//取出
          //判断res 的类型
          if(res.isInstanceOf[Exception]) {
            //输出异常信息
            println(res.asInstanceOf[Exception].getMessage)
          }else {
            printf("取出的队列头元素是%d", res)
          }
        case "peek" =>
          val res = queue.peek()//查看
          //判断res 的类型
          if(res.isInstanceOf[Exception]) {
            //输出异常信息
            println(res.asInstanceOf[Exception].getMessage)
          }else {
            printf("队列头元素是%d", res)
          }
      }
    }


  }
}

//定义一个类CircleArrayQueue 表环形队列
//该类会实现队列的相关方法
//数据结构 建立-遍历-测试--删除
class CircleArrayQueue(arrMaxSize: Int) {
  val maxSize = arrMaxSize
  val arr = new Array[Int](maxSize) //队列的数据存放的数组
  var front = 0 // front 初始化 = 0 , front 约定 指向队列的头元素
  var rear = 0 // rear 初始化为 = 0, rear 指向队列的 最后元素的后一个位置, 由于须要空出一个位置作约定

  //判断队列是否满
  def isFull(): Boolean = {
    (rear + 1) % maxSize == front
  }
  //判断队列是空,和前面同样
  def isEmpty(): Boolean = {
    rear == front
  }


  //从队列中取出数据
  //异常时能够加入业务逻辑
  def getQueue(): Any = {
    if(isEmpty()) {
      return new Exception("队列空,没有数据返回");
    }
    //返回front指向的值
    //1. 先把  arr(front) 保存到一个临时变量
    //2. front += 1
    //3. 返回临时变量
    var temp = arr(front)
    front = (front + 1) % maxSize
    return temp
  }

  //查看队列的头元素,可是不取出
  def peek(): Any = {
    if(isEmpty()) {
      return new Exception("队列空,没有数据返回");
    }
    return arr(front)
  }

  //给队列添加数据
  def addQueue(num: Int): Unit = {
    if (isFull()) {
      println("队列满,不能加入!!")
      return
    }
    //先把数据放入到arr(rear) 位置,而后后移rear
    arr(rear) = num
    rear = (rear + 1) % maxSize
  }

  //遍历队列
  def showQueue(): Unit = {
    if (isEmpty()) {
      println("队列空!!")
      return
    }
    //动脑筋
    //1. 从front 开始打印 , 打印几个元素
    for (i <- front until (front + size()) ) {
      printf("arr(%d)=%d\t", (i % maxSize) , arr(i % maxSize))
    }
    println()
  }

  //求出当前队列共有多少个数据
  def size(): Int = {
    (rear + maxSize - front) % maxSize
  }

}
View Code

 

3. 链表介绍

链表是有序的列表,可是它在内存中是存储以下: 链表在内存中不必定是连续分布.

          

单链表的介绍

单链表(带头结点) 逻辑结构示意图以下

 

单链表的应用实例

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

1) 完成对英雄人物的增删改查操做, 注: 删除和修改,查找能够考虑学员独立完成

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

3) 第二种方式在添加英雄时,根据排名将英雄插入到指定位置(若是有这个排名,则添加失败,并给出提示)

4) 对单链表的实现分析示意图

 

代码实现:

object SingleLinkedListDemo {
  def main(args: Array[String]): Unit = {
    val node1 = new HeroNode(1, "宋江", "及时雨")
    val node2 = new HeroNode(2, "卢俊义", "玉麒麟")
    val node3 = new HeroNode(3, "吴用", "智多星")
    val node4 = new HeroNode(3, "林冲", "豹子头")

    //建立后一个单向链表
    val singleLinkedList = new SingleLinkedList()
    //加入
//    singleLinkedList.add(node1)
//    singleLinkedList.add(node3)
//    singleLinkedList.add(node2)
    singleLinkedList.addByOrder(node2)
    singleLinkedList.addByOrder(node1)
    singleLinkedList.addByOrder(node3)
    singleLinkedList.addByOrder(node4)


    println("链表的状况是")
    singleLinkedList.list()

    /*
    //测试修改
    val node4 = new HeroNode(3, "武松", "行者")
    singleLinkedList.update(node4)
    println("链表修改后的状况")
    singleLinkedList.list()

    //测试删除节点

    singleLinkedList.del(3)
    singleLinkedList.del(1)
    singleLinkedList.del(2)
    println("删除后的链表")
    singleLinkedList.list() */

  }
}

//建立一个SingleLinkedLis 表示单向链表
class SingleLinkedList {
  //定义一个头结点
  //1 . 头结点不能动
  val head = new HeroNode(0, "", "")

  //删除
  //需求:由于咱们是单向链表,所以咱们要找到要删除节点的前一个节点,才能删除该节点
  def del(no:Int): Unit = {
    //先去检测是否为空链表,是退出
    if(head.next == null) {
      println("链表为空,不能遍历~")
      return
    }
    //由于head不能动,须要辅助变量
    var temp = head
    var flag = false
    breakable {
      //遍历,找到要删除的节点的前一个节点
      while (true) {
        if (temp.next.no == no) {
          //找到了
          flag = true
          break()
        }
        //退出条件
        if (temp.next.next == null) {
          break()
        }
        temp = temp.next //遍历
      }
    }
    if(flag) { //找到
        temp.next = temp.next.next
    } else {
      printf("你要删除的节点no=%d 不存在~", no)
    }
  }

  //修改
  //1. 给我一个新的结点,根据结点的no,去该对应的链表中 的结点的信息
  def update(heroNode: HeroNode): Unit = {
    //先去检测是否为空链表,是退出
    if(head.next == null) {
      println("链表为空,不能遍历~")
      return
    }
    //先找到要修改的结点
    var temp = head.next
    //设置一个标识变量,表示是否找到要修改的结点[小技巧]
    var flag = false
    breakable {
      while (true) {
        if (temp.no == heroNode.no) {
          //说明找到
          flag = true
          break()
        }
        if (temp.next == null) {
          break()
        }
        temp = temp.next //后移
      }
    }

    if(flag) {
      //修改
      temp.name = heroNode.name
      temp.nickName = heroNode.nickName
    }else {
      printf("你要修改的节点no=%d 不存在~", heroNode.no)
    }

  }

  //遍历
  def list(): Unit = {
    //先去检测是否为空链表,是退出
    if(head.next == null) {
      println("链表为空,不能遍历~")
      return
    }

    //1.定义一个辅助变量,帮助遍历整个链表
    //由于有效的节点是从head后一个
    var temp = head.next
    breakable {
      while (true) {
        //先输出temp指向的节点信息
        printf("结点信息为 no = %d name=%s nickname=%s\n", temp.no, temp.name, temp.nickName)
        //判断是否temp是否是最后一个结点
        if (temp.next == null) {
          break()
        }
        temp = temp.next //让temp后移,实现遍历
      }
    }

  }

  //添加结点到链表
  //第二种方式在添加英雄时,根据排名将英雄插入到指定位置(若是有这个排名,则添加失败,并给出提示)
  def addByOrder(heroNode: HeroNode): Unit = {
    var temp = head

    var flag = false
    breakable {
      while (true) {

        if (temp.next == null) {
          //加入到链表的最后
          flag = true
          break()
        }

        if (temp.next.no < heroNode.no) {
          //说明 heroNode 添加到temp的后面
          flag = true
          break()
        } else if (temp.next.no == heroNode.no) {
          //在链表中已经存在这个编号
          break()
        }

        temp = temp.next //后移
      }
    }

    if(flag) { //加入到temp的后面
      heroNode.next = temp.next //
      temp.next = heroNode
    }else {
      printf("你要加入的编号%d 已经存在不能添加\n", heroNode.no)
    }
  }

  //添加结点到单向链表
  def add(heroNode: HeroNode): Unit = {
    //1. 先找到链表的最后结点
    //2. 先定义一个辅助变量(指针),指向 head
    var temp = head
    //3.遍历链表,直到到temp.next == null
    breakable {
      while (true) {
        if (temp.next == null) {
          //temp已是最后结点
          break()
        }
        temp = temp.next //temp后移
      }
    }
    //4. 当退出while时,temp 指向最后结点
    temp.next = heroNode
  }
}

//建立一个HeroNode 表示节点
class HeroNode(hNo: Int, hName: String, hNickname: String) {
  val no = hNo
  var name = hName
  var nickName = hNickname
  var next: HeroNode = null //next默认为null
}
View Code

 

双向链表的应用实例

使用带head头的双向链表实现 –水浒英雄排行榜管理单向链表的缺点分析:

1) 单向链表,查找的方向只能是一个方向,而双向链表能够向前或者向后查找。

2) 单向链表不能自我删除,须要靠辅助节点 ,而双向链表,则能够自我删除,因此前面咱们单链表删除
时节点,老是找到temp的下一个节点来删除的(认真体会).

3) 示意图帮助理解删除

 

代码实现:

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

object DoubleLinkedListDemo {
  def main(args: Array[String]): Unit = {
    //测试双向链表的添加和遍历
    val node1 = new HeroNode2(1, "宋江", "及时雨")
    val node2 = new HeroNode2(2, "卢俊义", "玉麒麟")
    val node3 = new HeroNode2(3, "吴用", "智多星")
    val node4 = new HeroNode2(3, "林冲", "豹子头")

    val doubleLinkedList = new DoubleLinkedList()
    doubleLinkedList.add(node3)
    doubleLinkedList.add(node1)
    doubleLinkedList.add(node2)


    println("双向链表的状况")
    doubleLinkedList.list()

    doubleLinkedList.update(node4)
    println("双向链表修改后")
    doubleLinkedList.list()

    //作删除
    doubleLinkedList.del(3)
    println("双向链表删除结点后")
    doubleLinkedList.list()


  }
}

//定义一个带头结点的双向链表
//添加,遍历, 修改, 删除
class DoubleLinkedList  {
  //定义一个头结点
  //1 . 头结点不能动
  val head = new HeroNode2(0, "", "")
  // 增长一个 var last 在咱们的双向链表的最后
  // 1. 顺序遍历list
  // 2. 逆序遍历last-> pre ...

  //双向链表的第二种添加方式(顺序添加)
  //add2

  //删除-直接找到要删除的结点,自我删除
  def del(no:Int): Unit = {
    //先去检测是否为空链表,是退出
    if(head.next == null) {
      println("链表为空,不能遍历~")
      return
    }
    //直接让temp指向head.next,而后定位
    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
      }
    }

    //判断是否须要删除
    if(flag) {
      //删除
      temp.pre.next = temp.next
      if(temp.next != null) {//!!!
        temp.next.pre = temp.pre
      }

    }else {
      //没有找到
      printf("你要删除的no=%d 结点不存在", no)
    }


  }

  //修改和前面同样的
  //修改
  //1. 给我一个新的结点,根据结点的no,去该对应的链表中 的结点的信息
  def update(heroNode: HeroNode2): Unit = {
    //先去检测是否为空链表,是退出
    if(head.next == null) {
      println("链表为空,不能遍历~")
      return
    }
    //先找到要修改的结点
    var temp = head.next
    //设置一个标识变量,表示是否找到要修改的结点[小技巧]
    var flag = false
    breakable {
      while (true) {
        if (temp.no == heroNode.no) {
          //说明找到
          flag = true
          break()
        }
        if (temp.next == null) {
          break()
        }
        temp = temp.next //后移
      }
    }

    if(flag) {
      //修改
      temp.name = heroNode.name
      temp.nickName = heroNode.nickName
    }else {
      printf("你要修改的节点no=%d 不存在~", heroNode.no)
    }

  }

  //双向链表的遍历和单向链表同样(指的是从head开始)
  //遍历
  def list(): Unit = {
    //先去检测是否为空链表,是退出
    if(head.next == null) {
      println("链表为空,不能遍历~")
      return
    }

    //1.定义一个辅助变量,帮助遍历整个链表
    //由于有效的节点是从head后一个
    var temp = head.next
    breakable {
      while (true) {
        //先输出temp指向的节点信息
        printf("结点信息为 no = %d name=%s nickname=%s\n", temp.no, temp.name, temp.nickName)
        //判断是否temp是否是最后一个结点
        if (temp.next == null) {
          break()
        }
        temp = temp.next //让temp后移,实现遍历
      }
    }

  }

  //添加结点到双向链表
  def add(heroNode: HeroNode2): Unit = {
    //1. 先找到链表的最后结点
    //2. 先定义一个辅助变量(指针),指向 head
    var temp = head
    //3.遍历链表,直到到temp.next == null
    breakable {
      while (true) {
        if (temp.next == null) {
          //temp已是最后结点
          break()
        }
        temp = temp.next //temp后移
      }
    }
    //4. 当退出while时,temp 指向最后结点
    temp.next = heroNode
    heroNode.pre = temp
  }
}

//建立一个HeroNode 表示节点
class HeroNode2(hNo: Int, hName: String, hNickname: String) {
  val no = hNo
  var name = hName
  var nickName = hNickname
  var next: HeroNode2 = null //next默认为null
  var pre: HeroNode2 = null
}
View Code

 

5. 栈的介绍

1)  栈的英文为(stack)

2)栈是一个先入后出(FILO-First In Last Out)的有序列表。

3)  栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。容许插入和删除的一端,为变化的一端,称为栈顶(Top),另外一端为固定的一端,称为栈底(Bottom)。

4)  根据堆栈的定义可知,最早放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素恰好相反,最后放入的元素最早删除,最早放入的元素最后删除

栈的应用场景

1)  子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。      

2)   处理递归调用:和子程序的调用相似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。

3)   表达式的转换与求值(实际解决)。

4)  二叉树的遍历。

5)  图形的深度优先(depth一first)搜索法。

 

  1) 用数组模拟栈的使用,因为栈是一种有序列表,固然可使用数组的结构来储存栈的数据内容,
    下面咱们就用数组模拟栈的出栈,入栈等操做。

  2) 实现思路分析,并画出示意图

代码实现:

import scala.io.StdIn

object ArrayStackDemo {
  def main(args: Array[String]): Unit = {
    //测试栈
    val stack = new ArrayStack(4)
    var key = ""

    while (true) {
      println("list: 显示栈")
      println("push: 入栈")
      println("pop: 出栈")
      println("peek: 查看栈顶")
      key = StdIn.readLine()
      key match {
        case "list" => stack.list()
        case "push" =>
          println("请输入一个数")
          val value = StdIn.readInt()
          stack.push(value)
        case "pop" =>
          val res = stack.pop()
          if(res.isInstanceOf[Exception]) {
            println(res.asInstanceOf[Exception].getMessage)
          }else{
            printf("取出栈顶的元素是%d", res)
          }
        case "peek" =>
          val res = stack.peek()
          if(res.isInstanceOf[Exception]) {
            println(res.asInstanceOf[Exception].getMessage)
          }else{
            printf("栈顶的元素是%d", res)
          }
          //默认处理

      }
    }


  }
}

//ArrayStack 表示栈
class ArrayStack(arrMaxSize:Int) {
  var top = -1
  val maxSize = arrMaxSize
  val arr = new Array[Int](maxSize)

  //判断栈空
  def isEmpty(): Boolean = {
    top == -1
  }
  //栈满
  def isFull(): Boolean = {
    top == maxSize - 1
  }

  //入栈操做
  def push(num:Int): Unit = {
    if(isFull()) {
      println("栈满,不能加入")
      return
    }
    top += 1
    arr(top) = num
  }

  //出栈操做
  def pop(): Any = {
    if(isEmpty()) {
      return new Exception("栈空,没有数据")
    }
    val res = arr(top)
    top -= 1
    return res
  }

  //遍历栈
  def list(): Unit = {
    if(isEmpty()) {
      println("栈空")
      return
    }
    //使用for
    for(i <- 0 to top reverse) { //逆序打印
      printf("arr(%d)=%d\n", i, arr(i))
    }
    println()
  }

  //查看栈的元素是什么,可是不会真的把栈顶的数据弹出
  def peek(): Any = {
    if(isEmpty()) {
      return  new Exception("栈空")
    }
    return arr(top)
  }

}
View Code
相关文章
相关标签/搜索