이진 트리(Binary Tree)

 

이진 트리란?

  • 하나의 노드가 자식 노드2개 까지만 가질 수 있다.
  • 배열 또는 연결리스트로 표현이 가능하다.

이진 트리 종류

  • 포화 이진 트리
  • 완전 이진 트리
  • 균형 이진 트리
  • 전 이진 트리

포화 이진 트리

포화 이진 트리는 모든 내부 노드가 2개의 자식 노드를 가지고 노드의 Level또한 동일하다.

완전 이진 트리

완전 이진 트리는 마지막 Level을 제외하고 모든 Level이 채워져 있으며

마지막 Level의 노드는 왼쪽부터 채워져 있어야 한다.

균형 이진 트리

균형 이진 트리는 왼쪽과 오른쪽의 Level차이가 1만큼 난다.

전 이진 트리

전 이진 트리는 모든 노드의 자식이 0개 또는 2개의 자식 노드를 갖는다.

 

원형 큐(Circular Queue)

 

원형 큐란?

  • 선형 큐의 문제점을 보완하기 위한 자료구조이다.
  • Rear가 가르키는 마지막 인덱스가 DeQueue로 발생한 빈 공간을 활용 할 수가 없는걸 사용 할 수 있게 된다.

원형 큐의 일반적인 구조 그림

원형 큐의 주요 기능

  • EnQueue
  • DeQueue

원형 큐는 % Module연산을 통해서 순환 할 수 있는 구조를 만든다.

이때 중요한점이 위 그림처럼 4의 크기를 가지고는 있지만 실질적인 데이터는 3개뿐이 저장을 못하고

1개는 빈 공간인 구조를 가지게 된다. 이때 빈 공간은 Front의 인덱스가 된다.

 

원형 큐의 확인

DeQueue를 하다보면 Front와 Rear가 같아지게 되는데 이때 데이터가 비어있는걸 확인 할 수 있다.

또한 (Front + 1) % Capacity 를 해서 Front와 같으면 꽉차있는지 확인 할 수 있다.

bool MyQueue::IsEmpty()
{
    return Rear == Front ? true : false;
}
bool MyQueue::IsFull()
{
    return (Rear + 1) % Capacity == Front ? true : false;
}

 

원형 큐의 삽입

삽입은 Front의 공백자리 전까지 삽입이 가능하다.

차례대로 1,2,3을 EnQueue를 해주고 4를 EnQueue를 하게되면 꽉차있는 상태여서 삽입을 할 수 없게 된다.

int MyQueue::DeQueue()
{
    if (IsEmpty())
    {
        return -1;
    }
    Front = (Front + 1) % Capacity;
    int Value = Data[Front];
    return Value;
}

 

원형 큐의 삭제

삭제는 Front를 +1해준 자리의 인덱스 기준으로 삭제를 하게 된다.

void MyQueue::EnQueue(const int& NewData)
{
    if (!IsFull())
    {
        Rear = (Rear + 1) % Capacity;
        Data[Rear] = NewData;
    }
}

원형 큐의 메인 코드

int main()
{
    MyQueue* Queue = new MyQueue();

    Queue->CreateQueue(5);
    Queue->EnQueue(1);
    Queue->EnQueue(2);
    Queue->EnQueue(3);
    Queue->EnQueue(4);
    Queue->EnQueue(5);
    Queue->DeQueue();
    Queue->DeQueue();
    Queue->EnQueue(5);
    Queue->EnQueue(6);
    Queue->Show();
}

먼저 5개의 사이즈의 큐를 만들어 준다

EnQueue를 통해 1,2,3,4,5를 해주면 Front의 공백이 한개 있기때문에 4개까지만 사용이 가능해진다

이로서 1,2,3,4 만 큐에 담겨지고 DeQueue를 2번해서 앞에있는 데이터 2개를 1,2를 지우고

3,4인상태에서 5,6을 EnQueue를 해주며 출력을 하면

3,4,5,6이 출력됨을 확인 할 수 있다.

스택 응용 - 미로 찾기

 

스택을 활용하여 미로찾기

움직일 수 있는 공간 0 

장애물 숫자1

방문지점 숫자2

골인지점 숫자3

 

1. 출발지점 에서부터 다음 방향으로 이동 가능여부를 판단한다.

2. 다음 방향은 ↑ → ↓ ← 순으로 확인을 한다.

3. 움직일 수 있는 공간이면 현재 지점을 방문으로 지정하고 스택에 Push후 다음 지점으로 이동한다.

4. 이미 방문한 지점이고 이동할 수 없으면 스택에서 Pop을 한 후 이전지점의 으로 이동하고 방향을 확인한다.

5. 골인 지점에 도착하면 종료한다.

