반응형

 

 

 

 

 

 

 

1. 우선순위 큐 란?

: 우선 순위를 가진 항목들을 저장하는 큐이다. 

우선 순위가 높은 데이터가 먼저 삭제된다.

 

 

 

 

 

1) 구현 방법

- 배열 이용

- 연결 리스트 이용

- 힙(Heap) 이용 -> 가장 효율적이다.(트리 구조)

 

 

 

 

 

 

 

 

 

2. 힙(Heap) 이란?

: 노드들이 아래의 식을 만족하는 완전이진트리이다.

key(부모 노드) >= key(자식 노드)

 

 

* 완전이진트리 : 왼쪽부터 차례대로 이진트리를 꽉 채운 트리이다. 

 

1) 힙의 종류

- 최대 힙 : key(부모 노드) >= key(자식 노드)

- 최소 힙 : key(부모 노드) <= key(자식 노드)

 

 

 

2) 힙의 높이

: N 개의 노드를 가지고 있는 힙의 높이는 O(logN) 이다.

 

 

3) 힙의 구현방법

: 배열로 구현한다. 완전이진트리이므로 각 노드에 번호를 붙일 수 있다. 이 번호를 배열의 인덱스로 생각한다. 

 

* 배열로 트리구조 구현할 때 장점 - 부모노드를 쉽게 찾을 수 있다.

왼쪽 자식 인덱스 = (부모의 인덱스) * 2

오른쪽 자식 인덱스 = (부모의 인덱스) * 2 + 1

부모의 인덱스 = (자식의 인덱스) / 2

 

 

 

 

4) 기본 코드

#include <stdio.h>
#include <stdlib.h>
#define MAX_ELEMENT 200
typedef struct {
    int key;
} element;
typedef struct {
    element heap[MAX_ELEMENT];
    int heap_size;
} HeapType;


HeapType* create()
{
    return (HeapType*)malloc(sizeof(HeapType));
}

void init(HeapType* h)
{
    h->heap_size = 0;
}

void insert_max_heap(HeapType* h, element item)
{
    int i;
    i = ++(h->heap_size);

    while ((i != 1) && (item.key > h->heap[i / 2].key)) {
        h->heap[i] = h->heap[i / 2];
        i /= 2;
    }
    h->heap[i] = item;     // ªı∑ŒøÓ ≥εÂ∏¶ ª¿‘
}

element delete_max_heap(HeapType* h)
{
    int parent, child;
    element item, temp;

    item = h->heap[1];
    temp = h->heap[(h->heap_size)--];
    parent = 1;
    child = 2;
    while (child <= h->heap_size) {
        
        if ((child < h->heap_size) &&
            (h->heap[child].key) < h->heap[child + 1].key)
            child++;
        if (temp.key >= h->heap[child].key) break;
        
        h->heap[parent] = h->heap[child];
        parent = child;
        child *= 2;
    }
    h->heap[parent] = temp;
    return item;
}

void preorder1(HeapType *h, int root){
    if(root > h->heap_size)
        return;
    
    printf("%d ", h->heap[root].key);
    preorder1(h, root * 2);
    preorder1(h, root * 2 + 1);
}

void print_heap(HeapType *h){
    int num = 4;
    
    printf("%d\n", h->heap[1].key);
    
    for(int i=2; i<=h->heap_size; i++){
        printf("%d ", h->heap[i].key);
        if((i+1) == num){
            printf("\n");
            num *= 2;
        }
    }
    printf("\n");
}

int max(int a, int b){
    if (a >= b)
        return a;
    else
        return b;
}

int find(HeapType *h, int root, int key){
    if(root > h->heap_size)
        return 0;
    if(h->heap[root].key == key)
        return root;
    else if (h->heap[root].key < key)       // 중요
        return 0;
    
    return max(find(h, root * 2, key), find(h, root * 2 + 1, key));     // 중요
}

void print_sorted_value(HeapType *h){
    int size = h->heap_size;
    element e[10];
    
    for(int i=0; i< size; i++){
        e[i] = delete_max_heap(h);
        printf("%d ", e[i].key);
    }
    for(int i=0; i< size; i++){
        insert_max_heap(h, e[i]);
    }
    printf("\n");
}

void modify_priority(HeapType *h, int oldKey, int newKey){
    
    int i, child;
    
    if(oldKey == newKey)
        return;
    
    i = find(h, 1, oldKey);
    if(i == 0)
        return;
    
    if(newKey > oldKey){
        
        while ((i != 1) && (newKey > h->heap[i / 2].key)) {
            h->heap[i] = h->heap[i / 2];
            i /= 2;
        }
        h->heap[i].key = newKey;     // ªı∑ŒøÓ ≥εÂ∏¶ ª¿‘
        
    } else {
        element temp = h->heap[(h->heap_size)];
        
        child = i * 2;
        
        while (child <= h->heap_size) {
            
            if ((child < h->heap_size) &&
                (h->heap[child].key) < h->heap[child + 1].key)
                child++;
            if (temp.key >= h->heap[child].key) break;
            
            h->heap[i] = h->heap[child];
            i = child;
            child *= 2;
        }
        h->heap[i] = temp;
    }
}

