├── JAVA
│ └── src
│ ├── baekjoon : 백준 단계별 문제 풀이
│ ├── solvedClass : solved.ac class별 문제 풀이
│ └── AlgorithmType : 알고리즘 유형별 풀이
└── Python
└── src
└── AlgorithmType : 알고리즘 유형별 풀이
효율적인 알고리즘 - 시간이 짧고, 자원을 적게 사용
연산을 숫자로 표기
시간 복잡도를 더 중요하게 생각하기 때문에, 별다른 말이 없으면 시간복잡도를 뜻함
(실제 러닝타임을 표시하는 것 보다 데이터나 사용자의 증가율에 따른 알고리즘의 성능을 예측하는 게 목표이다.)
-> 상수와 같은 숫자들은 모두 1이 된다.
O(1) / 배열의 요소 참조
입력 데이터 타입의 크기에 상관 없이 언제나 일정한 시간이 걸리는 알고리즘을 말한다.
- 데이터가 증가함에 따라 성능 변화가 없다.
O(N) / 순차탐색
입력 데이터의 크기에 비례해서 처리 시간이 걸리는 알고리즘을 표현할 때 사용
- N의 크기만큼 처리 시간이 늘게된다.
O(Nn) /bubble sort
n을 돌리면서, n으로 또 돌릴 때 사용한다.
O(NM)
m을 n만큼 돌린다.
O(2n)
ex) 피보나치 수열
- 재귀적으로 구현 가능
- 함수를 호출할 때마다, 바로 전의 숫자와 전전숫자를 알아야 하기 때문에 매 번 함수를 호출할 때마다, 2번씩 호출한다. -> 2진트리의 형태 -> 트리의 높이만큼 반복한다.
O(logN) / 2진 검색
처리를 할 때마다, 처리 해야 할 데이터 양이 반씩 줄어드는 경우
O(sqrt(N)) / 소수 탐색
문제를 풀 수 있는 시간이 1초인 경우
- N의 범위 500인 경우 : O(n3)
- N의 범위 2,000인 경우 : O(n2)
- N의 범위 100,000인 경우 : O(NlogN)
- N의 범위 10,000,000인 경우 : O(N)
- 프로그램을 실행시킨 후 완료하는데 필요한 자원의 공간
입력이 n일 때 연산 횟수 -> 알고리즘 속도
- 연산이 커지면 계수는 의미없어지고, 차수가 중요하다! (큰 상수가 나올 수 있는 경우 조심)
int a[1000] : 4KB
int a[1000000] : 4MB
int a[2000][2000] : 16MB
- 데이터의 개수가 1000만 단위 넘어가지 않도록
- python 100만 이상 리스트 선언하는 경우 적음
import time
start_time = time.time() # 측정 시작
# 소스
end_time = time.time() # 측정 종료
print("time : ", end_time - start_time) # 출력
- ex) 배열, 연결리스트
- ex) 트리, 그래프, 해시
- 값 확인 변경 : O(1)
- 임의의 위치 값 추가/삭제 : O(n) / 마지막 위치 : O(1)
배열 | 연결리스트 | |
---|---|---|
k번째 원소의 접근 | O(1) | O(k) |
임의 위치 원소 추가 / 제거 | O(N) | O(1) |
메모리 상의 배치 | 연속 | 불연속 |
추가로 필요한 공간 | - | O(N) |
JAVA : HashMap<Integer,String> map = new HashMap<>(10)
- '키'와 '값'을 저장하는 자료 구조
- 해시 함수를 통해 '키'와 '값'이 저장되는 위치를 결정하므로, 사용자는 그 위치를 알 수 없고, 삽입되는 순서와 들어 있는 위치 또한 관계가 없다.
- 검색 과 저장의 평균적인 시간 복잡도가 O(1)에 수렴하게 된다.
- 값이 추가로 들어오면 List처럼 저장공간을 한 칸씩 늘리지 않고 약 두배로 늘린다.
-> 이 과정이 과부하가 많이 걸리기 때문에 초기에 저장해주는 것이 좋다.
- put() : HashMap에 키와 값을 저장
- get() : 지정된 키값을 반환한다.
- remove() : HashMap에서 지정된 키로 지정된 값을 제거한다
- clear() : HashMap의 모든 객체를 제거한다.
- containsKey() : HashMap에 지정된 키가 포함되어 있는지 알림
- isEmpty() : HashMap이 비어있는지 확인
- size() : HashMap에저장된 요소의 개수를 반환한다.
- keySet() : HashMap에 저장된 모든 키가 있는 Set을 반환한다.
- 참고 https://vaert.tistory.com/107
JAVA : Stack stack = new Stack<>();
- 원소 추가 O(1)
- 원소 제거 O(1)
- 원소 확인(TOP) O(1)
- FILO
- Top
- pop() : 가장 나중에 넣은 데이터 삭제
- push() : 데이터 추가
- peek() : 가장 나중에 넣은 데이터 확인
- isEmpty() : 비어있는지 확인
- size() : 스택에 있는 요소 크기 반환
JAVA : Queue queue = new LinkedList<>();
- 원소 추가/제거 : O(1)
- 제일 앞/뒤 원소 확인 : O(1)
- FIFO
- head, tail
- add() : 맨 뒤에 값 삽입 (반환)
- poll() : 맨 앞 값 제거 (반환)
- offer() : 맨 뒤에 값 삽입
- remove() : 맨 앞 값 제거
- peek() : 맨 앞 요소 반환 // 오리픽
JAVA : Deque deque = new LinkedList<>();
- 원소 추가/제거 : O(1)
- 제일 앞/뒤 원소 확인 : O(1)
- 이진트리 : 각 노드가 자식을 최대 2명을 가지는 트리
-> 전위순회, 중위순회, 후위순회 - 이진검색트리 : 왼쪽 서브트리에는 해당노드보다 작은 값 / 오른쪽 서브 트리에는 해당 노드보다 큰 값
-> 중위순회 방식을 쓴다.(정렬 순서대로 읽을 수 있음.) - Trie(트라이) 트리 : 문자열을 저장하고 효율적으로 탐색하기 위한 트리 형태의 자료구조
- 최대값이나 최소값을 빠르게 찾기위해 고안된 완전이진트리를 기반으로한 자료구조
MinHeap : 가장 작은값이 root노드
MaxHeap : 가장 큰 값이 root노드
- 노드 추가 시, 가장 마지막에 추가하고 위의 노드와 비교하는 방식으로 정렬
- 노드 삭제 시, root노드의 값을 빼고 정렬하는 방식
Tip!!
JAVA PriorityQueue 는 기본적으로 minHeap이다.
// 최소 힙
PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>();
// 최대 힙
PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(Comparator.reverseOrder());
// 최대 힙 version2
PriorityQueue<Integer> maxHeap = PriorityQueue<>(new Comparator<Integer>(){
@Override
public int compare(Integer i1, Integer i2) {
return i2-i1;
}
});
- 정점(vertex) / 간선(edge)
- 방향 유무에 따라 Directed / Undirected graph로 나뉜다.
- 써클 유무에 따라 Cyclic / Acyclic graph로 나뉜다.
- 1차원 리스트
- 장점
- 간선 개수에 비례하는 메모리 사용
- 특정 노드에서 모든 노드 연결 확인 O(E)
- 단점
- 특정 노드에서 노드 연결 확인 O(V)
- 장점
- 2차원 배열
- 장점
- 구현 쉬움
- 특정 노드에서 노드 연결 확인 O(1)
- 단점
- 특정 노드에서 모든 노드 연결 확인 O(V)
- 메모리 사용 큼
- 장점
- Depth First Search(DFS) : 깊이우선검색 -> Stack 이용
- Breadth First Search(BFS) : 너비우선검색 -> Queue 이용
- Arrays.sort(arr) / Collections.sort(arr) : 배열 오름차순 정렬
- Arrays.sort(arr, Collections.reverseOrder()); : 배열 내림차순 정렬
- ArraysSort : dual-pivot QuickSort 알고리즘 사용
- 분할 정복 알고리즘의 대표적인 예
- O(n + k) : n은 리스트의 개수, k는 정수의 최대값
- 정수로 표현할 수 있는 자료에 대해서만 적용 가능
- 카운트를 위한 공간을 할당하기 위해선 집합 내 가장 큰 정수를 알아야 한다.
- 완전탐색 알고리즘 -> 모든 경우의 수 탐색
- 걸리는 시간은 가능한 답의 수와 비례
- 주어진 문제를 선형 구조로 구조화
- 구조화된 문제공간을 적잘한 방법으로 해를 구성할 때까지 탐색한다.
- 구성된 해를 정리한다.
- 거리측정은 BFS만 가능!
- BFS, DFS 시간 복잡도
- 인접 리스트 구현 -> O(N+E)
- 인접 행렬 구현 -> O(N^2)
- 한 노드에서 갈 수 있는 노드 중 하나 선택
- 선택한 노드에서 갈 수 있는 노드 중 하나 선택 (방문한 노드는 제외)
- 갈 수 있는 노드가 존재하지 않으면 이전 노드로 돌아와서 갈 수 있는 노드 중 하나를 선택한다
- 미로 생성, Cycle Detection, 위상 정렬
- 한 노드에서 갈 수 있는 노드를 큐에 추가한다.
- 큐에서 노드를 하나 꺼내어 꺼낸 노드에서 갈 수 있는 노드를 큐에 추가한다.
- 큐에 노드가 존재하지 않으면 break 한다.
- 최단 경로, 임의 경로 탐색, 최소 신장 트리
-
상황마다 최적의 값 결정 -> 빠름
-
수행 과정 (선가검)
- 해 선택 : 최적 해 -> 부분 해 집합에 추가
- 실행 가능성 검사 : 문제의 제약조건에 위반되지 않는지 검사
- 해 검사 : 문제의 해가 되는지 검사 / 전체 문제의 해가 완성되지 않았으면 해 선택부터 다시 시작
- 전체 문제를 여러 개의 하위 문제로 나누어 풀고, 하위 문제들의 해결방법들을 결합하여 최종 문제를 해결하는 방식
- (작은 문제가 바뀌지 않음)
- 정답을 한번 구했으면 그 정답을 캐시에 저장 -> 메모이제이션(배열에 저장) 이라고 한다. (조건에 맞는 최적의 값 저장)
- 재귀 호출을 이용해 구현한다.
- 특정 조건(보통 바닥)까지 재귀호출 하고 특정 값(바닥 값)을 return한다.
- 반복문으로 구현한다.
- 바닥부터 시작해 n의 값을 알아낸다.
- 작은 문제로 나누어 푸는 방법
- 주어진 문제의 답을 구하기 위해 현재 상태에서 가능한 모든 후보군을 따라 가며 탐색하는 알고리즘 (보통 visited 배열로 상태 체크)
- 재귀호출을 하기전의 상태를 재귀호출이 끝난 뒤 되돌리는 것이 핵심!
- Olog(N)
- 재귀 함수는 재귀 깊이가 예측 가능한 경우 위주로 사용
- 순환 조건 / 종료조건
- (반드시 특정 입력에 대해서 자기 자신을 호출하지 않고 종료 - Base Codition) 필요!
-
- 자기참조를 하지 않는다.
-
- 원하는 결과를 낸다.
-
- 재귀호출의 방향이 기저사례로 가야 한다.
-
- 재귀 호출의 최대 깊이 (x)
-
- 재귀호출을 제외한 시간 복잡도 (y)
-
- 하나의 호출에서 재귀함수의 호출 횟수 (z)
- => O(y*z^x)
- 순서대로 검색
- 평균 비교 횟수 : (N+1)/2
- 정렬이 되어있을 경우 평균 비교 횟수가 반으로 줄어든다.
- 시간 복잡도 O(n)
-
가운데 키 값과 비교하여 다음 검색 위치 결정 후, 검색
-
자료가 정렬된 상태여야 한다.
-
시간 복잡도 O(logN)
-
시작점과 종료점을 이용하여 검색 반복
-
자료의 삽입이나 삭제가 발생하면 정렬상태로 유지하는 작업 필요
// TODO : 블로그 글 추가 예정
-
실수형 사용할 땐 double 쓰는게 좋다!
-
문자열 추가 : String class사용 StringBuilder class 사용
-
int형 자리수 구하기 : Math.log(숫자)+1;
-
거듭제곱 구하기 : Math.paw(a, b);
-
Int to String : Integer.toString();
-
String to Int : Integer.parseInt();