Bibi's DevLog ๐Ÿค“๐ŸŽ

[Swift] ์—ฐ๊ฒฐ๋ฆฌ์ŠคํŠธ LinkedList, ์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ๋ฆฌ์ŠคํŠธ DoublyLinkedList ๋ณธ๋ฌธ

ํ”„๋กœ๊ทธ๋ž˜๋ฐ/Data Structures ์ž๋ฃŒ๊ตฌ์กฐ

[Swift] ์—ฐ๊ฒฐ๋ฆฌ์ŠคํŠธ LinkedList, ์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ๋ฆฌ์ŠคํŠธ DoublyLinkedList

๋น„๋น„ bibi 2022. 10. 6. 23:21

์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ๋ž€

์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ๋Š” ๊ฐ ์š”์†Œ๊ฐ€ ๋…ธ๋“œ๋กœ ๋˜์–ด ์žˆ๋Š” ์—ฐ์†๋œ ๋ฐ์ดํ„ฐ๋“ค์ด๋‹ค.

  • node ๋…ธ๋“œ : ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ์˜ ํ•˜๋‚˜์˜ ์š”์†Œ๋ฅผ ๋งํ•จ
  • head : ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ์˜ ์‹œ์ž‘ ๋…ธ๋“œ๋ฅผ ์ฐธ์กฐํ•˜๋Š” ํฌ์ธํ„ฐ
  • tail : ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ์˜ ๋ ๋…ธ๋“œ๋ฅผ ์ฐธ์กฐํ•˜๋Š” ํฌ์ธํ„ฐ

์œ ํ˜•

  • ๋‹จ๋ฐฉํ–ฅ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ singly linked list : ๊ฐ ํ•ญ๋ชฉ์ด ๋‹ค์Œ ๋…ธ๋“œ์— ๋Œ€ํ•œ ์ฐธ์กฐ๋งŒ ๊ฐ€์ง
  • ์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ doubly linked list : ๊ฐ ํ•ญ๋ชฉ์ด ์•ž, ๋’ค ๋…ธ๋“œ์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ ๋ชจ๋‘ ๊ฐ€์ง

๋‹จ๋ฐฉํ–ฅ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ ๊ตฌํ˜„ํ•˜๊ธฐ (Swift)

https://www.raywenderlich.com/books/data-structures-algorithms-in-swift/v3.0/chapters/6-linked-list

ํ”„๋กœํ† ์ฝœ ์ฑ„ํƒํ•˜๋Š” ๋ถ€๋ถ„ ์ด์ „๊นŒ์ง€ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

Node

๋‹จ๋ฐฉํ–ฅ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ๋Š” ๋…ธ๋“œ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์œผ๋ฉฐ, ๊ฐ ๋…ธ๋“œ๋Š” value, next๋ฅผ ์ง€๋‹Œ๋‹ค.

  • value : ๋…ธ๋“œ๊ฐ€ ๋‹ด๋Š” ๊ฐ’
  • next : ๋‹ค์Œ ๋…ธ๋“œ์— ๋Œ€ํ•œ ํฌ์ธํ„ฐ
public class LinkedListNode<T> {
    public var value: T
    public var next: LinkedListNode?

    public init(value: T, next: LinkedListNode? = nil) {
        self.value = value
        self.next = next
    }
}

LinkedList

public struct LinkedList<T> {

    public typealias Node = LinkedListNode<T>

    public var head: Node?
    public var tail: Node?

    public init() {}

    public var isEmpty: Bool {
        head == nil
    }

    // node(at:) : O(i), i=index. ํŠน์ • ์ธ๋ฑ์Šค์˜ ๋…ธ๋“œ๋ฅผ ์ฐพ๊ธฐ
    public func node(at index: Int) -> Node? {
        var currentNode = head
        var currentIndex = 0

        while currentNode != nil && currentIndex < index {
            currentNode = currentNode!.next
            currentIndex += 1
        }
        // ๋ชฉ๋ก์ด ๋น„์–ด์žˆ๊ฑฐ๋‚˜ ์ธ๋ฑ์Šค๋ฅผ ๋„˜์–ด๊ฐ€๋ฉด nil ๋ฐ˜ํ™˜
        return currentNode
    }

    // MARK: Adding ์‚ฝ์ž…