int main(void)
{
    element e1 = { 10 }, e2 = { 5 }, e3 = { 30 };
    element eA = { 9 }, eB = { 19 }, eC = { 39 };
    element e4, e5, e6;
    int index;
    int key, oldKey, newKey;
    HeapType* heap;
    
    heap = create();
    init(heap);    // √ ±‚»≠

    printf("Step1 : 삽입된 10, 5, 30에 추가적으로 9, 19, 39를 삽입한다\n");
    insert_max_heap(heap, e1);
    insert_max_heap(heap, e2);
    insert_max_heap(heap, e3);
    insert_max_heap(heap, eA);
    insert_max_heap(heap, eB);
    insert_max_heap(heap, eC);
    
    printf("\nStep2 : preorder, print_heap 함수 테스트\n");
    preorder1(heap, 1);
    printf("\n");
    print_heap(heap);
    
    
    e4 = delete_max_heap(heap);
    printf("삭제 : %d 루트가 삭제됨\n", e4.key);
    print_heap(heap);
    
    printf("\nStep3 : find 함수 테스트\n");
    printf("찾을 key 입력(-1 for exit) : ");
    scanf("%d", &key);
    while(key != -1){
        if((index = find(heap, 1, key)) == 0)
            printf("%d는 없음\n", key);
        else
            printf("%d은 [%d]에 있음\n", key, index);
        printf("찾을 key 입력(-1 for index) : ");
        scanf("%d", &key);
    }
    
    printf("\nStep4 : print_sorted_value 함수 테스트\n");
    print_sorted_value(heap);
    
    
    printf("\nStep5 : modify_priority 함수 테스트\n");
    printf("바꿀 key 입력(-1 for exit) : ");
    scanf("%d", &oldKey);
    while(oldKey != -1){
        printf("새 key 입력 : ");
        scanf("%d", &newKey);
        modify_priority(heap, oldKey, newKey);
        print_heap(heap);
        
        printf("바꿀 key 입력(-1 for exit) : ");
        scanf("%d", &oldKey);
    }

    free(heap);
    return 0;
}

 

 

 

 

 

 

5) 삽입 연산

- 힙의 마지막 노드에 이어서 삽입한다.

- 삽입 후에 새로운 노드를 부모 노드들과 교환해서 힙의 성질을 만족시킨다.

 

void insert_max_heap(HeapType* h, element item)
{
    int i;
    i = ++(h->heap_size);

    while ((i != 1) && (item.key > h->heap[i / 2].key)) {
        h->heap[i] = h->heap[i / 2];
        i /= 2;
    }
    h->heap[i] = item;     // ªı∑ŒøÓ ≥εÂ∏¶ ª¿‘
}

 

 

 

 

 

 

6) 삭제 연산

- 루트 노드를 삭제한다.

- 마지막 노드를 루트 노드로 이동시킨다.

- 루트에서부터 단말 노드까지의 경로에 있는 노드들을 교환하여 힙의 성질을 만족시킨다.

 

element delete_max_heap(HeapType* h)
{
    int parent, child;
    element item, temp;

    item = h->heap[1];
    temp = h->heap[(h->heap_size)--];
    parent = 1;
    child = 2;
    while (child <= h->heap_size) {
        // 현재 노드의 자식 중 더 큰 자식 노드를 찾는다.
        if ((child < h->heap_size) &&
            (h->heap[child].key) < h->heap[child + 1].key)
            child++;
        if (temp.key >= h->heap[child].key) break;
        
        // 한 단계 아래로 이동한다.
        h->heap[parent] = h->heap[child];
        parent = child;
        child *= 2;
    }
    h->heap[parent] = temp;
    return item;
}

 

 

 

 

 

 

 

 

7) Find 연산

int find(HeapType *h, int root, int key){
    if(root > h->heap_size)
        return 0;
    if(h->heap[root].key == key)
        return root;
    else if (h->heap[root].key < key)       // 중요
        return 0;
    
    return max(find(h, root * 2, key), find(h, root * 2 + 1, key));     // 중요
}

 

- 기준 인덱스가 힙의 크기보다 크면 이미 힙을 초과한 것이므로 0을 반환한다.

- 키 값이 동일하면 기준 인덱스를 반환한다.

- 기준 노드의 값이 찾는 값보다 작으면 그 밑으로는 더 작은 값들밖에 없기 때문에 더 이상 값을 찾을 수 없으므로 0을 반환한다.

- 서브트리의 왼쪽과 오른쪽 노드에 find 연산을 수행하고 둘 중 큰 값을 반환한다.

값을 찾을 수 없는 경우 0을 반환하기 때문에 둘 중 큰 값을 반환하는 것이다.

 

 

 

 

 

 

 

3. 힙 정렬

- 하나의 요소를 힙에 삽입하거나 삭제할 때 시간이 O(logN)만큼 소요된다. 요소의 개수가 n개이므로 전체적으로 O(nlogN) 시간이 걸린다. (빠른 편)

- 힙 정렬이 최대로 유용한 경우는 가장 큰 값 몇 개만 필요할 때이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

 

 

 

1. 이진 탐색 트리란?

 

모든 노드가 아래의 조건을 만족하는 트리 자료구조이다.

key(왼쪽서브트리)≤key(루트노드)≤key(오른쪽서브트리)

