Open kyurimki opened 2 years ago
정점(node)과 그 정점을 연결하는 간선(edge)으로 이루어진 자료 구조
그래프를 탐색한다 = 하나의 정점으로부터 시작하여 차례대로 모든 정점들을 한 번씩 방문하는 것
루트 혹은 임의의 노드에서 시작하여 인접한 노드를 모두 탐색한 후, 다음 depth를 탐색
큐
를 이용하여 구현 (즉, FIFO 원칙으로 탐색)- 최단거리 구하기 문제에서 활용
- 탐색 대상의 규모가 크지 않고, 목표 대상이 멀지 않을 때 활용
장점
단점
루트 혹은 임의의 노드에서 시작하여 최대로 진입할 수 있는 깊이까지 탐색한 후 돌아와 다른 노드로 탐색하는 방식
재귀함수
혹은스택
으로 구현- 노드 방문 시 visited 여부를 반드시 검사해야한다. ⇒ 그렇지 않을 경우, 무한 루프에 빠질 가능성 !
- 경로의 특징을 저장해두어야 하는 문제에서 활용
- 탐색 대상 그래프의 규모가 클 경우 활용
장점
단점
시간 복잡도
인접 행렬 | 인접리스트 | |
---|---|---|
DFS | O(V^2) | O(V+E) |
BFS | O(V^2) | O(V+E) |
동작 원리
= 신장 트리 그래프의 최소 연결 부분 그래프 (즉, 그래프에서 일부 간선을 선택해서 만든 트리)
= 최소 신장 트리 간선의 가중치를 고려하여 최소 비용의 Spanning Tree를 선택하는 것
- 간선의 가중치의 합이 최소여야 한다.
- n개의 정점을 가지는 그래프에 대해 반드시 n - 1개의 간선만을 사용해야 한다.
- 사이클이 포함되어서는 안된다.
탐욕적인 방법(greedy method) 을 이용하여 네트워크(가중치를 간선에 할당한 그래프)의 모든 정점을 최소 비용으로 연결하는 최적 해답을 구하는 것
- 각 단계에서 사이클을 이루지 않는 최소 비용 간선 선택
- 간선 선택을 기반으로 하는 알고리즘
- 이전 단계에서 만들어진 신장 트리와는 상관없이 무조건 최소 간선만을 선택하는 방법
구현
시작 정점에서부터 출발하여 신장트리 집합을 단계적으로 확장 해나가는 방법
- 정점 선택을 기반으로 하는 알고리즘
- 이전 단계에서 만들어진 신장 트리를 확장하는 방법
구현
버블정렬은 가장 쉬운 정렬 알고리즘이지만 시간복잡도가 좋은 퍼포먼스를 내지 못해서 실제로는 잘 사용되지 않는다. 버블 정렬은 현재 원소와 다음 원소를 비교하여 조건에 맞으면 교환하는 식의 정렬이다. 원소가 거품처럼 올라오는 듯해 버블 정렬이라는 이름이 붙었다.
시간복잡도 : O(n²) 공간복잡도: 히나의 배열만 사용하여 정렬을 진행하기 때문에 O(n)이다.
최대 힙 트리나 최소 힙 트리를 구성해 정렬을 하는 방법으로 내림차순 정렬을 위해서는 최대 힙을 구성하고, 오름차순 정렬을 위해서는 최소 힙을 구성하면 된다. 최대 힙을 구성하여 정렬하는 방법은 아래 예와 같다.
n개의 노드에 대한 완전 이진 트리를 구성한다. 이때 루트 노드부터 부노드, 왼쪽 자노드, 오른쪽 자노드 순으로 구성한다.
최대 힙을 구성한다. 최대 힙이란 부노드가 자노드보다 큰 트리를 말하는데, 단말 노드를 자노드로 가진 부노드부터 구성하며 아래부터 루트까지 올라오며 순차적으로 만들어 갈 수 있다.
가장 큰 수(루트에 위치)를 가장 작은 수와 교환한다.
2와 3을 반복한다.
병합 정렬은 분할정복(Divide and Conquer)의 대표적인 예이다. n개의 데이터를 가진 배열을 오름차순으로 정렬하기 위해 병합정렬을 사용한다면 아래와 같은 3단계의 과정을 거치게 된다.
- Divide(분할) : n개의 원소를 갖는 배열을 n/2 개의 원소를 갖는 작은배열 2개로 나눈다.
- Conquer(정복) : 각각의 작은 배열들을 정렬한다.
- Combine(병합) : 정렬된 작은 배열들을 병합한다.
Not-in-place 알고리즘 : 별도의 데이터 저장소 필요 In-Place는 입력리스트 내부에서 정렬이 이뤄지는 경우를 가리키며, 반대는 정렬 도중에 별도 저장공간을 필요로 하는 경우. 병합정렬의 경우 입력리스트를 분할해 이를 정렬하고 다시 합치는 과정에서 분할된 리스트를 별도로 저장해 두어야 한다.
합병정렬과 비슷하게 분할정복(Divide and Conquer) 알고리즘이다. 평균적으로 매우 빠른 수행 속도를 자랑하는 정렬 방법으로 다음과 같은 과정을 거친다. 정렬할 전체 원소에 대해서 정렬을 수행하지 않고, 기준 값을 중심으로 왼쪽 부분 집합과 오른쪽 부분 집합으로 분할하여 정렬하는 방법이다.
- 리스트 안에 있는 한 요소를 선택한다. 이렇게 고른 원소를 pivot(피벗) 이라고 한다.
- pivot을 기준으로 pivot보다 작은 요소들은 모두 pivot의 왼쪽으로 옮기고 pivot보다 큰 요소들은 모두 pivot의 오른쪽으로 옮긴다.
- pivot을 제외한 왼쪽 리스트와 오른쪽 리스트를 다시 정렬한다. 3-1) 분할된 왼쪽 리스트와 오른쪽 리스트도 다시 pivot을 정하고 pivot을 기준으로 2개의 부분리스트로 나눈다. 3-2) 재귀를 사용하여 부분 리스트들이 더이상 분할이 불가능 할 때까지 반복한다.
퀵정렬은 재귀적으로 정의되므로 재귀 호출에 따른 스택이 사용된다. 이때 스택의 깊이는 n개의 원소에 대해 logn에 비례하므로 공간복잡도는 O(nlogn)이다
• 퀵정렬은 최악의 경우 pivot이 배열 내에서 가장 작은 값 또는 가장 큰 값으로 설정된다면 원소 n개에 대해서 n번, (n-1)번, (n-2)번...1번 의 비교가 필요하므로 시간 복잡도가 O(n^2) 된다. • 하지만 평균 시간 복잡도는 O(nlogn)으로 매우 빠르다. pivot 값이 적절히 설정된다면 그 속도가 매우 빠르다. 따라서 pivot값을 잘 설정하는 것이 중요하다. • 퀵 정렬은 중복된 키값이 순서대로 바뀌지 않을 수 있어 (예를 들어 {7,7,1}을 오름차순으로 퀵정렬해보라) not-stable 하다.
데이터의 비교를 통한 정렬이 아닌 분산 정렬을 이용한 정렬 방법이며 자릿수가 있는 데이터(정수, 문자열 등)에만 사용 가능. 각 자리수를 기준으로 점차 정렬을 진행한다.
정렬할 숫자의 자릿수를 d라고 하면 시간 복잡도는 O(dn)으로 표현. 예)가장 큰 수가 12345라면, 자리수는 5이므로 O(5n)
기수정렬은 '버킷'이라는 추가적인 공간할당이 필요하다. 가장 일반적으로 사용되는 것이 Queue
현재 위치에 들어갈 값을 찾아 정렬하는 배열이다. 주어진 자료들 중에 현재 위치에 맞는 자료를 찾아 선택하여 위치를 교환하는 정렬 알고리즘이다.
정렬 순서가 맞지 않으면 무조건 자리를 바꿔줬던 버블정렬과 달리, 1회 iteration마다 최소값(혹은 최대값)을 찾고 단 한번만 해당 요소 위치를 바꿔줌 --> swap의 수 상당히 감소
한번 순회를 돌게되면 알고리즘 상 전체 자료 중 가장 작은 값의 자료가 0번째 인덱스에 위치하게 되므로 그 다음 순회부터는 1번 인덱스부터 순회를 돌며 반복하면 된다.
- 전체 원소 중에서 가장 작은 원소를 찾아 선택하여 첫 번째 원소와 자리를 교환한다.
- 그다음 두 번째로 작은 원소를 찾아서 선택하여 두 번째 원소와 자리를 교환한다.
- 그다음에는 세 번째로 작은 원소를 찾아 선택하여 세 번째 원소와 자리를 교환한다.
- 이 과정을 반복하면서 정렬이 완성된다.
(시간복잡도, 공간복잡도, 효율성 ... ) 단순(구현 간단)하지만 비효율적인 방법 :삽입 정렬, 선택 정렬, 버블 정렬 복잡하지만 효율적인 방법 : 퀵 정렬, 힙 정렬, 합병 정렬, 기수 정렬
root
를 가진 비순환 그래프(acyclic graph)계층적 구조
왼쪽 서브트리의 모든 값 < 노드의 값 < 오른쪽 서브트리의 모든 값
을 만족한다.logN
(N은 트리의 노드 수)으로 유지 -> 검색, 삽입, 삭제의 시간 복잡도는 O(logN)
배열은 같은 타입의 데이터를 여러개 나열한 선형 자료구조이다. 연속적인 메모리 공간에 순차적으로 데이터를 저장한다. 배열은 선언할 때 크기를 정하면, 그 크기로 고정이 된다. 선언된 값은 다시 배열을 선언하지 않으면 변경할 수 없다.
시간복잡도 인덱스를 알고 있다면, 인덱스에 접근하는 시간복잡도는 O(1)이다. 데이터를 배열에 삽입을 하려면 기존에 있는 데이터를 한 칸 shift 한 후 데이터를 넣어야 하기에 시간복잡도는 O(n)이 걸린다. 마찬가지로 배열에서 데이터를 삭제하는 작업 또한 삭제한 뒤, 나머지 데이터들을 한 칸 shift 해줘야 해서 삽입과 마찬가지로 시간복잡도가 O(n)이 걸리게 된다.
장점 구현이 쉽다. 참조를 위한 추가적인 메모리가 필요하지 않는다. 연속적이므로 메모리 관리가 편하다. 인덱스를 통해 접근하므로 검색이 빠르다.
단점 배열의 크기를 변경할 수 없다. 크기를 변경하려면 새로운 배열을 만든 후, 기존의 데이터를 옮겨야 한다. 메모리 낭비가 발생하게 된다. 배열을 선언한다 하더라도 사용하지 않는 공간은 배열을 선언할 때 공간이 할당되어 있어서 낭비가 된다.
배열(Array)은 순차적으로 연결된 공간에 데이터를 나열하는 자료구조이고, 링크드 리스트(Linked List)는 떨어진 곳에 존재하는 데이터를 화살표로 연결해서 관리하는 자료구조이다. 배열은 미리 특정한 연결된 공간을 확보하고 데이터를 쓰고 있는 자료구조이고, 링크드 리스트는 필요할 때 마다 데이터를 추가할 수 있는 구조이다. 배열의 단점을 극복한 자료구조가 링크드 리스트라고 볼 수 있다.
연결리스트 구성 데이터를 보관하는 필드와 다음 노드와의 연결고리역할을 하는 포인터를 가진 노드로 구성되며, 노드들을 서로 연결해서 구성을 한다. 맨 앞의 노드를 head, 맨 뒤의 노드를 tail이라고 하며 tail의 포인터는 null값을 가지고있다.
링크드 리스트(Linked List)의 장점과 단점 링크드 리스트의 장점은 배열과 다르게 미리 데이터 공간을 할당하지 않아도 된다. 배열은 미리 데이터 공간을 할당해야 한다. 단점은 첫번째, 연결(pointer)를 위한 별도의 데이터 공간이 필요하므로 저장 공간의 효율이 높지 않다. 현재의 node 안에서 데이터 값만 갖고 있는 것이 아니라 그 다음의 데이터가 올 주소까지 포함해야 하기 때문에 추가적인 데이터 공간이 필요하다. 두번째, 연결 정보를 찾는 시간이 필요하므로 데이터 접근 속도가 느리다. 배열과 같이 특정 index가 존재하는 것이 아니기 때문에 원하는 데이터를 찾기 위해서는 연결된 링크를 따라서 순차적으로 확인하는 시간이 필요하다. 마지막으로 링크드 리스트에 데이터를 삽입 혹은 삭제시 전, 후 데이터의 연결을 재구성해야하는 부가적인 작업이 발생한다.
이중 연결리스트는 각 노드가 선행 노드와 후속 노드에 대한 링크를 가지는 리스트. 노드의 왼쪽 링크(left link)와 오른쪽 링크(right link)가 각각 다른 노드의 오른쪽 링크와 왼쪽 링크를 연결짓고 있으며 특별하게 헤드도 노드로 이루어져 있다.
원형 연결 리스트의 구조는 마지막 노드가 첫 번째 노드를 가리켜서, 연결의 형태가 원을 이루는 다음 구조의 연결 리스트를 가리킨다.
큐(queue)는 컴퓨터의 기본적인 자료 구조의 한가지로, 먼저 집어 넣은 데이터가 먼저 나오는 FIFO(First In First Out)구조로 저장하는 형식을 말한다. 영어 단어 queue는 표를 사러 일렬로 늘어선 사람들로 이루어진 줄을 말하기도 하며, 먼저 줄을 선 사람이 먼저 나갈 수 있는 상황을 연상하면 된다. 나중에 집어 넣은 데이터가 먼저 나오는 스택과는 반대되는 개념이다.
큐는 한쪽 끝(rear)에서는 삽입연산만 이루어지며 다른 한쪽 끝(front)에서는 삭제연산만 이루어지는 유한 순서 리스트이다.
deque 덱(deque, "deck"과 발음이 같음 ← double-ended queue)은 양쪽 끝에서 삽입과 삭제가 모두 가능한 자료 구조의 한 형태이다. 두 개의 포인터를 사용하여, 양쪽에서 삭제와 삽입을 발생시킬 수 있다. 큐와 스택을 합친 형태로 생각할 수 있다.
priority queue 우선순위 큐(Priority Queue)는 들어간 순서에 상관없이 우선순위가 높은 데이터가 먼저 나오는 것을 말한다. 모든 항목에는 우선순위가 있다. 우선 순위가 높은 요소는 우선 순위가 낮은 요소보다 먼저 queue에서 제외된다. 두 요소의 우선 순위가 같으면 queue의 순서에 따라 제공된다.
[ 소스 코드 ]
int BSearch(int arr[], int target) {
int low = 0;
int high = arr.length - 1;
int mid;
while(low <= high) {
mid = (low + high) / 2;
if (arr[mid] == target)
return mid;
else if (arr[mid] > target)
high = mid - 1;
else
low = mid + 1;
}
return -1;
}
[ 소스코드 ]
int sequentialSearch(int dataArr[], int length, int findData) {
for(int i = 0; i < length; i++)
if (dataArr[i] == findData) return i;
return -1;
}
[ 힙 종류 ]
https://gmlwjd9405.github.io/2018/05/10/data-structure-heap.html
[Contents] 1.
2.
3.
4.
5.