Skip to content

Latest commit

 

History

History
343 lines (240 loc) · 17.9 KB

Wiki.md

File metadata and controls

343 lines (240 loc) · 17.9 KB

--------------------------------------------------------- struct ---------------------------------------------------------



  • struct record
    • value : record 의 값을 저장한다. 이 코드에서는 key 와 같은 값을 가진다.


  • struct node
    • pointers : internal node라면 (num_keys+1)개의 자식노드를 가리키는 배열이다. leaf 라면 num_keys개의 record 와 다음 leaf를 가리키는 배열이다.
    • keys : 각 노드들에 들어있는 keys값을 저장하는 배열이다. (order - 1)개까지 가질 수 있다.
    • parent : 해당 노드들(internal node/leaf)의 부모노드를 가리키는 포인터이다. root 라면 NULL 을 가리킨다.
    • is_leaf : leaf 이면 true 를, 아니라면 false 를 가지는 bool 변수이다.
    • num_keys : 현재 가지고 있는 키값들의 개수이다. keys와 마찬가지로 (order - 1)까지 가능하다.
    • next : 각 노드들의 다음노드를 카리키는 포인터이다. print_tree 를 위한 queue 에서만 사용된다.


--------------------------------------------------------- function ---------------------------------------------------------



  • usage_1/2/3 :
    맨 처음 및 ? 를 입력했을 때 사용법을 출력해준다.


  • enqueue : [print_tree 에 이용됨]
    print_tree 를 위한 함수로 node* 를 인자로 받아서 queue에 쌓는다.


  • dequeue : [print_tree 에 이용됨]
    print_tree 를 위한 함수로 queue 의 가장 처음부분의 노드를 포인터형식으로 반환한다.


  • print_leaves :
    <0번째 leaf> (0번째 record 주소) 0번째 key 값 (1번째 record 주소) 1번째 key 값 ... (1번째 leaf 주소) |
    <1번째 leaf> (0번째 record 주소) 0번째 key 값 (1번째 record 주소) 1번째 key 값 ... (2번째 leaf 주소) | ... ... 0
    위와 같이 가장 왼쪽의 leaf 부터 가장 오른쪽의 leaf 까지 verbose_output 이 true 라면 괄호 안의 값과 함께, false 라면 key 값만 출력해준다.
    출력해주는 값들은 각 노드들의 num_key 만큼의 record 주소와 그에 해당하는 key값, 마지막으로 pointers 의 가장 마지막에 들어있는 다음 leaf의 주소이다. 마지막이 0 인 이유는 가장 오른쪽의 leaf 의 마지막 pointers 가 가리키는 것이 NULL 이기 때문이다.


  • height :
    (root 의 높이 = 0) 을 기준으로 leaf 의 높이를 반환한다.


  • path_to_root :
    node* 인 child 를 인자로 받아, 해당 child 부터 root 까지의 길이(= 높이)를 반환한다.


  • print_tree : [enqueue, dequeue, path_to_root 를 이용함]
    height = 0 인 root 부터 leaf 의 height 까지 각 높이의 node 들을 왼쪽부터 오른쪽의 순서로 enqueue 및 dequeue 해주며 각 노드들이 가지고 있는 값들을 출력해준다.
    ( (root 의 주소) ) (height = 1 의 0번째 노드주소-->A) 1번째 key 값 (height = 1 의 1번째 노드주소-->B) 2번째 key 값 ... (height = 1 의 root->num_keys번째 노드주소) |
    ( (A 의 주소) ) (height = 2 의 0번째 노드주소) 1번째 key 값 ... (height = 2 의 A->num_keys번째 노드주소) | ( (B 의 주소) ) ... ...|
    ... ...
    ( (0 번째 leaf 주소) ) (해당 leaf 의 0번째 record 주소) 0번째 key 값 ... (1 번째 leaf 주소) | ( (1 번째 leaf 주소) ) (해당 leaf 의 0번째 record 주소) 번째 key 값 ... ... 0
    출력은 위와 같이 된다. 가장 마지막에 NULL 값인 0 이 오는 이유는 print_leaves 와 같으며, leaf 를 제외한 노드들의 key 값이 0번째가 아닌 1번째 부터 시작하는 이유는, 같은 key 값을 가지는 leaf 의 index 가 오른쪽에 오기 때문이다.
    만약 tree 가 비어있다면 "Empty tree" 를 출력한다.


  • find_and_print : [find 를 이용함]
    key 값을 인자로 받아 find 함수를 이용하여 해당 key 값을 가진 record 를 찾고 없다면 찾을 수 없다는 메세지를 출력하고, 찾았다면 그 record 의 주소와 key 값, value 를 출력한다.


  • find_and_print_range : [find_range 를 이용함]
    해당 범위 key 개수 크기의 배열(예를들어 1 ~ 5 라면 5개, 4 ~ 6 이라면 3개)을 만든 후, find_range 함수를 이용하여 그 범위에 해당하는 record 의 주소와 개수를 얻어, 그 개수만큼 key값과 주소, value 를 출력한다. 만약 해당범위의 key 값을 찾지 못했다면 "None found"를 출력한다.


  • find_range : [find_and_print_range 에 이용됨 / find_leaf 를 이용함]
    해당 범위의 key 값을 갖는 leaf 를 find_leaf 를 통해 구한 뒤, returned_keys 와 returned_pointers 에 각각 key 값과 record 의 주소를 넣은 뒤 개수를 반환한다.


  • find_leaf : [find_range, insert 에 이용됨]
    인자로 받는 key 값이 있을 수 있는 leaf 를 찾아가는 함수로, root 부터 해당 leaf 를 찾아가며 verbose_output 가 true 일 경우
    [key1 key2 ...] n ->
    ...
    Leaf [key1 key2 ...] ->
    와 같이 경로를 출력한다. 만약 인자로 받은 root 가 NULL 일 경우 "Empty tree" 를 출력한다. 마지막에 해당 leaf 를 반환한다.


  • find : [find_and_print, insert 에 이용됨 / find_leaf 를 이용함]
    find_leaf 를 이용하여 인자로 받은 key 와 일치하는 key 값을 가지는 record 를 찾아 반환한다. 만약 없다면 NULL 을 반환한다.


  • cut : [insert_into_leaf_after_splitting, insert_into_node_after_splitting 에 이용됨]
    split 을 위한 함수로 인자로 받은 length 가 짝수이면 절반을, 홀수이면 절반의 올림을 반환한다.