- 따라서 이진 탐색 트리를 중위 순회하면 오름차순으로 정렬된 값을 얻을 수 있다.

 

 

 

1) 기본 코드

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

typedef int element;
typedef struct TreeNode {
    element key;
    struct TreeNode *left, *right;
} TreeNode;

TreeNode * search(TreeNode * node, element key)
{
    if (node == NULL) return NULL;
    if (key == node->key) return node;
    else if (key < node->key)
        return search(node->left, key);
    else
        return search(node->right, key);
}

TreeNode * new_node(element item)
{
    TreeNode * temp = (TreeNode *)malloc(sizeof(TreeNode));
    temp->key = item;
    temp->left = temp->right = NULL;
    return temp;
}

TreeNode * insert_node(TreeNode * node, element key)
{
    if (node == NULL) return new_node(key);

    if (key < node->key)
        node->left = insert_node(node->left, key);
    else if (key > node->key)
        node->right = insert_node(node->right, key);

    return node;
}

TreeNode * min_value_node(TreeNode * node)
{
    TreeNode * current = node;

    while (current->left != NULL)
        current = current->left;

    return current;
}

TreeNode * delete_node(TreeNode * root, element key)
{
    if (root == NULL) return root;

    if (key < root->key)
        root->left = delete_node(root->left, key);
    else if (key > root->key)
        root->right = delete_node(root->right, key);
    else {
        if (root->left == NULL) {
            TreeNode * temp = root->right;
            free(root);
            return temp;
        }
        else if (root->right == NULL) {
            TreeNode * temp = root->left;
            free(root);
            return temp;
        }
        TreeNode * temp = min_value_node(root->right);

        root->key = temp->key;
        root->right = delete_node(root->right, temp->key);
    }
    return root;
}

void preorder1(TreeNode * root) {
    if (root) {
        printf("%d ", root->key);
        preorder1(root->left);
        preorder1(root->right);
    }
}

int max(int a, int b){
    if(a >= b)
        return a;
    else
        return b;
}

int get_height(TreeNode *root){
    int height = 0;

    if(root != NULL){
        height = 1 + max(get_height(root->left), get_height(root->right));}
    
    return height;
}

int get_node(TreeNode *root){
    int count=0;
    if(root != NULL){
        count = 1 + get_node(root->left) + get_node(root->right);
    }
    return count;
}

int get_max(TreeNode *root){
    TreeNode * tmp = root;
    
    while(tmp->right != NULL){
        tmp = tmp->right;
    }
    return tmp->key;
}

int get_min(TreeNode *root){
    TreeNode * tmp = root;
    
    while(tmp->left != NULL){
        tmp = tmp->left;
    }
    return tmp->key;
}

int main(void)
{
    TreeNode * root = NULL;
    char res;
    int value;
    
    printf("Enter i<nsert>,d<elete>,s<earch>,p<rint>,h<eight>,c<ount>,m<ax>,n<min>,q<uit>:");
    scanf("%c", &res);
    
    while(res != 'q'){
        switch (res) {
            case 'i':
                printf("삽입할 값 입력:");
                scanf("%d", &value);
                root = insert_node(root, value);
                break;
            case 'd':
                printf("삭제할 값 입력:");
                scanf("%d", &value);
                root = delete_node(root, value);
                break;
            case 's':
                printf("탐색할 key값 입력:");
                scanf("%d", &value);
                if(search(root, value) == NULL)
                    printf("없음\n");
                else
                    printf("있음\n");
                break;
            case 'p':
                preorder1(root);
                printf("\n");
                break;
            case 'h':
                printf("트리의 높이는 %d\n", get_height(root));
                break;
            case 'c':
                printf("노드의 개수는 %d\n", get_node(root));
                break;
            case 'm':
                printf("가장 큰 값은 %d\n", get_max(root));
                break;
            case 'n':
                printf("가장 작은 값은 %d\n", get_min(root));
                break;
            default:
                break;
        }
        
        fflush(stdin);
        printf("Enter i<nsert>,d<elete>,s<earch>,p<rint>,h<eight>,c<ount>,m<ax>,n<min>,q<uit>:");
        scanf("%c", &res);
    }
    
    return 0;
}

 

 

 

2) 삽입 연산

TreeNode * new_node(element item)
{
    TreeNode * temp = (TreeNode *)malloc(sizeof(TreeNode));
    temp->key = item;
    temp->left = temp->right = NULL;
    return temp;
}

TreeNode * insert_node(TreeNode * node, element key)
{
    if (node == NULL) return new_node(key);

    if (key < node->key)
        node->left = insert_node(node->left, key);
    else if (key > node->key)
        node->right = insert_node(node->right, key);

    return node;
}

 

- 삽입하려는 값이 기준 노드의 값보다 작으면 기준 노드를 왼쪽으로, 크면 기준 노드를 오른쪽 노드로 이동한다. 

- 그러다가 트리의 끝에 도달하면 new_node(); 함수를 사용하여 노드를 삽입해준다.

- 만약, 이미 똑같은 값이 있다면 기존의 트리를 반환할 것이다.

 

 

 

 

 

 

 

 

3) 삭제 연산

 

삭제하려는 노드의 상황은 세 가지가 있다.

① 삭제하려는 노드가 단말 노드인 경우

