728x90

6. Data Structures(데이터 구조)

 

1) malloc과 포인터 복습

[생각해보기]

Q. 포인터를 초기화시키지 않고 값을 저장하면 어떤 오류가 발생할 수 있을까요?

A. 초기화시키지 않고 값을 저장하면 할당된 메모리 공간이 없기 때문에 쓰레기값이 들어가 예상치 못한 버그가 발생합니다. 또한 다른 포인터와 메모리 공간을 공유할 경우(한 포인터에 메모리가 할당된 다른 포인터를 대입), 참조한 포인터와 메모리 공간을 공유하고 있기 때문에 값을 변경했을 때 참조한 포인터의 값도 함께 바뀌게 되므로 코드 전반에 문제를 일으키게 된다.

 

 

2) 배열의 크기 조정하기

 

일정한 크기의 배열이 주어졌을 때, 그 크기를 키우려면 어떻게 해야 할까요?

 

단순하게 현재 배열이 저장되어 있는 메모리 위치의 바로 옆에 일정 크기의 메모리를 더 덧붙이면 되겠지만, 실제로는 다른 데이터가 저장되어 있을 확률이 높습니다.

 

따라서 안전하게 새로운 공간에 큰 크기의 메모리를 다시 할당하고 기존 배열의 값들을 하나씩 옮겨줘야 합니다.

따라서 이런 작업은 O(n), 즉 배열의 크기 n만큼의 실행 시간이 소요될 것입니다.

이 과정을 아래 코드와 같이 나타낼 수 있습니다.

#include <stdio.h>
// malloc 사용하기 위해 불러옴
#include <stdlib.h>

int main(void)
{
    //int 자료형 3개로 이루어진 list 라는 포인터를 선언하고 메모리 할당
    int *list = malloc(3 * sizeof(int));

    // 포인터가 잘 선언되었는지 확인
    if (list == NULL)
    {
        return 1;
    }

    // list 배열의 각 인덱스에 값 저장
    list[0] = 1;
    list[1] = 2;
    list[2] = 3;

    //int 자료형 4개 크기의 tmp 라는 포인터를 선언하고 메모리 할당
    int *tmp = malloc(4 * sizeof(int));

    if (tmp == NULL)
    {
        return 1;
    }

    // list의 값을 tmp로 복사
    for (int i = 0; i < 3; i++)
    {
        tmp[i] = list[i];
    }

    // tmp배열의 네 번째 값도 저장
    tmp[3] = 4;

    // list의 메모리를 초기화
    free(list);

    // list가 tmp와 같은 곳을 가리키도록 지정
    list = tmp;

    // 새로운 배열 list의 값 확인
    for (int i = 0; i < 4; i++)
    {
        printf("%i\n", list[i]);
    }

    // list의 메모리 초기화
    free(list);
}

 

위와 동일한 작업을 realloc 이라는 함수를 이용해서 보다 간단하게 수행할 수도 있습니다.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *list = malloc(3 * sizeof(int));
    if (list == NULL)
    {
        return 1;
    }

    list[0] = 1;
    list[1] = 2;
    list[2] = 3;

    // tmp 포인터에 메모리를 할당하고 list의 값 복사
    int *tmp = realloc(list, 4 * sizeof(int));
    if (tmp == NULL)
    {
        return 1;
    }

    // list가 tmp와 같은 곳을 가리키도록 지정
    list = tmp;

    // 새로운 list의 네 번째 값 저장
    list[3] = 4;

    // list의 값 확인
    for (int i = 0; i < 4; i++)
    {
        printf("%i\n", list[i]);
    }

    //list 의 메모리 초기화
    free(list);
}

 

[생각해보기]

Q. 이미 할당된 메모리의 크기를 조절할 때 임시 메모리를 새로 할당해줘야 하는 이유는 무엇인가요?

A. 메모리 할당은 순서대로 차곡차곡 쌓이는 것이 아니기 때문에, 이미 할당된 메모리의 앞뒤 메모리가 다른 값들로 차있을 수 있다. 따라서 바로 옆의 메모리가 비어있다는 보장이 없다. 더불어 기존 메모리에 새로 메모리를 할당한다면 기존값이 덮어쓰기되어 사라진다. 따라서 임시 메모리를 새로 할당한 후에 기존 값을 그 곳에 복사하고, 그다음에야 기존 포인터에 기존 값이 모두 복사된 임시 메모리를 대입하면 된다.

 

 

