반응형

개념

이진 트리 중 하나로 구간 합을 구하는데 사용된다. 말단 노드에는 원소들이 들어 있고 부모는 자식들의 합이 된다. 부모는 모든 자식들의 합이 되기 때문에 연속되는 구간의 합을 구할 수 있다. 또한 특정 위치의 값을 지속적으로 바꿔야하는 경우에도 사용된다.

https://blog.hexabrain.net/246

 

알고리즘 2-2강. 탐색 알고리즘 - 이진 탐색(Binary Search)

[탐색 알고리즘 강좌] 데이터를 찾아보자! 이진 탐색(Binary Search) 이번에는 순차 탐색에 이어 이진 탐색(Binary Search)에 대해 알아보도록 할텐데, 이 '이진 탐색(Binary Search)'이 왜 이진인지 짐작이 가

blog.hexabrain.net

 

조건

  • 부분 합을 계속해서 구해야 한다.
  • 특정 인덱스 변경이 지속적으로 일어 날 수 있다.

사용 하면 좋은 경우

  1. 부분 합을 계속 구해야 하는 경우
    • list에서 부분 합 배열을 구하는 경우 O(MN)의 시간이 필요하지만 Index Tree를 이용 할 경우 O(MlogN)의 시간으로 단축 시킬 수 있다.
  2. 특정 인덱스의 변경이 지속적으로 일어 날 경우
    • 부분 합 배열에서 특정 인덱스를 고칠 경우 그 인덱스를 포함하는 모든 합을 다시 구해야 하기 때문에 O(MN)의 시간이 필요한데 Index Tree를 이용 할 경우 O(MlogN)으로 시간을 단축 시킬 수 있다.

구현 방법

기본 이진 탐색

  1. 트리 사이즈 정하기
tydef long long ll;

ll getIndex(int num) { //num은 원소의 갯수
    ll tmp = 1;
    while(tmp < num) {
        tmp <<= 1;
    }
    return tmp * 2;
}

트리의 말단 노드에 모든 원소를 배치해야 하고 부모까지 배치해야하기 때문에 트리 배열의 사이즈는 $2^n$ ≥ (원소의 개수) 가 되는 n을 찾고 그거의 2배를 해야 부모 노드 까지 배치 할 수 있다.

  1. 노드 값 채우기
ll index = getIndex(n);
ll p = index/2;
for(int i = 0; i < n; i++) { //말단 노드에 원소 입력
    scanf("%lld", &t[p]);
    p++;
}
for(ll i = (index/2) - 1; i > 0; i--) {
    t[i] = t[i*2] + t[i*2 + 1]; //왼쪽 자식과 오른쪽 자식의 합
}

트리의 말단 노드에 원소들을 입력 받고 부모 노드들에 자식들의 합을 대입한다. 부모 노드는 아래에서 부터 대입해야 한다.

b 위치의 값을 c로 바꾸기

ll idx = (index/2) + (b-1);
ll pp = idx;
if(t[idx] >= c) {
  ll gap = t[idx] - c; //바꾸려는 값과 원래의 값 차이 
  t[idx] -= gap; //차이만큼 빼기
  pp >>= 1; // 부모 노드로 가기
  while(pp > 0) { //최상단 노드(1번째 인덱스)까지 업데이트
    t[pp] -= gap;
    pp >>= 1;
  }
}
else {
  ll gap = c - t[idx];
  t[idx] += gap;
  pp >>= 1;
  while(pp > 0) {
    t[pp] += gap;
    pp >>= 1;
  }
}

특정 말단 노드의 값을 바꾸면 해당 노드의 부모 노드들의 값을 같이 바꿔준다.

구간 합 출력하기 (b~c번째 인덱스)

ll left = (index / 2) + (b - 1);
ll right = (index / 2) + (c - 1);
ll answer = 0;
while(left <= right) {
    if(left % 2 == 0) { //왼쪽에 있는 노드가 부모기준에 왼쪽에 있을때
        left /= 2; // 부모 노드로 이동 
    }
    else{//부모 기준 오른쪽에 있을때
        answer += t[left]; 
        left ++; // 해당 값을 더해주고 오른쪽 옆으로 이동
        if(left == right) { //왼쪽과 오른쪽이 만나면 해당 값 더해주고 종료
            answer += t[left];
            break;
        }
        else { //왼쪽과 오른쪽이 다르면 왼쪽의 부모로 이동
            left /= 2;
        }
    }
    if(right % 2 == 0) { //오른쪽 노드가 부모 기준 왼쪽에 있을 때
        answer += t[right]; 
        right --; //해당 값을 더해주고 왼쪽 옆으로 이동
        right /= 2; //새로운 노드의 부모로 이동
    }
    else {
        right /= 2; //부모 노드로 이동
    }
}