② 삭제하려는 노드가 하나의 왼쪽 혹은 오른쪽 서브 트리 중 하나만 갖고 있는 경우

③ 삭제하려는 노드가 두 개의 서브 트리를 모두 갖고 있는 경우

 

 

CASE

: 단말 노드이기 때문에 자기 자신과 부모 트리에만 조치를 해 주면 된다.

 

 

 

 

CASE 

: 삭제되는 노드가 왼쪽이나 오른쪽 서브 트리 하나만 가지고 있을 때, 그 노드는 삭제하고 서브 트리를 부모 노드에 붙여준다.

 

 

case  

: 삭제 노드와 가장 비슷한 값을 가진 노드를 삭제 위치로 가져온다. 여기서 비슷한 값은 아래 트리에서 12나 22처럼 왼쪽 서브트리에서 가장큰 수이거나 오른쪽 서브트리에서 가장 작은 수이다.

 

 

 

TreeNode * delete_node(TreeNode * root, element key)
{
    if (root == NULL) return root;

    if (key < root->key)
        root->left = delete_node(root->left, key);
    else if (key > root->key)
        root->right = delete_node(root->right, key);
    else {
        if (root->left == NULL) {	// 단말노드, 오른쪽만 있는 노드
            TreeNode * temp = root->right;
            free(root);
            return temp;
        }
        else if (root->right == NULL) {		// 왼쪽만 있는 노드
            TreeNode * temp = root->left;
            free(root);
            return temp;
        }
        TreeNode * temp = min_value_node(root->right);

        root->key = temp->key;
        root->right = delete_node(root->right, temp->key);
    }
    return root;
}

 

- 기준 노드의 값보다 작으면 왼쪽 노드를, 크면 오른쪽 노드를 반환한다.

- 그러다가 기준 노드의 값과 삭제할 값이 같으면 !

단말노드, 오른쪽 서브트리만 있는 노드는

해당 노드는 삭제하고 오른쪽 서브트리를 부모에게 전달한다.

 

 왼쪽 서브트리만 있는 노드는

해당 노드는 삭제하고 왼쪽 서브트리를 부모에게 전달한다.

 

 왼쪽, 오른쪽 서브트리가 모두 있는 노드는

오른쪽 서브트리에서의 제일 작은 값을 현재 노드 값으로 설정하고, 

오른쪽 서브트리에 내려가서 제일 작은 값을 가지는 노드를 삭제한다.

 

 

 

 

 

 

 

4) 검색 연산

 

TreeNode * search(TreeNode * node, element key)
{
    if (node == NULL) return NULL;
    if (key == node->key) return node;
    else if (key < node->key)
        return search(node->left, key);
    else
        return search(node->right, key);
}

 

- 기준 노드의 값보다 찾는 값이 작으면 왼쪽 서브트리를 반환, 크면 오른쪽 서브트리를 반환한다.

- 값이 같으면 해당 노드를 반환한다.

 

 

 

 

 

5) 기타 연산

: 전위 순회, 트리의 높이 구하기, 노드의 개수, 제일 큰 값, 제일 작은 값

void preorder1(TreeNode * root) {
    if (root) {
        printf("%d ", root->key);
        preorder1(root->left);
        preorder1(root->right);
    }
}

int get_height(TreeNode *root){
    int height = 0;

    if(root != NULL){
        height = 1 + max(get_height(root->left), get_height(root->right));}
    
    return height;
}

int get_node(TreeNode *root){
    int count=0;
    if(root != NULL){
        count = 1 + get_node(root->left) + get_node(root->right);
    }
    return count;
}

int get_max(TreeNode *root){
    TreeNode * tmp = root;
    
    while(tmp->right != NULL){
        tmp = tmp->right;
    }
    return tmp->key;
}

int get_min(TreeNode *root){
    TreeNode * tmp = root;
    
    while(tmp->left != NULL){
        tmp = tmp->left;
    }
    return tmp->key;
}

- 트리의 높이 구하기 : 왼쪽 서브트리와 오른쪽 서브트리 중 더 높이가 높은 길이를 반환한다.

- 노드의 총 개수 구하기 : 루트에서 오른쪽, 왼쪽 내려가면서 NULL 이 아니면 1씩 더해서 총 노드의 개수를 구한다.

 

 

 

 

 

2. 이진 탐색 트리 성능 분석

 

1) 연산의 속도는 트리의 높이 h에 비례한다.

2) 최선의 경우 시간 복잡도 : O(log n)

 

 

3) 최악의 경우 시간 복잡도 : O(n)

 

 

 

 

 

3. 이진 트리의 성질

1) 노드의 개수가 n개이면, 간선의 개수는 n-1개 이다.

2) 높이가 h인 이진트리의 경우,

최소 h개의 노드를 가지며, 최대 2^h - 1 개의 노드를 가진다.

 

3) n개의 노드를 가지는 트리의 높이는

최대 n이고, 최소 [log(n+1)] 이다. -> 올림 기호

 

4) 포화 이진 트리(각 레벨에 노드가 꽉 차있는 이진트리)의 전체 노드 개수는

2^k - 1

-> k 는 높이

 

 

 

 

 

4. 트리의 용어

1) 단말 노드 : 자식이 없는 노드

2) 비단말 노드 : 적어도 하나의 자식을 가진 노드