3) 연결 리스트 : 도입

 

데이터 구조는 우리가 컴퓨터 메모리를 더 효율적으로 관리하기 위해 새로 정의하는 구조체입니다.

일종의 메모리 레이아웃, 또는 지도라고 생각할 수 있습니다.

이번 강의에서는 데이터 구조중 하나인 연결 리스트에 대해 알아보겠습니다.

 

배열에서는 각 인덱스의 값이 메모리상에서 연이어 저장되어 있습니다.

하지만 꼭 그럴 필요가 있을까요? 각 값이 메모리상의 여러 군데 나뉘어져 있다고 하더라도 바로 다음 값의 메모리 주소만 기억하고 있다면 여전히 값을 연이어서 읽어들일 수 있습니다.

이를 ‘연결 리스트’라고 합니다. 아래 그림과 같이 크기가 3인 연결 리스트는 각 인덱스의 메모리 주소에서 자신의 값과 함께 바로 다음 값의 주소(포인터)를 저장합니다.

 

 

연결 리스트의 가장 첫 번째 값인 1은 2의 메모리 주소를, 2는 3의 메모리 주소를 함께 저장하고 있습니다.

3은 다음 값이 없기 때문에 NULL (\0, 즉 0으로 채워진 값을 의미합니다)을 다음 값의 주소로 저장합니다. 

 

연결 리스트는 아래 코드와 같이 간단한 구조체로 정의할 수 있습니다.

typedef struct node
{
    int number;
    struct node *next; //위에서 struct node가 아니라 그냥 struct라고 하면 node가 정의되기 전이므로 사용불가
}
node; //struct node의 별칭으로 간단하게 node라고 해두는 것이 관례

node 라는 이름의 구조체는 number 와 *next  두 개의 필드가 함께 정의되어 있습니다.

number는 각 node가 가지는 값, *next 는 다음 node를 가리키는 포인터가 됩니다.

여기서 typedef struct 대신에 typedef struct node 라고 ‘node’를 함께 명시해 주는 것은, 구조체 안에서 node를 사용하기 위함입니다.

 

[생각해보기]

Q. 연결 리스트를 배열과 비교했을 때 장단점은 무엇이 있을까요?

A.

  • 장점 : 확장성이 좋다. 메모리를 동적으로 좁히거나 넓힐 수 있다. 배열의 경우처럼 메모리 공간의 변경이 발생할 때마다 임시 메모리를 할당하느라 메모리 공간이 일시적으로 낭비될 일이 없고, 매번 임시 메모리를 해제하는 불편한 작업을 수행할 필요도 없다.
  • 단점 : 배열같은 경우 인덱싱을 통해 쉽게 원하는 메모리에 접근할 수 있었지만 연결 리스트는 그것이 불가능하고, 데이터끼리 붙어있는 것이 아니므로 원하는 주소를 찾아가는 데에 드는 시간도 꽤든다. 따라서 상대적으로 자료 접근성이 떨어진다. 또한 하나의 데이터가 2가지 정보를 담고 있는 구조체이므로 메모리 공간을 더 많이 쓴다.

 

 

4) 연결 리스트 : 코딩

 

앞서 정의한 구조체를 활용해서 실제로 연결 리스트를 구현해보도록 하겠습니다.

아래 코드의 내용과 각 주석을 따라가 보세요.

#include <stdio.h>
#include <stdlib.h>

//연결 리스트의 기본 단위가 되는 node 구조체를 정의합니다.
typedef struct node
{
    //node 안에서 정수형 값이 저장되는 변수를 name으로 지정합니다.
    int number; 

    //다음 node의 주소를 가리키는 포인터를  *next로 지정합니다.
    struct node *next;
}
node;

