c++/자료구조

20210730 자료구조 erase ~ 정렬, Array class

TIN9 2021. 8. 1.
반응형

안녕하세요 틴구입니다

오늘은 자료구조 링크드 리스트에 이어서 erase, 정렬에 대해서 알아보도록 할게요.

어제와 똑같은 파일에서 추가하는 형식으로 해보도록 하겠습니다.

어제와 오늘의 코드 굵기와 구분선을 넣도록 하겠습니다.(맨 아래쪽에 있음)

어제 / 오늘

erase / 정렬

List.h

#pragma once

#include <assert.h>

// friend : 아래처럼 Listnode에 List를 friend로 지정하면 List에서는 ListNode의
// private에 접근할 수 있다.

template <typename T>
class CListNode
{
    template <typename T>
    friend class CList;

    template <typename T>
    friend class CListIterator;

private:
    CListNode() :
        m_Next(nullptr),
        m_Prev(nullptr)
    {
    }

    ~CListNode()
    {
    }

private:
    T m_Data;
    CListNode<T>* m_Next;
    CListNode<T>* m_Prev;
};

/*
iterator : 노드를 순차적으로 방문하기 위해서  iterator를 만들어서 제공한다.
iterator를 이용해서 앞에서 부터 혹은 뒤에서부터 노드를 순차적으로 접근할 수 있게 해준다.
*/
template <typename T>
class CListIterator
{
    template <typename T>
    friend class CList;

public:
    CListIterator() :
        m_Node(nullptr)
    {
    }

    ~CListIterator()
    {
    }

private:
    CListNode<T>* m_Node;

public:
    // iterator끼리 서로 가지고 있는 노드가 같을 경우 같다고 판단한다.
    bool operator == (const CListIterator<T>& iter) const
    {
        return m_Node == iter.m_Node;
    }

    bool operator != (const CListIterator<T>& iter) const
    {
        return m_Node != iter.m_Node;
    }

    bool operator == (const CListNode<T>* Node) const
    {
        return m_Node == Node;
    }

    bool operator != (const CListNode<T>* Node) const
    {
        return m_Node != Node;
    }

    void operator ++ ()
    {
        m_Node = m_Node->m_Next;
    }

    void operator ++ (int)
    {
        m_Node = m_Node->m_Next;
    }

    void operator -- ()
    {
        m_Node = m_Node->m_Prev;
    }

    void operator -- (int)
    {
        m_Node = m_Node->m_Prev;
    }

    T& operator * () const
    {
        return m_Node->m_Data;
    }
};

// 숙제 : reverseiterator 클래스를 만들어오기.
// reverseiterator 는 역방향으로 진행하는 iterator이다.


template <typename T>
class CList
{
public:
    CList()
    {
        m_Size = 0;

        // Begin과 End노드를 생성하고 두 노드를 서로 연결한다.
        m_Begin = new NODE;
        m_End = new NODE;

        m_Begin->m_Next = m_End;
        m_End->m_Prev = m_Begin;
    }

    ~CList()
    {
        // Begin노드는 이전노드가 nullptr이다.
        // End노드는 다음노드가 nullptr이다.
        PNODE DeleteNode = m_Begin;

        while (DeleteNode)
        {
            PNODE Next = DeleteNode->m_Next;

            delete DeleteNode;

            DeleteNode = Next;
        }
    }

private:

