먼저 들어온 데이터가 가장 먼저 나가는 특징을 가진 자료구조
📌 FIFO 구조 : ( 선입선출 ) 먼저 들어간 것이 먼저 나온다.
배열로 구현 후에 가장 마지막 인덱스부터 dequeue
해준다.
queue에 새로운 데이터를 삽입한다.
enqueue
는 기존 queue의 마지막(오른쪽)에 데이터를 삽입한다.
queue에 있는 데이터를 꺼낸다.
dequeue
는 기존 queue의 첫번째(왼쪽)의 데이터를 꺼낸다.
struct Queue<Element> {
private var queue: [Element] = []
var count: Int {
return queue.count
}
var isEmpty: Bool {
return queue.isEmpty
}
mutating func enqueue(_ element: Element) {
queue.append(element)
}
mutating func dequeue() -> Element? {
return isEmpty ? nil: queue.removeFirst()
}
}
이때 dequeue를 하게 되면 모든 데이터를 한 칸씩 이동해야 하는데 이때 오버헤드가 발생하고
O(n) 의 시간복잡도가 소요된다.
이를 해결하기 위해서 두가지 방법이 있다.
첫번째는 head
변수를 따로 두고 head
변수가 가리키는 인덱스를 dequeue 하면서 head를 이동시킨다. 이렇게되면 queue에서 배열을 움직이는 실제 작업을 하지 않아도 된다.
다시 말해 queue의 첫번째 위치로 데이터를 옮기는 것이 아닌 head
가 가리키는 것이 첫번째 위치가 되는 것이다.
struct Queue<Element> {
private var queue: [Element?] = []
var head = 0
var count: Int {
return queue.count
}
var isEmpty: Bool {
return queue.isEmpty
}
mutating func enqueue(_ i: Element) {
queue.append(i)
}
mutating func dequeue() -> Element? {
guard head <= count, let element = queue[head] else { return nil }
queue[head] = nil
head += 1
if head > 10 {
queue.removeFirst(head)
head = 0
}
return element
}
}
이때 어느정도 dequeue를 한 후에는 앞의 nil
로 변경된 배열요소들을 삭제하는 작업이 필요하다.
위의 코드에서는 10으로 설정했다.
이 경우 순수 dequeue 작업의 시간복잡도는 O(1)이지만
head 앞의 nil
이 된 항목들을 삭제하는 과정에서의 시간복잡도는 removeFirst(Int)
의 과정이 O(n)의 시간복잡도가 소요되기 때문에 O(n)이 소요된다.
두번째는 2개의 Stack 배열을 활용하는 방법이다.
하나의 Stack은 데이터를 삽입하는 enqueue만을 수행한다. (inStack
)
또 다른 Stack은 데이터를 삭제하는 dequeue만을 수행한다. (outStack
)
dequeue 작업은 inStack
을 reversed()
하여 outStack
에 넣은 후에 popLast()
메소드로 맨 뒤의 데이터를 삭제한다.
Swift 의 reversed()
시간복잡도는 O(1) 이고 popLast()
또한 O(1)의 시간복잡도가 소요되므로 dequeue 작업에 소요되는 시간복잡도는 O(1)이다.
struct DoublyStackQueue<Element> {
private var inStack: [Element] = []
private var outStack: [Element] = []
var isEmpty: Bool {
return inStack.isEmpty && outStack.isEmpty
}
mutating func enqueue(_ element: Element) {
inStack.append(element)
}
mutating func dequeue() -> Element? {
if outStack.isEmpty {
outStack = inStack.reversed()
inStack.removeAll()
}
return outStack.popLast()
}
}
이때 dequeue 작업에서 outStack
에 inStack
을 옮겨줬다면 inStack
은 비워주어야한다.
dequeue 작업이 끝나고 outStack
이 다시 비워졌을때 비워지지 않은 inStack
을 다시 outStack
에 넣어주게 되면 dequeue했던 데이터들이 다시 들어가기 때문이다.