int main(void)
{
    // list라는 이름의 node 포인터를 정의합니다. 연결 리스트의 가장 첫 번째 node를 가리킬 것입니다. 
    // 이 포인터는 현재 아무 것도 가리키고 있지 않기 때문에 NULL 로 초기화합니다.
    node *list = NULL;

    // 새로운 node를 위해 메모리를 할당하고 포인터 *n으로 가리킵니다.
    node *n = malloc(sizeof(node));
    if (n == NULL)
    {
        return 1;
    }

    // n의 number 필드에 1의 값을 저장합니다. “n->number”는 “(*n).numer”와 동일한 의미입니다. 
    // 즉, n이 가리키는 node의 number 필드를 의미하는 것입니다. 
    // 간단하게 화살표 표시 ‘->’로 쓸 수 있습니다. n의 number의 값을 1로 저장합니다.
    n->number = 1;

    // n 다음에 정의된 node가 없으므로 NULL로 초기화합니다.
    n->next = NULL;

    // 이제 첫번째 node를 정의했기 떄문에 list 포인터를 n 포인터로 바꿔 줍니다.
    list = n;

    // 이제 list에 다른 node를 더 연결하기 위해 n에 새로운 메모리를 다시 할당합니다.
    n = malloc(sizeof(node));
    if (n == NULL)
    {
        return 1;
    }

    // n의 number와 next의 값을 각각 저장합니다.
    n->number = 2;
    n->next = NULL;

    // list가 가리키는 것은 첫 번째 node입니다. 
    //이 node의 다음 node를 n 포인터로 지정합니다.
    list->next = n;

    // 다시 한 번 n 포인터에 새로운 메모리를 할당하고 number과 next의 값을 저장합니다.
    n = malloc(sizeof(node));
    if (n == NULL)
    {
        return 1;
    }

    n->number = 3;
    n->next = NULL;

    // 현재 list는 첫번째 node를 가리키고, 이는 두번째 node와 연결되어 있습니다. 
    // 따라서 세 번째 node를 더 연결하기 위해 첫 번째 node (list)의 
    // 다음 node(list->next)의 다음 node(list->next->next)를 n 포인터로 지정합니다.
    list->next->next = n;

    // 이제 list에 연결된 node를 처음부터 방문하면서 각 number 값을 출력합니다. 
    // 마지막 node의 next에는 NULL이 저장되어 있을 것이기 때문에 이 것이 for 루프의 종료 조건이 됩니다.
    for (node *tmp = list; tmp != NULL; tmp = tmp->next)
    {
        printf("%i\n", tmp->number);
    }

    // 메모리를 해제해주기 위해 list에 연결된 node들을 처음부터 방문하면서 free 해줍니다.
    while (list != NULL)
    {
        node *tmp = list->next;
        free(list);
        list = tmp;
    }
}

 

[생각해보기]

Q. 연결 리스트의 중간에 node를 추가하거나 삭제하는 코드는 어떻게 작성할 수 있을까요?

A.

추가:

1. 데이터로 1을 가진 node와 데이터로 3를 가진 node가 연결되어 있고 그 사이에 데이터 2를 가진 node를 추가한다고 하자.

2. 그럼 데이터 2를 가진 node의 next에 데이터 3 node를 가리키는 포인터를 대입한다.

3. 그다음 데이터 1을 가진 node의 next에 데이터 2를 가리키는 node를 가리키는 포인터를 대입한다.

 

삭제:

1. 위의 추가가 완료된 상태에서 다시 데이터 2 node를 삭제한다고 하자.

2. 임시 노드 포인터를 만들어 데이터 2를 가진 node를 대입한다.

3. 그럼 데이터 1 node의 next에 데이터 3 node를 가리키는 포인터를 대입힌다.

4. 삭제할 노드를 담고 있던 임시 노드 포인터를 free()함수를 이용해 해제한다.

 

#include <stdio.h>
#include <stdlib.h>

//연결 리스트의 기본 단위가 되는 node 구조체를 정의합니다.
typedef struct node
{
    //node 안에서 정수형 값이 저장되는 변수를 name으로 지정합니다.
    int number; 

    //다음 node의 주소를 가리키는 포인터를  *next로 지정합니다.
    struct node *next;
}
node;