    // typedef : 타입을 다른 이름으로 재정의 해주는 기능을 제공한다.
    typedef CListNode<T> NODE;
    typedef CListNode<T>* PNODE;

public:
    typedef CListIterator<T> iterator;

private:
    // Begin과 End는 데이터를 저장하기 위한 노드는 아니다.
    // 명시적으로 시작과 끝을 의미하는 노드로 사용하기 위해 할당해두고 사용한다.
    // 실제 데이터를 저장하는 노드는 Begin과 End노드 사이에 위치하게 될것이다.
    PNODE m_Begin;
    PNODE m_End;
    int m_Size;

public:
    void push_back(const T& data)
    {
        // 데이터를 저장해둘 노드를 생성한다.
        PNODE Node = new NODE;

        Node->m_Data = data;

        // End노드와 End노드의 이전노드 사이에 새로 생성된 노드를 추가해주도록 한다.
        PNODE Prev = m_End->m_Prev;

        // End노드의 이전노드의 다음노드를  End에서 새로 생성된 노드로 교체한다.
        Prev->m_Next = Node;

        // 새로 생성된 노드의 이전노드를 End의 이전노드로 지정한다.
        Node->m_Prev = Prev;

        // 새로 생성된 노드의 다음 노드를 End노드르 지정한다.
        Node->m_Next = m_End;

        // End노드의 이전노드를 새로 생성한 노드로 지정한다.
        m_End->m_Prev = Node;

        ++m_Size;
    }

    void push_front(const T& data)
    {
        PNODE Node = new NODE;

        Node->m_Data = data;

        // Begin노드와 Begin노드의 다음노드 사이에 새로 생성된 노드를 추가한다.
        PNODE Next = m_Begin->m_Next;

        // 새로 생성된 노드의 다음노드로 Begin노드의 다음노드를 지정한다.
        Node->m_Next = Next;

        // Begin노드의 다음노드의 이전노드를 새로 생성된 노드로 지정한다.
        Next->m_Prev = Node;

        // Begin노드의 다음노드로 새로 생성된 노드를 지정한다.
        m_Begin->m_Next = Node;

        // 새로 생성된 노드의 이전노드로 Begin노드를 지정한다.
        Node->m_Prev = m_Begin;

        ++m_Size;
    }

    // 마지막 노드를 제거한다.
    void pop_back()
    {
        if (empty())
        {
            assert(false);
        }

        // End노드의 이전 노드를 지워준다. 그러므로 End의 이전노드를 얻어오고
        // End의 이전 노드의 이전노드를 얻어와서 End와 연결해준다.
        PNODE DeleteNode = m_End->m_Prev;

        PNODE Prev = DeleteNode->m_Prev;

        // 지울노드의 이전노드의 다음노드를 End노드로 지정한다.
        Prev->m_Next = m_End;

        // End노드의 이전노드로 지울노드의 이전노드를 지정한다.
        m_End->m_Prev = Prev;

        // 노드를 제거한다.
        delete DeleteNode;

        --m_Size;
    }

    void pop_front()
    {
        if (empty())
        {
            assert(false);
        }

        // Begin노드의 다음 노드를 지워준다.
        PNODE DeleteNode = m_Begin->m_Next;

        // 지울 노드의 다음 노드를 얻어온다.
        PNODE Next = DeleteNode->m_Next;

        // Begin노드의 다음노드로 지울 노드의 다음노드를 지정한다.
        m_Begin->m_Next = Next;

        // 지울노드의 다음노드의 이전노드로 Begin노드를 지정한다.
        Next->m_Prev = m_Begin;

        // 노드를 제거한다.
        delete DeleteNode;

        --m_Size;
    }

    const T& front() const
    {
        if (empty())
            assert(false);

        return m_Begin->m_Next->m_Data;
    }

    const T& back() const
    {
        if (empty())
            assert(false);

        return m_End->m_Prev->m_Data;
    }


    // 클래스의 멤버함수는 함수의 뒤에 const를 붙일 수 있다.
    // 멤버함수의 뒤에 const가 붙으면 이 함수에서는 멤버변수의 값을 변경할 수 없게 된다.
    // const 객체가 만들어진 경우라면 뒤에 const가 붙은 멤버함수만 호출이 가능하다.
    int size() const
    {
        return m_Size;
    }

    bool empty() const
    {
        return m_Size == 0;
    }