노드의 위치를 통해 부모 노드로 값을 구할 수 없으면 정답에 더해주고 부모 노드에 포함 된다면 값을 더하지 않고 부모 노드로 이동한다.

전체 코드

int main(int argc, const char * argv[]) {
    vector<ll> t(8194304 + 1, 0);
    scanf("%d%d%d", &n, &m, &k);
    ll index = getIndex(n);
    ll p = index/2;
    for(int i = 0; i < n; i++) {
        scanf("%lld", &t[p]);
        p++;
    }
    for(ll i = (index/2) - 1; i > 0; i--) {
        t[i] = t[i*2] + t[i*2 + 1];
    }
    for(int i = 0; i < m + k; i++) {
        scanf("%d%lld%lld", &a, &b, &c);
        if(a == 1) {
            ll idx = (index/2) + (b-1);
            ll pp = idx;
            if(t[idx] >= c) {
                ll gap = t[idx] - c;
                t[idx] -= gap;
                pp >>= 1;
                while(pp > 0) {
                    t[pp] -= gap;
                    pp >>= 1;
                }
            }
            else {
                ll gap = c - t[idx];
                t[idx] += gap;
                pp >>= 1;
                while(pp > 0) {
                    t[pp] += gap;
                    pp >>= 1;
                }
            }
        }
        else if(a == 2) {
            //구간 합 출력
            ll left = (index / 2) + (b - 1);
            ll right = (index / 2) + (c - 1);
            ll answer = 0;
            while(left <= right) {
                if(left % 2 == 0) {
                    left /= 2;
                }
                else{
                    answer += t[left];
                    left ++;
                    if(left == right) {
                        answer += t[left];
                        break;
                    }
                    else {
                        left /= 2;
                    }
                }
                if(right % 2 == 0) {
                    answer += t[right];
                    right --;
                    right /= 2;
                }
                else {
                    right /= 2;
                }
            }
            printf("%lld\\n", answer);
        }
    }
    return 0;
}

시간 복잡도

  • O(MlogN)
반응형
반응형

개념

다익스트라(dijkstra)알고리즘은 다이나믹 프로그래밍(dp)를 활용한 대표적인 최단 경로 탐색 알고리즘이다. 다익스트라 알고리즘은 하나의 정점에서 다른 모든 정점으로 가는 최단 경로를 알려준다.

참고: https://blog.naver.com/PostView.naver?blogId=ndb796&logNo=221234424646&redirect=Dlog&widgetTypeCall=true&directAccess=false

 

