브레젠험 직선(Bresenham’s Line) 알고리즘 이란?

 

  • 브레젠험 직선(Bresenham’s Line) 알고리즘은 컴퓨터 그래픽스에서 복잡하고 계산을 느리게 만드는 실수 계산을 배제하고 정수 계산만으로도 직선을 그리기 위해 만들어진 알고리즘입니다.




브레젠험 직선 알고리즘을 사용하는 이유

 

  • 직선을 그릴때 'y = mx + b' 직선 방정식을 통해서 그릴 수 있습니다. 하지만, 이러한 직선 방정식을 그대로 사용하여 그리게 될 경우 실수를 반드시 사용해야 됩니다. 과거에는 실수 연산이 성능이 좋지 않아서 되도록 정수 연산만을 사용하도록 하였으며, 픽셀 단위로 직선을 그려야 할 때는 실수를 사용하는 직선 방정식보다 정수만을 가지고 직선을 그릴 수 있는 브레젠험 직선 알고리즘을 이용하는 것이 더 적합합니다.




브레젠험 직선(Bresenham’s Line) 알고리즘 원리

 

 

  • 브레젠험 직선 알고리즘은 위와 같이 기울기가 0보다 크고 1보다 작을 경우 (x + 1, y + 0.5) 좌표에서 직선이 위에 있을 경우 픽셀의 위치를 y축으로 1증가시켜 그리며, 아래 있을 경우 증가시키지 않은 위치에 픽셀을 그립니다.




기울기가 0이상 1 미만일 때 브레젠험 직선 알고리즘 판별식 구하는 방법

 

// a는 기울기, b는 y절편입니다.
1. y = ax + b


// x에는 +1, y에는 +0.5를 합니다.
2. (y + 0.5) = a(x + 1) + b;


// y절편을 구하는 공식은 y - (h/w)x 입니다.
3. (h/w)(x + 1) + y - (h/w)x = (y + 0.5)


// y가 더 클 경우 직선이 위에 있고, 더 작을 경우 직선이 아래에 있습니다.
4. (h/w)(x + 1) + y - (h/w)x < (y + 0.5)


// y의 위치를 옮김니다.
5. (h/w)(x + 1) + y - (h/w)x - (y + 0.5) < 0


// 각 항에 w를 곱하여 분자를 없앱니다.
6. h(x + 1) + wy - hx - w(y + 0.5) < 0


// x와 y에 0을 대입하고, 소수점을 없애기 위해서 2를 곱합니다.
7. 2h - w < 0

 

  • 위와 같이 기울기에 따라서 판별식을 구할 수 있으며, x가 1씩 증가할 때마다 판별식이 2h씩 증가하고 x와 y값이 1씩 증가할 때마다 2w + 2h씩 증가합니다. 즉, 2h - w < 0이 성립하지 않아서 x, y를 증가시켰다면, 2h - w 에 2h - 2w를 더하여 다음 판별식에 사용해야 합니다.




브레젠험 직선 알고리즘 코드

 

#include <iostream>

using namespace std;

constexpr int MAX_LEN = 30;

int m[MAX_LEN][MAX_LEN];

void makeBresenhamLine(int startX, int startY, int endX, int endY)
{
    int width = abs(startX - endX);
    int height = abs(startY - endY);

    int xFactor = startX > endX ? -1 : 1;
    int yFactor = startY > endY ? -1 : 1;

    if (width > height)
    {
        int y = startY;
        int dest = 2 * height - width;

        for (int x = startX; x != endX; x += xFactor)
        {
            m[y][x] = 1;

            if (dest < 0)
                dest += 2 * height;
            else
            {
                y += yFactor;
                dest += 2 * height - 2 * width;
            }
        }
    }
    else
    {
        int x = startX;
        int dest = 2 * width - height;

        for (int y = startY; y != endY; y += yFactor)
        {
            m[y][x] = 1;

            if (dest < 0)
                dest += 2 * width;
            else
            {
                x += xFactor;
                dest += 2 * width - 2 * height;
            }
        }
    }

    return;
}