3) 레벨 : 트리 각 층의 번호

4) 높이 : 트리의 최대 레벨

5) 차수 : 노드가 가지고 있는 자식 노드의 개수 

 

 

 

 

 

 

 

5. 쓰레드 이진 트리란?

: NULL 링크에 중위 순회 시에 후속 노드인 중위 후속자를 저장시켜 놓은 트리이다.

 

 

-> 중위순회 순서 ACBGDFE

 

 

#include <stdio.h>
#define TRUE 1
#define FALSE 0

typedef struct TreeNode {
	int data;
	struct TreeNode *left, *right;
	int is_thread;	// 스레드이면 TRUE
} TreeNode;


//		     G
//	     C		  F
//    A	   B   D     E
TreeNode n1 = { 'A',  NULL, NULL, 1 };
TreeNode n2 = { 'B',  NULL,  NULL, 1 };
TreeNode n3 = { 'C',  &n1,  &n2, 0 };
TreeNode n4 = { 'D', NULL, NULL, 1 };
TreeNode n5 = { 'E', NULL, NULL, 0 };
TreeNode n6 = { 'F', &n4,  &n5, 0 };
TreeNode n7 = { 'G', &n3,  &n6, 0 };
TreeNode * exp = &n7;

TreeNode * find_successor(TreeNode * p)
{
	// q는 p의 오른쪽 포인터
	TreeNode * q = p->right;
	// 만약 오른쪽 포인터가 NULL이거나 p가 스레드이면 오른쪽 포인터를 반환
	if (q == NULL || p->is_thread == TRUE)
		return q;

	// 쓰레드가 아니면, 오른쪽 노드 -> 다시 가장 왼쪽 노드로 이동
	while (q->left != NULL) q = q->left;
	return q;
}

void thread_inorder(TreeNode * t)
{
	TreeNode * q;

	q = t;
	while (q->left) q = q->left;// 가장 왼쪽 노드로 간다.
	do {
		printf("%c -> ", q->data);// 데이터 출력
		q = find_successor(q); // 후속자 함수 호출
	} while (q);			// NULL이 아니면
}
int main(void)
{
	// 스레드 설정 
	n1.right = &n3;
	n2.right = &n7;
	n4.right = &n6;
	// 중위 순회
	thread_inorder(exp);
	printf("\n");
	return 0;
}

 

 

 

- thread_inorder(); 에서 가장 왼쪽 노드로 간다.

- 노드의 값을 프린트하고

- find_successor(); 함수로 쓰레드이면 그 다음 포인터를 반환, 쓰레드가 아니면 오른쪽 -> 왼쪽 가장 밑으로 간다.

 

 

 

 

 

 

 

 

 

 

6. 완전 이진트리

: 레벨 1부터 k-1까지는 노드가 모두 채워져 있고, k레벨에서는 왼쪽부터 오른쪽으로 노드가 순서대로 채워져 있는 이진 트리이다.

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

 

 

 

 

 

 

 

 

1. 배경

: 주 메모리보다 용량이 큰 기억 공간에 주소 지정이 가능하다.

그렇게 하면 프로그램 일부분만 적재하고 실행시킬 수 있다.

 

-> 프로그래머에게 가상 메모리를 제공하여 이 공간에 주소를 지정한다.

 

 

 

 

 

- 부분 적재의 타당성

: 항상 전체 프로그램이 실행되는 것이 아니다.

 

- 부분 적재의 강점

: 메모리의 크기를 고려하지 않아도 되기 때문에 프로그래밍 작업이 쉬워진다.

: 공간의 제약이 없으므로 중첩이 불필요하다.

 

- 부분 적재의 단점

: 메모리와 디스크 사이의 이동량 증가

: 어느 시기에 어느 페이지를 적재 / 복귀할지 알고리즘이 필요

: 페이지 부재에 대한 처리 방안 요구

 

* 부분 적재 - 운영체제가 관리

* 중첩 - 프로그래머가 직접 설계 및 관리

 

 

 

 

 

 

 

 

2. 요구 페이징

: 페이지가 참조될 때에만, 그 페이지를 메모리에 적재한다.

 

 

 

1) 페이지 부재 오류

: 지정된 프레임 페이지가 메모리에 적재되지 않은 경우

- 페이지가 교체되는 동안 CPU는 다른 프로그램을 실행시킨다.

 

 

 

 

2) 요구 페이지의 성능

- 실제 접근 시간 (EAT)

: (1-p) x 메모리 접근 시간 + p x 페이지 부재 시간 ( p는 페이지 부재율이다. )

 

 

메모리 접근 시간 = 200 nanosecond
페이지 부재 처리 시간 = 8 msec = 8,000,000 nsec

EAT = (1 – p) x 200 + p x (8,000,000) = 200 + 7,999,800 x p (nsec)

10% 성능 감소(지연 시간이 20보다 작으려면?) : 20 > 7,999,800 x p p < 0.0000025

 

- 스왑 공간(메모리 쪽), 디스크 캐시(하드디스크 쪽) 활용

 

 

 

 

 

 

 

 

 

3. 페이지 교체

- 페이지 교체의 필요성 : 메모리에 빈 공간이 없는 경우

페이지 교체 알고리즘

1) 희생자 페이지 선택