    // 추가된 모든 노드를 제거한다.
    void clear()
    {
        if (empty())
            return;

        PNODE DeleteNode = m_Begin->m_Next;

        // End노드가 아니라면 while을 돌면서 제거해주도록 한다.
        while (DeleteNode != m_End)
        {
            // 지울 노드의 다음노드를 미리 받아둬야 한다.
            // 왜냐하면 노드를 먼저 지워버린다면 다음노드를 얻어올 수 없기 때문이다.
            PNODE Next = DeleteNode->m_Next;

            // 노드를 제거한다.
            delete DeleteNode;

            // 지울 노드를 다음 노드로 갱신한다.
            DeleteNode = Next;
        }

        // Begin노드와 End노드를 서로 연결해준다.
        m_Begin->m_Next = m_End;
        m_End->m_Prev = m_Begin;

        // 노드의 개수를 0으로 초기화해준다.
        m_Size = 0;
    }

    iterator begin() const
    {
        iterator iter;
        iter.m_Node = m_Begin->m_Next;
        return iter;
    }

    iterator end() const
    {
        iterator iter;
        iter.m_Node = m_End;
        return iter;
    }
       void operator = (const CList<T>& list)
    {
        // 기존 정보를 제거한다.
        clear();

        m_Size = list.m_Size;

        PNODE Node = list.m_Begin->m_Next;

        while (Node != list.m_End)
        {
            push_back(Node->m_Data);
            Node = Node->m_Next;
        }
    }

    // 삭제한 노드의 다음 노드를 가지고 있는 iterator를 반환해준다.
    iterator erase(const T& data)
    {
        iterator iter = Find(data);

        if (iter == end())
            return iter;

        return erase(iter);
    }

    iterator erase(const iterator& iter)
    {
        if (iter == end() || iter == m_Begin)
            return end();

        // 지울 노드의 이전노드와 다음노드를 얻어온다.
        // 지울 노드의 이전노드와 다음노드를 서로 연결시켜준다.
        PNODE Prev = iter.m_Node->m_Prev;
        PNODE Next = iter.m_Node->m_Next;

        Prev->m_Next = Next;
        Next->m_Prev = Prev;

        delete iter.m_Node;

        --m_Size;

        iterator result;
        result.m_Node = Next;

        return result;
    }

    iterator Find(const T& data)
    {
        iterator iter = begin();
        iterator iterEnd = end();

        for (; iter != iterEnd; ++iter)
        {
            if (*iter == data)
                return iter;
        }

        // 못찾을 경우 end를 리턴한다.
        return iterEnd;
    }

    // 정렬
    void sort(bool (*pFunc)(const T&, const T&))
    {

        // 두번째 부터 마지막까지 반복하기위해서 --iter1End;를 한다
        iterator iter1 = begin();
        iterator iter1End = end();
        --iter1End;

        iterator iter2;
        iterator iter2End = end();

        // 예를 들어 10개라면 0 ~ 8 까지만 반복한다.
        // 마지막 9번은 반복을 안한다.

        // 첫번째 꺼를 그 다음꺼랑 비교하기 때문에 ++iter2;를 한것이다.

        // 그리고 함수를 변수로 초기화시키고 사용을 하게 되면 더 빠르게 동작 시킬 수 있다.
        for (; iter1 != iter1End; ++iter1)
        {
            iter2 = iter1;
            ++iter2;
            for (; iter2 != iter2End; ++iter2)
            {
                if (pFunc(*iter1, *iter2))
                {
                    T data = *iter1;
                    *iter1 = *iter2;
                    *iter2 = data;
                }
            }
        }
    }
};


 


메인함수에서 이런식으로 코드를 짜볼게요

#include <iostream>
#include "List.h"
#include <time.h>

/*
자료구조 : 데이터를 관리하는 방법.
*/bool SortInt(const int& Left, const int& Right)
{
    return Left < Right;
}