    // push : O(1), ๋งจ ์ฒ˜์Œ์— ์‚ฝ์ž… head-first insertion
    public mutating func push(_ value: T) {
        head = Node(value: value, next: head)
        if tail == nil { // ๋นˆ ์—ฐ๊ฒฐ๋ฆฌ์ŠคํŠธ์— ๋„ฃ์€ ๊ฒฝ์šฐ ์ฒซ ์š”์†Œ๊ฐ€ head ์ด์ž tail์ž„
            tail = head
        }
    }

    // append: O(1), ๋งจ ๋’ค์— ์‚ฝ์ž… tail-end insertion
    public mutating func append(_ value: T) {
        if isEmpty { // ๋น„์–ด ์žˆ๋Š” ๋ฐฐ์—ด์— ๋„ฃ์„ ๋•Œ๋Š” push == append
            push(value)
            return
        }

        tail!.next = Node(value: value)
        tail = tail!.next
    }

    @discardableResult // ๋ฆฌํ„ด๊ฐ’์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋”๋ผ๋„ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๊ฒฝ๊ณ ๋ฅผ ๋ณด๋‚ด์ง€ ์•Š๊ฒŒ ํ•จ
    // insert(_:after:) : O(1), ํ•ด๋‹น ๋…ธ๋“œ ๋’ค์— ์‚ฝ์ž….
    public mutating func insert(_ value: T, after node: Node) -> Node {
        guard tail !== node else { // ์ฐธ์กฐ ๋น„๊ต
            // node๊ฐ€ tail์ธ ๊ฒฝ์šฐ insert == append
            append(value)
            return tail!
        }

        node.next = Node(value: value, next: node.next)
        return node.next!
    }

    // MARK: Removing ์‚ญ์ œ

    // pop : O(1). ๋งจ ์•ž์—์„œ ์ œ๊ฑฐ.
    @discardableResult
    public mutating func pop() -> T? {
        defer { // ํ•จ์ˆ˜ ์ข…๋ฃŒ ์ง์ „์— ์‹คํ–‰
            head = head?.next // head ์ œ๊ฑฐ - ์ฐธ์กฐ๊ฐ€ ์—†์œผ๋ฏ€๋กœ
            if isEmpty {
                tail = nil
            }
        }
        return head?.value
    }

    // removeLast() : O(n). ๋งจ ๋’ค์—์„œ ์ œ๊ฑฐ.
    @discardableResult
    public mutating func removeLast() -> T? {
        guard let head = head else { // ๋น„์–ด์žˆ์„ ๋•Œ
            return nil
        }

        guard head.next != nil else { // ๋…ธ๋“œ๊ฐ€ 1๊ฐœ๋ฟ์ผ๋•Œ
            return pop()
        }

        // ๋…ธ๋“œ๊ฐ€ 2๊ฐœ์ด์ƒ์ผ๋•Œ : ๋งจ ๋ ๋…ธ๋“œ๋ฅผ ์ฐพ์•„์•ผ ํ•จ
        var prev = head
        var current = head
        while let next = current.next { // next๊ฐ€ nil์ด ์•„๋‹ˆ๋ฉด ๋ฐ˜๋ณต
            prev = current
            current = next
        }
        // ์—ฌ๊ธฐ์— ๋„๋‹ฌํ•˜๋ฉด current๊ฐ€ ๋งˆ์ง€๋ง‰ ๋…ธ๋“œ๊ฐ€ ๋œ๋‹ค.
        // ๋งˆ์ง€๋ง‰ ๋…ธ๋“œ์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ ์—†์•  ์‚ญ์ œํ•œ๋‹ค
        prev.next = nil // ๋ฉ”๋ชจ๋ฆฌ์—์„œ ํ•ด์ œ
        tail = prev
        return current.value
    }

    // remove(after:) : O(1). ํŠน์ • ์œ„์น˜์˜ ํŠน์ • ๋…ธ๋“œ๋ฅผ ์‚ญ์ œ
    // ์‚ญ์ œ ๋…ธ๋“œ์˜ ์•ž ๋…ธ๋“œ์™€ ๋’ท ๋…ธ๋“œ๋ฅผ ์ด์–ด๋ถ™์—ฌ์ค€๋‹ค
    @discardableResult
    public mutating func remove(after node: Node) -> T? {
        // node == ์‚ญ์ œํ•  ๋…ธ๋“œ์˜ ์•ž ๋…ธ๋“œ
        defer {
            if node.next === tail { // tail ์•ž ๋…ธ๋“œ ์‚ญ์ œ ์‹œ
                tail = node
            }
            node.next = node.next?.next
        }
        return node.next?.value
    }
}