2) 희생자 페이지를 디스크에 출력하고, 페이지 테이블에 기록한다.

3) 요구된 페이지를 적재하고, 페이지 테이블 내용을 수정한다.

4) 사용자 프로세스를 다시 시작한다.

 

 

 

- 페이지의 부재와 프레임 개수

: 프레임 수가 증가할수록 페이지 부재 횟수가 감소한다.

 

- 참조 문자열

: 메모리를 참조하는 페이지를 의미한다.

 

 

 

 

 

 

 

 

 

4. 선입선출(FIFO) 교체 알고리즘

: 가장 오래된 페이지가 교체되도록 선택한다.

 

 

 

 

- 문제점 : 벨레디의 변이

: 할당되는 프레임의 수가 증가해도 페이지 부재율이 증가하는 현상

 

 

 

 

 

 

5. 최적 교체 알고리즘

: 앞으로 가장 오래 사용되지 않을 페이지를 교체한다. (미래를 예측)

-> 모든 알고리즘 중에 가장 낮은 페이지 부재율을 보장한다.

 

 

 

 

 

 

 

 

6. LRU(Least Recently Used) 알고리즘

: 가장 오랫동안 사용하지 않은 페이지를 교체한다.

-> 가장 최근에 사용된 것을 기준으로 !

 

 

 

 

 

- LRU의 구현

1) 계수기의 사용

: CPU에 논리 시계나 계수기 추가

 

2) 스택의 사용

 

 

 

 

 

 

 

7. LRU 근접 알고리즘

: LRU의 시간 기록하는 오버헤드를 줄이기 위한 알고리즘이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

 

 

 

 

 

진화된 페이지 테이블 구조

: 다중 계층 페이지 테이블

 

 

 

 

1. 2단계 페이징

: 페이지 테이블이 너무 커서 프레임에 안 들어가면 잘라서 저장,
페이지 테이블의 지도를 outer-page table이라고 함

 

 

 

:

 

 

 

 

 

 

 

 

2. 세그먼테이션 기법 (페이징의 변형 버전)

세그먼트 : 메모리의 가변적인 단위이다. 프로그램은 세그먼트들의 집합이다.

세그먼트 기법 : 각각의 크기에 따른 분산 적재

 

 

 

 

 

- 논리 주소는 <세그먼트 번호, 오프셋> 으로 구성

- 세그먼트 테이블 : 가변 크기의 세그먼트 위치 저장

기준(base) : 세그먼트의 시작 물리 주소

한계(limit) : 세그먼트의 길이

 

 

 

 

 

 

 

 

- 할당

: 최상 적합 / 최초 적합 등의 동적 기억 장치 할당 기법 사용한다.

: 외부 단편화 문제 발생

 

 

 

 

 

 

페이징과 세그먼테이션은 공통적으로 프로그램 코드 및 데이터의 공유한다.

 

3. 페이지화된 세그먼테이션

페이징 - 내부 단편화, 세그먼트 - 외부 단편화

페이지화된 세그먼테이션은 외부 단편화 문제 해결 ! but 내부 단편화 발생

 

 

 

 

 

 

 

 

4. 정보처리기사 문제

1) 프로그램을 고정된 크기의 일정한 블록으로 나누는 페이징 기법과 

가변적인 크기의 블록으로 나누는 세그먼테이션 기법이 있다.

 

2) 페이징 기법은 동적 주소 변환 기법을 사용하여 다중 프로그래밍의 효과를 증시킨다.

3) 페이징 기법에서 프로그램을 동일한 크기로 나눈 단위를 페이지라고 하며, 이 페이지를 블록으로 사용하는 기법이다.

4) 페이징 기법은 내부 단편화를 발생시킨다.

5) 페이지 맵 테이블이 필요하다.

6) 세그먼테이션 기법에서는 각 세그먼트는 고유한 이름과 크기를 갖는다.

7) 세그먼테이션 기법에서는 세그먼트 맵 테이블이 필요하다.

8) 20K, 16K, 8K, 40K 빈 기억공간에서 ‘worst fit’을 사용하여
17K의프로그램을 적재할 경우 내부 단편화의 크기는?
-> 23K

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형

'Computer Science > Operating System' 카테고리의 다른 글

Practice 5) Thread & Semaphore 1  (0) 2020.12.17
이론 7) 가상 메모리  (0) 2020.12.03
이론 6) 주 메모리 관리 기법 2  (0) 2020.12.02
이론 6) 주 메모리 관리 기법  (0) 2020.12.02
이론 5) 교착 상태 2  (0) 2020.12.02
반응형

 

 

 

 

 

 

 

 

 

1. 메모리 관리 기법 분류

 

1) 연속 메모리 할당 방식

: 프로그램(프로세스)를 적재하는 과정에서 연속적으로 메모리를 할당한다.

: 직접 배치, 중첩(오버레이), 분할 기법이 해당한다.

 

- 고정 분할 기법 : 메모리를 고정된 크기로 분할하여 프로세스에 제공한다. 

- 가변 분할 기법 : 각 프로세스의 크기에 따라 메모리를 분할한다.

 

 

2) 분산 메모리 할당 방식

: 프로세스가 페이지나 세그먼테이션 등의 단위로 보조기억장치에 적재되어 있다가, 프로세스의 요구에 의해 메모리의 여러 영역에 할당된다.