필요한 전역 함수

const int Width = 10;
const int Height = 10;
int Map[Height][Width] =
{
	0,1,1,1,1,1,1,1,1,1,
	0,0,0,0,0,0,0,0,1,1,
	1,1,1,0,1,1,1,1,1,1,
	1,1,1,0,1,1,1,1,0,1,
	1,1,1,0,1,1,1,1,0,1,
	1,0,0,0,0,0,0,0,0,1,
	1,0,1,1,1,1,1,1,1,1,
	0,0,1,1,1,0,0,0,0,1,
	0,1,1,1,1,0,1,1,0,1,
	0,0,0,0,0,0,1,1,0,3
};

int DirectionOffset[4][2] =
{
    {0, -1},
    {1, 0},
    {0, 1},
    {-1, 0}
};

스택 미로찾기 함수

MyStack* PathFinder()
{
    MyStack* Stack = new MyStack();

    Position Pos;
    Pos.X = 0;
    Pos.Y = 0;
    Pos.Dir = 0;

    int Dir = Pos.Dir;
    Stack->Push(Pos);

    while (1)
	{   
        Pos = Stack->Top();
        Dir = Pos.Dir;

		while (Dir < 4)
		{
			int NewX = Pos.X + DirectionOffset[Dir][0];
			int NewY = Pos.Y + DirectionOffset[Dir][1];

			if (NewX >= 0 && NewX < Width &&
				NewY >= 0 && NewY < Height &&
				Map[NewY][NewX] != 1 && Map[NewY][NewX] != 2)
			{
				Map[Pos.Y][Pos.X] = 2;

				Pos.X = NewX;
				Pos.Y = NewY;
				Pos.Dir = Dir + 1;
				Stack->Push(Pos);

				Dir = 0;

				if (Pos.X == 9 && Pos.Y == 9)
				{
                    			return Stack;
				}
			}
			else
			{
				Dir++;
			}
		}
		Stack->Pop();
	}

    return nullptr;
}

메인 함수

int main()
{
    MyStack* Stack = PathFinder();
    Stack->Show();
}

 

스택 응용 - 사칙연산 계산기

 

수식의 중위 표기법, 후위 표기법

스택으로 사칙연산 계산기를 구한하기전 수식에서 중위 표기법과 후위 표기법을 알아야 한다.

중위 표기법은 사람이 알기 쉽게 표현이 가능하고, 후위 표기법은 컴퓨터가 연산을 하기 쉽게 표현한 방법이다.

중위 표기법 후위 표기법
3 + 4 * 8 348*+

후위 표기 수식 -> 중위 표기수식 으로 변환하는 알고리즘

1. 피연산자를 만나면 스택에 Push

2. 연산자를 만나면 연산에 필요한 개수만큼 스택에서 Pop

3. 연산한 결과를 다시 스택에 Push

4. 더이상 없으면 연산 후 출력

======= 위 대한 그림 풀이=======
 
1. 5는 피연산자 이므로 스택에 Push
 - 스택 : 5
2. 6은 피연산자 이므로 스택에 Push
 - 스택 : 5, 6
3. 2는 피연산자 이므로 스택에 Push
 - 스택 : 5, 6, 2
4. *는 연산자 이므로 스택에서 2개를 Pop 후 (2, 6) * 연산
 - 스택 : 5
5.  결과값 ( 6 * 2 )을 다시 스택에 Push
 - 스택 : 5, (6 * 2)
6. 4는 피연산자 이므로 스택에 Push
 - 스택 : 5, (6 * 2), 4
7. -는 연산자 이므로 스택에서 2개를 Pop 후 ( (6 * 2) - 4) * 연산
 - 스택 : 5
8. 결과값 (6 * 2) - 4를 다시 스택에 Push
 - 스택 : 5, (6 * 2) - 4
9. +는 연산자 이므로 스택에서 2개를 Pop 후 ( 5 + (6 * 2) - 4)) * 연산
 - 더이상 검사할 데이터가 없으므로 결과값을 출력
5 + (6 * 2) - 4 = 13

==후위 수식을 중위 수식으로 바꾸고 그 값을 계산해서 반환해주는 함수==

double Calc(const string PostfixExpression)
{
    int Value = -1;

    MyStack* Stack = new MyStack();

    int PostfixLength = PostfixExpression.length();

    for (int i = 0; i < PostfixLength; ++i)
    {
        char Ch = PostfixExpression[i];

        int CheckCh = isdigit(Ch);

        if (CheckCh > 0 || Ch == '.')
        {
            Stack->Push(Ch);
        }
        else
        {
            if (!Stack->IsEmpty())
            {
                int Value1 = (int)Stack->Pop() - 48;
                int Value2 = (int)Stack->Pop() - 48;
                switch (Ch)
                {
                case '+':
                    Value = Value2 + Value1;
                    break;
                case '-':
                    Value = Value2 - Value1;
                    break;
                case '*':
                    Value = Value2 * Value1;
                    break;
                case '/':
                    Value = Value2 / Value1;
                    break;
                }

                Stack->Push(char(Value + 48));
            }
        }
    }

    return Value;
}

