지속해서 업데이트 됩니다. (11/13)
ctrl + d를 눌러 북마크를 추가할 수 있습니다. 
모든 질문은 커뮤니티 Q&A 게시판에서 받습니다.


C/C++

  

C#



Python



Java



Android



HTML/CSS



JavaScript



PHP



DataStructure



Algorithm



Database



Network





※  그래프란 비선형(non-linear) 자료구조이며 노드(node)와 엣지(edge)로 구성되어 있습니다.

- 노드는 꼭짓점(vertex)으로 표현됩니다

- 엣지는 두 노드를 연결하는 (line)으로 표현됩니다.


위 그래프를 V(vertex) = {0, 1, 2, 3, 4}와 E(edges) = {01, 12, 23, 34, 04, 14, 13}으로 표현할 수 있습니다.

그래프는 많은 일상 생활의 문제점을 해결하기 위해 사용됩니다. (네트워크의 표현 등) 


그래프의 표현

그래프를 인접 행렬(Adjacency Matrix) 또는 인접 리스트(Adjacency List)로 표현할 수 있습니다.


1. 인접 행렬 

인접행렬은 2차원 배열(v x v)로 표현될 수 있습니다. 엣지가 존재하는 노드의 짝(i, j)의 배열 값을 1로 설정하고, 엣지가 존재하지 않는 노드의 짝(i, j)의 배열 값을 0으로 설정합니다.가중치 그래프(Weighted Graph)에서는 배열 값을 w(가중치)로 설정 할 수 있습니다.


☞ 소스코드(C++)

#include <iostream>


using namespace std;


int main()

{

// 변수 선언

int vertex, **matrix;


// 정점(vetex) 개수 입력

cin >> vertex;


// 인접 행렬 동적할당 (v x v)

matrix = new int*[vertex]; 

for (int i = 0; i < vertex; i++) {

matrix[i] = new int[vertex];

}


// 인접 행렬 초기화

for (int i = 0; i < vertex; i++) {

for (int j = 0; j < vertex; j++) {

matrix[i][j] = 0;

}

}


// 간선(edge) 입력 (-1 입력 시 종료)

while (1) {

int edge1, edge2;

cin >> edge1 >> edge2;


// 종료 조건

if (edge1 == -1 || edge2 == -1)

break;


// matrix 범위 초과

else if (edge1 > vertex || edge2 > vertex)

continue;


// 이미 연결 된 간선인 경우

else if (matrix[edge1][edge2] == 1 && matrix[edge2][edge1] == 1)

continue;


// 인접 행렬에 입력

matrix[edge1][edge2] = 1;

matrix[edge2][edge1] = 1;

}


// 인접행렬 출력

for (int i = 0; i < vertex; i++) {

for (int j = 0; j < vertex; j++) {

cout << matrix[i][j] << " ";

}

cout << "\n";

}

// 메모리 해제

for (int i = 0; i < vertex; i++) {

delete[] matrix[i];

}

delete[] matrix;


// 프로그램 종료

return 0;

}


2. 인접 리스트

각 꼭짓점(vertex)의 리스트는 헤더(Header)노드를 가집니다. 각 헤더노드들은 배열을 이용하여 연결된 각각의 노드를 순차적으로 가리킵니다.(오름차순)


☞ 소스코드(C++)

#include <stdio.h>

#include <stdlib.h>


// 인접 리스트 구조체 선언

struct AdjListNode

{

    int dest;

    struct AdjListNode* next;

};

 // 인접 리스트 헤더 구조체 선언

struct AdjList

{

    struct AdjListNode *head; 

};

// 그래프 구조체

struct Graph

{

    int V;

    struct AdjList* array;

};

// 새로운 노드 추가

struct AdjListNode* newAdjListNode(int dest)

{

    struct AdjListNode* newNode =

     (struct AdjListNode*) malloc(sizeof(struct AdjListNode));

    newNode->dest = dest;

    newNode->next = NULL;

    return newNode;

}

// 그래프 생성

struct Graph* createGraph(int V)

{

    struct Graph* graph = 

        (struct Graph*) malloc(sizeof(struct Graph));

    graph->V = V;

 

    graph->array = 

      (struct AdjList*) malloc(V * sizeof(struct AdjList));


    int i;

    for (i = 0; i < V; ++i)

        graph->array[i].head = NULL;

 

    return graph;

}

// 엣지 추가

void addEdge(struct Graph* graph, int src, int dest)