int main()
{
    int startX, startY, endX, endY;

    cout << "시작 좌표와 끝 좌표를 입력해주세요 (x와 y의 최대 크기는 29입니다.)\n";

    cin >> startX >> startY >> endX >> endY;

    // y좌표 뒤집기
    startY = MAX_LEN - startY - 1;
    endY = MAX_LEN - endY - 1;

    makeBresenhamLine(startX, startY, endX, endY);

    for (int y = 0; y < MAX_LEN; ++y) {
        for (int x = 0; x < MAX_LEN; ++x) {
            cout << m[y][x] << " ";
        }
        cout << "\n";
    }

    return 0;
}




동적 계획법( DP : Dynamic Programming ) 이란?


  • 다이나믹 프로그래밍은 하나의 큰 문제를 여러 개의 작은 문제로 나누어서 그 결과를 저장( 메모이제이션 )하여 다시 큰 문제를 해결할 때 사용하는 것으로 특정한 알고리즘이 아닌 하나의 문제해결 방법입니다.




메모이제이션( memoization )이란?


  • 메모이제이션이란 어떤 로직에 의한 결과값이나 상태값을 자료구조에 저장하여 재사용할 수 있도록 하는 것을 말합니다.




DP 적용 조건


참조 투명성을 가져야 합니다.


  • 입력을 제외한 외적 요소에 결과값이 영향을 미치지 않아야 합니다.




Overlapping Subproblem



  • 겹치는 부분 문제가 존재해야 합니다.




Optimal Substructure


  • 최적 부분 구조를 가지고 있어야합니다. 즉, 최적의 해가 전체적인 글로벌한 최적해가 되는 것을 말합니다.




현실적인 코딩 테스트에서 DP를 적용하기까지 생각 과정


1. 완전탐색 문제인데?




2. 경우의 수가 너무나 큰데?




3. 메모이제이션이 가능한가?


  • 저장해야할 메모이제이션이 너무 클 경우 그리디로 풀어야합니다.




자료 참조 : https://blog.naver.com/jhc9639/222349317111

그리디 알고리즘( Greedy Algorithm )이란?

 

greedy algorithm의 한계

 

  • 각 단계마다 지역적 최적의 해가 궁극적으로 전역 최적의 해가 되는 것을 말합니다. 지금의 state 혹은 idx에서 최선이라고 생각하는 해가 결국은 이 문제의 답이 되는 것입니다.




그리기 알고리즘 두 가지 조건

 

  • 최적 부분 구조를 가지고 있어야 합니다. 지금 이 state에서 최선을 다해 선택하는 해가 결국에는 전역적인 최적해로 이어져야 합니다.

 

  • 탐욕적 속성이 증명이 되어야 합니다. 보통 귀류법으로 증명을 합니다.




증명의 한계

 

  • 그리디 알고리즘으로 풀기 위한 조건을 확인하기 위해서 코딩 테스트에서 증명까지 하는 것은 다소 무리가 있습니다. 그렇기 때문에 문제를 파악했을 때 너무 큰 공간, 너무 큰 시간 복잡도를 요구하는 문제라면은 "무식하게 풀기 -> DP -> 그리디 알고리즘" 순서로 문제에 접근해야 합니다. DP도 공간 복잡도의 한계 때문에 적합하지 않는 경우가 있는데 그럴 경우 그리디 알고리즘을 이용해 접근할 수 있습니다.




자료 참조 : https://blog.naver.com/jhc9639/222319124359

 

[알고리즘 강의] 5주차. 그리디 라인스위핑 투포인터

안녕하세요. 큰돌입니다. 이번 주차에는 그리디 라인스위핑 투포인터를 알아보도록 하겠습니다. 이 3가지 ...

blog.naver.com

 

트리 순회( tree traversal ) 순회 방법


  • 트리 순회( tree traversal )는 트리 구조에서 각각의 노드를 한 번씩만 체계적으로 방문하는 과정을 말합니다. 그리고 노드를 순회하는 순서에 따라서 후위 순회, 전위 순회, 중위 순회가 있습니다.




후위 순회( postorder traversal )


  • 후위 순회(postorder traversal)는 자식들 노드를 방문하고 자신의 노드를 방문하는 것을 말합니다.




후위 순회 pseudo code


postorder( node )
    if (node.visited == false)
        postorder( node->left )
        postorder( node->right )
        node.visited = true




전위 순회( preorder traversal )


  • 전위 순회( preorder traversal )는 먼저 자신의 노드를 방문하고 그 다음 자식 노드들을 방문하는 것을 말합니다.




