安卓& Kotlin Books 数据结构&Kotlin的算法

5
开奖结果3d 由Matei Suica,Kelvin Lau和Vincent Ngo撰写

我们都熟悉排队等候。无论您是符合您喜欢的电影或等待打印机打印文件的票证,这些现实生活场景都模仿了 开奖结果3d data structure.

开奖结果3d使用 FIFO 或者 首先,先 订购,这意味着添加的第一个元素将始终删除第一个。当您需要维护元素的顺序以后,开奖结果3d是友好的。

在本章中,您将了解开奖结果3d的所有常见操作,通过各种方法来实现开奖结果3d并查看每个方法的时间复杂度。

共同运作

首先,为开奖结果3d建立一个接口。在里面 根据 包,创建一个名为的文件 queue.kt. and add the following code defining the Queue interface.

interface Queue<T> {

  fun enqueue(element: T): Boolean

  fun dequeue(): T?

  val count: Int
    get

  val isEmpty: Boolean
    get() = count == 0

  fun peek(): T?
}

这将是你的起点。从现在开始,您所实施的一切都将遵守此接口的合同,该界面描述了开奖结果3d的核心操作。

开奖结果3d的核心操作是:

  • enqueue: Inserts an element at the back of the queue and returns true if the operation is successful.
  • dequeue:删除开奖结果3d前面的元素并返回它。
  • isEmpty:检查开奖结果3d是否使用 count property
  • peek:返回开奖结果3d前面的元素 没有 removing it.

请注意,开奖结果3d只关心从前面的前部和插入后部的拆卸。您不需要知道内容之间的内容。如果您所做,您可能会使用数组而不是开奖结果3d。

开奖结果3d

要了解开奖结果3d如何工作的最简单方法是查看一个工作示例。想象一群人排队等待电影票。

This queue currently holds Ray, Brian, Sam and Mic. Once Ray receives his ticket, he moves out of the line. When you call dequeue(), Ray is removed from the 正面 of the queue.

Calling peek() returns Brian since he’s now at the front of the line.

Now comes Vicki, who just joined the line to buy a ticket. When you call enqueue("Vicki"), Vicki gets added to the 背部 of the queue.

在以下部分中,您将学习以四种不同的方式创建开奖结果3d:

  • 使用基于数组的列表
  • 使用双重链接列表
  • 使用环形缓冲区
  • 使用两个堆栈

基于列出的实现

这 Kotlin standard library comes with a core set of highly optimized data structures that you can use to build higher-level abstractions. One of these is the 数组列表 , a data structure that stores a contiguous, ordered list of elements. In this section, you’ll use an 数组列表 to create a queue.

一个简单的kotlin`rasslist`可用于模拟开奖结果3d。
一个简单的kotlin`rasslist`可用于模拟开奖结果3d。

打开起动项目。在里面 列表 包,创建一个名为的文件 arraylistqueue.kt. 并添加以下内容:

class ArrayListQueue<T> : Queue<T> {

  private val list = arrayListOf<T>()
}

Here, you defined a generic 数组列表 Queue class that implements the Queue interface. Note that the interface implementation uses the same generic type T for the elements it stores.

Next, you’ll complete the implementation of 数组列表 Queue to fulfill the Queue contract.

利用ArrayList.

Add the following code to 数组列表 Queue:

override val count: Int
  get() = list.size

override fun peek(): T? = list.getOrNull(0)

Using the features of 数组列表 , you get the following for free:

  1. 使用列表的相同属性获取开奖结果3d的大小。
  2. 如果有的话,返回开奖结果3d前面的元素。

这些操作都是 O(1).

征服

Adding an element to the back of the queue is easy. You simply add the element to the end of the 数组列表 . Add the following:

override fun enqueue(element: T): Boolean {
  list.add(element)
  return true
}

无论列表的大小如何,eNUTUEING元素是一个 O(1)操作。这是因为列表在后面有空的空间。

在上面的示例中,请注意,一旦添加麦克风,列表有两个空的空格。

After adding multiple elements, the internal array of the 数组列表 will eventually be full. When you want to use more than the allocated space, the array must resize to make additional room.