{

    struct AdjListNode* newNode = newAdjListNode(dest);

    newNode->next = graph->array[src].head;

    graph->array[src].head = newNode;


    newNode = newAdjListNode(src);

    newNode->next = graph->array[dest].head;

    graph->array[dest].head = newNode;

}

// 그래프 출력

void printGraph(struct Graph* graph)

{

    int v;

    for (v = 0; v < graph->V; ++v)

    {

        struct AdjListNode* pCrawl = graph->array[v].head;

        printf("\n Adjacency list of vertex %d\n head ", v);

        while (pCrawl)

        {

            printf("-> %d", pCrawl->dest);

            pCrawl = pCrawl->next;

        }

        printf("\n");

    }

}

int main()

{

    int V = 5;

    struct Graph* graph = createGraph(V);

    addEdge(graph, 0, 1);

    addEdge(graph, 0, 4);

    addEdge(graph, 1, 2);

    addEdge(graph, 1, 3);

    addEdge(graph, 1, 4);

    addEdge(graph, 2, 3);

    addEdge(graph, 3, 4);

 

    printGraph(graph);

    return 0;

}





정보가 유익하셨다면 아래 공감버튼 눌러주시면 감사하겠습니다.

질문사항은 댓글로 달아주시면 성의껏 답변해드리겠습니다.


'자료구조, 알고리즘' 카테고리의 다른 글

[BOJ] 8979 올림픽 - 문제 풀이  (1) 2019.03.29
정렬 알고리즘 종류  (0) 2018.09.12
검색 알고리즘 종류  (2) 2018.09.11
자료구조 힙(Heap)  (0) 2018.09.05
자료구조 이진 탐색 트리(Binary Search Tree)  (0) 2018.09.01
자료구조 트리(Tree)  (0) 2018.08.30
[자료구조] 큐 QUEUE  (0) 2018.08.19
[자료구조] 스택 STACK  (0) 2018.08.19


이전 시간에 트리에 대해 공부했습니다. (참고 : 자료구조 트리)

이번 시간에는 이진 탐색 트리에 대해 공부해보겠습니다.


♪ 이진 탐색 트리(Binary Search Tree) : 노드기반(Node-Based) 이진 트리 자료구조입니다.


♪ 특징(Properties) 

좌측 하위 트리(Left Subtree)의 노드들은 상위 노드보다 작거나 같은 값입니다.

우측 하위 트리(Right Subtree)의 노드들은 상위 노드보다 큰 값입니다.

좌측 및 우측 하위트리 역시 이진 탐색 트리입니다. (하위트리의 하위트리들도 모두 위 특징에 해당합니다)


♪ 탐색(Search) 

☞ 탐색의 시작은 루트 노드(Root Node)에서 시작합니다. 만약 탐색하려는 값이 루트 노드의 값이라면 루트 노드의 값을 반환합니다.

☞ 만약 탐색하려는 값이 루트 노드의 값보다 작다면 좌측 하위 트리로 이동해서 탐색을 반복합니다.

☞ 만약 탐색하려는 값이 루트 노드의 값보다 크다면 우측 하위 트리로 이동해서 탐색을 반복합니다.


# 탐색 - 소스코드(C/C++)

struct node* search(struct node* root, int key) // 키 값 : 탐색하려는 값

{

if (root == NULL || root->key == key) // 루트 노드가 없거나 루트 노드의 값이 키 값인 경우

return root; // 루트 노드 반환

    

if (root->key < key) // 키 값이 루트 노드의 값보다 크다면

return search(root->right, key); // 우측 하위 트리 탐색

 

// 키 값이 더 작다면

return search(root->left, key); // 좌측 하위 트리 탐색

}



♪ 삽입(Insertion) : 새로운 노드는 항상 잎(Leaf) 노드에서 발생합니다.

☞ 루트 노드에서 탐색을 시작합니다. 만약 루트 노드가 없다면 노드를 삽입합니다.

☞ 잎(Leaf) 노드를 만날 때 까지 탐색을 시작합니다.

☞ 잎(Leaf) 노드를 만났다면 새로운 노드를 삽입합니다.(새 노드 값이 작다면 좌측, 크다면 우측)


# 삽입 - 소스코드(C/C++)

struct node *newNode(int item) // 새로운 노드 생성 함수

{

    struct node *temp =  (struct node *)malloc(sizeof(struct node)); // 새로운 노드를 동적할당

    temp->key = item; // 키 값을 설정