// F5 로 실행을 하면 Debuging 모드로 실행이 된다.
int main()
{
    srand((unsigned int)time(0));
    rand();

    CList<int> listInt;

    for (int i = 0; i < 100; ++i)
    {
        listInt.push_back(i);
    }

    std::cout << listInt.back() << std::endl;
    std::cout << listInt.front() << std::endl;

    CList<int>::iterator iter;
    

    // 위에서 for문의 push_back(i)에 의해서 0 ~ 99 숫자가 생성이 되는데 erase(80)을 하게되면 중간에 들어가는

    // 80이 사라지게 되고 0 ~ 79  81 ~ 99가 출력이 되게 된다
    iter = listInt.erase(80);
    
    std::cout << "Delete Next Node Data : " << *iter << std::endl;

    listInt.clear();



    // 0이상1000미만의 랜덤한 수를 100번 출력
    for (int i = 0; i < 100; ++i)
    {
        listInt.push_back(rand() % 1000);
    }

    
    std::cout << "======= Before =======" << std::endl;
    for (iter = listInt.begin(); iter != listInt.end(); ++iter)
    {
        std::cout << *iter << std::endl;
    }

    listInt.sort(SortInt);

    std::cout << "======= After =======" << std::endl;
    for (iter = listInt.begin(); iter != listInt.end(); ++iter)
    {
        std::cout << *iter << std::endl;
    }
    return 0;
}

이렇게 짜면 befor에서는 1000미만 100개의 랜덤한 수가 뒤죽박죽으로 생성되는데

after에서는 1000미만 100개의 랜덤한 수가 작은숫자부터 큰 숫자로의 정렬이 이루어지게 됩니다.

 

Array class

Array class 같은 경우는 배열을 사용하기 편하게 만들기 위한 목적으로 사용하는 class입니다.

만약에 선언되어 있는 배열이 꽉찼다면 그 배열을 늘려주는 방법을 배웠습니다

그렇게 하기위해서는 일반적인 배열로는 사용이 불가능하고 반드시 동적 배열로 처리를 해야합니다.

Array.h

 

#pragma once

#include <assert.h>
#include <string.h>

template <typename T>
class CArray
{
public:
    CArray()
    {
        m_Size = 0;
        m_Capacity = 4;

        m_Array = new T[m_Capacity];
    }
    // 복사 생성자
    CArray(const CArray<T>& Array)
    {
        m_Size = Array.m_Size;
        m_Capacity = Array.m_Capacity;

        m_Array = new T[m_Capacity];

        memcpy(m_Array, Array.m_Array, sizeof(T) * m_Size);
    }

    ~CArray()
    {
        delete[] m_Array;
    }

private:
    T* m_Array;
    int m_Capacity; // 실제 배열이 생성된 전체 개수
    int m_Size; // 생성된 배열중 채워진 개수

public:

    // Array 클래스 같은경우는 push_front를 하면 안 된다.
    void push_back(const T& data)
    {
        // 이미 모든 데이터가 다 채워져 있다면 공간을 늘려준다.
        if (m_Size == m_Capacity)
        {

            // 공간을 2배만큼 늘려준다
            m_Capacity *= 2;

            T* Array = new T[m_Capacity];

            // memcpy : 메모리 복사 기능이다.
            // 2번인자에 들어간 메모리 주소로부터 복사를 시작한다.
            // 3번인자의 크기만큼을 1번인자의 메모리주소에서 그 크기만큼 채워준다.

            // sizeof(T) * m_Size가 의미하는 것은 복사할 메모리 크기를 말하는 것이다. 하나의 크기 * 전체 개수이다.
            memcpy(Array, m_Array, sizeof(T) * m_Size);

            // 기존 공간을 제거한다.
            delete[] m_Array;
            // 제거한 공간에 복사한 주소를 불러온다.
            m_Array = Array;
        }

        m_Array[m_Size] = data;
        ++m_Size;
    }

    // push_back()에서 정한 마지막 인덱스 m_Size를 제거하게 되는 것이다.
    void pop_back()
    {
        if (empty())
            assert(false);

        --m_Size;
    }

    T& front() const
    {
        if (empty())
            assert(false);

        return m_Array[0];
    }

    T& back() const
    {
        if (empty())
            assert(false);

        return m_Array[m_Size - 1];
    }

    int size() const
    {
        return m_Size;
    }

    int capacity() const
    {
        return m_Capacity;
    }