调整大小是AN O(n) 手术。调整大小要求列表分配新内存并将所有现有数据复制到新列表。由于这种情况不会经常发生(由于每次增加大小),因此复杂性仍然是摊销 O(1).

排行

从前面删除项目需要更多的工作。添加以下内容:

override fun dequeue(): T? =
  if (isEmpty) null else list.removeAt(0)

If the queue is empty, dequeue() simply returns null. If not, it removes the element from the front of the list and returns it.

从开奖结果3d前面删除元素是一个 O(n) 手术。要排除,请从列表的开头删除元素。这始终是线性时间操作,因为它需要列表中的所有剩余元素以在内存中移位。

调试和测试

For debugging purposes, you’ll have your 开奖结果3d override toString(). Add the following at the bottom of the class:

override fun toString(): String = list.toString()

是时候尝试刚刚实施的开奖结果3d了。在 main.kt. , add the following to the bottom of main():

"Queue with ArrayList" example {
  val queue = ArrayListQueue<String>().apply {
    enqueue("Ray")
    enqueue("Brian")
    enqueue("Eric")
  }
  println(queue)
  queue.dequeue()
  println(queue)
  println("Next up: ${queue.peek()}")
}

这段代码将雷,Brian和Eric放在开奖结果3d中。然后在Brian删除雷,偷看,但它不会删除他。

长处和短处

Here’s a summary of the algorithmic and storage complexity of the 数组列表 -based queue implementation. Most of the operations are constant time except for dequeue(), which takes linear time. Storage space is also linear.

You’ve seen how easy it is to implement a list-based queue by leveraging a Kotlin 数组列表 . Enqueue is very fast, thanks to an O(1)附加操作。

这 re are some shortcomings to the implementation. Removing an item from the front of the queue can be inefficient, as removal causes all elements to shift up by one. This makes a difference for very large queues. Once the list gets full, it has to resize and may have unused space. This could increase your memory footprint over time. Is it possible to address these shortcomings? Let’s look at a linked list-based implementation and compare it to an 数组列表 Queue.

双重相关的列表实施

创建一个名为的新文件 linkedlist. queue.kt. 在里面 linkedlist. package. In this package, you’ll notice a DoublyLinkedList class. You should already be familiar with linked lists from Chapter 3, “Linked Lists”. A doubly linked list is simply a linked list in which nodes also contain a reference to the previous node.

Start by adding a generic LinkedListQueue to the same package, with the following content:

class LinkedListQueue<T> : Queue<T> {

  private val list = DoublyLinkedList<T>()

  private var size = 0

  override val count: Int
    get() = size
}

This implementation is similar to 数组列表 Queue, but instead of an 数组列表 , you create a DoublyLinkedList.

Next, you’ll start implementing the Queue interface starting from the count 财产 that the DoublyLinkedList doesn’t provide.

征服

要在开奖结果3d背面添加元素,请添加以下内容:

override fun enqueue(element: T): Boolean {
  list.append(element)
  size++
  return true
}

在幕后,双重链接列表将更新其尾部节点的上一个和下一个引用新节点。您还递增大小。这是一 O(1)操作。

排行

要从开奖结果3d中删除元素,请添加以下内容:

override fun dequeue(): T? {
  val firstNode = list.first ?: return null
  size--
  return list.remove(firstNode)
}

This code checks to see if the first element of the queue exists. If it doesn’t, it returns null. Otherwise, it removes and returns the element at the front of the queue. In this case it also decrement the size.

从列表的前面删除也是一个 O(1)操作。 Compared to the 数组列表 implementation, you didn’t have to shift elements one by one. Instead, in the diagram above, you simply update the next and previous pointers between the first two nodes of the linked list.

检查开奖结果3d的状态

Similar to the 数组列表 based implementation, you can implement peek() using the properties of the DoublyLinkedList.Add the following:

override fun peek(): T? = list.first?.value

调试和测试

有关调试目的,请在课堂底部添加以下内容:

override fun toString(): String = list.toString()

This leverages the DoublyLinkedList’s existing implementation for toString().