    temp->left = temp->right = NULL; // 좌측 및 우측 노드 null로 초기화

    return temp;

}

  

struct node* insert(struct node* node, int key) // 삽입 함수; node는 root노드, key는 삽입하려는 값

{

    if (node == NULL) return newNode(key); // 루트 노드가 없다면 바로 삽입

 

    if (key < node->key) // 새 노드가 루트노드 값보다 작다면

        node->left  = insert(node->left, key); // 좌측 서브트리로 이동해서 반복

    else if (key > node->key) // 새 노드가 루트노드 값보다 크다면

        node->right = insert(node->right, key); // 우측 서브트리로 이동해서 반복

 

    return node;

}



♪ 삭제(Delete) :  설명은 소스코드 주석으로 대체합니다.


# 삭제 - 소스코드(C/C++)

struct node * minValueNode(struct node* node) // 가장 작은 노드 반환 함수

{

    struct node* current = node; // 루트 노드 복사

 

    while (current->left != NULL) // 좌측 서브트리가 없을 때 까지 반복

        current = current->left;

 

    return current; 

}

 

struct node* deleteNode(struct node* root, int key) // 삭제 함수

{

    if (root == NULL) return root; // 루트 노드 null이라면 루트 노드 반환

 

    if (key < root->key) // 삭제하려는 노드의 값이 루트노드의 값보다 작다면

        root->left = deleteNode(root->left, key); // 좌측 서브트리로 이동해서 반복

 

    else if (key > root->key) // 크다면

        root->right = deleteNode(root->right, key); // 우측 서브트리로 이동해서 반복


    else // 삭제하려는 노드가 루트 노드

    {

        if (root->left == NULL) // 좌측 서브트리가 없는 경우

        {

            struct node *temp = root->right; // 우측 서브트리 복사

            free(root); // 루트 노드 삭제

            return temp; // 우측 서브트리 반환

        }

        else if (root->right == NULL) // 우측 서브트리가 없는 경우

        {

            struct node *temp = root->left; // 좌측 서브트리 복사

            free(root); // 루트 노드 삭제

            return temp; // 좌측 서브트리 반환

        }

 

        // 좌, 우측 서브트리 모두 있을 경우

        struct node* temp = minValueNode(root->right); // 우측 서브트리 중 가장 작은 값 복사

 

        root->key = temp->key; // 복사 한 값을 루트 노드에 복사

 

        root->right = deleteNode(root->right, temp->key); // 루트의 우측 서브트리 이동해서 반복

    }

    return root;

}





정보가 유익하셨다면 아래 공감버튼 눌러주시면 감사하겠습니다.

질문사항은 댓글로 달아주시면 성의껏 답변해드리겠습니다.


'자료구조, 알고리즘' 카테고리의 다른 글

정렬 알고리즘 종류  (0) 2018.09.12
검색 알고리즘 종류  (2) 2018.09.11
자료구조 그래프(Graph)  (1) 2018.09.06
자료구조 힙(Heap)  (0) 2018.09.05
자료구조 트리(Tree)  (0) 2018.08.30
[자료구조] 큐 QUEUE  (0) 2018.08.19
[자료구조] 스택 STACK  (0) 2018.08.19
[BOJ] 1912 연속합 - 동적계획법 풀이  (0) 2018.08.17


◎ 트리(Tree) : 배열(Array), 링크드 리스트(Linked List), 스택(Stack), 큐(Queue) 같이 선형 자료구조(Linear Data Structure)와 달리 트리는 계층적 자료구조(Hierarchical Data Structure) 입니다.


용어(Tree Vocabulary)

- 노드(Node) : 트리 구조의 각각의 요소(Element)

- 루트(Root) 노드 : 최상위(Topmost) 노드

- 자식(Child) 노드 : 해당 노드 바로 아래 노드

- 부모(Parent) 노드 : 해당 노드 바로 위 노드

- 잎(Leaf) 노드 : 자식 노드가 없는 노드



사용 목적

1. 컴퓨터 파일 시스템과 같이 계층을 형성하는 정보를 저장하기 위해 사용합니다.

2. 이진 탐색 트리와 같은 정렬되어있는 트리는 적당한(Moderate) 접근, 탐색을 제공합니다. (링크드 리스트보다 빠르지만 배열보다 느림)

3. 트리는 적당한(Moderate) 입력/삭제를 제공합니다. (배열보다 빠르지만 정렬되지 않은 링크드 리스트보다 느립니다.)