스택 응용 - 사칙연산 계산기

 

수식의 중위 표기법, 후위 표기법

스택으로 사칙연산 계산기를 구한하기전 수식에서 중위 표기법과 후위 표기법을 알아야 한다.

중위 표기법은 사람이 알기 쉽게 표현이 가능하고, 후위 표기법은 컴퓨터가 연산을 하기 쉽게 표현한 방법이다.

중위 표기법 후위 표기법
3 + 4 * 8 348*+

중위 표기 수식 -> 후위 표기수식 으로 변환하는 알고리즘

1. 피연산자를 만나면 바로 출력

2. 연산자를 만나게 되면 스택에 Push

3. 스택에 보관 중인 연산자 중에서 우선순위가 높은 연산자는 Pop을 하고 출력

4. 스택에 있는 연산자와 외부의 연산자는 우선순위가 다름

5. 닫는 괄호 연산자를 만나게 되면 스택에서 여는 괄호를 만날때 까지 스택에서 Pop을 하고 출력

 

이러한 규칙을 이용하여 중위 표기 수식을 후위 표기 수식으로 변환 할 수 있다.

1. 5는 피연산자 이므로 출력한다.

  - 출력 : 5

2. +는 연산자 이므로 스택에 넣어준다.

 - 출력 : 5

 - 스택 : +

3. 6은 피연산자 이므로 출력한다.

 - 출력 : 56

 - 스택 : +

4. *는 연산자 이므로 스택에 넣어준다. 이때 스택의 최상위 연산자는 +이다. *와 +연산자는 *연산자가 우선순위가 높기때문에 그대로 스택에 넣어준다.

 - 출력 : 56

 - 스택 : +*

5. 2는 피연산자 이므로 출력한다.

 - 출력 : 562

 - 스택 : +*

6. -는 연산자 이므로 스택에 넣어준다. 이때 스택의 최상위 연산자는 *이다. *와 -연산자는 *연산자가 우선위가 높기때문에 *의 연산자 즉 스택에 있는 *연산자는 출력해주고 -연산자는 스택에 넣어준다.

 - 출력 562*

 - 스택 : +-

7. 4는 피연산자 이므로 출력한다.

 - 출력 : 562*4

 - 스택 : +-

8. 더이상 식이 없기 때문에 스택에 존재하는 연산자를 차례대로 출력해준다.

 - 출력 : 562*4-+

 

==스택의 데이터에서 우선순위를 반환해주는 함수==

==스택의 데이터에서 우선순위를 반환해주는 함수==

int GetStackPriority(const char& StackTopData)
{
    switch (StackTopData)
    {
    case '(':
        return 0;
    case ')':
        return 4;
    case '*':
    case '/':
        return 2;
    case '+':
    case '-':
        return 1;
    }
}

==중위 수식의 데이터에서 우선순위를 반환해주는 함수==

int GetInFixPriority(const char& StackTopData)
{
    switch (StackTopData)
    {
    case '(':
        return 5;
    case ')':
        return 4;
    case '*':
    case '/':
        return 2;
    case '+':
    case '-':
        return 1;
    }
}

==중위 수식을 후위 수식으로 바꾸고 반환 해주는 함수==

string GetPostfix(const string& InfixExpression)
{
	int InfixLength = InfixExpression.length();
	
	string PostfixExpression;

    MyStack* Stack = new MyStack();

	for (int i = 0; i < InfixLength; ++i)
	{
		if (InfixExpression[i] == ' ')
		{
			continue;
		}
		char Ch = InfixExpression[i];

		int CheckCh = isdigit(Ch);

		if (CheckCh > 0 || Ch == '.')
		{
			PostfixExpression.append(1, Ch);
		}
		else
		{
            if (!Stack->IsEmpty())
            {
                if (Ch == ')')
                {
                    while (Stack->Top() != '(')
                    {
                        PostfixExpression.append(1, Stack->Pop());
                    }
                    Stack->Pop();
                    continue;
                }

                char StackPopCh = Stack->Top();
                int InFixPriority = GetInFixPriority(Ch);
                int StackPriority = GetStackPriority(StackPopCh);

                if (InFixPriority < StackPriority)
                {
                    PostfixExpression.append(1, Stack->Pop());
                }
            }
            Stack->Push(Ch);
		}
	}

    while (!Stack->IsEmpty())
    {
        PostfixExpression.append(1, Stack->Pop());
    }

	return PostfixExpression;
}

