[C++] 자료구조 – DoublyLinkedList 코드


SinglyLinkedList 에 이어 작성한 DoublyLinkedList 코드.  SinglyLinkedList 와 다른점은, SinglyLinkedList 는 ‘다음’ 노드만 조회 할 수 있지만 DoublyLinkedList 는 ‘다음’ 노드와 ‘이전’ 노드를 조회 할 수 있다. 추가적으로, DoublyLinkedList 가 tail 변수를 가지고 있어서 가장 뒤에 노드를 추가 할 때 일일히 처음부터 순회 할 필요가 없어서 편하다.

앞뒤로 조회가 가능하기 때문에 중간에 노드를 삽입하거나, 삭제 할 때도 처음부터 순회 할 필요 없이 작업 할 수 있다.

코드를 작성하면서 의외로 내가 실수를 많이 했었다.

가장 많이 실수를 했던 부분은 SinglyLinkedList에서 맨 뒤에 추가를 할 때 while(current->nextNode != NULL) 을 사용했어서 여기서 헷갈려서 print 할때라던지 갯수를 셀때 current 가 NULL 인지 확인해야하는걸 current->nextNode 를 확인해서 좀 오류가 났었었다. DoublyLinkedList 에서는 current->nextNode 를 확인 해야 할 필요가 전혀 없으니 앞으로도 주의하도록 해야겠다. 물론 지금 내 머리가 맑지 않아서 저지른 실수이겠지만

그리고, 노드를 삭제하거나 중간에 삽입 할 때 변동 된 부분을 다시 제대로 연결해주는게 좀 헷갈렸는데, 그냥 생각나는대로 작성하는것 보다는 왼쪽에서 오른쪽으로, 아니면 오른쪽에서 왼쪽으로 머릿속에서 그려가면서 연결해주면 더 확실해진다.

메모리를 관리하는건 C++ 의 생명이니까, doublyLinkedList 클래스가 dispose 될 때 메모리를 제대로 정리하도록 했다.

/*
 * DoublyLinkedList.cpp
 * @author velopert
 */

#include <iostream>

using namespace std;

/*
 * Node Class
 */

class Node {
    public:
        int data;
        Node* prevNode;
        Node* nextNode;

        Node(int data);
        void dispose();
};

Node::Node(int data) {
    this->data = data;
    this->prevNode = NULL;
    this->nextNode = NULL;
}

void Node::dispose() {
    cout << "disposing node.. data: " << this->data << endl;
    delete this;
}


/*
 * doublyLinkedList Class
 */

class doublyLinkedList {
    public:
        Node* head;
        Node* tail;

        doublyLinkedList();
        ~doublyLinkedList();

        void insertLast(Node* node);
        void insertFirst(Node* node);
        void insertAfter(Node* target, Node* node);
        void insertBefore(Node* target, Node* node);
        Node* getNodeAt(int loc);
        void removeNode(Node* node);
        int getLength();
        void print(bool isReverse);
};

doublyLinkedList::doublyLinkedList() {
    this->head = NULL;
    this->tail = NULL;
}

doublyLinkedList::~doublyLinkedList() {
    if(!this->head) return;

    Node* current = this->head;
    while(current->nextNode) {
       if(current->prevNode) current->prevNode->dispose();
        current = current->nextNode;
    }
    current->dispose();
}

void doublyLinkedList::insertLast(Node* node) {
    if(!this->head) {
        this->head = node;
        this->tail = node;
    } else {
        Node* current = this->tail;
        current->nextNode = node;
        node->prevNode = current;
        this->tail = node;
    }
}

void doublyLinkedList::insertFirst(Node* node) {
    if(!this->head && !this->tail) {
        this->head = node;
        this->tail = node;
    } else {
        Node* current = this->head;
        node->nextNode = current;
        current->prevNode = node;
        this->head = node;
    }
}

void doublyLinkedList::insertAfter(Node* target, Node* node) {
    if(target == this->tail) {
       target->nextNode = node;
       node->prevNode = target;
       this->tail = node;
    } else {
        target->nextNode->prevNode = node;
        node->nextNode = target->nextNode;
        node->prevNode = target;
        target->nextNode = node;
    }
}

void doublyLinkedList::insertBefore(Node* target, Node* node) {
    if(target == this->head) {
        target->prevNode = node;
        node->nextNode = target;
        this->head = node;
    } else {
        target->prevNode->nextNode = node;
        node->prevNode = target->prevNode;
        node->nextNode = target;
        target->prevNode = node;
    }
}