这就是使用链接列表来实现开奖结果3d的全部。在 main.kt. ,您可以尝试以下示例:

"Queue with Doubly Linked List" example {
  val queue = LinkedListQueue<String>().apply {
    enqueue("Ray")
    enqueue("Brian")
    enqueue("Eric")
  }
  println(queue)
  queue.dequeue()
  println(queue)
  println("Next up: ${queue.peek()}")
}

This test code yields the same results as your 数组列表 Queue implementation.

长处和短处

基于双链接列表总结实现的算法和存储复杂度的时间。

One of the main problems with 数组列表 Queue is that dequeuing an item takes linear time. With the linked list implementation, you reduced it to a constant operation, O(1). All you needed to do was update the node’s previous and next pointers.

这 main weakness with LinkedListQueue is not apparent from the table. Despite O(1) performance, it suffers from high overhead. Each element has to have extra storage for the forward and back reference. Moreover, every time you create a new element, it requires a relatively expensive dynamic allocation. By contrast, 数组列表 Queue does bulk allocation, which is faster.

你能消除分配开销和保存吗? O(1)出达?如果您不必担心您的开奖结果3d超出固定大小,您可以使用不同的方法 环形缓冲器 。例如,您可能有一场比赛 垄断 有五名球员。您可以基于环形缓冲区使用开奖结果3d,以跟踪下一个转弯即将到来。您将查看下一个环形缓冲区实现。

环形缓冲区实现

环形缓冲区,也称为a 圆形缓冲,是一个固定尺寸的阵列。当没有更多的项目到底时,此数据结构策略性地缠绕到开头。

换过一个可以使用环形缓冲区实现开奖结果3d如何实现开奖结果3d的简单示例:

首先创建一个具有固定大小的环形缓冲区 4。环形缓冲区有两个“指针”,可以跟踪两件事:

  1. 指针跟踪开奖结果3d前面。
  2. 指针会跟踪下一个可用插槽,以便您可以覆盖已读取的现有元素。

eNque一个项目:

每次将项目添加到开奖结果3d中, 指针递增一个。添加更多元素:

请注意 指针移动了两个斑点并领先于此 指针。这意味着开奖结果3d不为空。

接下来,Dequeue两项:

Dequeuing是读环缓冲器的等同物。注意这件事怎么样 pointer moved twice.

现在,浏览一个项目以填补开奖结果3d:

自从此以来 指针到达结束,它只是再次缠绕到起始索引。

最后,排除了两个剩余物品:

指针包裹到开头。

作为最终观察,请注意,只要读取和写入指针处于同一索引,这意味着开奖结果3d就是 空的 .

现在你有更好地了解戒指缓冲区如何制作开奖结果3d,你已经准备好实现了一个!

去吧 ringBuffer. 包并创建名为的文件 ringBuffer. queue.kt.. You’ll notice a RingBuffer class inside this package, which you can look at to understand its internal mechanics.

ringBuffer. queue.kt.,添加以下内容:


class RingBufferQueue<T>(size: Int) : Queue<T> {

  private val ringBuffer: RingBuffer<T> = RingBuffer(size)

  override val count: Int
    get() = ringBuffer.count

  override fun peek(): T? = ringBuffer.first
}

Here, you define a generic RingBufferQueue. Note that you must include a size parameter since the ring buffer has a fixed size.

To implement the Queue interface, you also need to implement peek() and the count 财产 using the same from the RingBuffer class. Once you provide the count property, the isEmpty 财产 is already defined in the Queue interface. Instead of exposing ringBuffer, you provide these helpers to access the front of the queue and to check if the queue is empty. Both of these are O(1)业务。

征服

Next, add the following method at the end of the RingBufferQueue class:

override fun enqueue(element: T): Boolean =
  ringBuffer.write(element)

To append an element to the queue, you call 写 () on the ringBuffer. This increments the pointer by one.

自从此以来 queue has a fixed size, you must now return true 或者 false to indicate whether the element has been successfully added. enqueue() is still an O(1)操作。

排行

要从开奖结果3d前面删除项目,请添加以下内容:

override fun dequeue(): T? =
  if (isEmpty) null else ringBuffer.read()

This code checks if the queue is empty and, if so, returns null. If not, it returns an item from the front of the buffer. Behind the scenes, the ring buffer increments the pointer by one.

调试和测试

To easily see the contents of your buffer during debugging, add the following to RingBufferQueue:

override fun toString(): String = ringBuffer.toString()

此代码通过将底线缓冲区委托创建开奖结果3d的字符串表示。

这里的所有都是它的。通过在底部添加以下内容来测试基于环形缓冲的开奖结果3d main.kt. , inside main():

"Queue with Ring Buffer" example {
  val queue = RingBufferQueue<String>(10).apply {
    enqueue("Ray")
    enqueue("Brian")
    enqueue("Eric")
  }
  println(queue)
  queue.dequeue()
  println(queue)
  println("Next up: ${queue.peek()}")
}

此测试代码类似于前面的示例,在Brian偷看。

长处和短处

环形缓冲区实现如何比较?让我们看一下算法和存储复杂性的摘要。

环形缓冲区的开奖结果3d具有相同的时间复杂性,以便eNqueue和deque作为链接列表实现。唯一的区别是空间复杂性。环形缓冲器具有固定尺寸,这意味着eNqueue可能会失败。

到目前为止,您已经看到了三种实现:阵列,双链接列表和环形缓冲区。

虽然它们似乎非常有用,但您将下次查看使用两个堆栈实现的开奖结果3d。您将看到其空间局域如何远远优于链接列表。它也不需要像环缓冲器这样的固定大小。

双堆叠实现

去吧 doublestack. 包裹并开始添加一个 strackqueue.kt. containing:

class StackQueue<T> : Queue<T> {
  private val leftStack = StackImpl<T>()
  private val rightStack = StackImpl<T>()
}

使用两堆背后的想法很简单。每当你征服一个元素时,它会进入 stack.

当您需要排除一个元素时,您将扭转正确的堆栈并将其放入其中 剩下 堆栈使您可以使用FIFO订单检索元素。

利用堆栈

从以下开始,实现开奖结果3d的公共功能:

override val isEmpty: Boolean
  get() = leftStack.isEmpty && rightStack.isEmpty

要检查开奖结果3d是否为空,只需检查左右堆栈的左侧和右堆栈是空的。这意味着没有留给Dequeue的元素,并且没有延长新的元素。

如您所知,将有一个时间需要将元素从右堆栈传输到左侧堆栈中。只要左堆栈为空即可发生这种情况。添加以下辅助方法:

private fun transferElements() {
  var nextElement = rightStack.pop()
  while (nextElement != null) {
    leftStack.push(nextElement)
    nextElement = rightStack.pop()
  }
}

使用此代码,您将弹出右侧堆栈的元素并将其推入左侧堆栈。您已从前一章中已知堆叠以Lifo(最后,先出来)工作。如果没有任何额外的工作,你会在逆转的订单中获得它们。

接下来,添加以下内容:

override fun peek(): T? {
  if (leftStack.isEmpty) {
    transferElements()
  }
  return leftStack.peek()
}

你知道偷看看看顶部元素。如果左堆栈不为空,则此堆栈顶部的元素位于开奖结果3d的前面。

If the left stack is empty, you use transferElements(). That way, 剩下 Stack.peek() will always return the correct element or null. isEmpty() is still an O(1) operation, while peek() is O(n )。

While this peek() implementation might seem expensive, it’s amortized to O(1) because each element in the queue only has to be moved from the right stack to the left stack once. If you have a lot of elements in the right stack, calling peek() will be O(n)只需一个呼叫即可移动所有这些元素时。任何进一步的电话都将是 O (1)再次。

笔记 : You could also make peak() operations precisely O(1) for all calls if you implemented a method in Stack that let you look at the very bottom of the right stack. That’s where the first item of the queue is if they’re not all in the left stack, which is what peek() should return in that case.

征服

接下来,添加以下方法:

override fun enqueue(element: T): Boolean {
  rightStack.push(element)
  return true
}