extension LinkedListNode: CustomStringConvertible {

    public var description: String {
        guard let next = next else {
            return "\(value)"
        }
        return "\(value) -> " + String(describing: next) + " "
    }
}

extension LinkedList: CustomStringConvertible {

  public var description: String {
    guard let head = head else {
      return "Empty list"
    }
    return String(describing: head)
  }
}

์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ ๊ตฌํ˜„ํ•˜๊ธฐ (Swift)

https://www.raywenderlich.com/947-swift-algorithm-club-swift-linked-list-data-structure

Node

์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ๋Š” ๋…ธ๋“œ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์œผ๋ฉฐ, ๊ฐ ๋…ธ๋“œ๋Š” value, next, previous๋ฅผ ์ง€๋‹Œ๋‹ค.

  • value : ๋…ธ๋“œ๊ฐ€ ๋‹ด๋Š” ๊ฐ’
  • next : ๋‹ค์Œ ๋…ธ๋“œ์— ๋Œ€ํ•œ ํฌ์ธํ„ฐ
  • previous : ์ด์ „ ๋…ธ๋“œ์— ๋Œ€ํ•œ ํฌ์ธํ„ฐ
public class DoublyLinkedListNode<T> {
    var value: T
    var next: DoublyLinkedListNode<T>? // ๋งˆ์ง€๋ง‰ ๋…ธ๋“œ์˜ next๋Š” nil์ด ๋˜๋ฏ€๋กœ
    weak var previous: DoublyLinkedListNode<T>? // ์ˆœํ™˜์ฐธ์กฐ ๋ฐฉ์ง€
    // weak ์—†์œผ๋ฉด A-B์ผ ๋•Œ A๋Š” next๋กœ B๋ฅผ, B๋„ previous๋กœ A๋ฅผ ์ฐธ์กฐํ•˜๊ฒŒ ๋˜์–ด ์ˆœํ™˜์ฐธ์กฐ ๋ฐœ์ƒ

    init(value: T) {
        self.value = value
    }
}

DoublyLinkedList

์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ์˜ ์‹œ์ž‘๊ณผ ๋์„ ์ถ”์ ํ•˜๋Š” ์—ญํ• ์ด๋‹ค.

  • append(value:) : ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ์— ์ƒˆ ๋…ธ๋“œ ์ถ”๊ฐ€
  • nodeAt(index: Int) -> Node? : ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ์˜ ํ•ด๋‹น ์ธ๋ฑ์Šค์— ์žˆ๋Š” ๋…ธ๋“œ๋ฅผ ๊ฐ€์ ธ์˜ด
  • removeAll() : ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ์˜ ๋ชจ๋“  ๋…ธ๋“œ ์‚ญ์ œ
  • remove(node: Node) -> String : ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ์˜ ํ•ด๋‹น ๋…ธ๋“œ ์‚ญ์ œ
    • ์ฒซ ๋…ธ๋“œ ์‚ญ์ œํ•˜๋Š” ๊ฒฝ์šฐ : head์™€ previous ํฌ์ธํ„ฐ ์ˆ˜์ • ํ•„์š”
    • ์ค‘๊ฐ„ ๋…ธ๋“œ ์‚ญ์ œํ•˜๋Š” ๊ฒฝ์šฐ : previous์™€ next ํฌ์ธํ„ฐ ์ˆ˜์ • ํ•„์š”
    • ๋งˆ์ง€๋ง‰ ๋…ธ๋“œ ์‚ญ์ œํ•˜๋Š” ๊ฒฝ์šฐ : tail๊ณผ next ํฌ์ธํ„ฐ ์ˆ˜์ • ํ•„์š”
  • extension LinkedList: CustomStringConvertible : ์ถœ๋ ฅ(print) ํ˜•ํƒœ ์ง€์ •
public class DoublyLinkedList<T> { // ์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ

    public typealias Node = DoublyLinkedListNode<T>

    fileprivate var head: Node?
    private var tail: Node?

    public var isEmpty: Bool {
        return head == nil
    }