전위 순회 pseudo code


preorder( node )
    if (node.visited == false)
        node.visited = true
        preorder( node->left )
        preorder( node->right )




중위 순회( inorder traversal )


  • 중위순회(inorder traversal)는 왼쪽 자식 노드를 먼저 방문 그다음의 자신의 노드를 방문하고 그 다음 오른쪽 자식 노드를 방문하는 것을 말합니다.

  • 이진 탐색 트리에서 오름 차순으로 접근할 때 중위 순회를 사용합니다.




중위 순회 pseudo code


inorder( node )
    if (node.visited == false)
        inorder( node->left )
        node.visited = true
        inorder( node->right )




깊이 우선 탐색( Depth First Search )이란?


  • DFS는 그래프를 탐색할 때 쓰는 알고리즘이며 특정 노드에서 인접한 노드들을 방문하며 방문한 정점은 다시 방문하지 않으며 각 분기마다 가능한 가장 멀리 있는 노드까지 탐색하는 알고리즘입니다.




DFS 구현 방법 두 가지


재귀를 이용한 DFS


#include <iostream>
#include <vector>

using namespace std;

constexpr int n = 6;
vector<bool> visited(n);
vector<int> adj[n];

void dfs(int node) {
    cout << node << "\n";
    visited[node] = true;
    for (auto v : adj[node]) {
        if (visited[v]) {
            continue;
        }
        dfs(v);
    }
    return;
}

int main()
{
    adj[1].push_back(2);
    adj[1].push_back(3);
    adj[2].push_back(4);
    adj[2].push_back(5);
    adj[4].push_back(2);

    dfs(1);

    return 0;
}




스택을 이용한 DFS 구현


#include <iostream>
#include <vector>
#include <stack>

using namespace std;

constexpr int n = 6;
stack<int> s;
vector<bool> visited(n);
vector<int> adj[n];

void dfs(int node) {
    visited[node] = true;
    s.push(node);
    while (!s.empty()) {
        int top = s.top();
        s.pop();
        cout << top << "\n";
        for (auto v : adj[top]) {
            if (visited[v]) {
                continue;
            }
            visited[v] = true;
            s.push(v);
        }
    }
    return;
}

int main()
{
    adj[1].push_back(2);
    adj[1].push_back(3);
    adj[2].push_back(4);
    adj[2].push_back(5);
    adj[4].push_back(2);

    dfs(1);

    return 0;
}




너비 우선 탐색( Breadth First Search )이란?


  • BFS는 그래프를 탐색하는 알고리즘이며 어떤 정점에서 시작해 주변 노드들을 순차적으로 탐색하며 방문한 정점은 다시 방문하지 않는 알고리즘입니다. 같은 가중치를 가진 그래프에서 최단거리를 구할 때 사용할 수 있습니다.




BFS 구현 방법


#include <iostream>
#include <vector>
#include <queue>

using namespace std;

constexpr int n = 6;
queue<int> q;
vector<bool> visited(n);
vector<int> adj[n];

void bfs(int node) {
    visited[node] = true;
    q.push(node);
    while (!q.empty()) {
        int top = q.front();
        q.pop();
        cout << top << "\n";
        for (auto v : adj[top]) {
            if (visited[v]) {
                continue;
            }
            visited[v] = true;
            q.push(v);
        }
    }
    return;
}

int main()
{
    adj[1].push_back(2);
    adj[1].push_back(3);
    adj[2].push_back(4);
    adj[2].push_back(5);
    adj[4].push_back(2);

    bfs(1);

    return 0;
}




BFS로 각 경로마다의 최단 거리


#include <iostream>
#include <vector>
#include <queue>

using namespace std;

constexpr int n = 6;
queue<pair<int,int>> q;
vector<bool> visited(n);
vector<int> adj[n];

void bfs(int node) {
    visited[node] = true;
    q.push({ node, 0});
    while (!q.empty()) {
        auto top = q.front();
        q.pop();
        cout << "노드 : " << top.first << ", 거리 : " << top.second << "\n";
        for (auto v : adj[top.first]) {
            if (visited[v]) {
                continue;
            }
            visited[v] = true;
            q.push({ v, top.second + 1});
        }
    }
    return;
}