23. 다익스트라(Dijkstra) 알고리즘

  다익스트라(Dijkstra) 알고리즘은 다이나믹 프로그래밍을 활용한 대표적인 최단 경로(Shortest P...

blog.naver.com

2차원 배열로 간선의 정보를 저장한다. 서로 연결되어 있지 않은 경우는 무한의 수로 설정한다.

  1. 음의 간선 정보는 포함 할 수 없다.
  2. 방문하지 않은 노드중 가장 비용이 적은 노드를 선택

구현 방법

  1. 간선 정보 2차원 배열로 생성, 최단 거리 배열 생성
#define INF 1e9 //엄청 큰 값인 10억

vector<pair<int,int>> v[10001]; // 비용, 연결점
int d[10001];
fill(d, d+10001, INF);

최단 거리 배열은 가장 작은 값을 나타내야 하기 때문에 우선은 가장 큰 값으로 초기화해 놓는다.

  1. priority_queue를 이용하여 최소비용 순서로 다익스트라 알고리즘 적용
void dijkstra(int start) {
	priority_queue<pair<int,int>> pq;
	pq.push({0,start});
	d[start] = 0;
	
	while(!pq.empty()) {
		int dist = -pq.top().fist; // 현재 노드까지의 비용, 작은것부터 뽑기위해 -로 저장해놨음
		int now = pq.top().second; // 현재 노드
		pq.pop();
	
		if(d[now] < dist) continue; // 현재 노드까지의 비용이 더 크면 볼 것도 없음
	
		for(int i = 0; i < v[now].size(); i++) {
			int cost = dist + v[now][i].first; //현재노드까지의 비용 + 다음 노드로 가는 비용
			if(cost < d[v[now][i].second]) {
				d[v[now][i].second] = cost
				pq.push(make_pair(-cost, v[now][i].second));
			}
		}
	}
}

현재까지의 비용이 가장 적은 노드 선택 후 그 노드와 연결된 노드들의 비용 값을 구한 후 만약 구한 값이 원래의 값보다 더 작다면 값을 업데이트 해 준 후 우선순위 큐에 해당 노드를 넣는다.

전체 코드

#include<iostream>
#include<vector>
#include<queue>
using namespace std;
#define INF 1e9 // 무한을 의미하는 값으로 10억을 설정

// 노드의 개수(N), 간선의 개수(M), 시작 노드 번호(Start)
// 노드의 개수는 최대 100,000개라고 가정
int n, m, start;

vector<pair<int, int> > graph[100001]; // 각 노드에 연결되어 있는 노드에 대한 정보를 담는 배열
int d[100001]; // 최단 거리 테이블 만들기

void dijkstra(int start)
{
    priority_queue<pair<int,int>>pq; // 거리, 노드 인덱스
    
    pq.push({0,start}); //시작 노드로 가기위한 최단 경로는 0으로 설정하여, 큐에 삽입.
    d[start]=0;
    
    while(!pq.empty())
    {
        int dist = -pq.top().first; //현재 노드까지의 비용
        int now = pq.top().second; // 현재 노드
        pq.pop();
        
        if(d[now]<dist) // 이미 최단경로를 체크한 노드인 경우 패스
            continue;
        
        for(int i=0; i<graph[now].size(); i++)
        {
            int cost = dist+graph[now][i].second; // 거쳐서 가는 노드의 비용을 계산
                                                  // 현재노드까지 비용 + 다음 노드 비용
            if(cost<d[graph[now][i].first]) // 비용이 더 작다면 최단경로 테이블 값을 갱신.
            {
                d[graph[now][i].first]=cost;
                pq.push(make_pair(-cost,graph[now][i].first));
            }
        }
    }
}

int main(void)
{
    cin >> n >> m >> start;
    
    // 모든 간선 정보를 입력받기
    for (int i = 0; i < m; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        // a번 노드에서 b번 노드로 가는 비용이 c라는 의미
        graph[a].push_back({b, c});
    }
    
    // 최단 거리 테이블을 모두 무한으로 초기화
    fill(d, d + 100001, INF);
    
    // 다익스트라 알고리즘을 수행
    dijkstra(start);
    
    // 모든 노드로 가기 위한 최단 거리를 출력
    for (int i = 1; i <= n; i++)
    {
        // 도달할 수 없는 경우, 무한(INFINITY)이라고 출력
        if (d[i] == INF) {
            cout << "INFINITY" << '\\n';
        }
        // 도달할 수 있는 경우 거리를 출력
        else {
            cout << d[i] << '\\n';
        }
    }
}

시간 복잡도

O(N * logN)

 

반응형

'코딩 낙서' 카테고리의 다른 글

카카오기출 - 순위 검색  (0) 2023.06.23
알고리즘 - 인덱스 트리(Index Tree)  (0) 2023.06.21
알고리즘 - 크루스칼(Kruskal)  (0) 2023.06.21
알고리즘 - 누적합(Prefix Sum)  (0) 2023.06.21
알고리즘 - Union & Find  (0) 2023.06.21
반응형

개념

가장 적은 비용으로 모든 노드를 연결하기 위해 사용하는 알고리즘이다. 최소 비용 신장 트리를 만들기 위한 대표적인 알고리즘으로 흔히 여러개의 도시가 있을 때 각 도시를 도로를 이용해 연결하고자 할 때 비용을 최소로 하기 위해 실제로 적용되는 알고리즘이다.

노드 = 정점 = 도시

간선 = 거리 = 비용

일단 모든 노드를 최대한 적은 비용으로 연결 시켜야하기 때문에 간선 정보를 오름차순으로 정렬 한 후 비용이 적은 간선부터 차근차근 그래프에 포함시킨다. 단 이때 사이클을 형성하게 될 경우 간선을 포함하지 않는다.

 

조건

  1. 간선 정보를 오름차순으로 정렬
  2. 사이클이 형성되지 않게 연결

사용하기 좋은 경우

  1. 모든 노드를 최소 비용으로 연결할 경우
  2. 연결되어있는 그룹의 수를 구하는 경우 → 크루스칼 알고리즘으로 다 연결 한 후 최상단 부모의 개수가 그룹의 수이다.

구현 방법

  1. 비용, 출발, 도착의 정보를 갖는 노드들을 비용을 기준으로 오름차순 정렬하기
typedef pair<int, int> pii;
for(int i = 0; i < m; i++) { //a: 출발지점, b: 도착지점, c: 비용
        scanf("%d%d%d", &a, &b, &c);
        v.push_back(pair<int, pii>(c, pii(a,b)));
    }
    int p = 0;
    sort(v.begin(), v.end()); //비용 기준으로 정렬
  1. 최상단 부모 찾기
int parent[10001];

int findParent(int child) {
    if(parent[child] == 0) {
        return child;
    }
    return parent[child] = findParent(parent[child]);
}

parent 배열의 인덱스는 자기 자신을 나타내고 값은 자신의 부모를 나타낸다. 값이 0일경우 부모가 존재하지 않는 것으로 최상단 노드를 의미하기 때문에 값이 0일때를 찾아서 return 한다.

  1. 부모 자식 관계 설정하기, 최소 비용 구하기
int unionSet(pair<int, pii> xy) {
    int xp = findParent(xy.second.first); //출발 지점의 부모 찾기
    int yp = findParent(xy.second.second); //도착 지점의 부모 찾기
    
    if(xp == yp) return xp; //부모가 같으면 이미 연결되어 있는 것이므로 종료
    else { //부모가 다르면 서로 다른 선에 연결되어 있기 때문에 한 쪽의 부모를 다른 쪽 부모에 연결
        cost[xp] += cost[yp] + xy.first; //연결 후 최상단 노드의 비용을 더해줌
        parent[yp] = xp; //한 쪽의 최상단 부모를 다른 쪽 최상단 부모로 설정
    }
    return xp; //새로운 연결선의 최상단 노드
}

부모가 같으면 이미 연결되어있는 상태이고 다르면 서로의 최상단 부모를 연결해준다.

전체 코드

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;
typedef pair<int, int> pii;
int n, m, a, b, c, answer;

vector<pair<int, pii>> v;

int parent[10001];
int cost[10001];

int findParent(int child) {
    if(parent[child] == 0) {
        return child;
    }
    return parent[child] = findParent(parent[child]);
}

int unionSet(pair<int, pii> xy) {
    int xp = findParent(xy.second.first);
    int yp = findParent(xy.second.second);
    
    if(xp == yp) return xp;
    else {
        cost[xp] += cost[yp] + xy.first;
        parent[yp] = xp;
    }
    return xp;
}

int main(int argc, const char * argv[]) {
    scanf("%d", &n);scanf("%d", &m);
    for(int i = 0; i < m; i++) {
        scanf("%d%d%d", &a, &b, &c);
        v.push_back(pair<int, pii>(c, pii(a,b)));
    }
    int p = 0;
    sort(v.begin(), v.end());
    for(int i = 0; i < v.size(); i++) {
        p = unionSet(v[i]);
    }
    answer = cost[p];
    printf("%d", answer);
    
    return 0;
}

시간복잡도

O(logN)

반응형
반응형

개념

이진 탐색이란 데이터가 정렬돼 있는 배열에서 특정한 값을 찾아내는 알고리즘이다. 배열의 중간에 있는 임의의 값을 선택하여 찾고자 하는 값 X와 비교한다. X가 중간 값보다 작으면 중간 값을 기준으로 좌측의 데이터들을 대상으로, X가 중간값보다 크면 배열의 우측을 대상으로 다시 탐색한다. 동일한 방법으로 다시 중간의 값을 임의로 선택하고 비교한다. 해당 값을 찾을 때까지 이 과정을 반복한다.

https://blog.hexabrain.net/246 참고

 

알고리즘 2-2강. 탐색 알고리즘 - 이진 탐색(Binary Search)

[탐색 알고리즘 강좌] 데이터를 찾아보자! 이진 탐색(Binary Search) 이번에는 순차 탐색에 이어 이진 탐색(Binary Search)에 대해 알아보도록 할텐데, 이 '이진 탐색(Binary Search)'이 왜 이진인지 짐작이 가

blog.hexabrain.net

조건

  • 수들이 오름차순 또는 내림차순으로 정렬 되어 있어야 한다.
  • 원하는 값을 찾으면 종료한다. (종료 조건1) → 값을 찾음
  • 왼쪽값이 오른쪽 값보다 커지면 종료한다. (종료조건2) → 값이 없음

사용 하면 좋은 경우

  1. 리스트에서 값을 찾을 때 시간을 단축해야 하는 경우
    • list에서 find() 함수를 사용하거나 반목문으로 값을 찾을 시 O(N)의 시간이 필요하기 때문에 이진 탐색을 사용하면 시간 효율성을 증가 시킨다.
  2. 조건에 맞는 최소 또는 최대값을 찾아야 하는 경우
    • 이러한 경우에는 index를 left, right로 두지 않고 left = 가장 작은 값, right = 가장 큰 값 으로 설정하여 배열에서의 위치가 아닌 조건에 맞는 값을 구 할 수 있도록 한다.

구현 방법

기본 이진 탐색

  1. 반복문
#include <iostream>
#include <vector>

using namespace std;

int BinarySearch(vector<int> num, int target) {
    int left = 0, right =(int) num.size(), mid = 0;
    sort(num.begin(), num.end()); //숫자 오름차순 정렬
    //num : {1, 2, 4, 5, 15, 56, 123, 125, 199, 3215}
    while(left <= right) { //종료 조건
        mid = (left + right) / 2;
        if(num[mid] == target) {// target을 찾았을 때
            return mid;
        }
        else if(num[mid] > target) { //target보다 클 때 -> 왼쪽 수들을 확인
            right = mid - 1;
        }
        else { //target보다 작을 때 -> 오른쪽 수들을 확인
            left = mid + 1;
        }
    }
    return -1;
}

  1. 재귀 함수
#include <iostream>
#include <vector>

using namespace std;
//재귀 함수는 num을 정렬 해서 함수 호출
int RecurBinarySearch(vector<int> num, int target, int left, int right) {
    if(left > right) return -1; //종료 조건
    int mid = (left + right) / 2;
    if(num[mid] == target) { //target을 찾았을 때
        return mid;
    }
    else if(num[mid] > target) { // target보다 클 때
        return RecurBinarySearch(num, target, left, mid-1);
    }
    else { //target보다 작을 때
        return RecurBinarySearch(num, target, mid+1, right);
    }
}

int main(int argc, const char * argv[]) {
    vector<int> num {1,5,2,199,56,3215,125,123,15,4};
    sort(num.begin(), num.end());
    int left = 0, right = (int)num.size();
    cout << RecurBinarySearch(num, 199, left, right) << endl;
    return 0;
}

UpperBound

목표값보다 큰 값이 처음 발견되는 곳 찾기

int UpperBound(vector<int> num, int target) {
    int left = 0, right = (int) num.size(), mid = 0;
    sort(num.begin(), num.end());
    int ans = 0;
    while(left <= right) {
        mid = (left + right) / 2;
        if(num[mid] <= target) { // 목표보다 작거나 같으면 오른쪽 보기
            left = mid + 1;
        }
        else { // 목표보다 크면 답의 후보로 두고 왼쪽 보기
            ans = mid;
            right = mid - 1;
        }
    }
    return ans;
}

LowerBound

목표값보다 작은 값이 처음 발견되는 곳 찾기

int LowerBound(vector<int> num, int target) {
    int left = 0, right = (int) num.size(), mid = 0;
    sort(num.begin(), num.end());
    int ans = 0;
    while(left <= right) {
        mid = (left + right) / 2;
        if(num[mid] >= target) { //목표보다 크거나 같으면 왼쪽 보기
            right = mid - 1;
        }
        else { // 목표보다 작으면 답의 후보로 두고 오른 쪽 보기
            ans = mid;
            left = mid + 1;
        }
    }
    return ans;
}

결과

int main(int argc, const char * argv[]) {
    vector<int> num {1,5,2,199,56,3215,125,123,15,4};
    sort(num.begin(), num.end());
    int left = 0, right = (int)num.size();
    for(int i = 0; i < num.size(); i++) {
        cout << num[i] << " ";
    }
    cout << endl;
    cout << "재귀 : " << RecurBinarySearch(num, 199, left, right) << endl;
    cout << "반복문 : " << BinarySearch(num, 199) << endl;
    cout << "UpperBound : " << UpperBound(num, 199) << endl;
    cout << "LowerBound : " << LowerBound(num, 199) << endl;
    return 0;
}
1 2 4 5 15 56 123 125 199 3215
재귀 : 8
반복문 : 8
UpperBound : 9
LowerBound : 9

시간 복잡도

  • O(logN)
반응형

'코딩 낙서' 카테고리의 다른 글

알고리즘 - 크루스칼(Kruskal)  (0) 2023.06.21
알고리즘 - 누적합(Prefix Sum)  (0) 2023.06.21
알고리즘 - Union & Find  (0) 2023.06.21
[LINUX] 기본 명령어 정리  (0) 2023.06.20
Linux  (0) 2023.06.14

+ Recent posts