: 현재의 가상 메모리 관리 기법으로 발전하였다. (MMU)

 

 

 

 

2. 연속 메모리 할당

메모리 분할 : 운영 체제 부분과 사용자 부분 둘로 나눈다.

 

 

1) 고정 분할

 

 

 

: 고정된 크기로 분할하기 때문에 논리 주소와 크기가 맞지 않으면 내부에 낭비되는 공간이 생긴다.

이를 "내부 단편화"라고 한다.

 

 

ex) 내부 단편화 예제

(a) 메모리 영역을 10KB, 4KB, 4KB, 4KB 로 나누는 경우

내부 단편화 = 3KB + 1KB = 4KB 이다. 메모리 할당이 되지 않은 영역은 내부 단편화가 아니다.

 

(b) 메모리 영역을 10KB, 8KB, 4KB 로 나누는 경우

내부 단편화 = 4KB + 1KB + 1KB = 6KB 이다. 

 

 

 

 

 

2) 가변 분할

: 고정된 경계를 없애고 각 작업이 필요한 만큼 메모리를 할당한다.

 

: 각 작업이 필요한 메모리만큼 메모리를 할당하기 때문에 해당 작업이 끝났을 때 동일한 크기의 메모리 할당이 필요치 않는다면 낭비되는 공간이 생긴다.

이를 "외부 단편화"라고 한다.

 

 

 

 

- 메모리 적합 기법

: 최초 적합, 최상 적합, 최악 적합

 

1) 최초 적합 기법

: 사용가능공간 리스트에서 충분히 큰 첫 번째 공백 분할 공간에 할당한다.

- 검색은 빠르나 공간 활용도가 떨어진다.

 

 

 

 

 

2) 최상 적합 기법

: 들어갈 수 있는 공간 중 가장 작은 크기의 공간에 할당한다.

- 사용가능공간에 대해 지속적으로 정렬해야 하므로 비효율적이다.

- 공간 활용도는 향상되나, 할당되는 과정에서 많은 시간이 소요된다.

 

 

 

3) 최악 적합 기법

: 작업을 가장 큰 사용가능공간에 할당한다. 

: 공간이 크기순으로 정렬되지 않았다면 전 리스트를 검색한다.

 

 

 

 

 

 

3. 단편화를 해결할 방법

 

1) 압축 (외부단편화 해결)

: 메모리의 여러 영역들을 옮겨 모든 작은 빈 공간들을 연속된 하나의 큰 빈 공간으로 만드는 작업이다.

: 재배치가 실행시간에 동적으로 이루어지는 경우에만 가능하다.

 

단점

- 압축할 동안 시스템은 모든 일을 정지한다.

- 재배치 정보를 관리할 필요가 있다.

 

 

 

 

 

 

2) 버디 시스템

: 요청된 크기가 버퍼보다 작으면 버퍼를 동일한 크기의 2개 버디로 나눈다.

: 가능할 때마다 인접한 자유로운 버퍼들을 합치는 과정을 반복한다.

+ 2의 배수로 설정한다.

 

 

ex) 7K, 6K, 3K 프로세스 할당

 

 

 

 

 

 

 

 

 

 

4. 분산 메모리 할당

 

1) 페이징 기법

: 프로그램을 페이지라는 고정 크기 단위로 나누어 분산 적재한다.

 

 

 

 

 

 

 

2) 페이징 시스템 하드웨어

: 논리 페이지에 물리 메모리(페이지) 프레임을 적재하는 과정을 수행하는 페이지 시스템 하드웨어 구조이다.

 

논리 주소 = 페이지 번호(p) + 페이지 변위(d)

p에 저장된 f값은 물리 메모리 페이지 번호이다.

페이지 변위란, 테이블의 자리 번호이다.

 

물리 주소 = 메인 메모리의 프레임 번호(f) + 페이지 변위(d)

f는 실제 메모리의 페이지 기준 주소로 메인 메모리의 프레임 번호이다.

 

 

 

 

페이징 시스템의 물리적 주소 변환의 예)

 

 

 

 

 

 

 

3) 페이징 스케줄링

: 새로운 프로세스를 페이지 단위로 표현하고 순서대로 사용가능한 프레임 리스트에 할당한다.

 

 

 

- 페이지 크기

: 페이지 크기는 하드웨어에 따라 결정된다.

: 일반적으로 2의 제곱, 512bytes ~ 16Kbytes

단순한 내부 단편화만 생각하면 페이지 크기가 작은 것이 바람직하다.

다만, 페이지 크기가 작을수록 페이지 테이블 유지 부담이 커지기 때문에 적절한 페이지 크기를 산정해야 한다.

 

 

 

 

 

 

 

 

 

 

 

5. 페이지 테이블의 구현

1) 전용 레지스터의 집합

: 페이지 테이블이 cpu에 위치한다. 

- 페이지 테이블이 작은 경우에만 가능하다.

 

2) 주기억 장치에 유지

: CPU 내의 테이블 기준 레지스터(PTBR) 에 저장한다.

- 기억 장치 위치에 접근하는 데 많은 시간이 소요된다. ( 두 번의 기억장치 접근 필요 )

 

 

 

 

 

 

