1 / 41

Chapter 3 스택과 큐 (Stack and Queue) 2009. 3. 30

Chapter 3 스택과 큐 (Stack and Queue) 2009. 3. 30. 스택 (Stack). 정의 top 이라고 하는 한쪽 끝에서 모든 삽입 (push) 과 삭제 (pop) 가 일어나는 순서 리스트 S = (a 0 , a 1 , …, a n-1 ), n  0 a 0 는 bottom, a n-1 은 top 의 원소 a i 는 원소 a i-1 (0<i<n) 의 위에 있음 후입선출 (Last-In-First-Out: LIFO) 리스트. a n-1. top. a n-2. …. a 1.

whitby
Download Presentation

Chapter 3 스택과 큐 (Stack and Queue) 2009. 3. 30

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Chapter 3스택과 큐(Stack and Queue) 2009. 3. 30

  2. 스택 (Stack) • 정의 • top 이라고 하는 한쪽 끝에서 모든 삽입(push)과 삭제(pop)가 일어나는 순서 리스트 • S = (a0, a1, …, an-1), n  0 • a0는 bottom, an-1은 top의 원소 • ai는 원소 ai-1(0<i<n)의 위에 있음 • 후입선출(Last-In-First-Out: LIFO) 리스트 an-1 top an-2 … a1 a0 bottom

  3. 스택 (Stack) • 스택에 원소 A,B,C,D,E를 삽입하고 한 원소를 삭제하는 예 top E top D D top D top C C C C B top B B B B A A A A A top A add (push) add add add add del (pop)

  4. 스택 (Stack) 이용 예 • 시스템 스택 • 프로그램 실행 시 함수 호출을 처리하는 방법 • 함수 호출시 스택 프레임(stack frame) 또는 활성 레코드(activation record)라는 구조를 생성하여 스택에 저장 • 복귀 주소: 호출자의 호출 다음 명령어에 대한 주소 • 함수가 종료된 후 실행되어야 할 명령문 위치 • 동적 링크: 호출자의 스택 프레임에 대한 포인터 • 함수 종료 시 스택 프레임 제거에 사용 • Stack Top 포인터를 이 값으로 갱신 • 매개 변수: 호출자가 제공한 값 또는 주소 • 지역 변수: 피호출 함수에서 정의된 변수 • 함수 복귀시 스택 프레임 삭제 • 순환 호출 (recursive call) • 순환 호출시마다 새로운 스택 프레임 생성 • 최악의 경우 가용 메모리를 모두 소모할 수 있음 Stack Top Local variables Parameters Dynamic link Return address Local variables Parameters Dynamic link Return address

  5. 스택 (Stack) 이용 예 • 함수 호출 구현 예 void C (int Q) { … } void A (int X) { int Y; … C(Y); } void B (float R) { int S, T; … A(S); } void main () { float P; … B(P); } 시스템 스택의 내용

  6. 스택 (Stack) ADT 정의 • 스택 추상 데이터 타입 structure Stack objects: 0개 이상의 원소를 가진 유한 순서 리스트 functions: 모든 stack∈ Stack, item∈ element, max_stack_size∈ positive integer Stack CreateS(max_stack_size) ::= 최대 크기가 max_stack_size인 공백 스택을 생성 Boolean IsFull(stack, max_stack_size) ::= if (stack의 원소수 == max_stack_size) return TRUE else return FALSE Stack Push(stack, item) ::= if (IsFull(stack)) stackFull else stack의 Top에 item을 삽입하고 return Boolean IsEmpty(stack) ::= if (stack의 원소수 == 0)) return TRUE else return FALSE Element Pop(stack) ::= if (IsEmpty(stack)) return else 스택 Top 의 item을 제거해서 반환

  7. 스택 (Stack) ADT 구현 • 스택 구현 • 일차원 배열 stack[MAX_STACK_SIZE] 사용 • i 번째 원소는 stack[i-1]에 저장 • 변수 top은 항상 스택의 최상위 원소를 가리키도록 함(초기화: top = -1; 공백스택을 의미함)

  8. 스택 (Stack) ADT 구현 • CreateS 와 stackFull의 구현 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 stackFull() { fprintf(stderr, “Stack is full, cannot add element”); exit(EXIT_FAILURE); }

  9. 스택 (Stack) ADT 구현 • Push 및 Pop 연산 구현 void push(element item) {/* 전역 stack에 item을 삽입 */ if (top >= MAX_STACK_SIZE-1) stackFull(); stack[++top] = item; } top = top + 1; stack[top] = item; 스택에 원소삽입 element pop() {/* stack의 최상위 원소를 삭제하고 반환 */ if (top == -1) return stackEmpty(); /* returns an error key */ return stack[top--]; } element e = stack[top]; top = top - 1; return e; 스택으로부터 삭제

  10. 동적 배열을 사용하는 스택 • 동적 배열을 이용 • 스택의 범위(MAX_STACK_SIZE)를 컴파일 시간에 알아야 하는 단점 극복 • 원소를 위해 동적으로 할당된 배열을 이용하고, 필요 시 배열의 크기를 증대시킴 Stack CreateS() ::= typedef struct { int key; /* 다른 필드 */ } element; element *stack; MALLOC(stack, sizeof(*stack)); int capacity = 1; int top = -1; Boolean IsEmpty(Stack) ::= top < 0; Boolean isFull(Stack) ::= top >= capacity-1; 초기 크기가 1인 동적할당 배열 stack을 사용

  11. 동적 배열을 사용하는 스택 • 스택 full에 대한 새로운 검사 이용 • MAX_STACK_SIZE를 capacity로 대체 • 함수 push 변경 • 함수 stackFull 변경 • 배열 stack의 크기를 확장시켜서 스택에 원소를 추가로 삽입할 수 있도록 함 • 배열 배가(array doubling) : 배열의 크기를 늘릴 필요가 있을 시 항상 배열의 크기를 두 배로 만듦 void stackFull() { REALLOC(stack, 2*capacity*sizeof(*stack)) capacity *= 2; }

  12. 참고: C++에서의 스택 ADT 구현 예 • stack 클래스 정의 #include <iostream.h> class stack { private: int* stackPtr; int maxLen; int topPtr; public: stack(int maxSize) { // constructor stackPtr = new int [maxSize]; maxLen = maxSize-1; topPtr = -1; } ~stack() { delete[] stackPtr; } //destructor void push(int number) { if (topPtr == maxLen) cerr << “Error in push - stack is full\n”; else stackPtr [++topPtr] = number; } void pop() { if (topPtr == -1) cerr << “Error in pop - stack is full\n”; else return stackPtr[topPtr--]; } int top() { return stackPtr[topPtr]; } int isEmpty() { return (topPtr == -1); } int isEull() { return (topPtr == maxLen); } }

  13. 참고: C++에서의 스택 ADT 구현 예 • stack 클래스 이용 void main() { int topOne; stack myStack(100); // stack 클래스의 instance 생성 … myStack.push(42); myStack.push(17); … topOne = myStack.top(); … topOne = myStack.pop(); … } Note: 스택 top이클래스 내에 private member로 정의되므로 외부에서 직접 이용할 수 없음 (스택 내부 구조에 대해 접근 불가)

  14. Ticket Box 큐(Queue) • 정의 • 한쪽 끝(rear)에서 삽입이 일어나고 그 반대쪽 끝(front)에서 삭제가 일어나는 순서 리스트 • Q = (a0, a1, …, an-1), n  0 • a0는 앞(front) 원소, an-1은 뒤(rear) 원소 • ai는 원소 ai-1(0i<n)의 뒤에 있다고 함 • 선입선출(First-In-First-Out: FIFO) 리스트 … an-2 an-1 a1 a0 front rear

  15. 큐(Queue) • 큐에 원소 A, B, C, D, E를 삽입하고 한 원소를 삭제하는 예 add B add A add B add C add D add E

  16. 큐(Queue)의 활용 • 응용 예 • 시뮬레이션의 대기열(공항에서의 비행기들, 은행에서의 대기열) • 통신에서의 데이터 패킷들의 모델링 • 프린터와 컴퓨터 사이의 버퍼링 • 많은 알고리즘에서 사용됨 data QUEUE 생산자 소비자

  17. 큐(Queue) ADT 정의 • 큐 추상 데이터 타입 structure Queue objects: 0개 이상의 원소를 가진 유한 순서 리스트 functions: 모든 queue∈Queue, item∈element,max_queue_size∈positive integer Queue CreateQ(max_queue_size) ::= 최대 크기가 max_queue_size인 공백 큐를 생성 Boolean IsFullQ(queue, max_queue_size ) ::= if (queue의 원소수 == max_queue_size) return TRUE else return FALSE Queue AddQ(queue, item) ::= if (IsFull(queue)) queueFull else queue의 뒤에 item을 삽입하고 이 queue를 반환 Boolean IsEmptyQ(queue) ::= if (queue의 원소수 == 0) return TRUE else return FALSE Element DeleteQ(queue) ::= if (IsEmpty(queue)) return else queue의 앞에 있는 item을 제거해서 반환

  18. 큐(Queue) ADT 구현 • 큐의 구현: 1차원 배열 이용 • 변수 front는 큐에서 첫 원소의 위치보다 하나 작은 위치 • 변수 rear는 큐에서 마지막 원소의 위치 • Note: IsFullQ() 구현은 앞의 ADT 명세와 다름 • rear = MAX_QUEUE_SIZE-1이나 front > -1 이 될 수 있기 때문 (즉, 큐의 원소수가 MAX_QUEUE_SIZE 보다 작음) 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 == rear Boolean IsFullQ(queue) ::= rear == MAX_QUEUE_SIZE-1

  19. 큐(Queue) ADT 구현 • Add 및 Delete 연산 구현 void addq(element item) {/* queue에 item을 삽입 */ if (rear == MAX_QUEUE_SIZE-1) queueFull(); queue[++rear] = item; } 큐에서의 삽입 element deleteq() {/* queue의 앞에 있는 원소를 삭제 */ if (front == rear) return queueEmpty(); /* returns an error key */ return queue[++front]; } 큐에서의 삭제

  20. 큐(Queue) ADT 구현 • 큐의 순차적 표현의 문제점 • 예제 3.2 [작업 스케쥴링] • 운영체제의 작업 큐(job queue): FCFS 스케쥴링 • 작업들이 큐에 들어가고 나옴에 따라 큐는 점차 오른쪽으로 이동  결국 rear 값이 MAX_QUEUE_SIZE-1과 같아져서 큐가 포화 상태가 됨

  21. 큐(Queue) ADT 구현 • queueFull() • 첫째 원소가 queue[0]에 가도록 전체 큐의 데이터를 왼쪽으로 이동(shift)  front를 -1로, rear 값도 조정 • 이러한 배열 이동에는 많은 시간이 소요됨  최악의 경우: O(MAX_QUEUE_SIZE) • 큐에 n개의 원소 J1,...,Jn이 있음 • 그 후 삽입과 삭제가 교대로 이루어짐  새 원소 삽입시마다 n-1개의 원소 가진 큐 전체 왼쪽 이동

  22. 원형 큐 (Circular Queue) • 순차적 큐의 문제 해결: 원형 큐 이용 • 배열 queue[MAX_QUEUE_SIZE]를 원형으로 취급 rear == MAX_QUEUE_SIZE-1이면, q[0]가 빈 경우 q[0]에 삽입 • 최대 원소 수: MAX_QUEUE_SIZE가 아니라 MAX_QUEUE_SIZE-1 front == rear일 경우, 포화상태인지 공백상태인지 구분하기 위해 하나의 공간은 항상 비워둠 rear rear rear C D C D C B B B A A front front front (a) 초기 (b) 삽입 (c) 삭제

  23. 원형 큐 (Circular Queue) • 초기상태: front = rear = 0 • 공백상태: front == rear • 포화상태: front =(rear+1) % MAX_QUEUE_SIZE 3 3 3 4 4 4 C D C D 2 2 2 5 5 5 B E B E F F A A 1 6 1 6 1 6 G G H 7 7 7 0 0 0 front front rear front rear rear (b) 포화상태 (c) 오류상태 (a) 공백상태

  24. 원형 큐 (Circular Queue) • 원형 큐 예 rear front rear front rear front rear front

  25. 원형 큐 (Circular Queue) • 원형 큐에 대한 삽입 연산 • rear 증가 (circular shift)if (rear == MAX_QUEUE_SIZE-1) k = 0;else k = rear+1;  k =(rear+1) % MAX_QUEUE_SIZE와 동등 • 포화 상태 검사(front == k), 포화 아니면 새 원소 삽입 void addq(element item) { /* queue에 item을 삽입 */ rear = (rear+1) % MAX_QUEUE_SIZE; if (front == rear) queueFull(); /* 오류 출력 후종료 */ queue[rear] = item; }

  26. 원형 큐 (Circular Queue) • 원형 큐에 대한 삭제 연산 • 먼저 empty 테스트 (front == rear), • front 이동: front = ++front % MAX_QUEUE_SIZE void deleteq() { element item; /* queue의 front 원소를 삭제하여 그것을 item에 놓음 */ if (front == rear) return queueEmpty(); /* 오류 키를 반환 */ front = (front+1) % MAX_QUEUE_SIZE; return queue[front]; }

  27. 동적 할당 배열을 이용하는 원형 큐 • 동적 할당 배열 사용 • capacity : 배열 queue의 위치 번호 • 원소 삽입 시, 배열 크기 확장해야 함 • 함수 realloc • 배열 배가 방법 사용 queue [0] [1] [2] [3] [4] [5] [6] [7] D C C D E F G A B E B front = 5, rear = 4 (b) 포화된 원형 큐를 펼쳐 놓은 모양 F A G [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10][11][12][13][14][15] C D E F G A B front = 5, rear = 4 rear = 4 front = 5 (c) 배열을 두 배로 확장한 뒤의 모양 (realloc을 이용하여 확장) (a) 포화된 원형 큐

  28. 동적 할당 배열을 이용하는 원형 큐  최대 복사 원소 수 : 2*capacity-2 [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10][11][12][13][14][15] C D E F G A B front = 13, rear = 4 (d)세그먼트를 오른쪽으로 이동한 뒤의 모양 [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10][11][12][13][14][15] A B C D E F G front = 15, rear = 6 (e) 같은 내용의 다른 모양 • 최대 복사 원소 수 capacity-1로 제한 (1) 크기가 두배 되는 새로운 배열 newQueue를 생성한다. (2) 두번째 부분(즉, queue[front+1]과 queue[capacity-1] 사이에 있는 원소들)을 newQueue의 0에서부터 복사해 넣는다. (3) 첫번째 부분(즉 queue[0]와 queue[rear]사이에 있는 원소들)을 newQueue의 capacity-front-1에서부터 복사해 넣는다.

  29. 동적 할당 배열을 이용하는 원형 큐 void queueFull() { /* 2배 크기의 배열을 할당 */ element* newQueue; MALLOC(newQueue, 2*capacity*sizeof(*queue)); /* queue로부터 newQueue로 복사 */ int start = (front+1) % capacity; if (start < 2) /* 둘러싸지 않음 */ copy(queue+start, queue+start+capacity-1, newQueue); else /* 큐가 주변을 둘러쌈 */ copy(queue+start, queue+capacity, newQueue); copy(queue, queue+rear+1, newQueue+capacity-start); } /* newQueue로 전환 */ front = 2*capacity-1; rear = capacity-2; capacity *= 2; free(queue); queue = newQueue; }

  30. add_rear add_front delete_rear delete_front get_front get_rear 전단(front) 후단(rear) 덱(deque) • double-ended queue • 큐의 전단(front)와 후단(rear)에서 모두 삽입과 삭제가 가능

  31. 덱(deque)의 연산 rear front rear front C A B D A add_front(dq, A) add_rear(dq, D) front rear rear front A B A B D add_rear(dq, B) delete_front(dq) rear rear front front A B C A B add_front(dq, C) delete_rear(dq)

  32. 큐의 응용: 버퍼(buffer) • 큐는 서로 다른 속도로 실행되는 두 프로세스 간의 상호 작용을 조화시키는 버퍼 역할을 담당 • CPU와 프린터 사이의 프린팅 버퍼, CPU와 키보드 사이의 키보드 버퍼 등 • 데이터를 생산하는 생산자 프로세스가 있고 데이터를 소비하는 소비자 프로세스가 있으며 이 사이에 큐로 구성되는 버퍼가 존재

  33. 큐의 응용: 시뮬레이션(simulation) • 큐잉 이론(queueing theory)에 따라 시스템의 특성을 시뮬레이션하여 분석하는 데 이용 • 큐잉 모델(queueing model)은 고객에 대한 서비스를 수행하는 서버와 서비스를 받는 고객들로 이루어진다 • 예 • 은행에서 고객이 들어와서 서비스를 받고 나가는 과정을 시뮬레이션 • 고객들이 기다리는 평균 대기 시간 등을 계산

  34. 스택 응용 1: 미로 탐색 • m x p 미로의 표현: 이차원 배열 maze[m+2][p+2] • 1 : 통로가 막혀 있음 • 0 : 통과할 수 있음 • 실제 미로의 길 위치 (i, j): 1im, 1jp 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 0 0 1 1 0 0 0 1 1 1 1 1 1 1 1 0 0 0 1 1 0 1 1 1 0 0 1 1 1 1 1 0 1 1 0 0 0 0 1 1 1 1 0 0 1 1 1 1 1 1 0 1 1 1 1 0 1 1 0 1 1 0 0 1 1 1 1 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 0 0 1 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 0 1 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 0 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 0 1 1 0 0 0 0 0 1 1 0 0 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 0 1 0 0 1 1 1 1 1 0 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 입구 출구 maze[m+2][p+2]

  35. 미로 탐색 • 현재의 위치 x : maze[i][j] 가능한 이동

  36. 미로 탐색 • 각 방향으로의 이동: move 배열 이용 typedef struct { short int vert; short int horiz; } offsets; offsetsmove[8]; /* 여덟 방향 이동에 대한 배열 */ maze[row][col]에서 dir 방향으로 이동시 다음 위치: maze[next_row][next_col], next_row = row + move[dir].vert; next_col = col + move[dir].horiz;

  37. 미로 탐색 • 경로 탐색 • 현위치를 저장 후 방향 선택 : N방향부터 시계 방향 순서대로 선택 • 잘못된 경로 선택 시, back-tracking 후 다음 방향 시도해야 함 • 위치 정보 저장 시 스택 이용 • 한 번 방문한 위치의 재방문 방지를 위해 보조 배열 mark[m+2][p+2] 이용 (bitmap) • 모든 값을 0으로 초기화 • maze[row][col] 방문 시 mark[row][col] = 1 로 변경 • 경로 유지 위한 스택 정의 #define MAX_STACK_SIZE 100 /* 스택의 최대 크기 */ typedef struct { short int row; short int col; short int dir; } element; elementstack[MAX_STACK_SIZE];

  38. 미로 탐색 • 미로 탐색 알고리즘 스택에 미로의 입구 좌표 저장, 방향(dir)은 N으로 초기화 while (stack is not empty) { /* 스택의 top에 있는 위치로 복귀 */ <rol, col, dir> = delete from top of stack; while (there are more moves from current position) { dir = next direction of move; <next_row, next_col> = coordinate of next move; if ((next_row == EXIT_ROW) && (next_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, next_dir> to the top of the stack; row = next_row; col = next_col; dir = north; } } } printf("No path found\n");

  39. 입구 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 1 0 0 0 0 0 0 0 1 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 1 0 0 0 0 0 0 0 1 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 출구 미로 탐색 • 스택의 최대 크기 • 미로의 각 위치는 최대 한 번씩만 방문 (값이 0인 위치) • m × p maze  최대 mp개의 원소 긴 경로를 가진 미로

  40. void path(void) { /* 미로를 통과하는 경로가 있으면 그 경로를 출력한다. */ int i, row, col, next_row, next_col, dir, found=FALSE; element position = { 1, 1, 0 }; mark[1][1]=1; top=-1; push(position); while (top>-1 && !found) { position = pop(); 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;             push(position); row = next.row; col = next.col; dir = 0; } else dir++; } }

  41. if (found) { printf("The path is:\n"); printf("row col\n"); for (i=0; i<=top; i++) printf("%2d%5d", stack[i].row, stack[i].col"); printf("%2d%5d\n", row, col); printf("%2d%5d\n", EXIT_ROW, EXIT_COL); } else printf("The maze does not have a path\n"); } • 시간 복잡도 • 미로의 각 위치는 최대 한 번씩만 방문 • 최악의 경우 연산시간 = O(mp)

More Related