--------------------------------------------------------- Insertion ---------------------------------------------------------



* make_record : 인자로 받는 value 를 가지는 record 를 만들어 반환한다. 만들 수 없다면 에러메시지와 함께 프로그램을 종료한다.

  • make_node : [make_leaf, insert_into_node_after_splitting 에 이용됨]
    leaf 를 포함한 모든 노드를 생성하는 함수이다. 새 노드를 생성 후, (order - 1) 크기의 keys, order 크기의 pointers 를 할당하며, 이 중 하나라도 안될시 에러메세지 출력과 함께 프로그램을 종료한다. 또, is_leaf = false, num_keys=0, parent = NULL, next = NULL 로 설정하고 해당 노드를 반환한다.


  • make_leaf : [insert_into_leaf_after_splitting, start_new_tree 에 이용됨 / make_node 를 이용함]
    make_node 를 통해 생성한 노드의 is_leaf 를 true 로 바꾼 후 반환한다.


  • get_left_index :
    인자로 받는 left 를 가리키는 부모노드의 pointers 배열의 인덱스를 반환한다.


  • insert_into_leaf :
    인자로 받은 key 값이 들어갈 수 있는 자리를 leaf 에서 찾은 후, record 가 들어갈 수 있도록 해당 자리부터 key 값들과 pointers 들을 한 칸씩 뒤로 물린다. 그 후, 그 leaf 를 반환한다.
    단, 이 함수 내에서 해당 자리에 key 값과 pointer 를 넣지는 않는다.


  • insert_into_leaf_after_splitting : [make_leaf, cut, insert_into_parent 를 이용함]
    order 개의 int 와 void* 를 받을 수 있는 temp_keys, temp_pointers 배열을 만든다. 인자로 받은 key 값을 temp_keys 와 temp_pointers의 올바른 자리에 들어갈 수 있도록 해당 자리부터 (num_keys - 1)번째의 leaf->keys 와 leaf->pointers 를 한칸씩 물린 뒤, 해당자리에 key 와 pointer 와 함께 넣어준다.
    기존 leaf 의 오른쪽에 들어갈 새로운 new_leaf 를 만든 뒤, temp_keys 와 temp_pointers 의 약 절반씩을 각각 leaf 와 new_leaf 에 넣어준 뒤, leaf 의 마지막 pointers 는 new_leaf 를, new_leaf 의 마지막 pointers 는 기존 leaf 의 다음 leaf 를 가리키게 한 후, new_leaf 의 index 를 부모 노드에 넣을 수 있도록 insert_into_parent 함수를 호출한다.


  • insert_into_node :
    인자로 받은 key 와 right 가 올바른 자리에 들어갈 수 있도록, 해당자리부터 num_keys 번째 자리까지 한 칸씩 뒤로 물린 후, pointers 와 keys 의 해당자리에 key 와 right 를 넣어준다. 값이 하나 더 들어갔으므로 num_keys 를 1 증가시킨 후, root 를 반환한다.


  • insert_into_node_after_splitting : [make_node, cut, insert_into_parent 를 이용함]
    leaf 가 아닌 internal node 에 insert 한 다는 점이 insert_into_leaf_after_splitting 과 다르지만 동작 원리는 비슷하다. 단, internal node 의 마지막 pointers 는 다음 노드를 가리킬 필요가 없으므로, 해당 연산은 수행하지 않는다. split 의 결과가 parent 에도 반영될 수 있도록 insert_int_parent 를 호출한다.


  • insert_into_parent : [insert_into_leaf/node_after_splitting 에 이용됨 / insert_into_new_root, get_left_index, insert_into_node(_after_splitting) 을 이용함]
    해당 노드의 부모가 없는 상태, 즉 해당 노드가 root 라면 insert_into_new_root 를 호출한다.
    부모노드의 keys 와 pointers 가 꽉 차지 않은 상태라면 insert_into_node 를 호출한다.
    부모노드의 keys 와 pointers 가 꽉 차서 split 이 필요한 경우 insert_into_node_after_splitting 을 호출한다.
    결국 마지막에는 root 를 반환한다.


  • insert_into_new_root : [make_node 를 이용함]
    새로운 루트를 만드는 함수로, make_node 를 통해 새 노드를 생성한 후, 인자로 받은 key 를 1번째 keys 에 넣고, 인자로 받은 left 와 right 를 각각 0 번째, 1 번째 pointers 에 넣는다.
    그리고 left 와 right 의 parent 를 새로 생성한 root 로 할당하고, 이 root 의 num_keys 를 1 로 바꿔주고 반환한다.


  • start_new_tree : [make_leaf 를 이용함]
    맨 처음 tree 에 insert 를 할 때 호출되는 함수로 인자로 받은 key 와 record 의 주소를 각각 keys 와 pointers 에 넣은 leaf 형태의 노드로 root 를 만들어 반환한다.


  • insert : [find(_leaf), make_record, start_new_tree, insert_into_leaf(_after_splitting) 을 이용함]
    Insertion 과 관련된 최상위 함수이다.
    인자로 받은 key 와 일치하는 key 가 이미 존재한다, insert 하지 않고 root 를 반환한다.
    root 가 존재하지 않는, 즉 맨 처음 tree 에 insert 를 하는 상황이라면 start_new_tree 를 호출한다.
    find_leaf 를 통해 insert 해야하는 leaf 를 찾은 후, split 없이 들어갈 수 있다면 insert_into_leaf 를 호출한다.
    만약 split 이 필요하다면 insert_into_leaf_after_splitting 을 호출한다.
    결국 마지막에는 root 를 반환한다.