回想一下 堆栈用于延长元素。

Previously, from implementing Stack, you know that pushing an element onto it is an O(1)操作。

排行

从基于两堆叠的实现中删除一个项目就像偷看一样棘手。添加以下方法:

override fun dequeue(): T? {
  if (leftStack.isEmpty) { // 1
    transferElements() // 2
  }
  return leftStack.pop() // 3
}

这是它的工作原理:

  1. 检查左侧堆栈是否为空。

  2. 如果左堆栈为空,则需要以反转顺序从右侧堆栈传输元素。

  1. 从左侧堆叠中删除顶部元素。

Remember, you only transfer the elements in the right stack when the left stack is empty. This makes dequeue() an amortized O(1) operation, just like peek().

调试和测试

To see your results, add the following to StackQueue:

override fun toString(): String {
  return "Left stack: \n$leftStack \n Right stack: \n$rightStack"
}

在这里,您可以打印代表开奖结果3d的两种堆栈的内容。

To try out the double-stack implementation, add the following to main():

"Queue with Double Stack" example {
  val queue = StackQueue<String>().apply {
    enqueue("Ray")
    enqueue("Brian")
    enqueue("Eric")
  }
  println(queue)
  queue.dequeue()
  println(queue)
  println("Next up: ${queue.peek()}")
}

Similar to the previous examples, this code enqueues Ray, Brian and Eric, dequeues Ray and then peeks at Brian. Note how Eric and Brian ended up in the left stack and in reverse order as the result of the dequeue operation.

长处和短处

以下是基于两堆叠的实现的算法和存储复杂性的摘要。

Compared to the list-based implementation, by leveraging two stacks, you were able to transform dequeue() into an amortized O(1)操作。

此外,您的双栈实现是完全动态的,并且没有固定大小的限制,即您的环形缓冲基于开奖结果3d实现具有。

最后,它在空间局部地击败了链接的清单。这是因为列表元素在内存块中彼此相邻。因此,大量元素将在第一次访问的缓存中加载。

比较以下页面上的两个图像;一个具有连续阵列中的元素,另一个具有遍布内存的元素:

连续阵列中的元素。
连续阵列中的元素。

链接列表中的元素,分散到内存。
链接列表中的元素,分散到内存。

在链接列表中,元素不在连续的内存块中。这可能导致更多的缓存未命中,这将增加访问时间。

挑战

认为你有开奖结果3d的句柄吗?在本节中,您将探讨与开奖结果3d相关的五个不同问题。这有助于巩固您的数据结构的基本知识。

挑战1:解释差异

解释堆栈和开奖结果3d之间的差异。为每个数据结构提供两个真实实例。

解决方案1

开奖结果3d有一个先进先出的行为。首先出现了什么必须先出来。开奖结果3d中的项目从后部插入并从前面删除。

开奖结果3d示例:

  1. 在电影院里:在购买门票时,您会讨厌人们在电影院切割线路!
  2. 打印机 :多人可以以类似的先行服务方式从打印机打印文档。

堆栈有一流的行为。堆叠上的项目插入顶部并从顶部删除。

堆栈示例:

  1. 堆盘子:将平板放在彼此之上,每次使用板都会拆下顶板。这比抓住底部的那个更容易吗?
  2. 撤消功能:想象在键盘上键入单词。大多数时候,您将在最后一次操作中使用撤消。

挑战2:订单是多少?

鉴于以下开奖结果3d:

提供逐步图,示出以下系列命令如何影响开奖结果3d:

enqueue("R")
enqueue("O")
dequeue()
enqueue("C")
dequeue()
dequeue()
enqueue("K")
}

执行以下操作:

  1. 数组列表
  2. 链接名单
  3. 环形缓冲器
  4. 双堆栈

Assume that the list and ring buffer have an initial size of 5.

解决方案2

数组列表

请记住,只要底层数组已满,并且您尝试添加新元素,将创建一个新的数组 两次 已复制现有元素的容量。

链接名单

环形缓冲器

双堆栈

挑战3:垄断