int main()
{
    adj[1].push_back(2);
    adj[1].push_back(3);
    adj[2].push_back(4);
    adj[2].push_back(5);
    adj[4].push_back(2);

    bfs(1);

    return 0;
}




인접 행렬(adjacency matrix)이란?

 

 

  • 인접 행렬(adjacency matrix)이란 그래프에서 정점과 간선의 관계를 나타내는 bool 타입의 정사각형 행렬을 의미합니다. 즉, 정사각형 행렬의 각 요소는 1 또는 0 값을 가지는데 0은 두 정점 사이의 경로가 없음을 의미하며 1은 두 정점 사이의 경로가 있음을 의미합니다.




인접 리스트(adjacency list)란?

 

 

  • 인접 리스트(adjacency list)는 그래프에서 정점과 간선의 관계를 나타내는 연결리스트를 의미합니다.




인접 행렬과 인접 리스트의 차이

 

공간 복잡도

 

  • 인접 행렬 : O(V^2)

 

  • 인접 리스트 : O(V + E)




시간 복잡도 : 간선 한 개 찾기

 

  • 인접 행렬 : O(1)

 

  • 인접 리스트 : O(V)




시간복잡도 : 모든 간선찾기

 

  • 인접 행렬 : O(V^2)

 

  • 인접 리스트 : O(V + E)




인접 행렬과 인접 리스트 사용해야 할 때

 

  • 그래프가 희소(sparse)할 때는 모든 간선에 대해서 2차원 배열로 미리 생성해야 하는 인접 행렬보다는 인접리스트를 사용하는 것이 좋습니다.

 

  • 그래프가 조밀(dense)할 때는 인접 행렬이 인접 리스트보다 좋습니다. 메모리 효율성은 동일한 반면에 정점 i에서 정점 j까지의 간선을 확인하는 속도가 더 빠르기 때문에 인접 행렬을 사용하는 것이 좋습니다.




이진 트리( Binary Tree )란?


  • 각각의 노드의 자식노드 수가 2개 이하로 구성되어있는 트리를 의미합니다.




이진 트리의 종류



  • 정이진 트리(full binary tree): 자식 노드가 0 또는 2개인 이진 트리를 의미합니다.

  • 완전 이진 트리(complete binary tree): 왼쪽에서부터 채워져 있는 이진 트리를 의미합니다. 마지막 레벨을 제외하고는 모든 레벨이 완전히 채워져 있으며 마지막 레벨의 경우 왼쪽부터 채워져 있습니다.

  • 변질 이진 트리(degenerate binary tree): 자식 노드가 하나밖에 없는 이진 트리를 의미합니다.

  • 포화 이진 트리(perfect binary tree): 모든 노드가 꽉 차 있는 이진 트리를 의미합니다.

  • 균형 이진 트리(balanced binary tree): 모든 노드의 왼쪽 하위트리와 오른쪽 하위트리의 높이 차이가 1이하인 트리입니다. map, set을 구성하는 레드블랙트리는 균형이진트리 중 하나입니다.




이진 탐색 트리( BST, Binary Search Tree )



  • 이진트리의 일종으로 노드의 오른쪽 하위 트리에는 "부모 노드의 값보다 큰 값"이 있는 노드만 포함되고 왼쪽 하위트리에는 "부모 노드의 값보다 작은값"이 들어있는 트리를 말합니다. 이러한 특징 덕분에 값을 검색하기 유용하며 균형이 잘 이루어져 있다면 O(logN)의 시간복잡도를 가집니다.




이진 탐색 트리와 삽입 순서



  • 이진 탐색 트리를 3, 2, 1을 순서대로 삽입했다면은 선형적인 구조가 됩니다. 이럴 경우 O(N)의 검색 시간 복잡도를 가지게 됩니다. 이러한 문제를 해결하기 위해 삽입, 삭제 시마다 동적으로 트리의 균형을 잡는 AVL 트리, 레드-블랙 트리가 있습니다.




자료 참조 : https://blog.naver.com/jhc9639/222289089015




트리(Tree) 자료구조란?



  • 트리는 자식 노드와 부모 노드로 이루어진 계층적인 구조를 가지며 무방향 그래프의 일종이자 사이클이 없는 자료구조를 의미합니다.

  • 트리는 그래프 자료구조의 일종입니다.