--------------------------------------------------------- Deletion ---------------------------------------------------------



* get_neighbor_index : 인자로 받은 노드가 부모노드의 pointers 의 n 번째에 있다면 (n-1) 을 반환한다.

  • remove_entry_from_node :
    인자로 받은 key 값과 일치하는 위치부터 오른쪽의 값을 한칸씩 땡겨오고, num_keys 를 1 줄인다.
    만약 delete 한 노드가 leaf 일 경우, 다음 leaf 를 가리키는 pointers 와 record 들을 가리키는 pointers 를 제외한 나머지에 NULL 을 할당한다.
    leaf 가 아닌 노드들이었다면 자식노드를 할당하고 있지 않은 pointers 들에 NULL 을 할당한다.
    그리고 delete 를 수행한 노드를 반환한다.


  • adjust_root :
    인자로 받은 root 가 비어있지 않다면 그냥 해당 root 를 반환한다.
    만약 비어있지 않고 leaf 가 아니라면 0번째 pointers 를 새 root 로 만든다.
    비어있지 않고 leaf 라면 그냥 새로운 root 를 만든다.
    마지막으로 root 를 반환한다.


  • coalesce_nodes : [delete_entry 를 사용함]
    기본적으로 delete 후 크기가 작아진 노드 n 과 그 왼쪽의 노드(= neighbor)를 합치는 함수이나, n 이 가장 왼쪽의 노드라면 그 오른쪽의 노드와 합친다.
    A. n 과 neighbor 가 leaf 가 아니라면 neighbor->keys 의 비어있는 뒷 칸에, parent 의 pointers 가 가리키는 n 과 neighbor 사이의 key 값과 n->keys 값 들을, neighbor->pointers 의 뒷 칸에 n->pointers 들을 넣는다. 그리고 모든 neighbor 의 자식 노드들의 부모 노드가 neighbor 가 되도록 만든다.
    B. n 과 neighbor 가 leaf 라면 n 의 keys 와 pointers 들을 모두 neighbor 에 옮긴 뒤, n 의 다음 노드를 neighbor 가 가리키도록 한다.
    A 와 B 의 경우 모두, 부모 노드가 n 과 neighbor 의 합병을 반영하도록 delete_entry 를 호출한다. 그리고 root 를 반환한다.


  • redistribute_nodes :
    노드 n 에 가장 가까운 neighbor 의 keys 값과 pointers 값을 하나씩 n 에 넣고 그 결과를 부모 노드에 반영하는 함수이다.
    즉, neighbor 가 n 의 왼쪽에 있다면 n 의 keys 와 pointers 값들을 한 칸씩 오른쪽으로 옮긴 후, neighbor 의 값들을 각각 n 의 0 번째들에 넣는다.
    오른쪽에 있다면 neighbor 의 0 번째 값들을 n 의 num_keys 번째에 넣은 후, neighbor 의 값들을 왼쪽으로 한 칸씩 옮긴다.
    그 후, n 과 neighbor 의 num_keys 를 각각 1씩 증가, 감소 시킨 후 그 결과를 부모 노드의 keys 에 반영한다.
    마지막으로 root 를 반환한다.


  • delete_entry : [remove_entry_from_node, adjust_root, cut, get_neighbor_index, coalesce_nodes, redistribute_nodes 를 이용함]
    가장 먼저 인자로 받은 key 와 일치하는 값을 delete 할 수 있도록 remove_entry_from_node 를 호출한다.
    delete 가 수행된 노드가 root 라면 adjust_root 를 호출한다.
    root 가 아니라면 internal node, 혹은 leaf 가 b-tree 로써 가져야하는 최소의 key 개수인지를 판별하여, 기준을 만족한다면 root 를 반환한다.
    만족하지 못 한다면 neighbor 와, n 과 neighbor 를 가리키는 부모 노드의 pointers 사이의 keys 값을 잡아(= k_prime) Coalescence 또는 redistribute_nodes 를 호출한다.
    neighbor 와 n 의 num_keys 를 합쳐도 허용범위 내라면 coalesce_nodes 를, 허용범위를 초과하면 redistribute_nodes 를 호출한다.


  • delete : [find(_leaf), delete_entry 를 이용함]
    Deletion 과 관련된 최상위 함수이다.
    find 와 find_leaf 를 수행하여 해당 key 값과 일치하는 record 와 leaf 가 있을 경우에만 delete_entry 를 수행한다.


  • destroy_tree(_nodes) :
    재귀적으로 모든 tree 의 노드들의 메모리 동적할당을 해제하는 함수이다.