Imagine you’re playing a game of Monopoly with your friends. The problem is that everyone always forgets whose turn it is! Create a Monopoly organizer that tells you whose turn it is. A great option is to create an extension function for Queue that always returns the next player. Here’s how the definition could look:

fun <T> Queue<T>.nextPlayer(): T?

解决方案3.

创建棋盘游戏经理很简单。你的主要关注是谁的转折。开奖结果3d数据结构是照顾游戏转弯的完美选择。

fun <T> Queue<T>.nextPlayer(): T? {
  // 1
  val person = this.dequeue() ?: return null
  // 2
  this.enqueue(person)
  // 3
  return person
}

这是它的作品:

  1. Get the next player by calling dequeue. If the queue is empty, return null, as the game has probably ended anyway.
  2. enqueue 同一个人,这将播放器放在开奖结果3d的末尾。
  3. 返回下一个球员。

时间复杂性取决于您选择的开奖结果3d实现。对于基于阵列的开奖结果3d,它整体_O(n) time complexity. dequeue takes _O(n)时间,因为每次删除第一个元素时都必须将元素转移到左侧。

测试它:

"Boardgame manager with Queue" example {
  val queue = ArrayListQueue<String>().apply {
    enqueue("Vincent")
    enqueue("Remel")
    enqueue("Lukiih")
    enqueue("Allison")
  }
  println(queue)

  println("===== boardgame =======")
  queue.nextPlayer()
  println(queue)
  queue.nextPlayer()
  println(queue)
  queue.nextPlayer()
  println(queue)
  queue.nextPlayer()
  println(queue)
}

挑战4:反向数据

实现一种使用扩展功能来反转开奖结果3d内容的方法。

Hint: The Stack data structure has been included in the project.

fun <T> Queue<T>.reverse()

解决方案4.

开奖结果3d首先使用,首先,堆栈使用持续,首先。您可以使用堆栈来帮助反转开奖结果3d的内容。通过将开奖结果3d的所有内容插入堆栈中,您基本上会在堆栈中弹出每个元素一旦弹出堆栈。

fun <T> Queue<T>.reverse() {
  // 1
  val aux = StackImpl<T>()

  // 2
  var next = this.dequeue()
  while (next != null) {
    aux.push(next)
    next = this.dequeue()
  }

  // 3
  next = aux.pop()
  while (next != null) {
    this.enqueue(next)
    next = aux.pop()
  }
}

For this solution, you added an extension function for any Queue implementation. It works the following way:

  1. 创建堆栈。
  2. dequeue 开奖结果3d中的所有元素到堆栈上。
  3. pop 所有元素都关闭堆栈并将它们插入开奖结果3d中。
  4. 返回逆转的开奖结果3d。

时间复杂性整体 O(n)。你穿过元素两次。有一次,用于从开奖结果3d中删除元素,然后用于从堆栈中删除元素。

测试它:

"Reverse queue" example {
  val queue = ArrayListQueue<String>().apply {
    enqueue("1")
    enqueue("21")
    enqueue("18")
    enqueue("42")
  }
  println("before: $queue")
  queue.reverse()
  println("after: $queue")
}

关键点

  • 开奖结果3d采用FIFO策略,首先添加一个元素,也必须首先删除。
  • eNqueue将元素插入开奖结果3d的背面。
  • 排行 在开奖结果3d前面删除该元素。
  • 阵列中的元素在连续的内存块中布置出来,而链接列表中的元素更加分散,具有缓存未命中的潜力。
  • 基于环形缓冲区的开奖结果3d实现对于具有固定大小的开奖结果3d非常有用。
  • Compared to other data structures, leveraging two stacks improves the dequeue() time complexity to an amortized O(1)操作。
  • 双堆叠实现在空间局部地区击败链接列表。

有一个技术问题?想报告一个错误吗? 您可以向官方书籍论坛中的书籍作者提出问题和报告错误 这里 .

有反馈分享在线阅读体验吗? 如果您有关于UI,UX,突出显示或我们在线阅读器的其他功能的反馈,您可以将其发送到设计团队,其中表格如下所示:

© 2021 Razeware LLC