int main(void)
{
    // list라는 이름의 node 포인터를 정의합니다. 연결 리스트의 가장 첫 번째 node를 가리킬 것입니다. 
    // 이 포인터는 현재 아무 것도 가리키고 있지 않기 때문에 NULL 로 초기화합니다.
    node *list = NULL;

    // 새로운 node를 위해 메모리를 할당하고 포인터 *n으로 가리킵니다.
    node *n = malloc(sizeof(node));
    if (n == NULL)
    {
        return 1;
    }

    // n의 number 필드에 1의 값을 저장합니다. “n->number”는 “(*n).numer”와 동일한 의미입니다. 
    // 즉, n이 가리키는 node의 number 필드를 의미하는 것입니다. 
    // 간단하게 화살표 표시 ‘->’로 쓸 수 있습니다. n의 number의 값을 1로 저장합니다.
    n->number = 1;

    // n 다음에 정의된 node가 없으므로 NULL로 초기화합니다.
    n->next = NULL;

    // 이제 첫번째 node를 정의했기 떄문에 list 포인터를 n 포인터로 바꿔 줍니다.
    list = n;

    // 이제 list에 다른 node를 더 연결하기 위해 n에 새로운 메모리를 다시 할당합니다.
    n = malloc(sizeof(node));
    if (n == NULL)
    {
        return 1;
    }

    // n의 number와 next의 값을 각각 저장합니다.
    n->number = 3;
    n->next = NULL;

    // list가 가리키는 것은 첫 번째 node입니다. 
    //이 node의 다음 node를 n 포인터로 지정합니다.
    list->next = n;

    // 다시 한 번 n 포인터에 새로운 메모리를 할당하고 number과 next의 값을 저장합니다.
    n = malloc(sizeof(node));
    if (n == NULL)
    {
        return 1;
    }
    
    // !!추가 과정!!
    n->number = 2;
    n->next = list->next;

    list->next = n;

    // 이제 list에 연결된 node를 처음부터 방문하면서 각 number 값을 출력합니다. 
    // 마지막 node의 next에는 NULL이 저장되어 있을 것이기 때문에 이 것이 for 루프의 종료 조건이 됩니다.
    for (node *tmp = list; tmp != NULL; tmp = tmp->next)
    {
        printf("%i\n", tmp->number);
    }
    
    // !!추가한 노드(number가 2인 노드)를 삭제!!
    
    node *temp = list->next; //임시 노드에 number가 2인 노드를 대입
    list->next = list->next->next; //next가 가리키는 노드를 2에서 3으로 바꾸기
    free(temp); //삭제할 노드를 담고 있던 임시 노드를 해제
    
    //제대로 해제 되었는지 출력해서 확인
    for (node *tmp = list; tmp != NULL; tmp = tmp->next)
    {
        printf("%i\n", tmp->number);
    }
    
    // 메모리를 해제해주기 위해 list에 연결된 node들을 처음부터 방문하면서 free 해줍니다.
    while (list != NULL)
    {
        node *tmp = list->next;
        free(list);
        list = tmp;
    }
}

 

위의 코드를 실행하면 제대로 추가 및 삭제된 결과가 나온다.

123이 추가 결과, 13이 삭제 결과

valgrind를 사용하여 누수되는 메모리가 있는지 확인해보면 아래와 같이 없다고 나온다.

소스코드명은 string으로 설정한 상태이다.

 

 

5) 연결 리스트 : 시연

 

[생각해보기]

Q. 배열이 정렬되어 있지 않은 경우의 검색 소요 시간을 연결 리스트의 검색 시간과 비교해보세요.

A. 배열이 정렬되지 않은 경우의 검색 시간은 O(n) 으로 연결 리스트  검색 시간과 같다.

 

 

6) 연결 리스트 : 트리

 

트리는 연결리스트를 기반으로 한 새로운 데이터 구조입니다.

연결리스트에서의 각 노드 (연결 리스트 내의 한 요소를 지칭)들의 연결이 1차원적으로 구성되어 있다면, 트리에서의 노드들의 연결은 2차원적으로 구성되어 있다고 볼 수 있습니다.

각 노드는 일정한 층에 속하고, 다음 층의 노드들을 가리키는 포인터를 가지게 됩니다. 

 

아래 그림은 트리의 한 예입니다. 나무가 거꾸로 뒤집혀 있는 형태를 생각하면 됩니다.

가장 높은 층에서 트리가 시작되는 노드를 ‘루트’라고 합니다. 루트 노드는 다음 층의 노드들을 가리키고 있고, 이를 ‘자식 노드’라고 합니다. 

 

 

위 그림에 묘사된 트리는 구체적으로 ‘이진 검색 트리’ 입니다.

각 노드가 구성되어 있는 구조를 살펴보면 일정한 규칙을 알 수 있습니다.

먼저 하나의 노드는 두 개의 자식 노드를 가집니다.

또 왼쪽 자식 노드는 자신의 값 보다 작고, 오른쪽 자식 노드는 자신의 값보다 큽니다.

따라서 이런 트리 구조는 이진 검색을 수행하는데 유리합니다.

 

아래 코드에서는 이진 검색 트리의 노드 구조체와 “50”을 재귀적으로 검색하는 이진 검색 함수를 구현하였습니다.

