본문 바로가기

공부를 합니다/컴퓨터 공학 (Computer Science)

CS50's Week 5_ Data Structures

Pointers


memory가 할당되지 않은 포인터 변수

 

int main(void)
{
    int *x;
    int *y;

    x = malloc(sizeof(int));

    *x = 42
    *y = 13 // buggy!
}

x에는 malloc을 이용하여 memory를 할당해 주었지만 y에는 해주지 않았기 때문에 y의 주소로 가 13을 대입할 수 없다.

Resizing arrays


  • Array로 사용되는 memory는 인접(side-by-side)해있기 때문에 arrays의 크기를 늘리고 싶으면 바로 옆의 memory를 array에 추가해야 한다.
  • 이 memory를 이미 다른 data가 사용하고 있을 수가 있으므로 다른 memory를 이용해 크기를 재정의 할 방법을 찾아봐야 한다.

공간에 여유가 있는 다른 영역으로 가 arrays크기만큼 재 할당한 뒤 기존의 array를 복사할 수 있다.

 

int main(void)
{
    // int형 포인터 list에 sizeof(int) * 3 만큼의 memory를 할당하고 값을 대입

    // 4칸짜리 int array를 만들기 위해 sizeof(int) * 4 만큼 새로운 공간에 memory를 할당
    // list에 직접 다시 할당시키면 기존에 list가 가리키던 memory가 orphaning(접근이 불가능한 상태) 된다
    int *tmp = malloc(4 * sizeof(int));

    // memory가 제대로 할당되었는지 확인
    if (tmp == NULL)
    {
        return 1;
    }

    // list array의 숫자를 tmp array로 복사
    for (int i = 0; i < 3; i++)
    {
        tmp[i] = list[i];
    }

    // 4번째 숫자 입력
    tmp[3] = 4;

    // 이전에 사용하던 list의 memory를 free 시킴
    free(list);

    // list포인터가 tmp를 가리키도록 함.
    list = tmp;

    // list array 출력 후 list를 free.
}

realloc


새롭게 memory를 할당하고 기존의 data를 복사해 오는 것을 realloc함수를 이용해 할 수 있다.

 

int main(void)
{
    // int형 포인터 list에 sizeof(int) * 3 만큼의 memory를 할당하고 값을 대입

    // malloc 대신에 realloc 사용
    int *tmp = realloc(4 * sizeof(int));

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

    list = tmp;

    tmp[3] = 4;

    // list array 출력 후 list를 free.
}

Data Structures


  • Data structure는 이전과 다르게 memory에 정보를 저장하는 다양한 방식(layout)을 말한다.
  • Data structure을 구성하는 목적은 data list 를 만들 때 memory를 보다 적게 사용해서 보다 짧은 시간 안에 data list를 수정하고 정리하기 위함이다.

Data structure를 만들기 위해서는 다음의 notation을 알아야 한다.

 

키워드 의미
struct custom data type을 만든다
. structure를 구성하고 있는 특정 property에 접근한다
* pointer가 가리키는 memory 주소로 접근한다
n->property (*n).property와 동일. n이 가리키는 주소에 있는 구조의 property로 접근한다

Linked Lists


  • Linked list는 각각 다른 위치의 memory에 흩어져 있는 값들을 연결해 나열된 값을 저장하는 구조로, 크기를 쉽게 확장할 수 있다.
  • Linked list를 만들기 위해서는 우리가 저장하려는 값과 다음 element를 가리키는 주소 모두를 저장하기에 충분한 memory가 있어야한다.

우선 이를 만족시키는 struct node를 정의

typedef struct node // 처음에 node를 정의해야 struct 안에서 pointer로 사용할 수 있다.
{
    int n; // 저장하려는 값
    struct node *next; // 다음 node struct를 가리키는 포인터
} node;



linked list를 만들기 위해서 출발점으로 empty node인 list를 정의한다.

node *list = NULL;

* 가장 마지막 data는 주소값으로 모두 0으로 이루어진 주소인(pointing nowhere) NULL값을 갖는다.



새 element를 생성

// 새 node를 위한 memory를 할당
node *n = malloc(sizeof(node));

if (n != NULL) // 할당이 정상적으로 이루어졌는지 확인
{
    n->number = 2; // 새 node n의 숫자 값을 지정
    n->next = NULL; // 새 node가 가리키는 node 주소를 NULL로 설정
}



현재 list의 끝에 새로 만든 element를 추가

// 임시 node tmp를 정의하고 list와 동일한 곳을 가리키게 함
node *tmp = list;

// tmp가 가리키는 곳이 없을 때 까지 현재 가리키는 node의 다음 node를 가리키게 함 (pointer를 따라가는 것)
while (tmp->next != NULL)
{
    tmp = tmp->next;
}

// tmp가 가리키는 node가 새로운 node n을 가리키도록 함
tmp->next = n;



만들어진 list 중간에 새로운 element를 추가

// 임시 node tmp를 정의하고 list와 동일한 곳을 가리키게 함
node *tmp = list;

