[알고리즘] 시간 복잡도와 알고리즘 심화
시간 복잡도(Time Complexity)
- 알고리즘 : 문제를 해결하는 최선의 선택
- 효율적인 방법으로 문제를 해결하기 : 시간 복잡도 고려
- 입력값의 변화에 따라 연산을 실행할 때, 연산 횟수에 비해 시간이 얼마만큼 걸리는가를 의미
- 효율적인 알고리즘 구현 : 입력값이 커짐에 따라 증가하는 시간의 비율을 최소화한 알고리즘 구성
- 시간 복잡도는 주로 빅-오 표기법을 사용해 표기
시간 복잡도 표기법
- Big-O(빅-오) : 최악의 경우를 고려
- Big-Ω(빅-오메가) : 최선의 경우를 고려
- Big-θ(빅-세타) : 중간(평균)
1. Big-O 표기법
시간 복잡도 표기법
- Big-O(빅-오) : 최악의 경우를 고려
- Big-Ω(빅-오메가) : 최선의 경우를 고려
- Big-θ(빅-세타) : 중간(평균)
- 최악의 경우를 고려하므로, 프로그램이 실행되는 과정에서 소요되는 최악의 시간까지 고려할 수 있음
최소한 특정 시간 이상이 걸린다
혹은이 정도 시간이 걸린다
를 고려하는 것보다이 정도 시간까지 걸릴 수 있다
를 고려해야 그에 맞는 대응이 가능
*최선의 경우만 고려한 경우 어디에서 문제가 발생했는지 알아내기 위해서는 로직의 많은 부분을 파악해야 하므로 문제를 파악하는 데 많은 시간이 필요- 최악의 경우가 발생하지 않기를 바라며 시간을 계산하는 것보다는 최악의 경우도 고려하여 대비하는 것이 바람직
- Big-O 표기법의 종류 : O(1) > O(logn) > O(n) > O(n^2) > O(2^n) 순서로 빠름
1초가 걸리는 입력의 크기
- O(n) : 100,000,000
- O(n log n) : 5,000,000
- O(n^2) : 10,000
- O(n^3) : 500
- O(2^n) : 26
- O(n!) : 11
1) O(1) : constant complexity
function O_1_algorithm(arr, index) {
return arr[index]
}
let arr = [1, 2, 3, 4, 5]
// arr의 길이가 100만이라도, 즉시 해당 index에 접근해 값을 반환할 수 있음
let index = 1
let result = O_1_algorithm(arr, index)
console.log(result)
// 2
- 입력값이 증가하더라도 시간이 늘어나지 않는데, 이는 입력값의 크기와 관계없이 즉시 출력값을 얻어낼 수 있음을 의미
2) O(n) : linear complexity
// 입력값(n)이 1 증가할 때마다 코드의 실행 시간이 1초씩 증가
function O_n_algorithm(n) {
for (let i = 0; i < n; i++) {
// do something for 1 second
}
}
// 입력값(n)이 1 증가할때마다 코드의 실행 시간이 2초씩 증가
function another_O_n_algorithm(n) {
for (let i = 0; i < 2n; i++) {
// do something for 1 second
}
}
- 입력값이 증가함에 따라 시간 또한 같은 비율로 증가하는 것을 의미
- 2n, 5n 을 모두 O(n)이라고 표기
*입력값이 커지면 커질수록 계수(n 앞에 있는 수)의 의미(영향력)가 점점 퇴색되기 때문
3) O(log n) : logarithmic complexity
let i = 4
while(Math.floor(i) > 0) {
i = i / 2
}
console.log(i) // 0.5
- Big-O표기법중 O(1) 다음으로 빠른 시간 복잡도를 가짐
- 같은 로직으로 O(log n)의 시간 복잡도를 가진 알고리즘(탐색기법)
- 이진탐색트리(Binary Search Tree) : 같은 로직으로 O(log n)의 시간 복잡도를 가진 알고리즘(탐색기법)
*원하는 값을 탐색할 때, 노드를 이동할 때마다 경우의 수가 절반으로 줄어듬
4) O(n^2) : quadratic complexity
// n^2 : 이중반복문
function O_quadratic_algorithm(n) {
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
// do something for 1 second
}
}
}
// n^3 : 삼중반복문
function another_O_quadratic_algorithm(n) {
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
for (let k = 0; k < n; k++) {
// do something for 1 second
}
}
}
}
- 입력값이 증가함에 따라 시간이 n의 제곱수의 비율로 증가하는 것을 의미
*1일 경우 1초가 걸리던 알고리즘에 5라는 값을 주었더니 25초가 걸리게 되는 알고리즘의 시간 복잡도 - n^3과 n^5 도 모두 O(n2)로 표기
*n이 커지면 커질수록 지수가 주는 영향력이 점점 퇴색되기 때문
5) O(2^n) : exponential complexity
function fibonacci(n) {
if (n <= 1) {
return 1
}
return fibonacci(n - 1) + fibonacci(n - 2)
}
- Big-O 표기법 중 가장 느린 시간 복잡도를 가짐
- 재귀로 구현하는 피보나치 수열은 O(2^n)의 시간 복잡도를 가진 대표적인 알고리즘
*n이 100 이상이면 평생 결과를 반환받지 못할 수 있음
2. 크롬 개발자 도구에서 함수 실행 시간 측정 방법
var t0 = performance.now()
fib(50) // 여기에서 함수 실행을 시켜주세요
var t1 = performance.now()
console.log("runtime: " + (t1 - t0) + 'ms')
Greedy Algorithm
- Greedy Algorithm(탐욕 알고리즘) : 매 순간 최적이라 생각되는 해답(locally optimal solution)을 찾으며, 이를 토대로 최종 문제의 해답(globally optimal solution)에 도달하는 문제 해결 방식
- 항상 최적의 결과를 도출하는 것은 아니지만, 어느 정도 최적에 근사한 값을 빠르게 도출할 수 있음
1. 단계적 절차
- 선택 절차(Selection Procedure) : 현재 상태에서의 최적의 해답을 선택
- 적절성 검사(Feasibility Check) : 선택된 해가 문제의 조건을 만족하는지 검사
- 해답 검사(Solution Check) : 원래의 문제가 해결되었는지 검사하고 해결되지 않았다면 선택 절차로 돌아가 위의 과정을 반복
2. 최적의 해를 보장하지 못하는 특정한 상황
- 탐욕적 선택 속성(Greedy Choice Property) : 앞의 선택이 이후의 선택에 영향을 주지 않음
- 최적 부분 구조(Optimal Substructure) : 문제에 대한 최종 해결 방법은 부분 문제에 대한 최적 문제 해결 방법으로 구성
*위의 2가지 조건을 성립해야 함
Dynamic Programming
- Dynamic Programming(DP) : 동적 계획법
- 주어진 문제를 여러 개의 하위 문제로 나누어 풀고, 하위 문제들의 해결 방법을 결합하여 최종 문제를 해결하는 문제 해결 방식
*탐욕 알고리즘과 같이 작은 문제에서부터 출발한다는 점은 같으나 Dynamic Programming은 모든 경우의 수를 조합해 최적의 해법을 찾는다는 점에 차이가 있음
1. 단계적 절차
- 하위 문제를 계산한 뒤 그 해결책을 저장
- 나중에 동일한 하위 문제를 만날 경우 저장된 해결책을 적용해 계산 횟수를 줄임
*하나의 문제는 단 한 번만 풀도록 하는 알고리즘
2. 사용 조건
- Overlapping Sub-problems : 큰 문제를 작은 문제로 나눌 수 있고, 이 작은 문제가 중복해서 발견되는 경우
*큰 문제로부터 나누어진 작은 문제는 큰 문제를 해결할 때 여러 번 반복해서 사용될 수 있어야 함
- Optimal Substructure : 작은 문제에서 구한 최적의 해결 방법(Optimal solution)의 결합으로 그것을 포함하는 큰 문제의 최적의 해법(Optimal solution)을 구할수 있는 경우
*작은 문제에서 구한 정답을 큰 문제에서도 사용될 수 있어야 함
3. 구현하기
1) Recursion + Memoization으로 구현하기
- 큰 문제를 해결하기 위해 작은 문제를 호출한다고 하여, 이 방식을 Top-down 방식이라고도 함
// fibMemo 함수의 파라미터로 n 과 빈 배열을 전달
// 빈 배열은 하위 문제의 결괏값을 저장하는 데에 사용
function fibMemo(n, memo = []) {
// 이미 해결한 하위 문제인지 찾아본다
if(memo[n] !== undefined) return memo[n]
if(n <= 2) return 1
// 없다면 재귀로 결괏값을 도출하여 res 에 할당
let res = fibMemo(n-1, memo) + fibMemo(n-2, memo)
// 추후 동일한 문제를 만났을 때 사용하기 위해 리턴 전에 memo 에 저장
memo[n] = res
return res
}
2) Iteration + Tabulation으로 구현하기
- 작은 문제에서부터 시작하여 큰 문제를 해결해 나가는 방법으로, 이 방식을 Bottom-up 방식이라고도 함
function fibTab(n) {
if(n <= 2) return 1
let fibNum = [0, 1, 1]
// n 이 1 & 2일 때의 값을 미리 배열에 저장
for(let i = 3; i <= n; i++) {
fibNum[i] = fibNum[i-1] + fibNum[i-2]
// n >= 3 부터는 앞서 배열에 저장해 놓은 값들을 이용하여
// n번째 피보나치 수를 구한 뒤 배열에 저장 후 리턴
}
return fibNum[n]
}
구현(Implementation)
- 구현 능력 : 정해진 시간 안에 빠르게 문제를 해결하는 능력
- 구현 문제 : 본인이 선택한 프로그래밍 언어의 문법을 정확히 알고 있어야 하며, 문제의 조건에 전부 부합하는 코드를 실수 없이 빠르게 작성하는 것을 목표로 두는 것
- 구현 능력을 보는 대표적인 사례 : 완전 탐색(brute force), 시뮬레이션(simulation)
1. 완전 탐색(brute force)
문제 해결을 할 때의 두 가지 규칙
- 문제를 해결할 수 있는가?
: 모든 문제는 완전 탐색으로 풀 수 있음
- 효율적으로 동작하는가?
: 최의 경우 끝까지 탐색해야 하기에 두 번째 규칙을 만족할 수 없음
- 모든 경우의 수를 탐색하는 모든 경우를 통칭
- 문제를 풀 수 있는 가능한 모든 방법을 고려한 후 효율적으로 동작하는 알고리즘이 전부 탐색할 수밖에 없다고 판단될 때 적용
- 완전 탐색 : Brute Force(무차별 대입), 재귀, 순열, DFS/BFS 등 여러 가지 존재
*Brute Froce : 조건/반복을 사용하여 해결
2. 시뮬레이션(simulation)
- 모든 과정과 조건이 제시되어, 그 과정을 거친 결과가 무엇인지 확인하는 유형
Author And Source
이 문제에 관하여([알고리즘] 시간 복잡도와 알고리즘 심화), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://velog.io/@sominpark/자료구조-알고리즘-시간-복잡도와-자료구조-심화
저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
*위의 2가지 조건을 성립해야 함
- Dynamic Programming(DP) : 동적 계획법
- 주어진 문제를 여러 개의 하위 문제로 나누어 풀고, 하위 문제들의 해결 방법을 결합하여 최종 문제를 해결하는 문제 해결 방식
*탐욕 알고리즘과 같이 작은 문제에서부터 출발한다는 점은 같으나 Dynamic Programming은 모든 경우의 수를 조합해 최적의 해법을 찾는다는 점에 차이가 있음
1. 단계적 절차
- 하위 문제를 계산한 뒤 그 해결책을 저장
- 나중에 동일한 하위 문제를 만날 경우 저장된 해결책을 적용해 계산 횟수를 줄임
*하나의 문제는 단 한 번만 풀도록 하는 알고리즘
2. 사용 조건
- Overlapping Sub-problems : 큰 문제를 작은 문제로 나눌 수 있고, 이 작은 문제가 중복해서 발견되는 경우
*큰 문제로부터 나누어진 작은 문제는 큰 문제를 해결할 때 여러 번 반복해서 사용될 수 있어야 함 - Optimal Substructure : 작은 문제에서 구한 최적의 해결 방법(Optimal solution)의 결합으로 그것을 포함하는 큰 문제의 최적의 해법(Optimal solution)을 구할수 있는 경우
*작은 문제에서 구한 정답을 큰 문제에서도 사용될 수 있어야 함
3. 구현하기
1) Recursion + Memoization으로 구현하기
- 큰 문제를 해결하기 위해 작은 문제를 호출한다고 하여, 이 방식을 Top-down 방식이라고도 함
// fibMemo 함수의 파라미터로 n 과 빈 배열을 전달
// 빈 배열은 하위 문제의 결괏값을 저장하는 데에 사용
function fibMemo(n, memo = []) {
// 이미 해결한 하위 문제인지 찾아본다
if(memo[n] !== undefined) return memo[n]
if(n <= 2) return 1
// 없다면 재귀로 결괏값을 도출하여 res 에 할당
let res = fibMemo(n-1, memo) + fibMemo(n-2, memo)
// 추후 동일한 문제를 만났을 때 사용하기 위해 리턴 전에 memo 에 저장
memo[n] = res
return res
}
2) Iteration + Tabulation으로 구현하기
- 작은 문제에서부터 시작하여 큰 문제를 해결해 나가는 방법으로, 이 방식을 Bottom-up 방식이라고도 함
function fibTab(n) {
if(n <= 2) return 1
let fibNum = [0, 1, 1]
// n 이 1 & 2일 때의 값을 미리 배열에 저장
for(let i = 3; i <= n; i++) {
fibNum[i] = fibNum[i-1] + fibNum[i-2]
// n >= 3 부터는 앞서 배열에 저장해 놓은 값들을 이용하여
// n번째 피보나치 수를 구한 뒤 배열에 저장 후 리턴
}
return fibNum[n]
}
구현(Implementation)
- 구현 능력 : 정해진 시간 안에 빠르게 문제를 해결하는 능력
- 구현 문제 : 본인이 선택한 프로그래밍 언어의 문법을 정확히 알고 있어야 하며, 문제의 조건에 전부 부합하는 코드를 실수 없이 빠르게 작성하는 것을 목표로 두는 것
- 구현 능력을 보는 대표적인 사례 : 완전 탐색(brute force), 시뮬레이션(simulation)
1. 완전 탐색(brute force)
문제 해결을 할 때의 두 가지 규칙
- 문제를 해결할 수 있는가?
: 모든 문제는 완전 탐색으로 풀 수 있음
- 효율적으로 동작하는가?
: 최의 경우 끝까지 탐색해야 하기에 두 번째 규칙을 만족할 수 없음
- 모든 경우의 수를 탐색하는 모든 경우를 통칭
- 문제를 풀 수 있는 가능한 모든 방법을 고려한 후 효율적으로 동작하는 알고리즘이 전부 탐색할 수밖에 없다고 판단될 때 적용
- 완전 탐색 : Brute Force(무차별 대입), 재귀, 순열, DFS/BFS 등 여러 가지 존재
*Brute Froce : 조건/반복을 사용하여 해결
2. 시뮬레이션(simulation)
- 모든 과정과 조건이 제시되어, 그 과정을 거친 결과가 무엇인지 확인하는 유형
Author And Source
이 문제에 관하여([알고리즘] 시간 복잡도와 알고리즘 심화), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://velog.io/@sominpark/자료구조-알고리즘-시간-복잡도와-자료구조-심화
저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
문제 해결을 할 때의 두 가지 규칙
- 문제를 해결할 수 있는가?
: 모든 문제는 완전 탐색으로 풀 수 있음 - 효율적으로 동작하는가?
: 최의 경우 끝까지 탐색해야 하기에 두 번째 규칙을 만족할 수 없음
*Brute Froce : 조건/반복을 사용하여 해결
Author And Source
이 문제에 관하여([알고리즘] 시간 복잡도와 알고리즘 심화), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@sominpark/자료구조-알고리즘-시간-복잡도와-자료구조-심화저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)