3) 변환 우선참조 버퍼 TLB 사용

: 페이지 테이블을 캐시에 저장한다.

- 탐색은 빠르지만, 하드웨어가 고가이다.

 

-> 주기억 장치에 유지하는 것처럼 기억 장치 위치에 접근하는 데 많은 시간이 소요되지는 않는다.

다만, 연관사상표에 일치하는 항목이 없을 때, 더 시간이 많이 걸리기 때문에 이 점을 감안해야 한다.

 

 

 

* 적중률 및 실제 접근 시간 *

- 연관 레지스터(캐시) 탐색 시 50ns, 메모리에 접근 시 750ns

IF 연관 레지스터에 있는 경우 : 50 + 750 = 800ns

IF 연관 레지스터에 없는 경우 : 50 + 750 + 750 = 1550ns

 

- 적중률 80%인 경우, 실제 접근 시간 = 800 * 0.8 + 1550 * (1-0.8) = 950ns

- 적중률 90%인 경우, 실제 접근 시간 = 800 * 0.9 + 1550 * (1-0.8) = 875ns

 

 

 

 

 

 

 

6. 페이지 보호 및 공유

: 페이지 단위로 판독 전용, 판독/기록 전용, 실행 전용 등의 보호를 지정한다.

 

 

 

- 공유 페이지

( 분산 메모리 관리의 장점 )

: 시분할 환경에서 중요한 공통된 코드들을 프로세스 사이에서 공유할 수 있다.

: 비슷한 프로세스끼리 논리 주소 공간에서 같은 위치에 놓을 수 있기 때문이다.

 

 

 

 

 

 

 

7. 페이징 기법의 장단점

장점

- 공유 페이지를 이용한다.

- 외부 단편화를 제거할 수 있다. ( 끼워 넣으면 되니까 )

- 외부 단편화가 없기 때문에 압축 기능이 필요없다. 

 

단점 

- 페이징 사상을 위한 하드웨어 준비로 인한 가격 상승

- 속도 저하 -> 캐시 사용으로 속도 향상 가능하다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형

'Computer Science > Operating System' 카테고리의 다른 글

이론 7) 가상 메모리  (0) 2020.12.03
이론 6) 주 메모리 관리 기법 3  (0) 2020.12.02
이론 6) 주 메모리 관리 기법  (0) 2020.12.02
이론 5) 교착 상태 2  (0) 2020.12.02
이론 5) 교착 상태  (0) 2020.12.02
반응형

 

 

 

 

1. 배경

: 다중 프로그래밍에서 주기억 장치를 여러 프로그램에 공유하려면 주기억 장치 관리 기법이 필요하다.

 

 

2. 주소 바인딩

: 프로그램의 명령어와 데이터를 기억 장치 주소 공간으로 할당하는 것을 의미한다.

 

1) 컴파일 시간 바인딩

: 프로그램의 메모리 저장 위치가 고정된다.

 

2) 적재 시간 바인딩

: 프로그램이 메모리에 적재되는 시기에 메모리 위치가 결정된다. 재배치가 가능하다.

 

3) 실행 시간 바인딩

: 실행 중간에 메모리 저장 위치가 MMU에 의해서 결정되며, 실행중에 위치 변경이 가능하다.

-> 연속 메모리 할당 방식 / 분산 메모리 할당 방식이 있다.

 

,

 

 

 

 

 

 

4) 논리 주소

: CPU가 생성하는 주소

 

5) 물리 주소

: 기억 장치에 나타나는 주소

-> 아래 그림에서 메모리(주기억장치)와 하드디스크(보조기억장치) 를 의미한다.

 

 

 

 

 

- 컴파일 시간 바인딩과 적재 시간 바인딩 : 논리 주소와 물리 주소가 같다.

- 실행 시간 바인딩 : 논리 주소와 물리 주소가 다르다.(MMU가 변경시킨다.)

 

 

 

 

 

 

3. 기억 장치 관리기(MMU)

: 논리 주소(가상 주소)의 물리 주소 사상을 수행하는 하드웨어이다.

: 재배치 레지스터를 사용한다.

 

 

 

 

 

 

 

 

4. 중첩( Overlays ) 

: 허용된 메모리 크기보다 큰 프로그램을 실행할 때 해결법이다.

 

- 오버레이 영역은?

: 필요에 따라서 프로그램을 적재하여 실행하고, 필요없게 되면 다른 프로그램으로 교체되는 부분이다.

: 프로그래머가 오버레이 구조를 설계하고 프로그램해야 한다.

 

 

 

 

 

 

 

 

5. 교체 (Swapping)

: 할당 시간량이 소진되거나 높은 우선 순위 프로세스가 도착할 때, 한 프로세스를 디스크로 보내고 새 프로세스를 불러들인다.

 

 

- 메모리가 큰 컴퓨터는 스와핑이 적어 성능이 좋다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형

'Computer Science > Operating System' 카테고리의 다른 글

이론 6) 주 메모리 관리 기법 3  (0) 2020.12.02
이론 6) 주 메모리 관리 기법 2  (0) 2020.12.02
이론 5) 교착 상태 2  (0) 2020.12.02
이론 5) 교착 상태  (0) 2020.12.02
이론 0) 운영체제 기본  (0) 2020.10.22

+ Recent posts