Bibi's DevLog ๐ค๐
[Swift ์๋ฃ๊ตฌ์กฐ] 5. Queue ํ ๋ณธ๋ฌธ
[Swift ์๋ฃ๊ตฌ์กฐ] 5. Queue ํ
๋น๋น bibi 2022. 12. 22. 22:01Data Structure in Swift (Queue)โ-โPart 5
swift-algorithm-club/Queue at master · kodecocodes/swift-algorithm-club
์ด ๊ธ์ ์์ ๊ธ๋ค์ ์ฐธ๊ณ ํ์ฌ ์์ฑ๋์์ต๋๋ค.
Queue ํ
ํ๋ ๋๊ธฐ์ด์ ๋ปํ๋ค.
์ฌ๋๋ค์ด ๋ฒ์ค๋ฅผ ํ๊ธฐ ์ํด ์ค์ ์ ๊ฒ์ ์๊ฐํ๋ฉด ๋๋ค - ๋จผ์ ์ค์ ์ ์ฌ๋๋ถํฐ ์์๋๋ก ๋ฒ์ค์ ํ๋ค.
- ์ ํ ์๋ฃ๊ตฌ์กฐ
- ์ฝ์
๋ฐ ์ญ์ : ์ ์
์ ์ถ, ์ฆ FIFO(First In, First Out)๋ก ๋์ํ๋ค.
- ์คํ๊ณผ์ ์ฐจ์ด์ : ํ๋ ๊ฐ์ฅ ์์ ์ ๋ค์ด์จ ๊ฒ๋ถํฐ ์ญ์ ํ๊ณ , ์คํ์ ๊ฐ์ฅ ์ต๊ทผ์ ๋ค์ด์จ ๊ฒ๋ถํฐ ์ญ์ ํ๋ค.
Queue์ ์ฐ์ฐ์
- Enqueue : ๋งจ ๋ค์ ์ ์์ ์ฝ์
- Dequeue : ๋งจ ์์ ์์ ์ญ์
- Front : ์ฒซ ๋ฒ์งธ ์์ ์กฐํ
- Rear : ๋ง์ง๋ง ์์ ์กฐํ
Queue์ ์ฌ์ฉ
- BFS ํ์์ ์ฌ์ฉ๋จ
- ๋ฉํฐํ๋ก๋ ์ค์ CPU ์ค์ผ์ค๋ง์ ์ฌ์ฉ๋จ
- ๋ง์ ์์ ์ ๋น๋๊ธฐ๋ก ์ํํ ๋ ์ฌ์ฉ๋จ (e.g. NSOperationQueue)
Queue์ ์ข ๋ฅ
- Priority Queue ์ฐ์ ์์ํ
- ํ์ ํ์ฅ ๋ฒ์ ์ผ๋ก, ๋ชจ๋ ์์๊ฐ ์ฐ๊ด๋ ์ฐ์ ์์๋ฅผ ๊ฐ๋๋ค.
- ํ๋ ์ด์์ ์์๊ฐ ๊ฐ์ ์ฐ์ ์์๋ฅผ ๊ฐ์ง ๋๋, ํ์ ์ถ๊ฐ๋ ์์์ ๋ฐ๋ผ ๋์ํ๋ค.
- e.g. CPU ์ค์ผ์ค๋ง, ๋ค์ต์คํธ๋ผ ์๊ณ ๋ฆฌ์ฆ
- Deque (Double Ended Queue) ๋ฑ
- ํ ์๋ฃ๊ตฌ์กฐ์ ์ผ๋ฐํ๋ ํํ. ์์์ ๊ณผ ๋์ (Front&Rear) ๋ชจ๋์์ ์ฝ์ ๊ณผ ์ญ์ ๋ฅผ ํ์ฉํจ.
- ์คํ๊ณผ ํ์ ์ฐ์ฐ์ ๋ชจ๋ ์ฌ์ฉ ๊ฐ๋ฅํ๋ค.
- Circular Queue ์ํ ํ
- ํ์ ํ์ฅ ๋ฒ์ ์ผ๋ก, ์์์ ๊ณผ ๋์ ์ด ์ด์ด์ ธ ์๋ ํํ์ด๋ค (๋ง์ง๋ง ์์์ ๋ค์ ์์ = ์ฒ์ ์์)
- Ring Buffer๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค.
- ํ์ ํฌ๊ธฐ๊ฐ ๊ณ ์ ๋์ด ์์ ๋ ์ฌ์ฉํ๊ธฐ ์ข๋ค.
Queue ๊ตฌํํ๊ธฐ
Queue๋ฅผ ๊ตฌํํ๋ ๋ฐฉ์์๋ ์ฌ๋ฌ ๊ฐ์ง๊ฐ ์๋ค.
์ฌ๊ธฐ์์๋ 4๊ฐ์ง ๋ฐฉ๋ฒ๋ค์ ์๊ฐํ๋ค.
- ๋ฐฐ์ด
- ์ฐ๊ฒฐ๋ฆฌ์คํธ (Singly Linked List)
- Two Stack
- head ์ฌ์ฉํ๊ธฐ
์ค๋ช ์์ด ๊ตฌํ ์ฝ๋๋ง ๋ณด๊ณ ์ถ๋ค๋ฉด ์ฌ๊ธฐ๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์.
Queue ๊ตฌํํ๊ธฐ - (1) ๋ฐฐ์ด
struct Queue<T>: CustomStringConvertible {
private var elements: [T] = []
public init() {}
var isEmpty: Bool {
elements.isEmpty
}
var peek: T? {
elements.first
}
var description: String {
if isEmpty { return "Queue is empty..." }
return "---- Queue start ----\n"
+ elements.map({"\($0)"}).joined(separator: " -> ")
+ "\n---- Queue End ----"
}
mutating func enqueue(_ value: T) {
elements.append(value)
}
mutating func dequeue() -> T? {
isEmpty ? nil : elements.removeFirst()
}
}
๋ฐฐ์ด์ ์ ๊ณต๋๋ ๋ฉ์๋๋ค๋ก ํ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ด๋ค.
์ฌ์ค ์ด ๋ฐฉ๋ฒ์ ํ๋ฅผ ๊ตฌํํ๋ค๊ธฐ๋ณด๋ค, ํ์ ๋์์ ํ๋ด๋๋ค๊ณ ๋ณด๋ ๊ฒ ๋ง๋ค. ์ค์ (์๊ณ ๋ฆฌ์ฆ, ๊ฐ๋ฐ)์ ์ฐ๊ธฐ์ ๋๋ฌด ๋๋ฆฌ๊ณ ๋นํจ์จ์ ์ด๊ธฐ ๋๋ฌธ์ด๋ค.
- enqueue๋ append()๋ฅผ ์ฌ์ฉํ๋ฏ๋ก
O(1)
์ด๋ผ ๊ด์ฐฎ๋ค. - ๊ทธ๋ฌ๋ dequeue๋
removeFirst()
๋ฅผ ์ฌ์ฉํ๋๋ฐ, ์ด๋O(n)
์ ์๊ฐ๋ณต์ก๋๋ฅผ ๊ฐ์ง๋ค.- ๋ฐฐ์ด์ด๋ผ๋ ์๋ฃ๊ตฌ์กฐ์ ๊ณต๋ถํด ๋ณธ ์ ์ด ์๋ค๋ฉด, ๋ฐฐ์ด์ ์ฒซ ์์๋ฅผ ์ญ์ ํ๋๊ฒ ์ผ๋ง๋ ๋นํจ์จ์ ์ธ์ง ์ ๊ฒ์ด๋ค - ๋งจ ์์ ์์๊ฐ ์ญ์ ๋๋ฉด ๊ทธ ์๋ฆฌ๋ฅผ ์ฑ์ฐ๊ธฐ ์ํด ๋๋จธ์ง ์์๋ค์ด ๋ชจ๋ ํ์นธ์ฉ ์์ผ๋ก ๋น๊ฒจ์ ธ์ผ ํ๊ธฐ ๋๋ฌธ.
- ๋, ๋ฐฐ์ด์ ๊ฒฝ์ฐ ์์๋ฅผ ์์ ์ ํฌ๊ธฐ ์ด์์ผ๋ก ์ฝ์ ํ๋ฉด, ๊ธฐ์กด ํฌ๊ธฐ๋ณด๋ค 2๋ฐฐ์ ๋์ ์ ๋ฐฐ์ด์ ๋ง๋ค์ด ์ฎ๊ธฐ๋ ์์ ์ด ํ์ํ๋ค(= ๋ฉ๋ชจ๋ฆฌ ์ฌํ ๋น)
์ด๋ฌํ ๋ฌธ์ ๋ค์ด ์๊ธฐ ๋๋ฌธ์ (1) ๋ฐฐ์ด๋ก ํ ๊ตฌํํ๊ธฐ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์ง ์๋ ๊ฒ์ด ์ข๋ค.
์๋์ ์ด ๋ฌธ์ ๋ค์ ๊ทน๋ณตํ ๋ค๋ฅธ ๊ตฌํ๋ฐฉ์์ด ์ ์๋์ด ์๋ค.
Queue ๊ตฌํํ๊ธฐ - (2) ์ฐ๊ฒฐ๋ฆฌ์คํธ(Singly Linked List)
class Node<T>: CustomStringConvertible {
var value: T
var next: Node?
var description: String {
guard let next = next else { return "\(value)" }
return "\(value) -> " + String(describing: next)
}
init(value: T, next: Node? = nil) {
self.value = value
self.next = next
}
}
struct Queue<T>: CustomStringConvertible {
var front: Node<T>?
var rear: Node<T>?
init() { }
var isEmpty: Bool {
return front == nil
}
var description: String {
guard let front = front else { return "Empty Queue" }
return String(describing: front)
}
var peek: T? {
return front?.value
}
mutating private func push(_ value: T) {
front = Node(value: value, next: front)
if rear == nil {
rear = front
}
}
mutating func enqueue(_ value: T) {
if isEmpty {
self.push(value)
return
}
rear?.next = Node(value: value)
rear = rear?.next
}
mutating func dequeue() -> T? {
defer {
front = front?.next
if isEmpty {
rear = nil
}
}
return front?.value
}
}
front
: ๋งจ ์ ๋ ธ๋๋ฅผ ๋ํ๋rear
: ๋งจ ๋ค ๋ ธ๋๋ฅผ ๋ํ๋- enqueue : ๋งจ ๋ค์ ์์ ์ถ๊ฐ.
O(1)
- rear ์ฐธ์กฐ๊ฐ์ ํ์ฉํด ๊ทธ๋ฅ ๋งจ ๋ง์ง๋ง์ ์ ์์๋ฅผ ๋ ธ๋๋ก ๋ง๋ค์ด ์ถ๊ฐํ๋ฉด ๋๋ค.
- dequeue : ๋งจ ์์ ์์ ์ญ์ .
O(1)
- ์ญ์ front ์ฐธ์กฐ๊ฐ์ ํ์ฉํด ๊ทธ๋ฅ ๋งจ ๋ง์ง๋ง์ ์ ์์๋ฅผ ๋ ธ๋๋ก ๋ง๋ค์ด ์ถ๊ฐํ๋ฉด ๋๋ค.
๋ค๋ง enqueue์์ ์์ ๋งค๋ฒ ๋์ ๋ฉ๋ชจ๋ฆฌํ ๋น ์๊ฐ์ด ์์๋๋ค.
Queue ๊ตฌํํ๊ธฐ - (3) Stack ๋ ๊ฐ๋ฅผ ์ฌ์ฉํด ๊ตฌํ (two stacks)
์ผ๋ช ํฌ์คํ์ด๋ผ ๋ถ๋ฆฌ๋ ๋ฐฉ๋ฒ์ด๋ค.
์ผ์ชฝ ์คํ๊ณผ ์ค๋ฅธ์ชฝ ์คํ์ ๊ฐ์ง๊ณ ํ๋ฅผ ๊ตฌํํ๋ค.
- Left Stack ์ผ์ชฝ ์คํ
- ๋ชจ๋ dequeue ์ฐ์ฐ์ ์ฌ๊ธฐ์์ ์ฒ๋ฆฌํ๋ค.
- Right Stack ์ค๋ฅธ์ชฝ ์คํ
- ๋ชจ๋ enqueue ์ฐ์ฐ์ ์ฌ๊ธฐ์์ ์ฒ๋ฆฌํ๋ค.
ํฌ์คํ ๋ฐฉ์์ ํน์ง
- ๋ฐฐ์ด์ ๊ธฐ๋ฐํ ๊ตฌํ๋ฐฉ์์
- ๋ฐฐ์ด์ ์ฐ์์ ์ธ ์ธ๋ฑ์ค์ ๋ฐ๋ผ ๋ชจ๋ ๊ฐ์ด ์ ์ฅ๋จ
- ๋ฐ๋ผ์ ๋ง์ ์์ ๋ฐ์ดํฐ๋ฅผ ์ฒซ ์ก์ธ์ค์ ์บ์์ ๋ก๋ํ ์ ์๋ค
- Circular Queue์ ๋ฌ๋ฆฌ ํฌ๊ธฐ ์ ํ์ด ์๋ค
struct TwoStackQueue<T>: CustomStringConvertible {
var leftStack: [T] = []
var rightStack: [T] = []
var isEmpty: Bool {
leftStack.isEmpty && rightStack.isEmpty
}
var peek: T? {
leftStack.isEmpty ? rightStack.first : leftStack.last
}
mutating func enqueue(_ element: T) {
rightStack.append(element)
}
mutating func dequeue() -> T? {
if isEmpty {
return nil
}
if leftStack.isEmpty {
leftStack = rightStack.reversed()
rightStack.removeAll()
}
return leftStack.removeLast()
}
}
extension TwoStackQueue {
var description: String {
if isEmpty {
return "Queue is empty..."
}
var allElements: [T] = []
if leftStack.isEmpty == false {
allElements.append(contentsOf: leftStack.reversed())
}
allElements.append(contentsOf: rightStack)
return "---- Queue start ----\n"
+ allElements.map({"\($0)"}).joined(separator: " -> ")
+ "\n---- Queue End ----"
}
}
๋์์๋ฆฌ
- enqueue()
append()
: Right Stack์ ๋ฐฐ์ด์ ๊ฐ์ ์ถ๊ฐํ๋ค.O(1)
์ ์๊ฐ๋ณต์ก๋๋ฅผ ๊ฐ์ง
- dequeue()
- ๋ง์ฝ Left Stack๊ณผ Right Stack์ด ๋ชจ๋ ๋น์ด์๋ค๋ฉด, ์ธ๋ํ๋ก์ฐ ์ํฉ์ด ๋๋ค.
- ๋ง์ฝ Left Stack์ด ๋น์ด์๋ค๋ฉด, Right Stack์ ๋ชจ๋ ์์๋ฅผ ์์๋ฅผ ๋ค์ง์ด Left Stack์ ๊ฐ์ ธ์ค๊ณ Right Stack์ ๋น์ด๋ค.
removeLast()
: Left Stack์ ๋งจ ๋ ์์๋ฅผ ์ญ์ ํ๋ค.
Queue ๊ตฌํํ๊ธฐ - (4) head๋ฅผ ์ฌ์ฉํด ๊ตฌํ
์ฐธ๊ณ : https://github.com/kodecocodes/swift-algorithm-club/tree/master/Queue#a-more-efficient-queue
(1)์ ๋ฐฐ์ด๋ก ๊ตฌํํ ๋ฐฉ์์์, dequeue์ ํด๋นํ๋ removeFirst()
๊ฐ O(n)
์ ์๊ฐ๋ณต์ก๋๋ฅผ ๊ฐ์ง๊ธฐ ๋๋ฌธ์ ๋ฌธ์ ๊ฐ ๋์์๋ค.
๋ฐ๋๋ก ๋งํ๋ฉด removeFirst()
๋์ ๋ค๋ฅธ ๋ฐฉ์์ผ๋ก dequeue๋ฅผ ํ๋ค๋ฉด ๊ด์ฐฎ๋ค๋ ์ด์ผ๊ธฐ์ด๋ค.
์๋ ๊ตฌํ๋ฐฉ์์ ๋ฐฐ์ด๋ก ๋ง๋ ํ์์ ์์ ์์๋ฅผ ์ง์ ‘์ง์ฐ๋’ ๋์ , nil
์ ํ ๋นํ๋ ๋ฐฉ์์ด๋ค.
- ๋งจ ์์ ์์์ ์ธ๋ฑ์ค๋ฅผ ๋ํ๋ด๋
head
๋ฅผ ๋ง๋ ๋ค - ์์๋ฅผ ์ง์ฐ๋ ๋์ , ๊ทธ ์๋ฆฌ์
nil
์ ํ ๋นํ๊ณhead
์ 1์ ๋ํ๋คO(1)
์ ์๊ฐ๋ณต์ก๋๋ฅผ ๊ฐ๋๋ค.
- ํ์์
head
์ ์์น๊ฐ ์ผ์ ์ธ๋ฑ์ค ์ด์์ด ๋๋ฉด (= nil์ด ์ด๋์ ๋ ์ด์ ๋ง์์ง๋ฉด), nil์ด ์๋ ์์๋ค์ ์์ผ๋ก ์ฎ๊ฒจ์จ๋ค.- ์ด ์์
์
O(n)
์ ์๊ฐ๋ณต์ก๋๋ฅผ ๊ฐ๋๋ค.
- ์ด ์์
์
public struct Queue<T> {
fileprivate var array = [T?]()
fileprivate var head = 0
public var isEmpty: Bool {
return count == 0
}
public var count: Int {
return array.count - head
}
public mutating func enqueue(_ element: T) {
array.append(element)
}
public mutating func dequeue() -> T? {
guard head < array.count, let element = array[head] else { return nil }
array[head] = nil
head += 1
let percentage = Double(head)/Double(array.count)
if array.count > 50 && percentage > 0.25 {
array.removeFirst(head)
head = 0
}
return element
}
public var front: T? {
if isEmpty {
return nil
} else {
return array[head]
}
}
}
head
: ๋ฐฐ์ด์์ ๋งจ ์์ ์๋ ๊ฐ์ฒด์ ์ธ๋ฑ์ค๋ฅผ ๋ํ๋head
๋array
์ ์ ์ฒด ํฌ๊ธฐ๋ณด๋ค ์์์ผ ํ๋ค
enqueue()
: ๋งจ ๋ค์ ์ ์์๋ฅผ ๋ถ์. ๋ฌ๋ผ์ง ์ ์์.O(1)
dequeue()
: ๊ฐ์ฅ ๋ฌ๋ผ์ง ๋ถ๋ถ.O(1)
- dequeue์ฐ์ฐ์ ํ๋ฉด ๋ฐฐ์ด์ ๋งจ ์์ ์์๋ฅผ ์ญ์ ํด์ผ ํ๋ค.
- ์ด๋ ์ง์ ์ญ์ ํ๋ ๋์ ,
head
๋ก ๋งจ ์ ๊ฐ์ฒด์ ์ธ๋ฑ์ค๋ฅผ ์์๋ด๊ณ , ๊ฑฐ๊ธฐ์ nil์ ํ ๋นํ๋ค. - ๊ทธ๋ฌ๋ฉด ์ญ์ ๋ ์์์ ๋ค์ ์์๊ฐ ์ฒซ ์์๊ฐ ๋๋ฏ๋ก
head += 1
์ ํด์ค๋ค.
๐ค dequeue๋ฅผ ๋ฐ๋ณตํ๋ฉด nil์ด ์ ์ ๋ง์์ ธ์ array
์ ์ฌ์ฉํ์ง ์๋ ๊ณต๊ฐ์ด ์ปค์ง๋๋ฐ ๋นํจ์จ์ ์ด์ง ์๋์?
- ๊ทธ๋์ ์ฃผ๊ธฐ์ ์ผ๋ก
array
์์ nil๋ค์ ์ณ๋ด๋ ์ฌ๋ฐฐ์น ์์ ์ด ํ์ํ๋ค. ์๋ ์ฝ๋๊ฐ ๊ทธ ์ญํ ์ ํ๋ค.
let percentage = Double(head)/Double(array.count)
if array.count > 50 && percentage > 0.25 {
array.removeFirst(head)
head = 0
}
- ์กฐ๊ฑด1 :
array
์ 25% ์ด์์ด ์ฌ์ฉ๋์ง ์๊ณ ์์ ๋ - ์กฐ๊ฑด2 :
array
์ ํฌ๊ธฐ๊ฐ 50์ด ๋์ ๋array
ํฌ๊ธฐ๊ฐ ์ถฉ๋ถํ ์ปค์ก์ ๋๋ง ์กฐ๊ฑด ํ์ธ ๋ฐ ์ฌ๋ฐฐ์นํ๋๋ก ํจ- 50์ด๋ผ๋ ์ซ์๋ ๊ฐ์ ์ํฉ์ ๋ฐ๋ผ ์กฐ์ ํ๋ฉด ๋๋ค.
- ์ฌ๋ฐฐ์น์์
:
removeFirst(k:)
๋ก, ๋ฐฐ์์ ์์์๋ถํฐhead
๊ฐ ๋งํผ์ ์์๋ค์ ์์ ๊ณ ,head
๋ฅผ 0์ผ๋ก ์ฌ์ค์ ํ๋ค- ์ด๋ ๊ฒ ํ๋ฉด ์์ ์๋ฆฌ๋ฅผ ์ฐจ์งํ๋
nil
๊ฐ์ฒด๋ค์ด ์ฌ๋ผ์ง๋ค. head
๋ ์ฒซ ์์์ ์ธ๋ฑ์ค์ด๋ฉด์ ๋์์ ์์nil
์ธ ์์๋ค์ ์๋ฅผ ๋ํ๋ด๊ธฐ๋ ํ๊ธฐ ๋๋ฌธ.
- ์ด๋ ๊ฒ ํ๋ฉด ์์ ์๋ฆฌ๋ฅผ ์ฐจ์งํ๋
(์ฐธ๊ณ ) Circular Queue ๊ตฌํํ๊ธฐ - Ring Buffer
์ผ๋ฐQueue๊ฐ ์๋ Circular Queue๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ด๋ค.
๋ง์ฝ ๊ณ ์ ๋ ์ฌ์ด์ฆ์ ํ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด, Circular Queue๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ํจ์จ์ ์ด๋ค.
struct RingBufferQueue<T>: CustomStringConvertible {
private var elements: [T?]
private var front = -1
private var rear = -1
init(count: Int) {
elements = Array(repeating: nil, count: count)
}
var isEmpty: Bool {
front == -1 && rear == -1
}
var isFull: Bool {
((rear + 1) % elements.count) == front
}
var description: String {
if isEmpty { return "Queue is empty..." }
return "---- Queue start ----\n"
+ elements.map({String(describing: "\($0)")}).joined(separator: " -> ")
+ "\n---- Queue End ----\n"
}
var peek: T? {
if isEmpty { return nil }
return elements[front]
}
}
extension RingBufferQueue {
// to enqueue an element
mutating func enqueue(_ element: T) -> Bool {
// if queue is empty
if front == -1 && rear == -1 {
front = 0
rear = 0
elements[rear] = element
return true
}
if isFull {
print("QUEUE IS FULL")
return false
}
rear = (rear + 1) % elements.count
elements[rear] = element
return true
}
}
extension RingBufferQueue {
// to dequeue an element
mutating func dequeue() -> T? {
if isEmpty {
print("QUEUE IS EMPTY")
return nil
}
// if queue has only single element
if front == rear {
defer {
elements[front] = nil
front = -1
rear = -1
}
return elements[front]
}
defer {
elements[front] = nil
front = (front + 1) % elements.count
}
return elements[front]
}
}
- enqueue :
O(1)
- dequeue :
O(1)