270 likes | 491 Views
자료 구조. 제 3 장 : 스택과 큐 이 형 원 강릉대학교 컴퓨터공학과. 학습 내용. 스택 ADT 큐 ADT 스택의 사용 예제 미로 문제 수식 계산 다중 스택과 큐. A. E D C B A. B A. C B A. D C B A. D C B A. top. top. top. top. top. top. 스택 (stack). 한쪽 끝 (top) 에서만 삽입 / 삭제가 되는 순서 리스트 Last-In-First-Out(LIFO)
E N D
자료 구조 제 3 장 : 스택과 큐 이 형 원 강릉대학교 컴퓨터공학과
학습 내용 • 스택 ADT • 큐 ADT • 스택의 사용 예제 • 미로 문제 • 수식 계산 • 다중 스택과 큐
A E D C B A B A C B A D C B A D C B A top top top top top top 스택(stack) • 한쪽 끝(top)에서만 삽입/삭제가 되는 순서 리스트 • Last-In-First-Out(LIFO) • 사용 예 : 시스템 스택
스택 ADT: structure Stack objects: 0개 이상의 원소를 가진 유한 순서 리스트 functions:stack∈Stack, item∈element, max_stack_size∈양정수 Stack CreateS(max_stack_size) ::= 최대 크기 max_stack_size인 공백 스택 생성Boolean IsFull(stack, max_stack_size) ::= if (stack의 원소수 == max_stack_size) return TRUE else return FALSEStack Add(stack, item) ::= if (IsFull(stack)) stack_full else stack top에 item을 삽입하고 returnBoolean IsEmpty(stack) ::= if (stack == CreateS(max_stack_size)) return TRUE else return FALSEElement Delete(stack) ::= if (IsEmpty(stack)) return else stack top의 item을 제거해서 반환
스택의 구현 Stack CreateS(max_stack_size) ::= #define MAX_STACK_SIZE 100 /*최대스택크기*/ typedef struct { int key; /* 다른 필드 */ } element; element stack[MAX_STACK_SIZE]; int top = -1;Boolean IsEmpty(Stack) ::= top < 0;Boolean IsFull(Stack) ::= top >= MAX_STACK_SIZE-1;void add(int *top, element item) { element delete(int *top) { if (*top >= MAX_STACK _SIZE-1) { if (*top == -1) stack_full(); return stack_empty(); return; return stack[(*top)--];} } } stack[++*top] = item;}
A B A C B A D C B A D C B rear rear rear front front rear front front rear front 큐(queue) • 한쪽 끝(rear)에서 삽입, 반대쪽 끝(front)에서 삭제가 일어나는 순서 리스트 • First-In-First-Out(FIFO)
큐 ADT: structure Queue objects: 0개 이상의 원소를 가진 유한 순서 리스트 functions: queue∈Queue, item∈element, max_queue_size∈양 정수Queue CreateQ(max_queue_size) ::=최대 크기가 max_queue_size인 공백 큐를 생성Boolean IsFullQ(queue, max_queue_size ) ::= if (queue의 원소수 == max_queue_size) return TRUE else return FALSEQueue AddQ(queue,item) ::= if (IsFull(queue)) queue_full else queue 뒤에 item 삽입하고 반환Boolean IsEmptyQ(queue) ::= if (queue == CreateQ(max_queue_size)) return TRUE else return FALSEElement DeleteQ(queue) ::= if (IsEmpty(queue)) return else queue 앞에 있는 item을 제거해서 반환
큐의 구현: 순차 배열 Queue CreateQ(max_queue_size) ::= #define MAX_QUEUE_SIZE 100 /* 큐의 최대크기 */ typedef struct { int key; /* 다른 필드 */ } element; element queue[MAX_QUEUE_SIZE];int rear = -1; int front = -1;Boolean IsEmptyQ(queue) ::= front == rearBoolean IsFullQ(queue) ::= rear == MAX_QUEUE_SIZE-1void addq(int *rear, element item) { element deleteq(int *front, int rear) { if (*rear == MAX_QUEUE_SIZE-1) { if (*front == rear) queue_full(); return queue_empty(); return; return queue[++*front]; } } queue[++*rear] = item;}
큐의 구현: 순차 배열(계속) • 단점: rear = MAX_QUEUE_SIZE - 1이 되는 경우 • 전체 큐를 왼쪽으로 이동, front=-1, rear 조정 Q. 시간 복잡도 ? • 예제 [작업 스케쥴링] : O.S.에 의한 작업 큐의 생성 front rearQ[0] Q[1] Q[2] Q[3]설 명 -1 -1 공백큐 -1 0 J1 Job 1의 삽입 -1 1 J1 J2 Job 2의 삽입 -1 2 J1 J2 J3 Job 3의 삽입 0 2 J2 J3 Job 1의 삭제 1 2 J3 Job 2의 삭제
큐의 구현: 원형 배열 • 초기화: front = rear = 0 • 공백 큐: front = rear • 포화 큐: 원소 수 = MAX_QUEUE_SIZE-1 Q. Why ?
큐의 구현: 원형 배열(계속) void addq(int *front, int *rear, element item) {*rear = (*rear+1) % MAX_QUEUE_SIZE; if (front == *rear) { queue_full(rear); /* rear를 리세트시키고 에러를 프린트 */ return; } queue[*rear] = item;} void deleteq(int *front, int rear) { element item; /* queue의 front 원소를 삭제하여 그것을 item에 놓음 */ if (*front == rear) return queue_empty(); *front = (*front+1) % MAX_QUEUE_SIZE; return queue[*front];}
미로 문제 : 자료 구조 • m x p 미로: 2차원 배열 maze[m+2][p+2] • maze[row][col] = 0(path) or 1(no path) • 가장 자리는 모두 1 • 입구 maze[1][1], 출구 maze[m][p] • 이동 방향: 미리 정의된 이동 테이블 move[8] • 구조 typedef struct { short int vert;short int horiz; } offsets; offsets move[8];
미로 문제 : 자료 구조(계속) • move[8]의 내용 name dir move[dir].vert move[dir].horiz N 0 -1 0 NE 1 -1 1 E 2 0 1 SE 3 1 1 S 4 1 0 SW 5 1 -1 W 6 0 -1 NW 7 -1 -1 • 현 위치 maze[row][col] 이동 위치 maze[next_row][next_col] next_row = row + move[dir].vert next_col = col + move[dir].horiz
미로 문제 : 자료 구조(계속) • 시도한 경로의 재시도 방지: mark[m+2][p+2] • mark[row][col] = 1 /* 한번 방문한 경우 (지나간 경로)*/ • 경로의 탐색 현위치 저장 후 방향 선택 : N에서 시계방향 잘못된 경로의 선택 시는 backtracking후 다른 방향 시도 • 시도한 경로의 유지: 스택 element stack[MAX_SIZE]; #define MAX_SIZE 100 /* 스택의 최대 크기 */ typedef struct { Q. 스택 크기의 최대값 ? short int row; short int col; short int dir; } element;
미로 문제 : 초기 미로 알고리즘 initiallize a stack to the maze's entrance coordinates and direction to north;while (stack is not empty) { /* 스택의 톱에 있는 위치로 이동*/ <rol, col, dir> = delete from top of stack; while (there are more moves from current position) { <next_row, next_col> = coordinate of next move; dir = direction of move; if ((next_row == EXIT_ROW) && (exit_col == EXIT_COL)) success; if (maze[next_row][next_col] == 0) && mark[next_row][next_col] == 0){ /* 가능하지만 아직 이동해보지 않은 이동 방향 */ mark[next_row][next_col] = 1; /* 현재의 위치와 방향을 저장 */ add <row, col, dir> to the top of the stack; row = next_row; col = next_col; dir = north; } }}printf("No path found");
미로 문제 : 미로 탐색 함수 void path(void) {int i, row, col, next_row, next_col, dir, found=FALSE;element position; mark[1][1]=1; top=0;stack[0].row=1; stack[0].col=1; stack[0].dir=1;while (top>-1 && !found) { position=delete(&top); row=position.row; col=position.col; dir=position.dir; while (dir<8 && !found) { next_row = row + move[dir].vert; /* dir 방향으로 이동 */ next_col = col + move[dir].horiz; if (next_row==EXIT_ROW && next_col==EXIT_COL) found = TRUE; else if (!maze[next_row][next_col] && !mark[next_row][next_col]) { mark[next_row][next_col]) = 1; position.row = row; position.col = col; position.dir = ++dir; add(&top, position); row = next.row; col = next.col; dir = 0; } else ++dir; }} …… } Q. 복잡도 ?
수식의 계산 • C의 연산자 우선 순위 계층 토큰 우선순위 결합성 --, ++ 16 LR &, *, unary - + 15 RL *, /, % 13 LR +, - 12 L R >, >=, <, <= 10 LR ==, != 9 LR && 5 L R || 4 LR
수식의 계산(계속) • 수식의 표현 중위 (infix) 표기 후위 (postfix) 표기 전위 (prefix) 표기 a * b / c a b * c / / * a b c (1 + 2) * 7 1 2 + 7 * * + 1 2 7 • 후위 표기식의 장점 : 컴파일러(기계)에게 편리 • 괄호 및 연산자간의 우선 순위가 불필요 • 한 번만 수식을 scan하면서 스택을 이용하여 쉽게 계산 중위 표기식을 후위 표기식으로 변환 후 계산
후위 표기식의 계산 • algorithm (예 : 3.14) L-->R로 scan피연산자 : 스택에 삽입연산자 : 필요한 피연산자 수만큼 스택에서 가져옴 연산 수행 후 결과를 스택에 저장식의 끝 : 스택의 top에서 해답을 가져옴
후위 표기식 처리 함수 typedef enum {lparen,rparen,plus,minus,times,divide,mod,eos,operand} precedence;int stack[MAX_STACK_SIZE]; /* 전역 배열 */char expr[MAX_EXPR_SIZE]; /* 입력 스트링 */int eval(void) { precedence token; char symbol; int op1,op2, top=-1, n=0; token = get_token(&symbol, &n); while (token != eos) { if (token == operand) add(&top, symbol-'0'); / *스택 삽입 */ else { op2 = delete(&top); op1 = delete(&top); switch(token) { case plus : add(&top, op1+op2); break; case minus : add(&top, op1-op2); break; …… case mod : add(&top, op1%op2); } } token = get_token(&symbol, &n); } return delete(&top); /* 결과를 반환 */}
후위 표기식 처리 함수(계속) precedence get_token (char *symbol, int *n) {/* 다음 토큰을 취한다. symbol은 문자 표현이며, token은 그것의 열거된 값으로 표현되고, 명칭으로 반환된다. */*symbol = expr[(*n)++];switch (*symbol) { case '(' : return lparen; case ')' : return rparen; case '+' : return plus; case '-' : return minus; case '/' : return divide; case '*' : return times; case '%' : return mod; case ' ' : return eos; default : return operand; } }
중위 표기 후위 표기 • 수작업에 의한 중위->후위 변환 ① 식을 괄호로 묶음 ② 연산자를 오른쪽 괄호와 대치 ③ 괄호 삭제 • 예. 전위 표기 : a / b - c + d * e - a * c ((((a / b) - c) + (d * e)) - (a * c)) ((((a b) / c) - (d e) *) + (a c) *) - 후위 표기 : a b / c - d e * + a c * - ==> 컴퓨터 처리로는 비효율적 (∵ 2 pass가 필요 ) • How to infix to postfix in one pass ? • 피연산자들의 순서: 중위/후위 동일 scan하면서 바로 출력 가능 • 연산자 순서: 높은 우선 순위가 먼저 출력 스택을 이용
중위 표기 후위 표기(계속) • 중위 후위 변환의 핵심 1. token = 피연산자: 출력 2. token = ‘)’: (가 나올 때까지 스택의 연산자 출력 3. 우선순위(top) ≥ 우선순위(token): 스택의 연산자 출력 4. 우선순위(top) < 우선순위(token): 스택에 token 삽입 주의. ‘)’: 스택에 넣지 않음 ‘(‘ : 스택 밖에서는 가장 높은 우선 순위 but 스택 안에서는 가장 낮은 우선 순위Q. Why ?
중위 표기 후위 표기(계속) • 중위 표기식 a*(b+c)*d의 후위 표기식으로의 변환 토큰 스택 Output [0] [1] [2] a a * * a( * ( a b * ( ab + * ( + abc * ( + abc ) * abc+ * * abc+*d * abc+*d eos abc+*d*
중위 표기 후위 표기(계속) precedence stack[MAX_STACK_SIZE]; static int isp[] = {0, 19, 12, 12, 13, 13, 13, 0}; /* 연산자 ( ) + - * / % eos의 우선순위 값 */ static int icp[] = {20, 19, 12, 12, 13, 13, 13, 0}; void postfix(void) { char symbol; precedence token; int n = 0; int top = 0; stack[0] = eos; for (token==get_token(&symbol,&n); token!=eos; token==get_token(&symbol,&n)) { if (token == operand) printf("%c", symbol); else if (token == rparen) { while (stack[top] != lparen) print_token(delete(&top)); delete(&top); /* 좌괄호를 버린다 */ } else { /* symbol의 isptoken의 icp :symbol을 제거하고 출력 */ while (isp[stack[top]] >= icp[token]) print_token(delete(&top)); add(&top, token); } } while ((token=delete(&top)) != eos) print_token(token); printf(”\n");}
다중 스택 • 2 스택 : 각 스택의 최하단 원소를 양끝으로 • n (>2) 스택 • 이용 가능한 기억장소를 n개의 세그먼트로 분할 • 예상 크기를 아는 경우 : 크기에 따라 분할 • 예상 크기를 모르는 경우 : 똑같이 분할 • Memory[m]에서 n개 스택의 초기 구성 Top[0] Top[1] 0 1 m/n 2 m/n m-1 …. …. …. Boundary[1] Top[1] Boundary[2] Top[2] Boundary[n] Boundary[0] Top[0]
다중 스택 함수 #define MEMORY_SIZE 100 /* memory 크기 */#define MAX_STACKS 10 /* 가능한 스택의 최대수 + 1 */element memory[MEMORY_SIZE];int top[MAX_STACKS];int boundary[MAX_STACKS];void create(int n) { /* n :사용자가 입력한 스택의 수 */ top[0] = boundary[0] = -1; for (i = 1; i < n; i++) top[i] = boundary[i] = (MEMORY_SIZE/n) * i; boundary[n] = MEMORY_SIZE -1;}void add(int i, element item) { /* item을 i번째 스택에 삽입 */ if (top[i] == boundary[i+1]) stack_full(i); Q. How to implement ? memory[++top[i]] = item;} element delete(int i) { /* i번째 스택에서 top 원소를 제거 */ if (top[i] == boundary[i]) return stack_empty(i); return memory[top[i]--];}