트리(Tree)의 특징


  • 부모, 자식 계층 구조를 가집니다. 즉, 같은 경로상에 자신보다 위에 있는 노드를 부모 밑에 있는 노드를 자식 노드라고 부릅니다.

  • V - 1 = E라는 특징이 있습니다. 간선 수는 노드 수 - 1입니다.

  • 임의의 두 노드 사이의 경로는 '유일무이'하게 존재합니다. 즉, 트리 내의 어떤 노드와 어떤 노드까지의 경로는 반드시 있으며 하나밖에 없습니다.




트리의 구조


루트 노드

  • 가장 위에 있는 노드를 뜻합니다.




내부 노드


  • 루트 노드와 리프 노드 사이에 있는 노드를 말합니다.




리프 노드


  • 트리에서 자식 노드가 없는 노드를 리프 노드라고 말합니다.




트리의 높이


  • 트리의 높이는 루트 노드부터 리프 노드까지의 길이를 말합니다.




트리의 깊이


  • 트리에서의 깊이는 각각의 노드마다 다르며 루트 노드에서 특정 노드까지 최단 거리로 갔을 때 거리를 말합니다. ( 문제에 따라서 깊이와 레벨은 같습니다. )




서브 트리


  • 트리 내의 하위 집합을 서브트리라고 합니다. 트리 내에 있는 부분집합이라고도 보면 됩니다.




그래프

 

 

  • 정점과 간선들로 이루어진 집합을 "그래프(Graph)"라고 합니다.




그래프의 구조

 

정점

 

  • 정점(vertex)은 노드라고도 불리며 그래프를 형성하는 기본 단위입니다. 정점은 분할할 수 없는 객체이자 "점"으로 표현되는 위치, 사람, 물건 등이 될 수 있습니다.




간선

 

  • 간선(Edge)은 정점을 잇는 선을 의미합니다. 관계, 경로 등이 될 수 있습니다.

 

  • 간선의 종류로는 단방향, 양방향이 있습니다.




가중치

 

  • 그래프에석 가중치란 정점에서 다른 정점으로 이동하는 비용을 말합니다.




indegree, outdegree

 

  • 정점에서 나가는 간선을 outdegree라고 합니다.

 

  • 다른 정점에서 들어오는 간선을 indegree라고 합니다.




누적합( prefix sum )이란?

 

  • 누적합이란 요소들의 누적된 합의 의미로 어떠한 배열을 기반으로 앞에서 부터 요소들의 누적된 합을 저장해 새로이 배열을 만들어서 이를 활용하는 것을 말합니다.




누적합 예시

 

#include <iostream>
#include <vector>

using namespace std;

int a[100004], pSum[100004], n;

int main()
{
    cin >> n;

    // 누적합 1부터 시작
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        pSum[i] = a[i] + pSum[i-1];
    }

    return 1;
}

 

  • 위 코드와 같이 0번째는 비워두고 구현하는 것이 훨씬 간결합니다.




누적합을 활용한 문제풀이

 

  • N개의 카드를 주며 M개의 질문을 던질 때 그 질문은 나열한 카드 중 A번째부터 B번째까지의 합을 구하여라.

 

#include <iostream>
#include <vector>

using namespace std;

int a[100004], b, c, pSum[100004], n, m;

int main()
{
    cin >> n >> m;

    // 누적합
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        pSum[i] = a[i] + pSum[i-1];
    }

    for (int i = 0; i < m; ++i) {
        cin >> b >> c;

        cout << pSum[c] - pSum[b - 1] << "\n";
    }

    return 1;
}

 

  • 위와 같은 문제를 누적합을 사용하지 않고 일일이 합을 구하여 푸는 것은 매우 비효율적이기 때문에 누적합으로 미리 합을 구하여 조건에 맞게 연산하면 됩니다.




'알고리즘 & 자료구조' 카테고리의 다른 글

트리(Tree) 자료구조란?  (0) 2023.02.03
그래프( Graph )란?  (0) 2023.02.03
공간 복잡도( Space Complexity )란?  (0) 2023.01.28
시간 복잡도( Time Complexity )란?  (0) 2023.01.28
C++에서 split 구현  (0) 2023.01.28

+ Recent posts