4. 링크드 리스트와 마찬가지로 배열과 달리 트리는 노드들이 포인터로 연결되어있어 노드수의 상한선(Upper limit)이 없습니다.


트리 응용(Applications)

1. 계층적 데이터를 다룹니다.(Manipulate)

2. 정보를 검색하기 쉽게 만듭니다. - 트리 순회(Tree Traversal) 참조

3. 정렬된(Sorted) 리스트 데이터를 다룹니다.

4. 라우터 알고리즘(Router Algorithms)


이진 트리(Binary Tree) : 자식 노드의 수가 2개 이하인 트리를 이진 트리라고 합니다. 이진 트리 각각의 노드들은 오직 2개의 자식만 가질 수 있으며, 이를 일반적으로(typically) 왼쪽 자식, 오른쪽 자식이라고 부릅니다.


- C에서 이진 트리 표현(Binary Tree Representation in C) : 트리에서 최상위 노드는 포인터로 표현됩니다. 트리가 비어있다면(empty) 루트의 값은 NULL입니다.

- C에서 트리 노드는 구조체로 표현합니다. 아래 예제는 integer형 자료를 저장하는 트리 노드의 예제입니다.


struct node  // 노드 구조체

{

int data; // 데이터 값

struct node *left; // 왼쪽 자식

struct node *right; // 오른쪽 자식

};


- C에서 4개의 노드를 생성하는 간단한 예제입니다.


struct node* newNode(int data) // 노드 생성

{

struct node* node = (struct node*) malloc(sizeof(struct node)); // node 구조체를 동적 생성


node -> data = data; // 입력 한 값을 저장 

node -> left = NULL; // 왼쪽 자식 : 없음

node -> right = NULL; // 오른쪽 자식 : 없음


return node; // 노드 반환

}


int main()

{

struct node* root = new Node(1); // 노드 생성(루트 노드, 값 = 1)


root -> left = newNode(2); // 노드 생성(왼쪽 자식, 값 = 2)

root -> right = newNode(3); // 노드 생성(오른쪽 자식, 값 = 3)

root -> left -> left = newNode(4); // 노드 생성(왼쪽 자식의 왼쪽 자식, 값 = 4)


return 0;

}


위의 소스코드를 그림으로 표현하면 다음과 같습니다.



정보가 유익하셨다면 아래 공감버튼 눌러주시면 감사하겠습니다.

질문사항은 댓글로 달아주시면 성의껏 답변해드리겠습니다.





1. 의미
- 큐(Queue)의 사전적 의미는 무엇을 기다리기 위해 서는 줄, 대기열입니다.
예를들어 우리가 게임을 할 때 '큐를 잡는다, 큐를 기다린다'라는 말의 큐는 바로 줄, 대기열을 의미합니다. 우리가 줄을 서면 가장 앞에있는 사람(가장 먼저 들어온 데이터)이 가장 먼저 들어가죠? 이러한 구조를 선입선출(First In First Out; FIFO)구조라고 합니다. 

2. 연산
- 일반적으로 큐 자료구조의 앞을 Front, 뒤를 Rear라고 칭합니다. 그림으로 표현하면 다음과 같습니다.


큐가 비어있는 상태를 front == rear(같음) 상태로 표현합니다.
(초기화시 front = rear = 0으로 초기화)



삽입(Enqueue)연산은 rear의 위치에 데이터를 삽입하고 rear의 위치를 1 증가 시켜줍니다.



삭제(Dequeue)연산은 front 위치의 데이터를 반환하고 front의 위치를 1 증가 시켜줍니다.
이러면 front와 rear가 같은 값이 되므로 다시 큐가 비어있는 상태가 됩니다.



큐의 끝(rear)가 배열의 마지막을 가리키고있는 상태가 큐가 가득 찬 상태입니다.
이렇게 큐를 배열로 구현했을 경우 0번 인덱스는 사용하지 않는 메모리가 되었습니다.
물론 고정적인 배열의 크기도 문제가 됩니다.
이를 해결하기 위해 원형 큐, 링크드 리스트로 구현한 큐... 등 여러가지 큐가 존재합니다.

3. 사용
프로세스 관리
운영체제(OS)의 작업 큐
프린터 출력의 문서 대기...등 
입력된 시간 순서대로 처리해야하는 모든 상황에 거의 큐가 사용됩니다.

4. 구현
배열로 구현한 큐)
#include <iostream> 
using namespace std;