    bool empty() const
    {
        return m_Size == 0;
    }
    // 대입연산자
    void operator = (const CArray<T>& Array)
    {

        // 대입연산의 경우 기존것을 먼저 제거해야 한다
        delete[] m_Array;

        m_Size = Array.m_Size;
        m_Capacity = Array.m_Capacity;

        m_Array = new T[m_Capacity];

        memcpy(m_Array, Array.m_Array, sizeof(T) * m_Size);
    }
    

    // 일반 배열처럼 사용하기 위한 operator
    T& operator [](int Index) const
    {
        if (Index < 0 || Index >= m_Size)
            assert(false);

        return m_Array[Index];
    }

    bool erase(const T& data)
    {
        // Index를 -1로 설정한 이유는 for문에서 못찾았을경우 for문 밖 if문으로 들어가

        // false를 리턴시켜주기 위함이다.
        int Index = -1;

        for (int i = 0; i < m_Size; ++i)
        {
            if (m_Array[i] == data)
            {
                Index = i;
                break;
            }
        }

        if (Index == -1)
            return false;

        return eraseIndex(Index);
    }
    // 위에서 data를 찾았으면 그 데이타의 인덱스 값을 eraseIndext로 리턴을해서 아래의 함수로 넘어가 eraseIndex의      // Index가 적용이된다
    bool eraseIndex(int Index)
    {
        if (Index < 0 || Index >= m_Size)
            return false;

        for (int i = Index; i < m_Size - 1; ++i)
        {    

            // 만약에 Index 값이 80이라면 80대신 81번째 인덱스 값을 넣어준다
            m_Array[i] = m_Array[i + 1];
        }
        // 위에서 하나의 인덱스를 지웠기때문에 --m_Size를 해준다.
        --m_Size;

        return true;
    }
    // 정렬
    void sort(bool (*pFunc)(const T&, const T&))
    {
        for (int i = 0; i < m_Size - 1; ++i)
        {
            for (int j = i + 1; j < m_Size; ++j)
            {
                if (pFunc(m_Array[i], m_Array[j]))
                {
                    T Temp = m_Array[i];
                    m_Array[i] = m_Array[j];
                    m_Array[j] = Temp;
                }
            }
        }
    }
};



main.cpp

#include <iostream>
#include "Array.h"
#include <time.h>



// sort함수의 인자에 값을 넣어주기 위한 SortInt()함수
bool SortInt(const int& Left, const int& Right)
{
    return Left > Right;
}

int main()
{
    srand((unsigned int)time(0));
    rand();

    CArray<int> Arr1;

    for (int i = 0; i < 10; ++i)
    {
        Arr1.push_back(rand() % 100);
    }

    // 복사생성자

    // 아래처럼 바로 선언하자마자 대입시키면 복사생성자가 된다.
    CArray<int> Arr2 = Arr1;

    // 대입연산자

    // 아래처럼 선언을 하고 그 아래에서 대입을하면 operator의 대입연산자로 선언이 된다.
    CArray<int> Arr3;


    Arr3 = Arr1;

    // Arr1.의 사이즈를 받는다.
    int Size = Arr1.size();
    for (int i = 0; i < Size; ++i)
    {
        std::cout << Arr1[i] << std::endl;
    }

    std::cout << "Sort" << std::endl;

    // Arr1의 데이터에 정렬함수를 이용해 정렬해준다
    Arr1.sort(SortInt);

    Size = Arr1.size();
    for (int i = 0; i < Size; ++i)
    {
        std::cout << Arr1[i] << std::endl;
    }

    return 0;
}


여기까지 erase, sort, Array class까지 배워보았습니다!!

다음에 또 만나요~~

반응형

'c++ > 자료구조' 카테고리의 다른 글

20210805 자가균형 이진트리 AVLTree  (0) 2021.08.06
20210804 이진 트리 순회, erase  (0) 2021.08.05
20210803 이진 트리 (재귀 함수 활용)  (0) 2021.08.04
20210802 Stack, Queue, CircleQueue  (0) 2021.08.03
20210729 자료구조  (0) 2021.07.30

댓글