    public var first: Node? {
        return head
    }

    public var last: Node? {
        return tail
    }

    public func append(value: T) {
        // 1. value๋ฅผ ๊ฐ–๋Š” ์ƒˆ ๋…ธ๋“œ ์ƒ์„ฑ
        let newNode = Node(value: value)
        // 2-1. tailNode๊ฐ€ nil์ด ์•„๋‹˜ = ๋งํฌ๋“œ๋ฆฌ์ŠคํŠธ์— ๋ฌด์–ธ๊ฐ€ ์žˆ์Œ. ๋”ฐ๋ผ์„œ ๊ธฐ์กด์˜ tail ๋’ค์— ์ƒˆ ๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€.
        if let tailNode = tail {
            newNode.previous = tailNode
            tailNode.next = newNode
        // 2-2. ๋งํฌ๋“œ๋ฆฌ์ŠคํŠธ์— ์•„๋ฌด๊ฒƒ๋„ ์—†์Œ. ์ƒˆ ๋…ธ๋“œ๊ฐ€ head๊ฐ€ ๋จ
        } else {
            head = newNode
        }
        // 3. ๋‘ ๊ฒฝ์šฐ ๋ชจ๋‘ newNode๋ฅผ tail๋กœ ์ง€์ •ํ•˜๊ฒŒ ๋จ
        tail = newNode
    }

    public func nodeAt(index: Int) -> Node? {
        // ํŠน์ • ์ธ๋ฑ์Šค์˜ ๋…ธ๋“œ๋ฅผ ๋ฐ˜ํ™˜
        if index >= 0 { // index๊ฐ€ ์Œ์ˆ˜์ผ ๋•Œ ๋ฌดํ•œ๋ฃจํ”„ ๋ฐฉ์ง€
            var node = head
            var i = index
            while node != nil { // ํ•ด๋‹น ์ธ๋ฑ์Šค์˜ ๋…ธ๋“œ์— ๋„๋‹ฌํ•  ๋•Œ ๊นŒ์ง€ ๋ฐ˜๋ณต
                if i == 0 {
                    return node
                }
                i -= 1
                node = node!.next
            }
        }
        return nil // index๊ฐ€ ์Œ์ˆ˜์ด๊ฑฐ๋‚˜ ์—ฐ๊ฒฐ๋ฆฌ์ŠคํŠธ์˜ ํ•ญ๋ชฉ๋ณด๋‹ค ํด ๋•Œ
    }

    public func removeAll() {
        head = nil
        tail = nil
    }

    // MARK: ๋น„์ •์ƒ ๋™์ž‘
    public func remove(node: Node) -> T {
        let prev = node.previous
        let next = node.next

        if let prev = prev { // prev๊ฐ€ ์žˆ๋‹ค๋ฉด ์ฒซ ๋…ธ๋“œ๊ฐ€ ์•„๋‹Œ ๊ฒƒ์ด๋ฏ€๋กœ prev์˜ next ์ˆ˜์ •
            prev.next = next
        } else { // prev๊ฐ€ ์—†๋‹ค๋ฉด head์ด๋ฏ€๋กœ ๋‹ค์Œ ๋…ธ๋“œ๋ฅผ head๋กœ ์ง€์ •
            head = next
        }
        next?.previous = prev // next์˜ previous๋ฅผ ์‚ญ์ œ๋˜๋Š” ๋…ธ๋“œ์˜ prev๋กœ ์ง€์ •.
        if next == nil { // next๊ฐ€ ์—†๋Š” tail์ธ ๊ฒฝ์šฐ tail์„ ์ˆ˜์ •
            tail = prev
        }


        node.previous = nil
        node.next = nil // ์‚ญ์ œํ•˜๋Š” ๋…ธ๋“œ๋ฅผ ์ฐธ์กฐํ•˜๋Š” previous์™€ next์— nil ํ• ๋‹น (๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ)

        return node.value
    }
}

extension DoublyLinkedListNode: CustomStringConvertible {
    public var description: String {
        return "\(value)"
    }
}

extension DoublyLinkedList: CustomStringConvertible {
    public var description: String {
        var text = "["
        var node = head
        while node != nil {
            text += "\(node!.value)"
            node = node!.next
            if node != nil {
                text += ", "
            }
        }
        return text + "]"
    }
}