// tmp가 가리키는 node의 숫자가 새로만든 node n의 숫자보다 커질 때 까지 tmp를 다음 노드로 이동.
while (n->number < tmp->number)
{
    tmp = tmp->next;
}

// node n이 tmp가 현재 가리키고 있는 것을 가리킨 뒤, tmp가 n을 가리키게 함.
// list가 n을 먼저 가리키게 되면 list 이후로의 node들에 접근이 불가하게 되므로 조심해야 함.
n->next = tmp->next;
tmp->next = n;

 

  • Arrays와는 다르게 linked list에서는 n번째의 값에 임의로 접근하는 것이 불가능하다. (arrays는 back-to-back으로 있기 때문에 가능)
  • 특정 값에 접근하기 위해서는 pointer를 처음부터 따라가야 한다.
  • 그러므로 수행 시간(running time)은 탐색(search)시 O(n) 자료 삽입(insert)시 O(n)이다.

More Data Structures


linked list 외에도 다양한 data structure가 존재.

(Binary Search) Tree


  • Tree는 하나의 node가 다른 두 node를 가리키고 있는 구조이다.
  • 왼쪽 node는 보다 작은 값을 오른쪽 node는 보다 큰 값을 갖는다.
  • 가장 중앙에서 출발지 역할을 하는 node를 root라고 한다.

Tree의 각 branch들이 동일한 구조를 갖고 있으므로 recursively searching을 할 수 있다.

 

// node pointer가 2개인 node를 정의
typedef struct node
{
    int number;
    struct node *left;
    struct node *right;    
}

bool search(node *tree)
{
    // base case: tree가 아무것도 가리키고 있지 않음
    if (tree == NULL)
    {
        return false;
    }

    // tree가 가리키는 숫자가 기준값보다 작은 경우 왼쪽으로 이동
    else if (기준값 < tree->number)
    {
        return search(tree->left);
    }

    // tree가 가리키는 숫자가 기준값보다 큰 경우 오른쪽으로 이동
    else if (기준값 > tree->number)
    {
        return search(tree->right);
    }

    // tree가 가리키는 숫자가 기준값과 같은 경우 true를 반환
    else
    {
        return true;
    }
}

 

  • linked list와 유사한 방식으로 다른 element의 이동 없이 nodes를 추가할 수 있다.
  • 수행 시간은 탐색시 O(log n) 자료 삽입시 O(log n)으로 memory를 더 사용해서 linked list보다 빠르게 처리할 수 있다.

Hash Table


  • Hash table 수행시간이 constant time에 가까운 data structure이다.
  • Hash table은 liked list와 array를 조합하여 만드는데 array는 대상을 분류하는 기준(hash function)이 되고 각 array에 들어있는 linked list는 기준을 만족시키는 element를 갖는다.
  • 예를 들어 이름을 정리할 때, 이름의 첫 알파벳을 기준으로 분류하면 array는 순서대로 알파벳 글자를 담고 있고 해당 알파벳으로 시작하는 이름들이 linked list를 이루고 있는 것이다.
  • array가 특정 기준에 따라 순차적으로 나열되어 있으므로 random access가 가능하며 해당 항목에 여러 element가 있을 때는 linked list로 연결하기 때문에 효율적이다.
  • 이상적인 hash function을 정해서 각 array 항목당 1개의 element를 갖게 되면 수행시간은 O(1)(constant time)이 될 수 있다.
  • 예를 들어 동일한 알파벳으로 시작하는 이름이 여러개 있을 경우에 카테고리를 첫 알파벳 두글자, 첫 알파벳 세글자로 점차 세분화 시키면 array 크기는 커지지만 중복이 없애 O(1)에 도달할 수도있다.
  • 하지만 결과적으로, 최악의 경우에는 모든 이름이 같은 알파벳으로 시작해 data structure가 single linked list와 같아지기 때문에 hash table의 수행시간은 O(n)이다.

Trie


  • Trie는 tree와 비슷하지만 모든 노드가 array인 data structure이다.
  • 위와 동일한 예시에서 모든 array에는 알파벳 26글자가 각각 저장되고 각 단어에 대해 단어를 이루는 문자가 순서대로 다음 배열을 가리키는 방식으로 모든 단어를 표현할 수 있다.
  • 특정 단어를 찾기 위한 수행 시간은 단어의 길이와 같으며 Big-O event는 가장 긴 길이의 단어를 찾는 시간으로 상수이다 O(1).
  • 하지만 이를 위해서는 각 문자에 필요한 memory의 26를 사용해야 한다는 단점이 있다.

참고


Hash Function

String Hashing

2020년 3월 7일에 작성하고 2020년 3월 9일에 마지막으로 수정함.

'공부를 합니다 > 컴퓨터 공학 (Computer Science)' 카테고리의 다른 글

CS50's Web Track-1  (0) 2020.05.10
CS50's Week 8_Information  (0) 2020.05.07
CS50's Week 7_SQL  (0) 2020.04.22
CS50's Week 6_ Python  (0) 2020.03.21
CS50's Week 2_Arrays  (0) 2020.03.06