728x90

균형잡힌 세상 

한국어   
 
시간 제한메모리 제한제출정답맞힌 사람정답 비율
1 초 128 MB 127782 43201 33559 32.668%

문제

세계는 균형이 잘 잡혀있어야 한다. 양과 음, 빛과 어둠 그리고 왼쪽 괄호와 오른쪽 괄호처럼 말이다.

정민이의 임무는 어떤 문자열이 주어졌을 때, 괄호들의 균형이 잘 맞춰져 있는지 판단하는 프로그램을 짜는 것이다.

문자열에 포함되는 괄호는 소괄호("()") 와 대괄호("[]")로 2종류이고, 문자열이 균형을 이루는 조건은 아래와 같다.

  • 모든 왼쪽 소괄호("(")는 오른쪽 소괄호(")")와만 짝을 이뤄야 한다.
  • 모든 왼쪽 대괄호("[")는 오른쪽 대괄호("]")와만 짝을 이뤄야 한다.
  • 모든 오른쪽 괄호들은 자신과 짝을 이룰 수 있는 왼쪽 괄호가 존재한다.
  • 모든 괄호들의 짝은 1:1 매칭만 가능하다. 즉, 괄호 하나가 둘 이상의 괄호와 짝지어지지 않는다.
  • 짝을 이루는 두 괄호가 있을 때, 그 사이에 있는 문자열도 균형이 잡혀야 한다.

정민이를 도와 문자열이 주어졌을 때 균형잡힌 문자열인지 아닌지를 판단해보자.

입력

각 문자열은 마지막 글자를 제외하고 영문 알파벳, 공백, 소괄호("( )"), 대괄호("[ ]")로 이루어져 있으며, 온점(".")으로 끝나고, 길이는 100글자보다 작거나 같다.

입력의 종료조건으로 맨 마지막에 온점 하나(".")가 들어온다.

출력

각 줄마다 해당 문자열이 균형을 이루고 있으면 "yes"를, 아니면 "no"를 출력한다.

예제 입력 1 복사