//이진 검색 트리의 노드 구조체
typedef struct node
{
    // 노드의 값
    int number;

    // 왼쪽 자식 노드
    struct node *left;
 
   // 오른쪽 자식 노드
    struct node *right;
} node;

// 이진 검색 함수 (*tree는 이진 검색 트리를 가리키는 포인터)
bool search(node *tree)
{
    // 트리가 비어있는 경우 ‘false’를 반환하고 함수 종료
    if (tree == NULL)
    {
        return false;
    }
    // 현재 노드의 값이 50보다 크면 왼쪽 노드 검색
    else if (50 < tree->number)
    {
        return search(tree->left);
    }
    // 현재 노드의 값이 50보다 작으면 오른쪽 노드 검색
    else if (50 > tree->number)
    {
        return search(tree->right);
    }
    // 위 모든 조건이 만족하지 않으면 노드의 값이 50이므로 ‘true’ 반환
    else {
        return true;
    }
}

이진 검색 트리를 활용하였을 때 검색 실행 시간과 노드 삽입 시간은 모두 O(log n) 입니다.

[생각해보기]
Q. 값을 검색할 때 이진 검색 트리가 기본 연결 리스트에 비해 가지는 장점과 단점은 무엇이 있을까요?

A. 장점:

  • 이진검색이 가능하다. 이진검색은 시간복잡도가 좋아 많은 알고리즘 문제에서 다뤄지고 있으니 '시간초과'가 나는 경우 떠올려 풀 수 있다.
  • 이진 검색 트리는 최대 2개의 자식 노드를 가지고 있고 왼쪽은 포인터의 값보다 작은 값, 오른쪽은 큰 값으로 '정렬'이 되어있기 때문에 이진 검색이 가능하다.
  • 따라서 실행시간과 삽입시간이 둘 다 O(log n)으로 감소한다.

단점:

  • 계속해서 한 노드에만 값을 추가할 경우 트리의 균형이 무너져 검색 시간이 늘어나고 더 많은 메모리를 소요한다.
  • 포인터를 하나 더 할당해야 하므로 메모리를 추가로 사용하는 단점이 있다.

 

 

7) 해시 테이블

 

해시 테이블은 ‘연결 리스트의 배열’입니다. 여러 값들을 몇 개의 바구니에 나눠 담는 상황을 생각해 봅시다.

각 값들은 ‘해시 함수’라는 맞춤형 함수를 통해서 어떤 바구니에 담기는 지가 결정 됩니다.

각 바구니에 담기는 값들은 그 바구니에서 새롭게 정의되는 연결 리스트로 이어집니다.

이와 같이 연결 리스트가 담긴 바구니가 여러개 있는 것이 ‘연결 리스트의 배열’, 즉 ‘해시 테이블’이 됩니다.

 

만약 해시 함수가 이상적이라면, 각 바구니에는 단 하나의 값들만 담기게 될 것입니다.

따라서 검색 시간은 O(1)이 됩니다.

하지만 그렇지 않은 경우, 최악의 상황에는 단 하나의 바구니에 모든 값들이 담겨서 O(n)이 될 수도 있습니다.

일반적으로는 최대한 많은 바구니를 만드는 해시 함수를 사용하기 때문에 거의 O(1)에 가깝다고 볼 수 있습니다.

 

[생각해보기]

Q. 해시 함수는 어떻게 만들 수 있을까요?

A. 

1.자릿수 선택(digit selection)

     : 키값 중, 일부 자릿수 골라내어 인덱스 생성

     : h(121234345656) ⇒ 113355 *key값 (예.주민번호) 중 홀수자릿수 선택

2.자릿수 접기(digit folding)

     : 키값 각각의 자릿수를 더해 인덱스 생성

     : h(123456) = 1+2+3+4+5+6 = 21

3.모듈로 연산(modulo function) - 많이 사용됨.

     : 키를 해쉬테이블의 크기로 나눈 나머지를 인덱스로 생성

     : h(157) ⇒ mod(157) = 7 *h(KEY) = KEY MOD TABLESIZE

https://makemethink.tistory.com/139?category=768133

 

[자료구조] 11-3. 해쉬

지금까지 배워온 탐색, 서치 알고리즘 중에 가장 안정적인 성능을 가진 것이 이진 탐색이었다. O(log n)이 나오는다는 의미를 생각해보면, 데이터의 개수가 10, 100, 1000, 10000...이렇게 증가해 나갈

makemethink.tistory.com

 

 

8) 트라이

 