스택(Stack)

스택 이란?

  • 가장 마지막에 들어간 데이터가 제일 먼저 나온다 (Last In - First Out) LIFO
  • 가장 먼저들어간 데이터는 가장 나중에 나온다 (First In - Last Out) FILO
  • 변수를 선언 후 수명주기가 끝나면 자동으로 제거해주는 메모리도 스택으로 구현되어 있다.

스택의 주요 기능

헤드의 앞부분에 노드를 삽입 해준다.
존재하는 노드의 뒤쪽에 추가를 하는게 아닌 헤드의 앞쪽에 삽입 해준다.

위의 이미지는 싱글 링크드 리스트를 이용하여 스택을 구현한 그림이다.

처음 데이터를 헤드앞부분에 추가를 해주고 그다음 들어오는 데이터 또한 헤드 앞부분에 추가를 해준다.

그럼 처음 들어온 데이터는 자연히 뒤쪽으로 가게 되며 나중에 들어온 데이터는 헤드의 다음노드를 가르키는 포인터가

자연히 첫 데이터가 된다. 이를 이용하여 스택의 기능을 구현 할 수가 있다.

  • 삽입
  • 제거

링크드 리스트를 이용한 스택 클래스

#include <iostream>
 struct StackNode
{
    int Data;
    StackNode* Next;
};

class MyStack
{
public:
    MyStack();
private:
    StackNode* Head;

public:

    int Pop();
    void Push(const int& NewData);
    bool IsEmpty();
    void Clear();
    void Show();
};

저는 단순히 구조체를 이용했지만 노드를 클래스화 하고 프렌드를 사용해도 똑같을 것 이다.

링크드 리스트를 이용한 스택  삽입

위 그림처럼 헤드 앞부분에 삽입을 해주어야 한다.

그래야 스택처럼 먼저 들어온 데이터는 뒤쪽에 저장이되고 나중에 들어온 데이터를 헤드의 다음노드를 가르키는 부분부터 참조하여 먼저 빼낼 수 가 있다.

새로운 데이터를 삽입하여 적용 된 링크트 리스트의 그림

void MyStack::Push(const int& NewData)
{
    StackNode* NewNode = new StackNode();
    NewNode->Data = NewData;
    NewNode->Next = Head->Next;
    Head->Next = NewNode;
}

링크드 리스트를 이용한 스택  제거

스택에서 Pop은 제일 마지막에 들어온 데이터를 제거해준다.

위의 그림에서 제일 마지막에 들어온 데이터는 30을 가진 노드이다. 이 노드를 삭제 하기전에

헤드의 다음노드를 가르키는 포인터를 삭제되는 다음 노드를 가르키는 포인터쪽에 재설정을 해주어야 한다.

그럼 위의 그림처럼 30을 가진 데이터는 빠져나가고 순서대로 10의 데이터 20의데이터를 가진 리스트를 볼 수 있다.

int MyStack::Pop()
{
    if (!IsEmpty())
    {
        if (Head->Next != nullptr)
        {
            StackNode* RemoveNode = Head->Next;
            Head->Next = RemoveNode->Next;
            int Data = RemoveNode->Data;
            delete RemoveNode;
            return Data;
        }
    }
    return -1;
}

 

링크드 리스트를 이용한 스택  전체 제거

void MyStack::Clear()
{
    if (IsEmpty())
    {
        std::cout << "데이터가 없습니다" << std::endl;
        return;
    }

    StackNode* CurrentNode = Head;
    StackNode* RemoveNode = nullptr;

    while (CurrentNode->Next != nullptr)
    {
        RemoveNode = CurrentNode->Next;
        CurrentNode->Next = RemoveNode->Next;

        int Data = RemoveNode->Data;
        std::cout << Data << "삭제" << std::endl;

        delete RemoveNode;
    }
}

메인 코드

int main()
{
    MyStack* Stack = new MyStack();
    Stack->Push(1);
    Stack->Push(2);
    Stack->Push(3);
    Stack->Pop();
    Stack->Show();
    Stack->Clear();
}

Push함수를 통해 1,2,3을 집어넣으면 리스트엔 헤드부분 부터 3,2,1 순으로 저장되어있다.

그리고 Pop함수를 호출하면 3이 빠져나가고 2,1이 저장되거 있을 것 이다.

이를 Show함수를 통해 출력하면 2,1이 출력되고 Clear함수를 통해 리스트의 데이터를 지워버리면

헤드의 앞부분 부터 차례대로 지워질 것 이다.

 

 

 

+ Recent posts