So when I die (the [first] I will see in (heaven) is a score list).
[ first in ] ( first out ).
Half Moon tonight (At least it is better than no Moon at all].
A rope may form )( a trail in a maze.
Help( I[m being held prisoner in a fortune cookie factory)].
([ (([( [ ] ) ( ) (( ))] )) ]).
 .
.

예제 출력 1 복사

yes
yes
no
no
no
yes
yes

힌트

7번째의 " ."와 같이 괄호가 하나도 없는 경우도 균형잡힌 문자열로 간주할 수 있다.

 

 

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char check(char* start, char* end) {
	int check_small = 0;
	int check_big = 0;
	char* position_small = NULL;
	char* position_big = NULL;

	char* small_history[101] = { NULL };
	char* big_history[101] = { NULL };
	int small_history_index = 0;
	int big_history_index = 0;

	
	for (char* i = start; i <= end; i++) {
		if (*i == '(') {
			check_small++;
			position_small = i;
			small_history[small_history_index++] = position_small;
		}
		else if (*i == '[') {
			check_big++;
			position_big = i;
			big_history[big_history_index++] = position_big;
		}
		else if (*i == ')') {
			check_small--;
			if (check_small < 0 || check_big < 0) {
				return 0;
			}
			if (check(position_small + 1, i - 1) == 0)
				return 0;
			if (small_history_index >= 2) {
				small_history_index--;
				position_small = small_history[small_history_index-1];
			}
				
			else
				position_small = NULL;
			
		}
		else if (*i == ']') {
			check_big--;
			if (check_small < 0 || check_big < 0) {
				return 0;
			}
			if (check(position_big + 1, i - 1) == 0)
				return 0;
			if (big_history_index >= 2) {
				big_history_index--;
				position_big = big_history[big_history_index-1];
			}
				
			else
				position_big = NULL;
		}
		
	}
	if (check_small == 0 && check_big == 0) {
		return 1;
	}
	else {
		return 0;
	}
	
}

char check_gpt(char* start, char* end) {
	char stack[101]; // 스택으로 사용할 배열
	int stackIndex = 0; // 스택의 현재 인덱스
	for (char* i = start; i != end; i++) {
		if (*i == '(' || *i == '[') {
			// 여는 괄호를 스택에 추가
			stack[stackIndex++] = *i;
		}
		else if (*i == ')' || *i == ']') {
			// 스택이 비었거나, 짝이 맞지 않는 경우
			if (stackIndex == 0 || (stack[stackIndex - 1] == '(' && *i != ')') || (stack[stackIndex - 1] == '[' && *i != ']')) {
				return 0;
			}
			// 짝이 맞는 경우 스택에서 제거
			stackIndex--;
		}
	}
	// 스택이 비어있지 않다면 괄호의 짝이 맞지 않음
	return stackIndex == 0 ? 1 : 0;
}

int main() {
	char input[101] = { 0, };
	char* end_of_string = NULL;

	while (1) {
		fgets(input, sizeof(input), stdin);
		end_of_string = strrchr(input, '.');

		if (end_of_string == NULL) //'.'을 안친 경우
			continue;

		if (end_of_string == input) // '.'만 친 경우
			break;
		else {
			if (check_gpt(input, end_of_string))
				printf("yes\n");
			else
				printf("no\n");
		}
			
	}
	return 0;
}

 

본래 생각했던 풀이 방법은, '('와 '['의 위치를 계속 기록하고, 만약 괄호가 닫힌다면 괄호 사이에 있는 문자열을 인자로 재귀호출하여, 내부 문장이 적절한지 판단하는 방법이였다.

 

이전 괄호의 위치를 포인터 배열로 관리하며, 재귀호출로 인해 코드가 읽기 힘들고, 비효율적으로 동작하는 안좋은 코드가 만들어졌다.

 

반면 gpt가 작성한 코드는 stack을 이용해서 짝이 맞는 경우아 최종적으로 스택에 아무것도 남지 않을 경우 1을 리턴, 짝이 맞지 않는 경우 0을 리턴하는 코드를 작성하였다. 이는 훨씬 직관적이고 이해하기 쉬우며, 효율적이다.

 

함수를 직관적으로 작성하다 재귀함수를 사용해야 할 경우, 스택을 이용해볼 생각을 하는게 좋다고 한다.

하지만, 스택을 이용했을때 보기 더 힘들거나, 재귀 함수를 이용하므로써 tail recursion optimization을 받을 수 있는 경우엔 재귀 함수를 사용하는게 더 좋다고 한다.

 

자료구조를 배운 만큼, 사용해보려 노력해야겠다 ㅜ

728x90

'자료구조론 > 백준' 카테고리의 다른 글

[백준] 11866 요세푸스 문제 0  (0) 2024.02.25
[백준] 10866 덱  (0) 2024.02.25
[백준] 2164 카드2  (1) 2024.02.24
[백준] 1966 프린터 큐  (1) 2024.02.24
[백준] 1874 스택 수열  (2) 2024.02.18
728x90

요세푸스 문제 0 

 
시간 제한메모리 제한제출정답맞힌 사람정답 비율

 

2 초 512 MB 74836 42388 35521 56.483%

문제

요세푸스 문제는 다음과 같다.

1번부터 N번까지 N명의 사람이 원을 이루면서 앉아있고, 양의 정수 K(≤ N)가 주어진다. 이제 순서대로 K번째 사람을 제거한다. 한 사람이 제거되면 남은 사람들로 이루어진 원을 따라 이 과정을 계속해 나간다. 이 과정은 N명의 사람이 모두 제거될 때까지 계속된다. 원에서 사람들이 제거되는 순서를 (N, K)-요세푸스 순열이라고 한다. 예를 들어 (7, 3)-요세푸스 순열은 <3, 6, 2, 7, 5, 1, 4>이다.

N과 K가 주어지면 (N, K)-요세푸스 순열을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N과 K가 빈 칸을 사이에 두고 순서대로 주어진다. (1 ≤ K ≤ N ≤ 1,000)

출력

예제와 같이 요세푸스 순열을 출력한다.

예제 입력 1 복사

7 3

예제 출력 1 복사

<3, 6, 2, 7, 5, 1, 4>

 

해당 문제는 queue를 이용해서, 일정한 간격대로 pop하는 문제이다.

본인은 이 문제를 출제 의도에 맞지 않게 풀었는데, 방법으론 원형 링크드 리스트를 사용해 모든 수를 출력할때까지, 일정한 간격으로 출력하는 방법이였다. 의도에 맞지 않게 풀었기에, 코드가 보기 좋지 않다.

 

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

typedef struct CircleQueue {
	int id;
	char is_out;
	struct CircleQueue* next;
}UserQueue;

int main() {
	int n, k;
	int printed_num = 0, count = 1;
	scanf("%d %d", &n, &k);
	UserQueue* p = (UserQueue*)malloc(sizeof(UserQueue) * n);

	for (int i = 0; i < n - 1; i++) {
		p[i].id = i + 1;
		p[i].is_out = 0;
		p[i].next = &p[i + 1];
	}

	p[n-1].id = n; //원형 링크드 리스트를 위해 직접 초기화
	p[n-1].is_out = 0;
	p[n-1].next = &p[0];//마지막 구조체는 첫 구조체를 가리킴

	UserQueue* temp = &p[0]; //체크를 위한 임시 구조체 할당, 첫번째 사람으로 설정

	while (printed_num != n) { //다 출력할때까지 반복


		while (count != k) { //나갈 차례가 될 때까지 반복
			if (temp->is_out == 0)//나가지 않은 사람들은 
				count++; //count. 나간 사람의 자리도 count하지 않기 위함
			temp = temp->next; //다음 자리로
		}

		//나갈 차례가 된 경우
		if (count == k && temp->is_out != 1) { //차례가 되었고 해당 자리가 나간 사람의 자리가 아닌 경우
			if (printed_num == 0) {//첫 출력일 경우, 
				printf("<%d", temp->id); //'<'를 붙여줌
			}
			else {//기본 출력 양식
				printf(", %d", temp->id);
			}
			printed_num++; //출력된 횟수 업데이트
			
			count = 1; //count 초기화
			temp->is_out = 1; //나간 자리로 표시
		}
		//차례가 되었지만 해당 자리가 나간 사람의 자리인 경우, pass

		temp = temp->next; //다음 자리로 
	}
	printf(">"); //출력이 끝난 경우, '>'을 붙여줌

	free(p);

	return 0;
}

 

 

이 문제를 푸는 정석 코드는 다음과 같다.

 

//#11866 요세푸스 문제 0
#include <stdio.h>
#include <iostream>
#include <stack>
#include <queue>
#include <algorithm>
using namespace std;
int n,k;

int main()
{
	scanf("%d %d", &n, &k);
	queue<int> q;
	for(int i=1; i<=n; i++)
		q.push(i);
		
	int t = 1;
	cout << "<";
	while(q.size()>1) 
	{
		if(t%k==0)
		{
			cout << q.front() << ", ";
			q.pop();
		}
		else
		{
			q.push(q.front());
			q.pop();
		}
		t++;
	}
	cout << q.front()<<">";
}

//출처: 백준, id : myjun0321
728x90

'자료구조론 > 백준' 카테고리의 다른 글

[백준] 4949 균형잡힌 세상, (스택과 재귀함수에 대하여)  (1) 2024.02.27
[백준] 10866 덱  (0) 2024.02.25
[백준] 2164 카드2  (1) 2024.02.24
[백준] 1966 프린터 큐  (1) 2024.02.24
[백준] 1874 스택 수열  (2) 2024.02.18
728x90

 

 
시간 제한메모리 제한제출정답맞힌 사람정답 비율
0.5 초 (추가 시간 없음) 256 MB 82123 45389 38327 56.025%

문제

정수를 저장하는 덱(Deque)를 구현한 다음, 입력으로 주어지는 명령을 처리하는 프로그램을 작성하시오.

명령은 총 여덟 가지이다.

  • push_front X: 정수 X를 덱의 앞에 넣는다.
  • push_back X: 정수 X를 덱의 뒤에 넣는다.
  • pop_front: 덱의 가장 앞에 있는 수를 빼고, 그 수를 출력한다. 만약, 덱에 들어있는 정수가 없는 경우에는 -1을 출력한다.
  • pop_back: 덱의 가장 뒤에 있는 수를 빼고, 그 수를 출력한다. 만약, 덱에 들어있는 정수가 없는 경우에는 -1을 출력한다.
  • size: 덱에 들어있는 정수의 개수를 출력한다.
  • empty: 덱이 비어있으면 1을, 아니면 0을 출력한다.
  • front: 덱의 가장 앞에 있는 정수를 출력한다. 만약 덱에 들어있는 정수가 없는 경우에는 -1을 출력한다.
  • back: 덱의 가장 뒤에 있는 정수를 출력한다. 만약 덱에 들어있는 정수가 없는 경우에는 -1을 출력한다.

입력

첫째 줄에 주어지는 명령의 수 N (1 ≤ N ≤ 10,000)이 주어진다. 둘째 줄부터 N개의 줄에는 명령이 하나씩 주어진다. 주어지는 정수는 1보다 크거나 같고, 100,000보다 작거나 같다. 문제에 나와있지 않은 명령이 주어지는 경우는 없다.

출력

출력해야하는 명령이 주어질 때마다, 한 줄에 하나씩 출력한다.

예제 입력 1 복사

15
push_back 1
push_front 2
front
back
size
empty
pop_front
pop_back
pop_front
size
empty
pop_back
push_front 3
empty
front

예제 출력 1 복사

2
1
2
0
2
1
-1
0
1
-1
0
3

예제 입력 2 복사

22
front
back
pop_front
pop_back
push_front 1
front
pop_back
push_back 2
back
pop_front
push_front 10
push_front 333
front
back
pop_back
pop_back
push_back 20
push_back 1234
front
back
pop_back
pop_back

예제 출력 2 복사

-1
-1
-1
-1
1
1
2
2
333
10
10
333
20
1234
1234
20

 

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int deque[200001];
int front_index = 100000;
int back_index = 100001;

int size() {
	return (back_index - front_index) - 1;
}
char empty() {
	if (size() == 0)
		return 1;
	else
		return 0;
}

void push_front(int num){
	deque[front_index--] = num;
}

void push_back(int num){
	deque[back_index++] = num;
}

int pop_front() {
	if (!empty()) {
		front_index++;
		return deque[front_index];
	}
	return -1;
}
int pop_back() {
	if (!empty()) {
		back_index--;
		return deque[back_index];
	}
	return -1;
}

int front() {
	if(!empty())
		return deque[front_index + 1];
	return -1;
}
int back() {
	if(!empty())
		return deque[back_index - 1];
	return -1;
}

int main() {
	int num = 0;
	char line[100] = { 0, };
	char cmd[11] = { 0, };
	int arg = 0;

	scanf("%d", &num);
	fgets(line, sizeof(line), stdin);

	for (int i = 0; i < num; i++) {
		memset(line, 0, sizeof(line));
		memset(cmd, 0, sizeof(cmd));
		arg = 0;

		fgets(line, sizeof(line), stdin);
		sscanf(line, "%s %d", cmd, &arg);

		if (!strncmp("push_front", cmd, 10)) {
			push_front(arg);
		}
		else if (!strncmp("push_back", cmd, 9)) {
			push_back(arg);
		}
		else if (!strncmp("pop_front", cmd, 9)) {
			printf("%d\n", pop_front());
		}
		else if (!strncmp("pop_back", cmd, 8)) {
			printf("%d\n", pop_back());
		}
		else if (!strncmp("size", cmd, 4)) {
			printf("%d\n", size());
		}
		else if (!strncmp("empty", cmd, 5)) {
			printf("%d\n", empty());
		}
		else if (!strncmp("front", cmd, 5)) {
			printf("%d\n", front());
		}
		else if (!strncmp("back", cmd, 4)) {
			printf("%d\n", back());
		}
	}
}

 

전역변수로 아주 거대한 배열을 덱으로 활용하려 할당하였다.

덱은, 스택과 큐를 섞어놓은 자료구조로 front, back 모두 입출력이 가능하다.

특징으로 pop 연산은, 인덱스만 조정후 덱을 초기화하진 않는다.

따라서, 큰 배열 전체를 검색하여 사이즈를 구하는것에 비해 인덱스를 이용하여 사이즈를 계산하므로 더 빠른 동작을 할 수 있다.

 

하지만, 해당 코드에 인덱스값에 이상이 생기는 순간 보안취약점이 생기므로 인덱스 확인 로직 추가, 좀 더 효율적인 공간 할당방법을 택하면 좋은 코드가 되지 않을까?

 

또한 새로 배운점으로, 

stdin에 엔터를 기준으로 스트링을 받아놓은 후, 띄어쓰기를 기준으로 sscanf를 이용해 다시 스트링의 위치를 할당할 수 있다. (c언어의 입출력은 항상 까다로운것 같다)

728x90
728x90

카드2 성공

시간 제한메모리 제한제출정답맞힌 사람정답 비율
2 초 (추가 시간 없음) 128 MB 113816 59132 46098 50.951%

문제

 

N장의 카드가 있다. 각각의 카드는 차례로 1부터 N까지의 번호가 붙어 있으며, 1번 카드가 제일 위에, N번 카드가 제일 아래인 상태로 순서대로 카드가 놓여 있다.

이제 다음과 같은 동작을 카드가 한 장 남을 때까지 반복하게 된다. 우선, 제일 위에 있는 카드를 바닥에 버린다. 그 다음, 제일 위에 있는 카드를 제일 아래에 있는 카드 밑으로 옮긴다.

예를 들어 N=4인 경우를 생각해 보자. 카드는 제일 위에서부터 1234 의 순서로 놓여있다. 1을 버리면 234가 남는다. 여기서 2를 제일 아래로 옮기면 342가 된다. 3을 버리면 42가 되고, 4를 밑으로 옮기면 24가 된다. 마지막으로 2를 버리고 나면, 남는 카드는 4가 된다.

N이 주어졌을 때, 제일 마지막에 남게 되는 카드를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 정수 N(1 ≤ N ≤ 500,000)이 주어진다.

출력

첫째 줄에 남게 되는 카드의 번호를 출력한다.

예제 입력 1 복사

6

예제 출력 1 복사

4

 

 

이 문제는 STL을 활용하여 간단하게 풀 수 있다.

문제에서 카드를 섞고 버리는 방식은 queue 자료구조 (FIFO)이므로, c++ 의  queue라이브러리를 활용하여 작성하였다.

#include <queue>
#include <iostream>
using namespace std;
int main() {
	queue<int> q;
	int num, temp;
	cin >> num;

	//initial
	for (int i = 1; i <= num; i++) {
		q.push(i);
	}

	//pop_push
	while (q.size() != 1) {
		temp = 0; //initialize

		q.pop();
		temp = q.front();
		q.pop();
		q.push(temp);

	}

	cout << q.front();
}

 

1부터 차례로 카드를 쌓으므로 for문을 활용하여 차례대로 push하고, 

while문에서 첫 카드를 버리고 (pop), 두번째 카드를 뽑아 가장 아래 위치로 넣는 작업 (temp에 front를 저장한 후, pop, push(temp)) 을 구현하였다.

 

728x90

'자료구조론 > 백준' 카테고리의 다른 글

[백준] 11866 요세푸스 문제 0  (0) 2024.02.25
[백준] 10866 덱  (0) 2024.02.25
[백준] 1966 프린터 큐  (1) 2024.02.24
[백준] 1874 스택 수열  (2) 2024.02.18
[백준] 10845번 큐  (2) 2023.12.25
728x90

프린터 큐 

시간 제한메모리 제한제출정답맞힌 사람정답 비율
2 초 128 MB 75441 43455 34243 58.540%

문제

여러분도 알다시피 여러분의 프린터 기기는 여러분이 인쇄하고자 하는 문서를 인쇄 명령을 받은 ‘순서대로’, 즉 먼저 요청된 것을 먼저 인쇄한다. 여러 개의 문서가 쌓인다면 Queue 자료구조에 쌓여서 FIFO - First In First Out - 에 따라 인쇄가 되게 된다. 하지만 상근이는 새로운 프린터기 내부 소프트웨어를 개발하였는데, 이 프린터기는 다음과 같은 조건에 따라 인쇄를 하게 된다.

  1. 현재 Queue의 가장 앞에 있는 문서의 ‘중요도’를 확인한다.
  2. 나머지 문서들 중 현재 문서보다 중요도가 높은 문서가 하나라도 있다면, 이 문서를 인쇄하지 않고 Queue의 가장 뒤에 재배치 한다. 그렇지 않다면 바로 인쇄를 한다.

예를 들어 Queue에 4개의 문서(A B C D)가 있고, 중요도가 2 1 4 3 라면 C를 인쇄하고, 다음으로 D를 인쇄하고 A, B를 인쇄하게 된다.

여러분이 할 일은, 현재 Queue에 있는 문서의 수와 중요도가 주어졌을 때, 어떤 한 문서가 몇 번째로 인쇄되는지 알아내는 것이다. 예를 들어 위의 예에서 C문서는 1번째로, A문서는 3번째로 인쇄되게 된다.

입력

첫 줄에 테스트케이스의 수가 주어진다. 각 테스트케이스는 두 줄로 이루어져 있다.

테스트케이스의 첫 번째 줄에는 문서의 개수 N(1 ≤ N ≤ 100)과, 몇 번째로 인쇄되었는지 궁금한 문서가 현재 Queue에서 몇 번째에 놓여 있는지를 나타내는 정수 M(0 ≤ M < N)이 주어진다. 이때 맨 왼쪽은 0번째라고 하자. 두 번째 줄에는 N개 문서의 중요도가 차례대로 주어진다. 중요도는 1 이상 9 이하의 정수이고, 중요도가 같은 문서가 여러 개 있을 수도 있다.

출력

각 테스트 케이스에 대해 문서가 몇 번째로 인쇄되는지 출력한다.

예제 입력 1 복사

3
1 0
5
4 2
1 2 3 4
6 0
1 1 9 1 1 1

예제 출력 1 복사

1
2
5

 

 

큐를 직접 구현하는것도 좋지만, 이번엔 stl문제인 만큼 오랜만에 c++ 라이브러리를 활용하여 풀어보았다.

 

#include <iostream>
#include <deque>
using namespace std;

struct PrintJob {
	int priority;
	bool check;
};

void printer_queue(int num_of_document, int index_of_searching){
	
	int most_prior = 0;
	int num_printed = 1;
	std::deque<PrintJob> q1;
	struct PrintJob temp = { 0,0 };

	//우선순위 배열 입력받기
	for (int i = 0; i < num_of_document; i++) {
 		std::cin >> temp.priority;

		if (i == index_of_searching) {
			temp.check = 1;
			q1.push_back(temp);
		}
		else {
			temp.check = 0;
			q1.push_back(temp);
		}
	}

	while (q1.size() != 0) {
		most_prior = 0; //초기화

		for (int i = 0; i < q1.size(); i++) { //가장 우선순위 탐색
			if (q1[i].priority > most_prior)
				most_prior = q1[i].priority;
		}

		while (q1[0].priority != most_prior) { //가장 앞의 문서가 가장 우선순위가 높은 문서일때까지
			if (q1[0].priority < most_prior) { //우선순위가 낮은 문서는 뒤로
				temp = q1[0];
				q1.pop_front();
				q1.push_back(temp);
			}
		}

		if (q1[0].check == 1) { //찾는 문서이면 출력 횟수를 출력한 후, 리턴
			cout << num_printed << endl;
			return;
		}
		else { //찾는 문서가 아닐 경우, pop하고 다시 탐색
			q1.pop_front();
			num_printed++;	
		}
		
	}
	

}


int main() {
	int num_of_testcase;
	int num_of_document;
	int index_of_searching;

	std::cin >> num_of_testcase;
	for (int i = 0; i < num_of_testcase; i++) {
		std::cin >> num_of_document; 
		std::cin >> index_of_searching; 
		printer_queue(num_of_document, index_of_searching);
	}
	return 0;
}
728x90

'자료구조론 > 백준' 카테고리의 다른 글

[백준] 11866 요세푸스 문제 0  (0) 2024.02.25
[백준] 10866 덱  (0) 2024.02.25
[백준] 2164 카드2  (1) 2024.02.24
[백준] 1874 스택 수열  (2) 2024.02.18
[백준] 10845번 큐  (2) 2023.12.25
728x90

스택 수열 

 
시간 제한메모리 제한제출정답맞힌 사람정답 비율
2 초 128 MB 152513 59312 41371 37.955%

문제

스택 (stack)은 기본적인 자료구조 중 하나로, 컴퓨터 프로그램을 작성할 때 자주 이용되는 개념이다. 스택은 자료를 넣는 (push) 입구와 자료를 뽑는 (pop) 입구가 같아 제일 나중에 들어간 자료가 제일 먼저 나오는 (LIFO, Last in First out) 특성을 가지고 있다.

1부터 n까지의 수를 스택에 넣었다가 뽑아 늘어놓음으로써, 하나의 수열을 만들 수 있다. 이때, 스택에 push하는 순서는 반드시 오름차순을 지키도록 한다고 하자. 임의의 수열이 주어졌을 때 스택을 이용해 그 수열을 만들 수 있는지 없는지, 있다면 어떤 순서로 push와 pop 연산을 수행해야 하는지를 알아낼 수 있다. 이를 계산하는 프로그램을 작성하라.

입력

첫 줄에 n (1 ≤ n ≤ 100,000)이 주어진다. 둘째 줄부터 n개의 줄에는 수열을 이루는 1이상 n이하의 정수가 하나씩 순서대로 주어진다. 물론 같은 정수가 두 번 나오는 일은 없다.

출력

입력된 수열을 만들기 위해 필요한 연산을 한 줄에 한 개씩 출력한다. push연산은 +로, pop 연산은 -로 표현하도록 한다. 불가능한 경우 NO를 출력한다.

예제 입력 1 복사

8
4
3
6
8
7
5
2
1

예제 출력 1 복사

+
+
+
+
-
-
+
+
-
+
+
-
-
-
-
-

예제 입력 2 복사

5
1
2
5
3
4

예제 출력 2 복사

NO

힌트

1부터 n까지에 수에 대해 차례로 [push, push, push, push, pop, pop, push, push, pop, push, push, pop, pop, pop, pop, pop] 연산을 수행하면 수열 [4, 3, 6, 8, 7, 5, 2, 1]을 얻을 수 있다.

 

 스택이 오름차순으로 올라갈 수 있다.

문제를 예를 들어서 설명하면, 만약 3가지 숫자의 배열 1 3 2 를 스택에 넣고 빼서 구현하고 싶다면, 

 

스택에 1을 push

스택 상단에서 1을 pop

스택에 2를 push

스택에 3을 push

스택 상단에서 3을 pop

스택 상단에서 2를 pop

하면 된다.

 

이를 코드로 구현하면, 

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct stack {
	int stack[100001];
	int stack_index;
	int high_num; //넣은 수 중 가장 큰 수
} custom_stack;


void push(custom_stack* p, char* ans, int search_value) {
	do {
		p->high_num++;
		p->stack[p->stack_index++] = p->high_num;
		strncat(ans, "+\n", 3);//printf("+\n");
	} while (p->high_num != search_value);
}

/*
* sequence_sort의 반환값은, 
* 1 이상의 정수를 리턴할 경우, 해당 값은 스택 최상단에서 pop 된 값이고
* 0 을 반환할 경우 에러이다.
*/
int sequence_sort(custom_stack* p, char* ans, int search_value, int num) {
	//search and pop
	if (search_value <= p->high_num) {//이전에 넣은 수보다 작은 값을 찾으면 스택 최상단에서 찾고, 
		if (p->stack[p->stack_index - 1] == search_value) {
			strncat(ans, "-\n", 3);
			return p->stack[--(p->stack_index)]; //pop
		}
		else {
			return 0; //case NO
		}
	}
	else if (search_value <= num) { //이전에 넣은 수보다 큰 값을 찾으면 push, 대신 num 범위 안에서만
		push(p, ans, search_value);
		strncat(ans, "-\n", 3);//printf("-\n");
		return p->stack[--(p->stack_index)];
	}
	else
		return 0;
}

int main() {
	int num = 0;
	scanf("%d", &num); //user input 1

	//declare
	int flag = 1;
	int* array = (int*)malloc(sizeof(int) * num);
	char* ans = (char*)malloc(sizeof(char) * 400001);
	custom_stack* my_stack = (custom_stack*)malloc(sizeof(custom_stack));
	
	//initialization
	if (ans != NULL && my_stack != NULL && array != NULL) {
		memset(array, 0, sizeof(array)); //int pointer array
		memset(ans, 0, sizeof(ans)); //char pointer ans
		memset(my_stack->stack, 0, sizeof(my_stack->stack)); //struct pointer my_stack
		my_stack->high_num = 0;
		my_stack->stack_index = 0;
	}
	else {
		printf("Allocation failed\n");
		return -1;
	}

	for (int i = 0; i < num; i++) {
		scanf("%d", &array[i]);  //user input 2~N
	}
	for (int i = 0; i < num && flag; i++) {
		flag = sequence_sort(my_stack, ans, array[i], num);
	}
	if (flag) {
		printf("%s", ans);
	}
	else
		printf("NO\n");

	//free allocated area
	free(array);
	free(my_stack);
	free(ans);

	return 0;
}

 

 코드의 특징으론, 전역변수를 많이 사용하거나 함수 인자를 많이 사용하지 않기 위해 구조체를 사용하였고, 

답안을 제출하기 위해 "ans"  char 포인터를 선언하였고, +,-를 strncpy로 공간에 넣어준다.

또한, 만약에 불가능한 경우가 조건문에 걸린다면, "flag"로 설정한 "sequence_sort" 반환값을 활용하여(오류일 경우 반환값이 0) for 루프의 동작을 멈추고, NO를 출력하도록 하였다.

 

 array의 크기를 100,001로 설정한 이유는, 문제의 조건에서 N의 최댓값이 100,000이라고 하였고, 현재 스택 포인터는 가장 앞(가장 밑)에 비어있는 스택 위치를 가리키도록 설정하였기 때문에, worst-case인 100,000개가 모두 들어왔을 경우, index는 100,001을 가르키고 있기 때문이다.

또한, ans 배열의 크기를 400,001로 설정한 이유는, 한개의 데이터에 push-pop 두가지 연산이 와야하므로 100,000 x 2 = 200,000에, push-pop 각각 "+\n", "-\n" 2글자씩 들어가므로 (\n은 문자 하나로 메모리에 들어간다) 200,000 x 2 = 400,000에, worst-case 경우 null값까지 고려하여 400,001로 설정하였다. 

 

728x90

'자료구조론 > 백준' 카테고리의 다른 글

[백준] 11866 요세푸스 문제 0  (0) 2024.02.25
[백준] 10866 덱  (0) 2024.02.25
[백준] 2164 카드2  (1) 2024.02.24
[백준] 1966 프린터 큐  (1) 2024.02.24
[백준] 10845번 큐  (2) 2023.12.25
728x90

문제

정수를 저장하는 큐를 구현한 다음, 입력으로 주어지는 명령을 처리하는 프로그램을 작성하시오.

명령은 총 여섯 가지이다.

  • push X: 정수 X를 큐에 넣는 연산이다.
  • pop: 큐에서 가장 앞에 있는 정수를 빼고, 그 수를 출력한다. 만약 큐에 들어있는 정수가 없는 경우에는 -1을 출력한다.
  • size: 큐에 들어있는 정수의 개수를 출력한다.
  • empty: 큐가 비어있으면 1, 아니면 0을 출력한다.
  • front: 큐의 가장 앞에 있는 정수를 출력한다. 만약 큐에 들어있는 정수가 없는 경우에는 -1을 출력한다.
  • back: 큐의 가장 뒤에 있는 정수를 출력한다. 만약 큐에 들어있는 정수가 없는 경우에는 -1을 출력한다.
 

입력

첫째 줄에 주어지는 명령의 수 N (1 ≤ N ≤ 10,000)이 주어진다. 둘째 줄부터 N개의 줄에는 명령이 하나씩 주어진다. 주어지는 정수는 1보다 크거나 같고, 100,000보다 작거나 같다. 문제에 나와있지 않은 명령이 주어지는 경우는 없다.

 

'큐' 자료구조는  First In, First Out 의 구조를 가진다. 

따라서 스택과 달리 데이터 접근점이 두개(top, rear 혹은 left, right 혹은 front, back)가 필요하다.

데이터가 들어가는 지점을 하나 지정하고, 나오는 점을 하나 지정해야 한다.

 

 

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

typedef enum{
    PUSH,
    POP,
    SIZE,
    EMPTY,
    FRONT,
    BACK,
    UNKNOWN    
} CommandType;

void push(int **q_front, int **q_back); //정수 X를 큐에 넣는 연산이다.
int pop(int **q_front, int **q_back); //큐에서 가장 앞에 있는 정수를 빼고, 그 수를 리턴한다. 만약 큐에 들어있는 정수가 없는 경우에는 -1을 리턴한다.
int size(int **q_front, int **q_back); //큐에 들어있는 정수의 개수를 리턴한다.
int empty(int **q_front, int **q_back); //큐가 비어있으면 1, 아니면 0을 리턴한다.
int front(int **q_front, int **q_back); //큐의 가장 앞에 있는 정수를 리턴한다. 만약 큐에 들어있는 정수가 없는 경우에는 -1을 리턴한다.
int back(int **q_front, int **q_back); //큐의 가장 뒤에 있는 정수를 리턴한다. 만약 큐에 들어있는 정수가 없는 경우에는 -1을 리턴한다.

CommandType getCommandType(const char* input){
    if (strcmp(input, "push") == 0 || strcmp(input, "PUSH") == 0)
        return PUSH;
    else if (strcmp(input, "pop") == 0 || strcmp(input, "POP") == 0)
        return POP;
    else if (strcmp(input, "size") == 0 || strcmp(input, "SIZE") == 0)
        return SIZE;
    else if (strcmp(input, "empty") == 0 || strcmp(input, "EMPTY") == 0)
        return EMPTY;
    else if (strcmp(input, "front") == 0 || strcmp(input, "FRONT") == 0)
        return FRONT;
    else if (strcmp(input, "back") == 0 || strcmp(input, "BACK") == 0)
        return BACK;
    else 
        return UNKNOWN;
}

int main(int argc, char *argv[]){
    int number_of_commands = 0;
    int i = 0, j = 0;
    char user_input_command[6];
    int *queue;
    int *q_front, *q_back;

    scanf("%d",&number_of_commands);

    if(number_of_commands > 10000 || number_of_commands < 1)
        return -1;

    queue = (int *)malloc(sizeof(int)*number_of_commands);
    q_front = queue;
    q_back = queue;

    for(i = 0; i < number_of_commands; i++){
        scanf("%s", user_input_command);
        CommandType cmd = getCommandType(user_input_command);
        
        switch(cmd){
            case PUSH:
                push(&q_front, &q_back);
                break;
            case POP:
                printf("%d\n",pop(&q_front, &q_back));
                break;
            case SIZE:
                printf("%d\n",size(&q_front, &q_back));
                break;
            case EMPTY:
                printf("%d\n",empty(&q_front, &q_back));
                break;
            case FRONT:
                printf("%d\n",front(&q_front, &q_back));
                break;
            case BACK:
                printf("%d\n",back(&q_front, &q_back));
                break;
            case UNKNOWN:
                break;
            default:
                printf("Error on switch.\n");
                return -1;                
        }

        //memset(user_input_command, 0, sizeof(user_input_command));
    }
    free(queue);
    return 0;
}

void push(int **q_front, int **q_back){
    int X = 0;
    scanf("%d",&X);
    if(X <= 100000 && X >= 1){
        **q_back = X;
        (*q_back)++; 
    }
    
}//정수 X를 큐에 넣는 연산이다.

int pop(int **q_front, int **q_back){
    if(*q_front == *q_back)
        return -1;
    int temp = **q_front;
    (*q_front)++;
    return temp;
} //큐에서 가장 앞에 있는 정수를 빼고, 그 수를 리턴한다. 만약 큐에 들어있는 정수가 없는 경우에는 -1을 리턴한다.

int size(int **q_front, int **q_back){
    if(*q_back == *q_front)
        return 0;
    else
        return (*q_back - *q_front);// / sizeof(int)라 생각했으나, 컴파일러에서 포인터간의 차를 계산할때 자동으로 타입 크기로 나눠준다고 한다!;
} //큐에 들어있는 정수의 개수를 리턴한다.

int empty(int **q_front, int **q_back){
    return *q_front == *q_back;
} //큐가 비어있으면 1, 아니면 0을 리턴한다.

int front(int **q_front, int **q_back){
    if(*q_front != *q_back)
        return **q_front;
    else
        return -1;
} //큐의 가장 앞에 있는 정수를 리턴한다. 만약 큐에 들어있는 정수가 없는 경우에는 -1을 리턴한다.

int back(int **q_front, int **q_back){
    if(*q_front != *q_back)
        return *(*q_back-1);
    else
        return -1;
} //큐의 가장 뒤에 있는 정수를 리턴한다. 만약 큐에 들어있는 정수가 없는 경우에는 -1을 리턴한다.

 

 

enum을 활용하여 switch_case구문으로 메뉴를 구성하였다. (3개 이상의 if문은 컴파일러에 따라 비효율적일 수 있다고 한다)

 

위 코드의 로직대로 공간을 할당하면, 큐의 크기를 push할 때 확인하지 않아도 된다는 장점이 있지만, 명령의 수만큼 공간을 할당하므로 실제 사용하는 양에 비해서 불필요할 정도로 공간을 할당한다. 따라서, 최적화를 위해서는 push를 할 때마다 malloc하는 방식을 생각해 볼 수도 있을 것 같다.

728x90

'자료구조론 > 백준' 카테고리의 다른 글

[백준] 11866 요세푸스 문제 0  (0) 2024.02.25
[백준] 10866 덱  (0) 2024.02.25
[백준] 2164 카드2  (1) 2024.02.24
[백준] 1966 프린터 큐  (1) 2024.02.24
[백준] 1874 스택 수열  (2) 2024.02.18
728x90
#include <stdio.h>
int recursive_ackermann(int m, int n) {
	if (!m)
		return n + 1;
	else if (!n)
		recursive_ackermann(m - 1, 1);
	else
		recursive_ackermann(m - 1, recursive_ackermann(m, n - 1));
}

int loop_ackermann(int m, int n) {
    int stack[1000], top = 0, current = 0;
    stack[0] = m;

    while (top >= 0) {
        current = stack[top]; //현재 top이 가르키고 있는 값
        if (current == 0) // 그 값이 만약 0이라면
        {
            n++; // n을 n+1로 업데이트
            top--; //top를 한칸 뒤로 옮김
        }
        else if (n == 0) {
            n = 1;
            stack[top] = current - 1;
        }
        else{
            n--; //n을 n - 1로 업데이트
            stack[top] = current - 1; //지금 top칸을 m - 1로 업데이트
            stack[++top] = current; //그 다음 top칸을 m으로 업데이트
        }        
    }
    return n;

}

int main(int argc, char* argv[]) {
	int m,n;
	scanf_s("%d %d", &m, &n);
    printf("achkermann with recursive : %d\n", recursive_ackermann(m, n));
	printf("achkermann with loop : %d\n", loop_ackermann(m, n));
}

재귀함수를 반복문으로 구현하는 방법중 하나로 스택을 이용할 수 있다.

 

위 ackermann은 m,n값의 조건에 따라서, f(m,n)으로 표현되는데 

m != 0 && m!= 0인 경우, f(m-1,f(m,n-1))로 표현된다.

 

재귀함수를 이용할 경우, 언어 그대로 작성하기에 작성 속도는 빠르지만, 함수 호출이 많으므로 m,n값이 커지면 반복문에 비해 비효율적이다. 

 

위 ackermann은 최종적으로 m = 0일때 함수가 종료되고, 반환하는 값은 n이다. 즉, 이 함수의 리턴값은 결국 n이고, 생각해야할 변수는 m이다. 

 

함수를 합성함수라 생각하면, f(m-1,f(m,n-1))의 경우,

현재 가르키는 값은 m이 되어야 하고( f(m,n-1)값을 먼저 계산해야 하기에), 

위의 계산 후 가르켜야 할 값은 m - 1이다. ( f(m-1,*) , *은 위의 식 연산의 결과)

 

요약하면, 함수의 합성 연산이 진행될수록 연산시 사용할 m의 값이 쌓이는데

나중에 쌓인 m의 값을 먼저 연산에 사용하므로 stack을 사용한다.(반대의 경우는 큐)

 

 재귀함수를 반복문으로 표현한다는 것은

 할 일을 미뤄두는 느낌으로, 어떠한 저장공간에 연산에 필요한 값을 저장하고,

저장 방식은 함수의 합성과 같이 어떠한 연산의 패턴에 따라 바뀌는 것 같다고 생각한다.

728x90

'자료구조론' 카테고리의 다른 글

fibonacci  (0) 2023.03.30
factorial  (0) 2023.03.30
check_perfectNumber  (0) 2023.03.30
binomial_coefficient  (0) 2023.03.30
비둘기 집  (0) 2023.03.30

+ Recent posts