520 likes | 898 Views
연결 리스트 (Linked List). 단순 연결 리스트 (Simple Linked List). 단순 연결 리스트 > 정의. 정보를 저정하는 노드와 바로 다음의 노드를 가리키는 링크 하나로 구성됨. 단순연결리스트의 노드 정의. Struct _node { int key; /* 정보저장 * / struct _node *next; /* 다음 노드의 위치 저장 * / };. 개선. Typedef struct _node { int key;
E N D
연결 리스트 (Linked List) 단순 연결 리스트 (Simple Linked List)
단순 연결 리스트 > 정의 • 정보를 저정하는 노드와 바로 다음의 노드를 가리키는 링크 하나로 구성됨 • 단순연결리스트의 노드 정의 Struct _node { int key; /* 정보저장 */ struct _node *next; /* 다음 노드의 위치 저장 */ }; 개선 Typedef struct _node { int key; struct _node *next; } node;
단순연결리스트 > 초기화 • Init_list() 함수 node *head, *tail; /* 전역변수로 정의 */ void init_list(void) { head = (node*) malloc(sizeof(node)); tail = (node*) malloc(sizeof(node)); head->next = tail; tail->next = tail; }
단순연결리스트 > 삽입 • insert_after() 함수 node *insert_after(int k, node* t) { node *s; s = (node *)malloc(sizeof(node)); s->key = k; s->next = t->next; t->next = s; return s; }
단순연결리스트 > 삭제 • delete_next() 함수 int delete_next(node *t) { node *s; if(t->next==tail) return 0; s = t->next; t->next = t->next->next; free(s); return 1; }
단순연결리스트 > 찾기 • find_node 함수 • 인자 k 의 값을 가지는 노드를 찾는다. • 호출측에서는 리턴값이 tail 인지 아닌지 검사하면 검색에 실패했는지 알수 있도록 한다. node *find_node(int k) { node *s; s = head->next; while(s->key != k && s != tail) s = s->next; return s; }
단순연결리스트 > 찾아서 삭제 • delete_node() 함수 int delete_node(int k) { node *s; /* 검색할 노드 */ node *p; /* s 가 가리키는 노드의 앞노드 */ p = head; s = p->next; while(s->key != k && s != tail) { p = p->next; /* p는 다음 노드로 */ s = p->next; /* s는 p의 다음 노드로 */ } if(s != tail) { /* 키 값을 찾음 */ p->next = s->next; /* p의 다음 노드는 s의 다음노드 */ free(s); /* s는 연결에서 빠진다(삭제) */ return 1; } else return 0; } }
단순연결리스트 > 찾아서 삽입 • insert_node() 함수 • 주어진 키 k 앞의 노드에 t 값을 갖는 노드를 삽입 node *insert_node(int t, int k) { /* k 앞에 t를 삽입 */ node *s; /* 키 검색을 따라가는 포인터*/ node *p; /* s의 앞 노드를 가리키는 포인터 */ node *r; /* 삽입하는 노드를 만들기 위한 포인터 */ p = head; s = p->next; while(s->key != k && s != tail) { p = p->next; s = p->next; } if(s != tail){ /* 찾았으면 */ r = (node *)malloc(sizeof(node)); r->key = t; p->next = r; r->next = s; } return p->next; /* 삽입된 노드의 주소값 */ }
단순연결리스트 > 동적인 정렬 • ordered_sort() • 지금의 연결리스트가 오름차순으로 정렬되어 있다고 가정함 • 주어진 키 값에 대해서 정렬순서를 깨지 않도록 삽입 node *ordered_insert(int k) { node *s; /* 검색을 따르는 포인터 */ node *p; /* s의 앞노드를 가리키는 포인터 */ node *r; /* p와 s 사이에 삽입될 노드의 포인터 */ p = head; s = p->next; while(s->key <= k && s != tail) { /* k가 들어갈 장소를 찾음 */ p = p->next; s = p->next; } r = (node *)malloc(sizeof(node)); r->key = k; p->next = r; r->next = s; return r; }
단순연결리스트 > 출력 • print_list() 함수 void print_list(node* t) { printf(“\n”); while(t != tail) { printf(“%-8d”, t->key); t = t->next; } }
단순연결리스트 > 모든요소 삭제 • delete_all() 함수 node *delete_all(void) { node *s; node *t; t = head->next; /* 머리 다음부터 삭제 */ while( t != tail) { s = t; /* s는 삭제할 다음 노드를 물고 있음 */ t = t->next; /* t는 다음 노드로 */ free(s); } head->next = tail; return head; }
단순연결리스트 > 정리 #include<stdio.h> typedef struct_node { int key; struct _node *next; } node; node *head, *tail; void main(void) { node *t; init_list(); ordered_insert(10); ordered_insert(5); ordered_insert(8); ordered_insert(3); ordered_insert(1); ordered_insert(7); ordered_insert(8); printf(“\nInitial Linked List is “); print_list(head->next); printf(“\nFinding 4 is %ssuccessful “, find_node(4) == tail ? “un” : “”); t = fined_node(5); printf(“\nFinding 5 is %ssuccessful “, t==tail ? “un” : “”); printf(“\nInserting 9 after 5”); insert_after(9, t); print_list(head->next);
단순연결리스트 > 정리(계속) t = find_node(10); printf(“\nDeleting next last node”); delete_next(t); print_list(head->next); t=find_node(3); printf(“\nDeleting next 3”); delete_next(t); print_list(head->next); printf(“\nInsert node 2 before 3”); insert_node(2, 3); print_list(head->next); printf(“\nDeleting node 2”); if(!delete_node(2)) printf(“\n deleting 2 is unsuccessful “); printf_list(head->next); printf(“\nDeleting node 1”); delete_node(1); print_list(head->next); printf(“\nDeleting all node”); delete_all(); print_list(head->next); }
연결 리스트(Linked List) 환형 연결 리스트(Circular Linked List)
환형 연결 리스트 > 개념 • 환형 연결 리스트 • 단순 연결 리스트와 같은 노드 구조를 가지고 있다. • 제일 마지막 노드는 가장 처음의 노드를 가리키고 있다. • tail 이라는 개념이 없다.
환형연결리스트 > 문제 제시 • 요셉의 문제 • A 부터 J 까지의 10명의 사람이 시계 방향 순서대로 원을 지어 앉아있다. • A 부터 시작하여 4명 간격으로 사람을 그 원에서 뽑아낸다고 하면 그 순서는 어떻게 될 것인가? • 프로그램은 사용자로부터 키보드로 사람의 수 n 과 간격 step 을 입력받아 사람의 수만큼 1 부터 n까지의 정수를 키로 가지는 노드를 환형 연결리스트로 구성한다. • 다음에 처음의 노드로부터 시작하여 step만큼 이동한 다음 그 위치의 노드를 삭제하고, 다음에 또 step 만큼 이동하고 그 노드를 삭제하는 식으로 계속하여 노드가 하나도 남지 않을 때까지 반복한다.
환형연결리스트 > Code 1 #include<stdio.h> #include<stdlib.h> typedef struct _node { int key; struct _node *next; } node; node *head; void insert_nodes(int k) { /* 1부터 k 까지의 값을 가지는 환형 연결리스트 구성 */ node *t; int i; t = (node *)malloc(sizeof(node)); t->key = 1; head = t; /* 연렬 리스트의 시작점 */ for(i=2 ; i<=k ; i++) { t->next = (node *)malloc(sizeof(node)); t = t->next; t->key = i; } t->next = head; } void delete_after(node *t) { /* t 다음의 노드를 삭제 */ node *s; s = t->next; t->next = t->next->next; free(s); }
환형연결리스트 > Code 2 void josephus(int n, int m) { /* 요셉의 문제를 풂, n 개의 노드를 m 간격으로 */ node *t; int i; insert_nodes(n); /* 환영 연결 리스트를 구성 */ t = head; printf("\nAnswer : "); while( t != t->next){ /* 연결 리스트가 남아있을 동안 */ for(i=0 ; i<m-1 ; i++) t = t->next; printf("%d ", t->next->key); delete_after(t); /* 출력하고 삭제 */ } printf("%d", t->key); /* 마지막 노드 출력 */ } void main(void) { int n, m; printf("\nIf you want to quit, enter 0 or minus value"); while(1) { printf("\nEnter N and M -> "); scanf("%d %d", &n, &m); if(n<=0 || m<=0) return; josephus(n, m); } }
이중 연결 리스트 • 장단점 • 바로 전의 노드에도 접근할 수 있다. • 노드당 2~4 바이트 정도가 더 소모된다. • 함수들이 일관된 방식을 가지고 있다. • 삽입 삭제시 좀더 복잡하다. Typedef struct _dnode { int key; struct _dnode *prev; struct _dnode *next; } dnode;
Init_dlist() 함수 Dnode *head, *tail; Void init_dlist(void) { head = (dnode *)malloc(sizeof(dnode)); tail = (dnode *)malloc(sizeof(dnode)); head->next = tail; head->prev = head; tail->next = tail; tail->prev = head; } 그림
Insert_dnode_ptr() 함수 • 포인터 t의 앞에 k를 가지는 노드를 하나 삽입 dnode * insert_dnode_ptr(int k, dnode *t) { /* t 앞에 k를 삽입 */ dnode *i; // 삽입될 노드 if(t==head) //머리 앞에는 아무것도 삽입할 수 없다. return NULL; i = (dnode *)malloc(sizeof(dnode)); i->key = k; t->prev->next = i; i->prev = t->prev; t->prev = i; i->next = t; return i; }
delete_dnode_ptr() 함수 • 주어진 노드의 포인터 p를 이중 연결 리스트에서 삭제한다. int delete_dnode_ptr(dnode *p) { if (p==head || p==tail) return 0; /* 머리나 꼬리는 지울 수 없다 */ p->prev->next = p->next; p->next->prev = p->prev; free(p); return 1; }
find_dnode() 함수 dnode *find_dnode(int k) { dnode *s; s = head->next; while(s->key != k && s != tail) s = s->next; return s; }
delete_dnode() 함수 • k 값을 찾아서 해당 노드를 삭제한다. • find_dnode() 함수를 사용한다. int delete_dnode(int k) { dnode *s; s = find_dnode(k); if(s != tail) { /* s가 tail 이 아니면 찾은 것이다 */ s->prev->next = s->next; s->next->prev = s->prev; free(s); return 1; } return 0; }
insert_dnode() 함수 • t 값을 가지는 노드를 찾아서 그 앞에 k값을 가지는 노드를 삽입 dnode *insert_dnode(int k, int t) { dnode *s; dnode *i = NULL; s = find_node(t); if(s != tail) { /* 찾았다면 */ i = (dnode *)malloc(sizeof(dnode)); i->key = k; s->prev->next = i; i->prev = s->prev; s->prev = i; i->next = s; } return i; /* 못찾았으면 NULL 을 리턴한다 */ }
ordered_insert() 함수 • 입력되는 정수들을 오름차순으로 정렬된 상태로 삽입 dnode *ordered_insert(int k) { dnode *s; dnode *i; s = head->next; while(s->key <= k && s != tail) s = s->next; i = (dnode*)malloc(sizeof(dnode)); i->key = k; s->prev->next = i; i->prev = s->prev; s->prev = i; i->next = s; return i; }
단순연결리스트의 응용 명함관리
명함관리 프로그램 • NAMECARD.C • 개선사상 • gets() 함수를 이용한 입력은 배열의 크기를 넘는 문자열을 입력받을 수 있다. • 명함의 입력시 순서를 유지하면서 노드를 삽입하게 하면 이진검색을 사용할 수 있다.
스택의 개념 • 개념
스택의 구현(1) • 구현 #define MAX 10 int stack[MAX]; // 스택의 긴 통 int top; // 스택의 상단 void init_stack(void) { top = -1; } int push(int t) { if(top >= MAX-1) //스택이 꽉 찼는가? { printf("\n Stack overflow."); return -1; } stack[++top] = t; return t; } int pop(void) { if(top < 0) //스택이 텅 비었는가? { printf("\n Stack underflow."); return -1; //에러 표시 } return stack[top--]; }
스택의 구현(2) 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); 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("\nNow stack is full, push 3"); push(3); print_stack(); printf("\nInitialize stack"); init_stack(); print_stack(); printf("\nNow stack is empty, pop"); i = pop(); print_stack(); printf("\n popping value is %d", i); }
연결리스트를 이용한 스택의 구현 int pop(void) { node *t; int 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; } void clear_stack(void) { node *t, *s; t = head->next; while(t != tail) { s = t; t = t->next; free(s); } head->next = tail; } 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; } } typedef struct _node { int 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; } int push(int 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; }
수식의 표기법 • 중위표기법(Infid notation) • (A + (B * C)) • 후위표기법(Prefix notation ) • A B C * + • 우선순위를 고려하지 않은 중위표기법을 후위표기법으로 변환하는 방법 • ‘(‘문자는 무시하고 넘어간다 • 피연산자는 그대로 출력한다 • 연산자는 스택에 푸시한다 • ‘)’를 만나면 스택에서 팝하여 출력한다.
우선선위를 고려하지 않은 구현 • postfix1() 함수 void postfix1(char *dst, char *src) { char c; init_stack(); /* 스택 초기화 */ while(*src) { /* 중위표기법의 수식이 남아있는 동안 */ if(*src == ')') { *dst++ = pop(); *dst++ = ' '; /* 문자의 구분을 위해 출력 */ src++; } else if(*src=='+' || *src=='-' || *src=='*' || *src=='/') { push(*src); src++; } else if(*src>='0' && *src<='9') { do { *dst++ = *src++; }while(*src>='0' && *src<='9'); *dst++ = ' '; } else src++; } *dst = 0; }
우선순위 고려 • 우선순위를 고려한 중위표기법을 후위표기법으로 변환하는 방법 • ‘(‘를 만나면 스택에 푸시한다. • ‘)’를 만나면 스택에서 ‘(‘가 나올 때까지 팝하여 출력하고 ‘(‘는 팝하여 버린다. • 연산자를 만나면 스택에서 그 연산자보다 낮은 우선순위의 연산자를 만날 때까지 팝하여 출력한뒤 자신을 푸시한다. • ‘(‘ = 0 • +, - = 1 • *, / = 2 • 피연산자는 그냥 출력한다. • 오든 입력이 끝나면 스택에 있는 연산자들을 모두 팝하여 버린다. • (2*(3+6/2)+2)/4+3 2 3 6 2 / + * 2 + 4 / 3 +
연습문제 • 다음의 수식을 후위표기법으로 변환하세요. • 2 + 3 * 4 • ((5+4*7)+3)/7 • (3+4)*(4-7) • ((3+4)*(4-7))+(2+7*8/7)
우선순위를 고려한 구현 void postfix(char *dst, char *src) { char c; init_stack(); /* 스택의 초기화 */ while(*src) { if(*src == '(') { /* 스택에 푸시 */ push(*src); src++; } else if(*src==')') { /* (가 나올 때까지 팝 */ while(get_stack_top() != '(') { *dst++ = pop(); *dst++ = ' '; } pop(); /* (는 버린다 */ src++; } else if(is_operator(*src)) { /* 연산자 이면 */ while(!is_stack_empty() && precedence(get_stack_top()) >= precedence(*src)) { /* 우선순위가 높은 연산자들은 모두 팝 */ *dst++ = pop(); *dst++ = ' '; } push(*src); src++; } else if(*src>='0' && *src<='9') { /* 피연산자는 그냥 출력 */ do { *dst++ = *src++; }while (*src>='0' && *src<='9'); *dst++ = ' '; } else src++; } while(!is_stack_empty()) { /* 모두 끝났으면 스택에 있는 내용을 모두 팝 */ *dst++ = pop(); *dst++ = ' '; } dst--; *dst = 0; }
수식의 평가 int cal(char *p) { int i; init_stack(); while (*p) { if(*p>='0' && *p<='9') { i=0; do { i=i*10 + *p-'0'; p++; }while(*p>='0' && *p<='9'); push(i); } else if(*p=='+') { push(pop() + pop()); p++; } else if(*p=='*') { push(pop() * pop()); p++; } else if(*p == '-') { /* 교환법칙이 성립하지 않는 연산자들 */ i = pop(); push(pop()-i); p++; } else if(*p=='/') { i=pop(); push(pop()/i); p++; } else p++; } return pop(); }
큐의 개념 • 개념
큐의 구현(배열) • 배열을 이용한 큐의 구현 #define MAX 10 int queue[MAX]; int front, rear; 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; }
큐의 구현(연결리스트) • 이중연결리스트 이용 • 단순연결리스트를 이용할 경우 get은 자연스러우나 • put 동작의 꼬리노드의 앞에 새로운 노드를 삽입해야 한다. • 노드의 정의 typedef struct _dnode { int key; struct _dnode *prev; struct _dnode *next; } dnode; dnode *head, *tail;
큐의 초기화 • init_queue() 함수 void init_queue(void) { head = (dnode *)malloc(sizeof(dnode)); tail = (dnode *)malloc(sizeof(dnode)); head->prev = head; head->next = tail; tail->prev = head; tail->next = tail; }
put 동작 • put() 함수 int put(int k) { dnode *t; if((t=(dnode *)malloc(sizeof(dnode)) == NULL) { /* 메모리가 다 되었으면 */ printf(“\n Out of memory.”); return –1; } t->key = k; tail->prev->next = t; /* t를 꼬리의 앞에 삽입함 */ t->prev = tail->prev; tail->prev = t; t->next = tail; return k; }
get 동작 • get() 함수 int get(void) { dnode *t; int i; t = head->next; if(t==tail) { printf(“\n Queue underflow.”); return –1; } i = t->key; /* 리턴 할 값을 물려둠 */ head->next = t->next; /* 머리 다음 노드를 삭제 */ t->next->prev = head; free(t); return i; }
큐의 모든 내용을 삭제 • clear_queue() 함수 void clear_queue(void) { dnode *t; dnode *s; t = head->next; while(t!=tail) { s = t; /* 삭제를 위해 물려둠 */ t = t->next; /* t는 다음으로 넘어감 */ free(s); } head->next = tail; tail->prev = head; }