오늘도 더 나은 코드를 작성하였습니까?

원형 이중 연결리스트(Circular Doubly Linked List) feat.kotlin 본문

자료구조

원형 이중 연결리스트(Circular Doubly Linked List) feat.kotlin

hik14 2023. 11. 27. 21:44

원형 이중 연결 리스트의 구조는 이중 연결 리스트의 포인터 공간이 두 개가 있고 각각의 포인터는 앞의 노드와 뒤의 노드를 가리키는 양방향 참조 노드를 이용하며, 마지막 노드와 처음 노드를 연결시켜 원형으로 만든 구조이다. 

여기에 연산및 구현의 편의성을 위해 데이터가 없으며 원형의 시작부분을 표시 하기 위한 Dummy Node를 추가 해준다.

Dummy Node

 

더미 노드 기준으로 이중 연결 리스트 기반으로 순환하여 만든 리스트이기에 데이터를 앞뒤로 삽입/삭제 연산이 매우 빠르다.

 

pushFront() O(1)

pushBack() O(1)

 

popFront() O(1)

popBack() O(1)

 

특정 데이터를 가진 노드를 찾는 연산은 더미 노드를 시작으로 1바퀴 모든 노드를 돌아야 되기 때문에 리스트에 들어있는 개수만큼 시간 복잡도를 가진다. 

search() O(N)

class CircularDoublyLinkedList {
    class Node(var key: Int?, prev: Node?, next: Node?) {
        var prev: Node
        var next: Node

        init {
            this.prev = prev ?: this
            this.next = next ?: this
        }
    }

    private val head: Node = Node(null, null, null)
    var size = 0


    /*

       1. a = b일 수 있지만, a의 next 링크를 따라 가다보면 b가 있다.
       2. a ---> b 두 노드 사이에 head 노드는 존재하지 않는다.
       3. a ---> b 두 노드 사이에 x 노드는 존재하지 않는다.

       a ---> b 의 연결을 뗴어 내어 x노드 다음에 연결
     */
    private fun splice(a: Node, b: Node, x: Node) {
        val ap = a.prev
        val bn = b.next
        val xn = x.next

        ap.next = bn
        bn.prev = ap

        x.next = a
        a.prev = x

        b.next = xn
        xn.prev = b
    }

    /*
        원형 연결 상태에서 node(a)를 node(x) 뒤로 이동
     */
    private fun moveAfter(a: Node, x: Node) = splice(a, a, x)


    /*
        원형 연결 상태에서 node(a)를 node(x) 앞으로 이동
     */
    private fun moveBefore(a: Node, x: Node) = splice(a, a, x.prev)

    /*
        입력된 key 값을 가진 노드를 생성해서, node(x) 뒤로 삽입
     */
    private fun insertAfter(key: Int, x: Node) {
        val node = Node(key, null, null)
        moveAfter(node, x)
    }

    /*
        입력된 key 값을 가진 노드를 생성해서, node(x) 앞으로 삽입
     */
    private fun insertBefore(key: Int, x: Node) {
        val node = Node(key, null, null)
        moveBefore(node, x)
    }

    /*
       원형 연결 리스트에 key을 head 기준 다음 노드에 삽입힌다.
    */
    fun pushFront(key: Int) {
        insertAfter(key, head)
        size += 1
    }

    /*
       원형 연결 리스트에 key을 head 기준 이전 노드에 삽입힌다.
    */
    fun pushBack(key: Int) {
        insertBefore(key, head)
        size += 1
    }

    private fun searchNode(key: Int): Node? {
        var node = head.next
        while (node.key != null) {
            if (key == node.key)
                return node
            node = node.next
        }
        return null
    }

    private fun removeNode(node: Node) {
        if (node.key == null) return
        val prev = node.prev
        val next = node.next

        prev.next = next
        next.prev = prev
    }

    fun popFront() = removeNode(head.next)

    fun popBack() = removeNode(head.prev)

    fun deleteByKey(key: Int): Int? {
        val node = searchNode(key) ?: return null
        removeNode(node)
        size -= 1
        return node.key
    }

    override fun toString(): String {
        val sb = StringBuilder()
        var node = head.next
        while (node.key != null) {
            sb.append("${node.key} -> ")
            node = node.next
        }
        return sb.toString()
    }
}

fun main() {

    val list = CircularDoublyLinkedList()
    list.pushFront(3)
    list.pushFront(10)
    list.pushFront(9)
    list.pushFront(116)
    list.pushBack(2023)

    println(list)
    list.deleteByKey(10)
    println(list)
}