class queue
{
private:
     int *queue_arr; // 큐 배열(동적 할당을 위해 포인터로 선언) 
     int queue_size; // 큐의 크기 
     int 
front, rear;

public:
     queue() // 디폴트 생성자 함수 
     {
          
front = 0; rear = 0// front와 rear 위치를 0으로 초기화
          queue_size = 5// 매개변수 없이 객체 생성 시 스택 크기 5로 지정 
          queue_arr = new int[queue_size]; // 배열 동적 할당 
          for (int i = 0; i < queue_size; i++)
          {
               queue_arr[i] = 0// 큐의 배열은 모두 0으로 초기화 
          }
     }
     queue(int 
size// 생성자 함수 
     {
          front = 0; rear = 0// front와 rear 위치를 0으로 초기화
          queue_size = 
size// 입력한 매개변수 크기만큼 스택 크기 지정 
          queue_arr = new int[queue_size]; // 배열 동적 할당 
          for (int i = 0; i < queue_size; i++)
          {
               queue_arr[i] = 0// 큐의 배열은 모두 0으로 초기화 
          }
     }
     ~queue() // 소멸자 함수 
     {
          delete[] queue_arr;
     }
     void enqueue(int data) // 삽입 함수 
     {
          if (queue_is_full()) // 큐가 가득 차 있는 경우 
          {
               cout << "큐가 가득 차 있습니다." << "\n";
               return// 함수 종료 
          }
          else // 아닐 경우
          {
               queue_arr[rear++= data;
               // 큐 배열 rear 위치의 데이터를 삽입 후 rear의 크기 1 증가
               // 위 코드가 이해가지 않는다면 후위 증감 연산자에 대해 공부할 것
               cout << "데이터 삽입 성공!" << "\n";
          }
     }
     void dequeue() // 삭제 함수 
     {
          if (queue_is_empty()) // 큐가 비어있는 경우 
          {
               cout << "큐가 비어있습니다." << "\n";
               return// 함수 종료 
          }
          else // 아닐 경우 
          {
               cout << "반환 된 데이터 : " << queue_arr[front<< "\n"// 데이터 반환 
               queue_arr[front++= 0
               // 큐 배열 front 위치의 데이터를 삭제 후 front 크기 1 증가
          }
     }
     bool queue_is_empty() // 큐가 비어있는지 확인 함수 
     {
          if (front == rear) // front와 rear가 같은 위치라면
               return true;
          else // 아닐 경우 비어있지 않음 
          return false;
     }
     bool queue_is_full() // 큐가 가득 차 있는지 확인 함수 
     {
          if (rear == queue_size - 1// rear가 큐 배열의 마지막 위치라면
               return true;
          else // 아닐 경우 가득 차 있지 않음 
               return false;
     }
     void print_queue() // 큐 배열 전체 출력 함수 
     {
          cout << "Queue : [ ";
          for (int i = 0; i < queue_size; i++)
          {
               if (i != queue_size - 1)
               {
                    cout << queue_arr[i] << " , ";
               }
               else
               {
                    cout << queue_arr[i];
               }
          }
     cout << " ] " << "\n";
     cout << "front 위치 : " << front << "\n";
     cout << "rear 위치 : " << rear << "\n";
     }
};

int main()
{
     queue *que;
     int input_size;
     cout << "큐 배열 크기 입력 : ";
     cin >> input_size;
     if (input_size <= 0)
     {
          cout << "잘못 된 입력입니다. 디폴트 사이즈(5)로 생성합니다." << "\n";
          que = new queue();
     }
     else
     {
          que = new queue(input_size);
     }
     while (1)
     {
          int input;
          cout << "\n" << "[명령어 입력]" << "\n";
          cout << "1. ENQUEUE " << "\n" << "2. DEQUEUE " << "\n" << "3. PRINT " << "\n" << "4. EXIT : ";
          cin >> input;
          switch (input)
          {
          case 1:
               int input_data;
               cout << "삽입 할 데이터를 입력하세요 : ";
               cin >> input_data;
               que->enqueue(input_data);
               break;
          case 2:
               que->dequeue();
               break;
          case 3:
               que->print_queue();
               break;
          case 4:
               cout << "프로그램을 종료합니다." << "\n";
               return 0;
          default:
               cout << "잘못 된 입력입니다. 다시 입력해 주세요" << "\n";
          }
     }     
}

질문 사항은 댓글로 남겨주세요!


to Top