Node* doublyLinkedList::getNodeAt(int loc) {
    Node* node = this->head;
    while(loc--) {
       node = node->nextNode;
    }
    return node;
}

void doublyLinkedList::removeNode(Node* node) {
    if(node==this->head) {
        node->nextNode->prevNode = NULL;
        this->head = node->nextNode;
    } else if (node==this->tail) {
        node->prevNode->nextNode = NULL;
        this->tail = node->prevNode;
    } else {
        node->prevNode->nextNode = node->nextNode;
        node->nextNode->prevNode = node->prevNode;
    }
    node->dispose();
}

int doublyLinkedList::getLength() {
    int length = 0;
    if(!this->head) return length;

    Node* current = this->head;
    while(current) {
        current = current->nextNode;
        length++;
    }

    return length;
}

void doublyLinkedList::print(bool isReverse=false) {
    cout << "LinkedList" << (isReverse ? "(reversed): " : ": ");
    Node* current = isReverse ? this->tail : this->head;

    if(isReverse) {
        while(current) {
            cout << current->data;
            current = current->prevNode;
            if(current) {
                cout << " <- ";
            }
        }

    } else {
       while(current!=NULL) {
            cout << current->data;
            current = current->nextNode;
            if(current) {
               cout << " -> ";
            }
       }
    }

    cout << endl;

}


/*
 * testing
 */

int main() {
    cout << "[TEST] Node creation & disposal" << endl;
    Node* node = new Node(5);
    cout << node->data << endl;
    node->dispose();

    doublyLinkedList dll;

    cout << "[TEST] insertLast & insertFirst & print" << endl;
    for(int i = 0; i < 10; i++) {
        dll.insertLast(new Node(i));
    }
    dll.print();

    for(int i = 10; i <20; i++) {
        dll.insertFirst(new Node(i));
    }
    dll.print();
    dll.print(true);

    cout << "[TEST] node search" << endl;

    node = dll.getNodeAt(3);
    cout << node->data << endl;



    cout << "[TEST] length: " << dll.getLength() << endl;


    cout << "[TEST] insert before & after" << endl;
    dll.insertBefore(dll.getNodeAt(0), new Node(100));
    dll.print();
    dll.insertBefore(dll.getNodeAt(3), new Node(101));
    dll.print();

    dll.insertAfter(dll.getNodeAt(dll.getLength()-1), new Node(102));
    dll.print();
    dll.insertAfter(dll.getNodeAt(2), new Node(103));
    dll.print();

    cout << "[TEST] removing" << endl;

    dll.removeNode(node);
    dll.print();

    dll.removeNode(dll.getNodeAt(0));
    dll.print();

    dll.removeNode(dll.getNodeAt(dll.getLength()-1));
    dll.print();

    cout << "DLL disposal & end of program" << endl;
}

근데 내가 자료구조 공부를 하는 걸 재밌게 할 수 있을 지 몰랐다.

옛날에, 무작정 책 읽고 이해하면서 따라서 코드를 작성 할 때는 이게 도대체 뭐야~ 하면서 굉장히 지루해했었는데, 지금은 어느정도 지식의 기반이 잡힌 상태에서 그냥 내 맘대로 코드를 작성하니깐 흠흠 은근히 재미있다.

초심을 잃지 말고 앞으로도 틈틈히, 열심히 공부하고 복습도 자주 해야겠다.

근데,, 재밌긴 해도 아무래도 난 조만간 초심을 잃을 것 같다..
솔직히 난 개발을 좋아하는 편인데, 자료구조..를 난 개발로 간주하지 않는다
그냥 언젠가 볼 코딩면접 대비와 왠지 해야 할 것만 같은 압박감?

아무리 자료구조가 프로그래머의 기본인건.. 인정하는 바 이지만,
그냥 이해만 하고, 필요할때 갖다 쓸 수 있으면 됐지,
언제나 백지에서부터 시작해서 처음부터 구현 할 능력을 갖출 필요가 있나 싶다.

그리고 그렇게 할 수 있는 프로그래머라고 해서 개발을 잘하는가…

그래도, 자의로 자료구조를 한번 제대로 복습하는건 뭐 나쁘지 않으니까 노력은 해보자 🙂