--------------------------------------------------------- Summary ---------------------------------------------------------

insert -- (이미 key 가 존재) insert 없이 root 반환  
        |  
        ㄴ(일치하는 key 없음) -- (처음으로 insert 실행) start_new_tree 호출  
                               |  
                               ㄴ(처음이 아님) -- find_leaf 호출 -- (해당 노드에 insert 가능) insert_into_leaf 호출  
                                                                  |  
                                                                  ㄴ(꽉 참) insert_into_leaf_after_splitting 호출 -- insert_into_parent 호출  
  
    
insert_into_parent -- (해당 노드가 root) insert_into_new_root 호출  
                    |  
                    ㄴ(부모 노드가 있음) get_left_index 호출 -- (부모 노드가 꽉 차지 않음) insert_into_node 호출  
                                                              |  
                                                              ㄴ(꽉 참) insert_into_node_after_splitting 호출 -- insert_into_parent 호출  
   



  
  
delete -- (해당 key 가 없음) root 반환  
        |  
        ㄴ(해당 key 있음) delete_entry 호출 -- remove_entry_from_node 호출(= key, record 삭제)  
  
  
remove_entry_from_node 호출 후 -- (해당 노드가 root) adjust_root -- (아직 key 값 남음) root 반환  
                                |                                 |  
                                |                                 ㄴ(key 값 없음) -- (root == leaf) 새 root 생성  
                                |                                                  |  
                                |                                                  ㄴ(root != leaf) root->pointers[0] 을 새 root 로  
                                |  
                                ㄴ(root 가 아님) -- (key 개수 >= 최소개수) -- root 반환  
                                                  |  
                                                  ㄴ(허용범위 밖) -- (neighbor, n 의 key 개수 합 < 허용범위) coalesce_nodes 호출(= neighbor 와 n 합병)  
                                                                   |  
                                                                   ㄴ(neighbor, n 의 key 개수 합 >= 허용범위) redistribute_nodes 호출(= keys, pointers 하나씩 neighbor -> n)  

--------------------------------------------------------- How to On-Disk ---------------------------------------------------------



현재 변경 후
프로그램 종료시 tree 가 삭제된다 종료 후에도 유지될 수 있도록 저장해야 한다
node* 인 root 를 통해 search, insert, delete 가 이루어진다 File Manager 를 거치도록 한다
tree 의 전반적인 정보를 알기 힘들다 해당 파일의 전반적인 정보를 담은 header page 를 추가한다
leaf node 가 record 의 포인터를 배열로 갖는다 leaf node 가 record 자체를 배열로 갖는다