문제해결(Problem-Solving)이란?
사전적으로는 문제가 주어지고, 그 문제를 푸는 모든 행위입니다.
우리는 무의식적으로 문제를 푸는 게 아니라 의식적으로 그 과정을 나눌 수 있습니다.
문제 해결과정은 오른쪽 그림과 같이 크게 4가지 과정으로 이루어집니다.
- 문제를 정의하는 과정
- 문제의 대한 풀이법을 연상하는 과정
- 이 때 떠오른 풀이법에 대해 평가하는 과정
- 그 풀이법에 따라 문제를 푸는 과정
이 때, 평가과정이나 푸는 과정(3번 또는 4번 과정)에서, 풀이법의 부정확성 또는 비효율성이 발견되면
다시 연상과정(2번의 과정)으로 돌아갑니다.
그렇다면 컴퓨터 과학에서의 PS는 어떨까요?
컴퓨터과학에서의 PS도 일반적인 PS과정과 큰 차이가 없습니다.
다만, 우리는 이 모든 단계를 컴퓨터를 이용해 푼다는 전제가 있기 때문에
- 연산장치 (e.g CPU, GPU)
- 메모리 (e.g RAM)
- 프로그래밍 언어 (e.g C, C++, JAVA, Python)
위와 같은 컴퓨터적인 요소를 항상 고려 해야한다는 것이 핵심입니다.
※ 이 글 밑에서부터 PS의 용어는 컴퓨터과학에서의 PS를 말하는 것입니다.
PS는 우리에게 왜 필요할까요?
PS가 필요한 이유는 컴퓨터과학의 기초체력 훈련과 같기 때문입니다.
사실, 실제 현장에서는, 높은 PS 능력을 요구하는 일은 자주 없습니다.
예를 들면, ICPC 수준의 문제(어느 정도 고난이도의 PS능력을 요구하는)를
머리를 쥐어짜면서 풀다보면 ‘내가 이걸 진짜 현장에서 쓸까?’ 라는 의문이 절로 나오게 됩니다.
이런 의문에도 불구하고, PS 능력을 계속 길러야 하는 이유는 무엇일까요?
조금은 뜬금없지만, 개인적으로 스포츠 분야의 생리에서 이유를 찾을 수 있습니다.
스포츠 분야에선, 어떤 종목 (예로: 축구, 농구, 야구)이든 공통적으로 하는 훈련이 있습니다.
그것은 바로 러닝(달리기) 훈련입니다.
휼륭한 스포츠 선수들은 이 훈련을 절대 게을리 하지 않습니다
한 선수가 아무리 스킬적인 부분이 좋더라도 심폐지구력, 주력등 필수역량들이 부족하면
그 선수는 스킬로만 커버할 수 있는 선을 넘은 이후 성장의 한계를 맞게 됩니다
이 훈련을 하는 이유는 심폐지구력,주력 등 모든 종목에 필수적인 역량을 기르기 위함입니다.
따라서 개인적으로 PS를 소프트웨어 분야의 컴퓨터과학의 러닝훈련 이라고 생각합니다.
이 PS를 통해 개발할 수 있는 능력들은 대부분 소프트웨어분야(앱개발, 웹 개발, 머신러닝 등)에
필수적인 기본 역량들이고, 이를 잘하는 건 장기적으로 퍼포먼스의 향상과 연결이 됩니다.
만약 소프트웨어분야 직업종사자로서 살아간다면, 평생 훈련해야할 영역이 아닐까요?
PS로 어떤 능력을 얻을 수 있을까요?
PS를 훈련하는 이유가 컴퓨터 과학의 기초훈련이 된다는 것은 위에서 언급한 바가 있습니다.
그렇다면 PS를 훈련하는 것이 어떻게 ‘컴퓨터 과학의 기본역량‘을 키울 수 있을까요?
PS를 한다는 건 논리적 사고력, 알고리즘 지식 및 적용
데이터구조 지식 및 적용, 프로그래밍 능력(구현력) 의 능력을 얻을 수 있음을 의미하며
이 네 가지 능력은 대표적인 ‘컴퓨터 과학의 기본역량’의 구성요소이기 때문에.
PS를 훈련하는 것은 컴퓨터 과학의 기본역량을 키우는 것이라고 할 수 있습니다.
그렇다면 각 구성요소에 대해서 간단하게 알아보면 다음과 같습니다.
- 논리적 사고력:
논리적 사고력이란 수학문제 풀 때 능력과 굉장히 유사합니다.
이건 PS에 있어 가장 중요한 능력이며, 짧은 시간 안에 키우기 힘든 능력이기도 합니다.
흔히, 수학을 잘하는 사람이 PS 실력이 빨리 느는 이유도 이 때문이라고 할 수 있습니다.
좀 더 파고들어가면, 이 능력은 여러 부분을 내포하고 있습니다.
- 주어진 문제에서 핵심(core)를 파악하는 능력 (logic 주제 찾기)
- 문제에 적합한 풀이도구 찾는 능력 (logic 도구 찾기)
- 문제의 풀이법을 도식화 및 구상 하는 능력 (logic 만들기)
- 문제의 풀이법이 충분히 Reasonable 한지 파악하는 능력 (logic 판단)
-
알고리즘 지식 및 적용:
알고리즘은 컴퓨터의 연산장치와 메모리를 활용하는 풀이법라고 볼 수 있습니다.
알고리즘을 제대로 알고, 적절한 알고리즘을 적용, 조합 시키는 능력은
PS를 통해 얻는 핵심적인 능력이고, 컴퓨팅적 사고력 또한 키울 수 있습니다.
수학문제 풀 때는 수학 공식이 있었다면, PS는 알고리즘이 있다고 생각하면 됩니다.
하지만, 종이에 푸는 수학문제와는 다르게 우리는 컴퓨터를 사용해서 문제를 풉니다.
이는, 컴퓨터의 연산장치와 메모리도 사용할 수 있다라는 점을 이용할 수 있고
이를 활용해, 우리는 이전의 손으로는 풀지 못했던 문제를 풀 수 있게 되었습니다.
- 더 자세한 건, 알고리즘을 참조 -
데이터 구조 지식 및 적용:
데이터 구조는 컴퓨터의 연산장치와 메모리를 활용할 수 있는 라는 점을 이용하여
시간-효율성을 증진시키고, 문제를 단순화 시키기 위해 고안된 방법입니다.
어떤 데이터 구조도 사용하지 않고, 알고리즘으로만 풀 수 있는 문제도 있지만
데이터 구조를 제대로 알고, 적절한 데이터 구조를 적용하는 능력은
인풋(Input)의 크기와 값이 커지고, 문제가 복잡해질 수록 빛을 내는 능력입니다.
- 더 자세한 건, 데이터 구조를 참조 -
프로그래밍 능력(코드 구현력):
프로그래밍 능력은 컴퓨터를 활용할 수 있는 능력 그 자체를 말합니다.
프로그래밍 능력은 정말 많은 능력을 내포하는 능력이며
리펙토링 포함한 readible한 코드 작성과 하드웨어 이해를 고려한 구현까지
포함하며 여기서는 많은 것들 중 몇 가지 능력만 나열하겠습니다.- 자신이 생각한 풀이법을 그대로 코드로 구현하는 능력
- 구현에 사용한 프로그래밍 언어에 대한 이해와 적용력
- 나를 포함한 다른 사람도 쉽게 이해할 수 있는 코드를 적는 능력
- 자신이 생각한 풀이법을 그대로 코드로 구현하는 능력
어떻게 PS를 사고해야 할까?
우리 뇌는 안타깝게도, 멀뚱하게 문제만 본다고 사고 과정이 이루어지지 않습니다.
따라서 PS를 사고하기 위해서는 의식적으로 다음 과정들을 진행 해야합니다.
각 과정들은 맨 위에서 언급했던 4가지 과정이며 과정별로 중요한 포인트가 있습니다.
-
문제 정의하기:
이 과정은 풀이법과 구현을 고려하지 않고, 문제 그 자체에만 집중을 해야하는 과정입니다.
문제를 정의한다는건 여러가지를 파악한다는 개념인데, 예로 아래의 것들을 파악합니다.
1) Input들의 크기와 범위 2) Ouput의 종류 3) 문제의 전체적인 플로우
문제의 전체적인 플로우를 파악하는 건 머릿 속에서 하기에는 많이 까다로운 작업입니다.
이 때 글로 적기, 도식화 하기, 그림 또는 플로우차트 그리기 등이 큰 도움이 됩니다. -
문제의 풀이법 연상하기:
PS의 내공이 쌓였거나, 문제가 쉬운 경우에는 이전 과정에서 자연스럽게 연상이 됩니다.
하지만 문제는 해결법이 연상이 안되는 경우인데 이 떄는 여러 전략을 써야 합니다.
이 중 가장 자주 쓰이는 것은 Guess and Check와 Look for a pattern이라고 생각합니다.
예로, 이 문제는 Greedy하게 풀 수 있지 않을까? 라고 추측 한 후에 체크를 하는 식 입니다.
앞의 과정을 제대로 수행 했다면 추측 할 전략들이 무한하지는 않을 것입니다.
또 다른 방법으론 효율성을 고려하지 않은 Brute-force 한 방법으로 풀고
일정한 패턴을 찾습니다. 패턴을 찾았다면, 효율성을 높일 방법은 찾기 수월합니다
‘How to solve it’은 한 번쯤 꼭 읽으셨으면 합니다. 정석적인 PS의 패러다임을 말합니다.
-참조: Pólya의 How to Solve It(1945) -
이 때 떠오른 풀이법에 대해 평가하기:
문제가 간단하면, 이전 과정에서 자연적으로 이루어지는 경우도 많습니다.
하지만 문제가 복잡해질 수록, 의식적으로 이 과정에 신경을 써야 합니다.
4번의 구현과정에서 문제가 생기면 이미 많은 시간을 소비한 후이기 떄문이죠.
문제에 따라, limit이 주어지는데, 미리 limit을 초과 하는지 체크하는 게 좋습니다.
Amortized analysis로 미리 Time-complexity를 구하는 것도 좋은 방법입니다. -
풀이법에 따라 문제를 풀기:
PS의 문제 풀기의 최종 과정은 코드로 구현한다는 것을 의미합니다.
쉬운 문제의 경우 사실 구현과정에서도 어려움 없이 구현하지만, 문제가 어려울 수록
체계적이고 깔끔하게 디자인하고 구현하는 게 정말 중요한데, 최종 답에 문제가 생기면
구현과정의 문제인지, 풀이법 자체가 문제인건지 신속히 판단해야하기 떄문입니다.
구현과정에서는 아래의 탑-다운 디자인과 유닛 테스트 검증 개발를 추천합니다.
- 참조(탑-다운 디자인): 워스(N. Wirth)의 조금씩 개선하기(Stepwise refinement)
- 참조(유닛 테스트): Unit_testing
어떻게 PS를 학습해야 할까요?
학습법은 기본적으로는 학습 매커니즘을 바탕에 두고 이루어집니다.
-
시도하기:
먼저, 아무 배경 지식 없이 혼자 힘으로 풀어보기(최대한 노력하기)
※ 최대한 노력하기는 저의 기준에선 시간 정량적으론 3시간 정도 고민하기를 말합니다. -
자신이 지금 지식단계의 어느 지점인지 진단하기:
자신이 너무 부족해 문제 자체를 못 건들정도 경우 -> 인지적 도제학습으로
대부분의 개념은 풀었지만 한 두개의 개념에서 막힌다 -> 인풋-아웃풋 학습으로 -
인지적 도제학습(모방학습):
Github에서의 코드 또는 블로그들의 정답 코드를 의식적인 copy-coding 하기. -
인풋-아웃풋 학습:
인풋: 학습 교재, Geeks and Geeks 또는 google에서 부족한 개념을 학습하기
아웃풋: 다시 코드를 짜서 문제 풀기, 완성된 코드와 함께 배웠던 걸 글로 적어보기
-
의식적인 노력을 위한 Milestone:
- 논리적 구도가 간단하고 명료한가?
- 최적의 효율성이 나오는 알고리즘을 사용했는가?
- 최적의 효율성이 나오는 자료구조를 사용했는가?
- 좋은 코드 작성법을 따랐는가?
- 논리적 구도가 간단하고 명료한가?