‘트라이’는 기본적으로 ‘트리’ 형태의 자료 구조입니다.

특이한 점은 각 노드가 ‘배열’로 이루어져있다는 것입니다.

예를 들어 영어 알파벳으로 이루어진 문자열 값을 저장한다고 한다면 이 노드는 a부터 z까지의 값을 가지는 배열이 됩니다.

그리고 배열의 각 요소, 즉 알파벳은 다음 층의 노드(a-z 배열)를 가리킵니다. 

 

아래 그림과 같이 Hermione, Harry, Hagrid 세 문자열을 트라이에 저장해보겠습니다.

루트 노드를 시작으로 각 화살표가 가리키는 알파벳을 따라가면서 노드를 이어주면 됩니다.

 

 

위와 같은 트라이에서 값을 검색하는데 걸리는 시간은  ‘문자열의 길이’에 의해 한정됩니다.

단순히 문자열의 각 문자를 보며 트리를 탐색해나가기만 하면 되니까요.

일반적인 영어 이름의 길이를 n이라고 했을 때, 검색 시간은 O(n)이 되지만, 대부분의 이름은 그리 크지 않은 상수값(예, 20자 이내)이기 때문에 O(1)이나 마찬가지라고 볼 수 있습니다.

 

시간복잡도

 

20이든 30이든 상관 없습니다. 결국 고정된 값이죠. 사람의 이름은 시간이 지날수록 길어지진 않죠. 고정된 상한이 있습니다. 그래서 엄밀히 말하면 Harry나 Hagrid나 Hermionie를 찾기 위해 각각 5단계, 6단계, 8단계가 걸리기 때문에 그 시간은 O(1) 상수 시간이 됩니다. 그래서 이 자료 구조를 탐색할 때 이 자료 구조에 삽입할 때 k 값이 상수인 O(k)라는 것을 달성할 수 있습니다. 그러나 상수는 점근적으로 3주차에서 말씀 드렸듯이 O(1)과 같습니다. Harry를 찾기 위해 H a r r y 만 보기 때문에 실질적으로 상수 시간입니다.

 

[생각해보기]

Q. 트라이가 해시 테이블에 비해 가지는 장점과 단점은 무엇일까요?

A. 보다 빠르다는 장점이 있지만, 보다 많은 메모리를 사용한다는 단점이 있다.

 

 

9) 스택, 큐, 딕셔너리

 

우리가 여태껏 배운 배열, 연결 리스트, 해시 테이블, 트라이 외에도 많은 자료 구조들이 있습니다.

또는 위의 자료 구조를 기반으로 해서 문제를 해결하는데 적합한 새로운 자료 구조를 만들 수도 있습니다. 

아래와 같이 세 가지의 대표적인 자료 구조를 간단하게 알아보겠습니다.

 

 

큐는 메모리 구조에서 살펴봤듯이 값이 아래로 쌓이는 구조입니다.

값을 넣고 뺄 때 ‘선입 선출’ 또는 ‘FIFO’라는 방식을 따르게 됩니다. 가장 먼저 들어온 값이 가장 먼저 나가는 것이죠.

은행에서 줄을 설 때 가장 먼저 줄을 선 사람이 가장 먼저 업무를 처리하게 되는 것과 동일합니다.

배열이나 연결 리스트를 통해 구현 가능합니다.

 

 

스택

반면 스택은 역시 메모리 구조에서 살펴봤듯이 값이 위로 쌓이는 구조입니다.

따라서 값을 넣고 뺄 때 ‘후입 선출’ 또는 ‘LIFO’라는 방식을 따르게 됩니다. 가장 나중에 들어온 값이 가장 먼저 나가는 것이죠.

뷔페에서 접시를 쌓아 뒀을 때 사람들이 가장 위에 있는(즉, 가장 나중에 쌓인) 접시를 가장 먼저 들고 가는 것과 동일합니다.

역시 배열이나 연결 리스트를 통해 구현 가능합니다.

 

 

딕셔너리

딕셔너리는 ‘키’ ‘값’이라는 요소로 이루어져 있습니다.

‘키’에 해당하는 ‘값’을 저장하고 읽어오는 것이죠. 마치 대학교에서 ‘학번’에 따라서 ‘학생’이 결정되는 것과 동일합니다.

일반적인 의미에서 ‘해시 테이블’과 동일한 개념이라고도 볼 수 있습니다.

역시 ‘키’를 어떻게 정의할 것인지가 중요합니다.

 

 

728x90