1.02k likes | 1.25k Views
C 중급. 3. 자료구조. 배열 , 행렬 , 레코드. 연접리스트. 연결 리스트. 단순 , 이중 , 원형 , 복합 연결 리스트. 선형 구조. 스택. 큐. 출력제한 , 입력제한 데크. 테크. 자료구조. 일반 트리 , 이진 트리. 트리. 비선형 구조. 그래프. 방향 그래프 , 무방향 그래프. 직접 파일. 파일 구조. 순차 파일. 색인 순차 파일. 3.1 자료구조란. 다루고자 하는 자료 항목들간의 관계를 기술하는 것이다. 3.2 선형 자료 구조.
E N D
3. 자료구조
배열, 행렬, 레코드 연접리스트 연결 리스트 단순, 이중, 원형, 복합 연결 리스트 선형 구조 스택 큐 출력제한, 입력제한 데크 테크 자료구조 일반 트리, 이진 트리 트리 비선형 구조 그래프 방향 그래프, 무방향 그래프 직접 파일 파일 구조 순차 파일 색인 순차 파일 3.1 자료구조란 • 다루고자 하는 자료 항목들간의 관계를 기술하는 것이다.
3.2 선형 자료 구조 • 1. 자료구조는 실제적으로 리스트의 한 형태인데 저장구조의 차이점 때문에 리스트를 다양한 형태로 나누고 있다. • 2. 자료구조를 리스트로 취급하는 이유는 기억매체의 접근이 1차원이라는 데 있다. • 3. 모든 항목들이 일 대 일의 관계를 가지는 리스트를 선형 리스트라 한다. 이러한 선형 리스트에는 순차(연접)리스트와 연결 리스트의 2 가지 종류가 있다. • 순차리스트 • 연속적으로 저장되는 고정된 갯 수의 데이터 item • index에 의해 접근 가능 • 배열 • 연결리스트 • 동적으로 item을 삽입하거나 삭제하는 것이 가능 • 각item들을 효율적으로 재배치하는 것이 가능 • 단순 연결리스트, 원형 연결리스트, 이중 연결리스트, 복합 연결리스트 • 스택, 큐,데크
L 1 2 3 I I+1 B 3.2.1 연접리스트 • 연접리스트(dense list) • 기억 장소에 연속적으로 저장되는 리스트를 의미한다. • 첫 번째 원소의 위치를 B, 원소의 길이를 L이라고 가정한다면, • 두 번째 원소의 위치는 B+L, • 세 번째 원소의 위치는 B+2L 이 된다. • I번째 원소가 시작되는 저장 장소의 위치는 다음과 같다 • I번째 원소의 저장 장소의 위치 = B+(I-1)L
3.2.2 연결 리스트 • 연접리스트 • 연접리스트의 장점 • 간단한 구조 • 구현하기가 간단하다. • 연접리스트의 단점 • 삽입과 삭제 작업이 발생할 경우 많은 양의 자료가 이동해야 하는 문제점이 있다. • 연결 리스트 • 이러한 문제를 해결하기 위해 각 노드에 다음 노드를 가리키는 포인터를 둔다. • 리스트내의 모든 노드가 다음 노드의 위치를 지적하기 위한 포인터를 갖는다. • 이러한 리스트를 연결 리스트(linked list)라고 한다. • 연결 리스트 구조를 표현 • 각 노드는 자료 필드(data field)와 연결 필드(link field)가 필요하게 된다. • 노드의 구조는 다음과 같다.
A B C 3.2.2 연결 리스트 데이터 링크 포인터 D ^ • 연결 리스트의 장,단점 • 장점 • 노드의 삽입, 삭제가 용이하다. • 링크 필드의 주소 변경만으로 작업이 가능 • 연속적으로 기억 공간이 없어도 저장이 가능 • 단점 • 연접 리스트나 배열보다 기억 공간이 많이 필요하다. • 알고리즘 구현이 복잡하다. • 특정 노드 검색 시 무한 루프(infinite loop)에 빠질 수 있다.
... NULL NULL ... start 3.2.3 연결 리스트의 종류 단순 연결리스트 NULL ... start 원형 연결리스트 이중 연결리스트
3.2.4 단순 연결 리스트 • 단순 연결 리스트의 특징 • 1. 리스트에 삽입과 삭제가 용이하게 수행될 수 있다. • 2. 하나의 노드를 데이터 필드와 링크 필드로 나눈다. • 3. 링크 필드에 다음 노드를 가리키는 포인터를 저장한다. • 4. 마지막 노드의 링크 필드는 널(NULL)로 저장하는 자료 구조이다. • 5. 포인터의 특수한 형태인 NULL포인터는 아무런 항목도 가리키지 않는다는 의미이며, 마지막 노드를 인지하는 포인터의 변수이다. • 6.자료 구조의 검색은 한쪽 방향으로만 진행된다.
3.2.4 단순 연결 리스트 • 단순 연결리스트의 구조 • 1. 단순 연결 리스트는 가장 기본적인 연결 리스트 구조이다. • 2. 단순 연결 리스트의 노드는 데이터 값(value)을 저장하는 부분과 다음 노드를 가리키는 포인터로 이루어져 있다. • 3 여러 개의 노드들이 연결되어 단순 연결 리스트가 만들어진다. • 4.연결 리스트의 시작점은 임의의 포인터가 가리키고 있으며, 그 포인터로부터 연결 리스트의 각 노드를 참조할 수 있다. • 5. 연결 리스트의 시작점을 가리키는 포인터가 그 리스트의 이름이 된다.
3.2.4 단순 연결 리스트 • 단순 연결 리스트의 장단점 • 장점 • 구조가 간단하다 • 기억장소의 소모가 적다 • 단점 • 역 방향으로 리스트를 탐색할 수 없기 때문에 노드를 삽입하거나 삭제하려면 별도의 포인터가 필요하다. • 문제해결 이중 연결리스트를 사용
3.2.4 단순 연결 리스트 • 단순 연결 리스트의 동작 • 단순 연결 리스트에 노드 삽입(insert) • start 포인터: • 노드를 삽입하려는 연결리스트의 시작점을 가리킨다. • current 포인터: • 현재 참조중인 노드 • new 포인터: • 새로 삽입할 노드를 가리킨다. ... NULL start current new
3.2.4 단순 연결 리스트 • Insert • 현재 참조중인 노드가 연결 리스트의 중간 노드인 경우 • 1."new" 노드의 포인터는 "current"노드의 포인터가 가리키는 노드를 가리키도록 한다. • 2."current" 노드의 포인터는 "new" 노드를 가리키도록 한다. • 3.순서를 지키는 것이 중요하다. new ... NULL start current
NULL 3.2.4 단순 연결 리스트 • Insert • 현재 참조중인 노드가 연결 리스트의 마지막 노드인 경우 • current 노드의 포인터가 새로운 노드를 가리키도록 해주면 된다. NULL new ... start current
3.2.4 단순 연결 리스트 • Delete • 현재 참조중인 노드가 연결 리스트의 처음 노드인 경우 • start = start->next; • free(current); ... NULL start start current
3.2.4 단순 연결 리스트 • Delete • 현재 참조중인 노드가 연결 리스트의 중간 노드인 경우 • new->next = current->next; • free(current); ... NULL start new current
3.2.4 단순 연결 리스트 • Delete • 현재 참조중인 노드가 연결 리스트의 마지막 노드인 경우 • new->next = current->next; • free(current); ... NULL start new current
3.2.4 단순 연결 리스트 #include <stdio.h> #include <malloc.h> #include <conio.h> #include <string.h> typedef struct record { char name[20]; int birth; struct record* next; } *LINK; void list_record(); void insert(char*, int); int del(char*); LINK start; void list_record() { LINK current; printf("\nstart = 0x%5x\n", start); for(current = start; current; current = current->next) printf("0x%5x %-20s 0x%5x\n", current, current->name, current->next); }
3.2.4 단순 연결 리스트 void insert(char*name, int year) { LINK newone, current; newone = (LINK) malloc(sizeof(struct record)); strcpy(newone->name, name); newone->birth = year; newone->next = NULL; if(start) { for(current = start; current->next; current = current->next) ; current->next = newone; } else start = newone; }
3.2.4 단순 연결 리스트 int del(char* name) { LINK current, help; if(!start) return -1; for(help = current = start; current; current = current->next) { if(strcmp(current->name, name) == 0) { if(current == start) start = current->next; else help->next = current->next; free(current); return 0; } help = current; } return -1; }
3.2.4 단순 연결 리스트 void main() { start = NULL; insert("KIM", 1996); insert("LEE", 1961); insert("BAE", 1962); insert("HWANG", 1963); insert("OH", 1964); puts("listing !\n"); list_record(); del("LEE"); list_record(); del("KIM"); list_record(); del("HWANG"); list_record(); insert("RYU", 1961); list_record(); insert("JUNG", 1996); list_record(); del("OH"); list_record(); insert("MYUNG", 1965); list_record(); insert("KONG", 1960); list_record(); getch(); }
... 3.2.5 원형 연결 리스트 • 원형 연결리스트는 연결 리스트의 마지막 노드의 링크 필드 값이 널 링크가 아닌 첫번째 노드주소를 갖는 구조이다. • 원형 연결리스트 이용 시 한 노드에서 모든 노드로의 접근이 가능하고, 삽입과 삭제연산이 편리한 장점이 있으나, 특정 노드 검색 시 무한 루프에 빠질 가능성이 있다. • 따라서 검색을 끝낼 수 있는 노드가 존재하여야 한다. • 이를 위해 헤드(head)를 두는 것도 한 방법이 될 수 있다.
3.2.5 원형 연결 리스트 #include <stdio.h> #include <malloc.h> typedef struct node { int key; struct node* next; }*LINK; void main() { int i,N,M; LINK start, current, help; scanf("%d %d", &N, &M); start = current = (LINK) malloc(sizeof(struct node)); current->key = 1; for(i = 2; i <= N; i++) { current->next = (LINK) malloc(sizeof(struct node)); current = current->next; current->key = i; } current->next = start;
3.2.5 원형 연결 리스트 while(current->next != current) { for(i=1; i<M; i++) { printf("0x%5x %d 0x%5x\n", current, current->key, current->next); current = current->next; } printf("%d %d\n\n", current->key, current->next->key); help = current->next; free(help); for(i=1; i<M; i++) { printf("0x%5x %d 0x%5x\n", current, current->key, current->next); current = current->next; } } printf("%d\n", current->key); }
3.2.6 스택 • 스택의 정의 • 1. 여러 개의 데이타 항목들이 일정한 순서로 나열된 자료 구조 • 2. 한쪽 끝에서만 새로운 항목을 삽입하거나 기존 항목을 삭제할 수 있도록 고안 • 3. 선형 리스트의 특별한 경우로서 TOP 이라는 자료 구조의 끝에서 자료의 삽입과 삭제가 발생하는 자료 구조이다. • 스택의 원리 • 1. 스택은 동전을 넣고 뺄 수 있도록 되어 있는 동전 케이스와 같은 작동 원리 • 스택의 성격 • 2. 후입 선출 리스트(Last- In-First-Out List) : 스택에 저장된 데이타 항목들 중에 먼저 삽입된 것은 나중에 삭제되고, 나중에 삽입된 것이 먼저 삭제된다.
3.2.6 스텍 • 스택의 구조 • 1. 스택은 base로부터 데이타 항목들을 차례로 쌓아올린 모양을 가진다. • 2. 삽입과 삭제는 현재 저장된 최상위 항목이 위치한 top에서만 일어난다. • 3. top 위치는 "스택 포인터"라는 지시자가 가리킨다. • 4. 스택 포인터는 스택 기저에서 시작하여 항목이 삽입되면 하나 증가하고, 삭제되면 하나 감소한다. • 5. 스택에는 한계가 있어서 그 한계를 초과하여 삽입할 수 없다.
삭제순서: D,C,B,A 삽입순서: A, B, C, D index index 4 4 D 3 3 C 2 2 B 1 1 A 삭제 삽입 3.2.6 스텍 • 그림과 같이 삽입을 한 상태에서 D가 제거되면 top은 3이 되고, C가 제거되면 top은 2가 되며,B가 제거 되면 top은 1이 된다. 마지막 A가 제거되면 top은 0이 된다.
3.2.6 스텍 • 스택의 입출력 • 삽입(push) Full 검사 • 삭제(pop) Empty 검사 • 1. 스택에 데이타 항목 삽입 (push) • 데이타 항목을 삽입하려면 스택 포인터를 하나만큼 증가시켜 주고 스택의 top 에 데이타 항목을 저장한다. 데이타 항목을 삽입하기 전에 새로운 항목을 저장할 빈 공간이 있는지 검사(full 검사)해야 한다. • stack[++top] = 데이터; • 2. 스택에서 데이타 항목 삭제 (pop) • 데이타 항목을 삭제하려면 스택의 top 에 있는 데이타 항목을 제거하고 스택 포인터를 하나만큼 감소시켜 준다. 데이타 항목을 삭제하기 전에 스택이 비어있는지를 검사 • (empty 검사 )해야 한다. • return stack[top--];
삭제순서: D,C,B,A 삽입순서: A, B, C, D index index 4 4 D 3 3 C 2 2 B 1 1 A 삭제 삽입 3.2.6 스텍 • 그림과 같이 삽입을 한 상태에서 D가 제거되면 top은 3이 되고, C가 제거되면 top은 2가 되며,B가 제거 되면 top은 1이 된다. 마지막 A가 제거되면 top은 0이 된다.
3.2.6 스텍 /*STACK : array version */ #include <stdio.h> #define MAX 5 int stack[MAX]; int top; void init_stack(void) { top = -1; } int push(int t) { if(top >= MAX-1) { printf("Stack overflow.\n"); return -1; } stack[++top] = t; return t; }
3.2.6 스텍 int pop(void) { if(top < 0) { printf("Stack underflow.\n"); return -1; } return stack[top--]; } void print_stack(void) { int i; printf("Stack contents: Top ---> Bottom\n"); if(top == -1) printf("[Stack empty]\n"); for(i = top; i >= 0; i--) { printf("%d", stack[i]); } printf("\n"); }
3.2.6 스텍 void main() { int i; init_stack(); printf("\nPush 3,2,5,7,2\n"); push(3); push(2); push(5); push(7); push(2); print_stack(); printf("\nNow statck is full, push 3\n"); push(3); print_stack(); printf("\nInitailize stack\n"); init_stack(); print_stack(); printf("\nNow stack is empty, pop\n"); i = pop(); printf("popping value is %d\n", i); print_stack(); }
3.2.6 스텍 // 링크드 리스트를 이용한 스텍 #include "stdio.h" #include "malloc.h" typedef struct struct_node { char key; struct_node* next; }node; node *head, *tail; void init_stack(void) { head = (node*)malloc(sizeof(node)); tail = (node*)malloc(sizeof(node)); head->next = tail; tail->next = tail; }
3.2.6 스텍 void clear_stack(void) { node *t, *s; t = head->next; while(t != tail) { s = t; t = t->next; free(s); } head->next = tail; } int push(char k) { node *t; if((t = (node*)malloc(sizeof(node))) == NULL) { printf("\n Out of memory..."); return -1; } t->key = k; t->next = head->next; head->next = t; return k; }
3.2.6 스텍 char pop(void) { node* t; char i; if(head->next == tail) { printf("\n Stack underflow."); return -1; } t = head->next; i= t->key; head->next = t->next; free(t); return i; }
3.2.6 스텍 void print_stack(void) { node* t; t = head->next; printf("\n Stack contents: Top---->Bottom\n"); while(t != tail) { printf("%-6d", t->key); t = t->next; } } void main(void) { int i; init_stack(); printf("\nPush 5,4,7,8,2,1"); push(5); push(4); push(7); push(8); push(2); push(1);
3.2.6 스텍 print_stack(); printf("\nPop"); i = pop(); print_stack(); printf("\n popping value is %d", i); printf("\nPush 3,2,5,7,2"); push(3); push(2); push(5); push(7); push(2); print_stack(); printf("\nInitailize stack"); clear_stack(); print_stack(); printf("\nNow stack is empty. pop"); i = pop(); print_stack(); printf("\n popping value is %d", i); }
3.2.7 큐(Queue) • 큐의 정의 • 1. 여러 개의 데이타 항목들이 일정한 순서로 나열된 자료 구조 • 2. 한쪽 끝에서 삽입, 삭제할 수 있도록 되어 있다. • 3. 스택에서 자료의 삽입/삭제는 top이라는 스택 포인터에서 이루어지는데 반해 큐의 제거는 앞에서, 삽입은 뒤에서 발생되도록 제한된다. -> 선입 선출(FIFO) • 큐의 원리 • 1. 큐는 매표소에서 표를 사기 위해 기다리는 대기자 열과 같은 원리 • 2. 대기자 열에는 먼저 온 사람부터 차례로 대기자들이 늘어서 있다. 앞쪽 끝에서는 기다리던 사람이 표를 사서 빠져나가고 (삭제), 뒤쪽 끝에서는 새로운 사람들이 대기자 열로 들어온다(삽입). • 큐의 성격 • 1. 선입 선출 리스트 (First-In-First-Out:FIFO) : • 큐에 저장된 데이타 항목들 중에 먼저 삽입된 것은 먼저 삭제되고, 나중에 삽입된 것은 나중에 삭제된다.
3.2.7 큐(Queue) • 큐의 종류 • 1. 선형 큐(linear queue) : 한 방향으로 데이타 항목들이 삽입/삭제 • 2.원형 큐(circular queue) : 시작점과 끝점이 서로 연결
삽입순서: A, B, C, D 삭제순서: A, B, C, D A B C D front rear front rear 3.2.7 선형 큐(Queue) • 큐는 운영체제에서 수행할 작업들을 스케줄링 할 때 자주 이용되는데, 큐 동작을 이해하기 위해 5개의 작업이 큐에서 운영되는 예를 표로 만들면 다음 페이지와 같다. • 이때 주의할 점은 큐의 공백(empty) 상태를 구분하기 위해 front는 큐의 실제 앞(front)보다 1이 작은 위치를 가리키게 하고, rear는 큐에 마지막으로 삽입된 원소를 가리키게 한다는 점이다.
Q(1) Q(2) Q(3) Q(4) Q(5) … front rear 0 0 Queue Empty 초기조건 0 1 J1 큐에 Job1 입력 0 2 J1 J2 큐에 Job2 입력 0 3 J1 J2 J3 큐에 Job3 입력 1 3 J2 J3 큐에 Job1 출력 1 4 J2 J3 J4 큐에 Job4 입력 2 4 J3 J4 큐에 Job2 출력 2 5 J3 J4 J5 큐에 Job5 입력 3.2.7 선형 큐(Queue)
3.2.7 선형 큐(Queue) • 선형 큐 • 1. 선형 큐의 구조 (Queue1) • rear : 새로운 데이터 항목의 삽입 • front : 기존 데이터 항목의 삭제 • 2. front 포인터와 rear 포인터는 초기에 하한에서 시작하여 삽입과 삭제가 반복됨에 따라 상한쪽으로 이동한다 • 선형 큐의 동작 • 1. 선형 큐에 데이타 항목 삽입 • (full 검사 : front 포인터 = rear 포인터) • rear 포인터를 하나만큼 증가시켜 주고 그 위치에 데이타 항목을 저장한다. • 2. 선형 큐에서 데이타 항목 삭제 • (empty 검사 : front 포인터 = rear 포인터) • 큐의 front 포인터를 하나만큼 증가시키고 그 위치에 있는 데이타 항목을 삭제한다.
3.2.7 선형 큐(Queue) 큐의 삽입과 삭제
3.2.7 선형 큐(Queue) #include <stdio.h> #define MAX 10 int queue[MAX]; int front, rear; void init_queue(void) { front = rear = 0; } void clear_queue(void) { front = rear; }
3.2.7 선형 큐(Queue) int put(int k) { if((rear + 1) % MAX == front) { printf("\n Queue overflow."); return -1; } queue[rear] = k; rear = ++rear % MAX; return k; } int get(void) { int i; if(front == rear) { printf("\n Queue underflow."); return -1; } i = queue[front]; front = ++front % MAX; return i; }
3.2.7 선형 큐(Queue) void print_queue(void) { int i; printf("\n Queue contents: Front ---> Rear\n"); for(i = front; i != rear; i = ++i % MAX) printf("%-6d", queue[i]); } void main(void) { int i; init_queue(); printf("\nPut 5,4,7,8,2,1"); put(5); put(4); put(7); put(8); put(2); put(1); print_queue(); printf("\nGet"); i=get();
3.2.7 선형 큐(Queue) print_queue(); printf("\n getting value is %d", i); printf("\nPut 3,2,5,7"); put(3); put(2); put(5); put(7); print_queue(); printf("\nNow queue is full, put 3"); put(3); print_queue(); printf("\nInitialize queue"); clear_queue(); print_queue(); printf("\nNow queue is empty, get"); i = get(); print_queue(); printf("\n getting value is %d", i); }
3.2.7 원형 큐(Queue) • 원형 큐 • 원형 큐는 선형 큐의 문제점을 보완하기 위해 만들어진 자료구조 • 앞의 표에서 큐의 크기가 5라고 가정하고, front가 2이고 rear가 5인 마지막 상태에서 새로운 작업 J6을 큐에 입력할 경우 FULL이 발생되어 더 이상 입력할 수 없다. • 그러나 앞의 표를 보면 큐의 마지막 상태는 Q(1)과 Q(2)가 비어 있어서 자료를 더 입력 할 여분의 공간이 있다. 이러한 유효 공간을 활용하기 위한 방법으로 만들어진 것이 원형 큐이다.
Q(5) Q(5) … … Q(4) Q(4) D Q(n-4) Q(n-4) C Q(3) Q(3) A Q(n-3) Q(n-3) B B Q(2) Q(2) A C Q(n-2) Q(n-2) D Q(1) Q(1) Q(n-1) Q(n-1) Q(0) Q(0) front = 0 rear = 4 3.2.7 원형 큐(Queue) • 아래 그림에서 데이터 A, B, C, D가 원형 큐에 입력될 때 처음 입력된 데이터가 Q(1)에 들어가고 이때 front는 0이 된다. • Q()는 공백 조건을 위해 존재하는 것이기 때문에 n개의 원소가 아닌 (n-1)개의 원소만이 Q(1: n-1)에 저장될 수 있다. 결국 큐 내의 기억장소 하나를 손실하고 알고리즘의 수행속도를 개선시키는 효과가 있다. front = n-4 rear = 0
3.2.7 원형 큐(Queue) • 지금까지 설명한 원형 큐를 표현하기 위한 조건을 요약하면 다음과 같다. • 포인터가 큐의 끝점에 있을 때 삽입이나 삭제가 일어나면 포인터가 reset 되어 다시 시작점을 가리킨다 • front: 큐에 첫번째 원소로부터 반시계 방향으로 하나 앞에 위치한다. • rear : 큐에 마지막으로 삽입된 원소를 가리킨다. • 공백 조건: front = rear • FULL 조건: front = rear empty full