안녕하세요.

오늘은 이진 탐색트리 에 대해 포스팅 합니다.

이진 탐색트리 란

이진 탐색트리이진 탐색(Binary Search)의 아이디어를 채용한 자료구조 입니다.

이진 탐색은 원소가 정렬되어 있다는 조건아래, 정렬된 특징을 이용해 반씩 쪼개가며 검색을 합니다.

그러므로, 검색 횟수가 획기적으로 낮아지는 특징이 있습니다.

이는, 이진 탐색트리도 똑같이 원소가 정렬된 형태로 지니고 있어야 하고 반씩 쪼개가며 검색을 할 수 있는 구조를 갖추고 있어야 함을 의미합니다.

그러면... 이진 탐색 사용하면 되는거 아닌가요?

그렇다면, 이진 탐색트리이진 탐색을 사용하는 배열 자료구조보다 나은점이 무엇일까요?

아시다 시피, 배열은 조회에 강점이 있는 자료구조 입니다. 그렇다 보니, 검색만 한다고 가정할 때는 트리 구조가 굳이 필요하지 않습니다.

하지만, 원소가 삽입/삭제 될 수 있다고 가정하면 어떨까요? 배열 자료구조는, 삽입/삭제Index 를 찾은 후, 그 Index 뒤에 위차한 원소들을 한칸씩 전부 뒤로 이동해야 하는 수고가 존재합니다. 또한, 배열공간이 모자라, 배열 크기를 증가시키거나 혹은 공간이 너무 남아, 축소 시키는 작업은 덤으로 발생할 수 있습니다.

그에 반면에, 트리 구조는 참조(포인터)만 변경해주면 되므로, 원소의 대량 이동이 필요 없고, 공간 복잡도를 신경안써도 되는 장점이 있습니다. 그렇다보니, 삽입/삭제 연산이 한번이라도 존재한다면, 이는 삽입이 연속적으로 N번 발생할 수도 있다는 의미이므로 굳이 배열 자료구조를 사용할 필요가 없습니다.

사실 조회 자체도 이진 탐색 구조상 트리 도 특정 노드의 Index 검색이 아닌, 특정 노드로부터 순차대로 검색하는 구조이므로 배열이랑 크게 차이가 없습니다.

예를들어, 연결 리스트는 특정 Index 노드 검색 시간복잡도는 O(n)이지만, 전체 순회를 할 때는 각 노드의 참조값을 순차대로 탐색하므로 각 노드의 검색 시간이 O(1) 입니다.

결론적으론, 이진 탐색트리가 더 보편적으로 사용될 수 있다고 말할 수 있겠습니다.

이진탐색을 위한 트리 구조

이진 탐색트리트리에 어떤식으로 데이터의 구조를 형성하길래, 이진 탐색이 가능한걸까요?

아래와 규칙으로 데이터를 쌓기 때문입니다!

규칙만 봐서는 이해가 어렵습니다. 아래의 예시 이미지와, 삽입과정 설명을 보시는걸 추천합니다!

  • 루트 노드가 비었다면, 삽입 노드루트 노드로 채웁니다.
  • 루트 노드가 존재한다고 가정할 때, 삽입 노드의 값과 비교하여 삽입 노드의 값이 작으면 루트 노드의 오른쪽, 크면 오른쪽으로 이동합니다.
  • 이동시, 노드가 비었다면 삽입 노드로 채웁니다.
  • 만약 노드가 존재한다면, 2번~3번 과정을 반복합니다.

이진 탐색트리 순회

이진 트리는 대표적인 DFS 순회방법 3가지가 있습니다.

  • 전위 순회(pre-order traversal)
  • 중위 순회(in-order traversal)
  • 후위 순회(post-order traversal)

이때, 중위 순회를 이용하면 정렬된 원소의 목록을 획득 할 수 있습니다.

위 예시를 중위 순회를 해보면 다음과 같은 원소의 목록을 얻을 수 있습니다.

1 > 2 > 3 > 4 > 5 > 7 > 9

따라서, 해당 트리 구조는 트리 정렬 알고리즘 으로도 사용되며,

이진 탐색트리를 제공하는 프로그래밍 API 에서는 정렬을 이용한 기능을 제공하는 경우가 많습니다.

ex) Java Collection API: TreeMap, TreeSet 은 이진 탐색 트리 구조로 트리를 쌓습니다. 따라서 특정 값들이 정렬로 보관되어 있고, 정렬특징을 이용하는 메서드들이 존재합니다.

이진 탐색트리 검색

예시의 이진 탐색트리 에서 4 노드를 검색한다고 가정해봅시다.

  • 첫번째로 만나는 노드는 5 입니다. 검색노드가 5 보다 작으므로, 왼쪽으로 이동합니다.
  • 두번째로 만나는 노드는 3 입니다. 검색노드가 3 보다 크므로, 오른쪽으로 이동합니다.
  • 세번째로 만나는 노드는 4 입니다. 검색노드와 일치 하므로 해당 노드를 리턴 합니다.
Q. 4 의 존재유무를 검색 해보는건 의미가 있을거 같은데, 어차피 똑같은 값인 노드를 리턴하는게 의미가 있을까요? A. 노드는 꼭 하나의 값을 가진 자료형이 아닙니다. 예를들어 아래의 노드의 자료형을 구성한다고 생각해봅시다. 이때 4 로 검색하면 아래의 노드가 리턴 되고 리턴된 노드에서 제공하는 정보를 활용할 수 있습니다.
{
  "name": "Mommoo",
  "job": "Developer",
  "value": 4
}

이진 탐색트리 삽입

이진 탐색트리 에 노드를 넣는 순서는 다음과 같다고 가정합니다.

5, 3, 1, 4, 7, 9, 2

위에서 설명한 규칙대로 삽입과정을 거칩니다.

검색과 마찬가지로 이동하며, 자리를 찾아 노드를 저장합니다.

이진 탐색트리 삭제

이진 탐색트리의 검색/삽입 연산과 달리 삭제는 조금 복잡합니다.

고려해야 할 케이스를 3개로 나눌 수 있습니다.

  • 삭제할 노드가 말단 노드인 경우(자식노드가 없는 경우)
  • 삭제할 노드의 자식 노드가 1개인 경우
  • 삭제할 노드의 자식 노드가 2개인 경우

삭제할 노드가 말단 노드인 경우

해당 경우는 간단합니다. 삭제할 노드만 삭제해주면 됩니다.

아래의 예시는 2 노드를 지웁니다.

삭제할 노드의 자식 노드 1개인 경우

이 경우도 어렵지 않습니다. 삭제할 노드의 자식들의 트리 구조 규칙은 삭제할 노드의 조상으로 부모를 바꾸더라도 규칙이 깨지지 않기 때문입니다.

이래의 예시는 7 노드를 지웁니다.

삭제할 노드의 자식 노드가 2개인 경우

이 경우가 살짝 복잡합니다. 삭제할 노드의 자리에 대신 채울 노드를 찾아야 하는 과정이 필요합니다.

대신 채울 노드를 찾는 방법은 2가지 존재합니다.

  • 삭제할 노드의 왼쪽 서브 트리에서 최대 값 노드
  • 삭제할 노드의 오른쪽 서브 트리에서 최소 값 노드

아래의 예시는 20 노드를 삭제할 때, 서브 트리에서 위 방법으로, 노드를 찾아 대체합니다.

이진 탐색트리 시간 복잡도

검색/삽입 시간복잡도

위에서 검색/삽입 연산을 살펴보시면 아시겠지만, 모든 원소를 탐색하지 않는것이 특징입니다.

최악의 경우는 트리의 높이 만큼 탐색하는 경우라 볼 수 있겠습니다.

따라서 높이가 H 인경우의 검색/삽입의 시간 복잡도는 O(H) 입니다.

하지만, 시간복잡도는 원소의 개수로 표기할 때, 조금 더 좋은 표현이라 할 수 있습니다.

H 를 원소의 개수 N 으로 표현해볼 수 없을까요?

이진 트리는 대개, 원소의 개수가 N 이면 log2Nlog_2N 으로 치환 가능합니다.

이진 검색트리 구조가 완전 이진 트리라 가정할 때의 경우로 한정적이긴 합니다. 그래서 최악의 경우로 규정짓는 빅오 표기법으로는 애매하긴 합니다.

따라서, 검색/삽입 연산의 시간복잡도는 O(log2N)O(log_2N)으로 표기 가능합니다.

삭제 시간복잡도

삭제 시간복잡도는 왜 따로 다룰까요?

검색/삽입과 동일하게 다룰 수 없는 찜찜한 구석이 있지 않았나요?

네 맞습니다. 삭제할 노드의 자식 노드가 2개인 경우 에는 추가 연산이 있기에 시간 복잡도를 다시 따져봐야 할거 같습니다.

검색 시간복잡도는 O(H) 로 삭제할 대상을 검색하는 것도 동일하게 시간이 소모됩니다.

이때, 삭제할 노드가 단말 노드, 삭제할 노드의 자식 노드가 1개 인 경우는 지우거나 참조값만 붙여주면 되므로, O(1) 의 시간이 소모된다고 볼 수 있습니다. 따라서 총 시간 복잡도는 O(H + 1)O(H) 라 할 수 있겠네요.

그렇다면 우리가 궁금한 삭제할 노드의 자식 노드가 2개 인 경우는 어떨까요?

검색 후, 대체할 노드만 찾는다면 대체 노드의 참조값만 바꿔주면 되므로 O(1) 입니다.

그말은, 대체할 노드를 찾는게 얼마나 걸리는지에 따라 시간 복잡도가 규정 된다는 점입니다.

생각해보자면, 대체할 노드를 찾는것도 결국 탐색이 필요합니다. 다만 Full-Tree 가 아닌 Sub-Tree 에서의 검색이지요. 시간복잡도를 간단하게 정의해본다면 O(H-K) 정도가 될것입니다.

K 는 탐색을 시작할 높이 값입니다.

시간복잡도는 최악의 경우를 생각해야 하므로, 최악의 경우의 K0 이 되는 경우가 있겠네요.

이 경우는 루트를 지운다는 의미입니다.

결국 서브트리를 탐색하는 시간복잡도도 O(H) 라 생각할 수 있겠습니다.

정리해보자면, 아래의 작업이 필요합니다.

  • 삭제 노드를 탐색 O(H)
  • 대체 노드를 찾기 위한 서브 트리를 탐색 O(H)
  • 참조값 변경 O(1)

따라서 삭제 연산의 시간 복잡도는 O(H + H + 1)O(2H)O(H) 라 볼 수 있습니다. 위에서 N으로 치환한것과 마찬가지로 O(log2N)O(log_2N) 으로 표기 가능하겠습니다.

결론적으로 탐색/삽입/삭제 시간복잡도는 전부 동일하게 O(log2N)O(log_2N) 라고 정의할 수 있습니다.

이진 탐색트리 사용시 고려할 점

중복값 처리 (주관적 의견. 뇌피셜 주의보!)

지금까지 보여드린 예시는 중복값이 없는 예시입니다. 이진 탐색트리 는 중복값을 허용하면 안된다는 자료가 많이 보이는거 같습니다.

현실적으로 중복값이 충분히 나올 수 있는지라, 중복값 비허용 규칙은 너무 잔인하다고 느꼈습니다.

제가 짧게 연구 해봤을때, 중복값을 노드의 왼쪽에 배치할 것인지, 오른쪽에 배치할 것인지만 정하고 배치하면 무리없이 동작합니다.

다만, 중요한것은 중복의 개수만큼 끝까지 내려간 후, 연결되어야 한다는 점입니다.

아래의 예시에서 왼쪽의 경우는 안됩니다. 트리 규칙이 깨지게 됩니다.

다만 중복의 개수가 많아지면, 트리의 높이가 너무 커지게 되고 시간복잡도가 비약적으로 올라가게 될것입니다.

그렇다보니, 다음과 같은 아이디어도 좋을거 같습니다.

  • 단순히 값만 정의된 노드라면, 노드에 카운팅을 추가.
  • 특정 자료형으로 정의된 노드라면, 해당 노드를 여러개 저장할 수 있는 자료구조를 감싼 wrapper 노드로 트리를 구성.

다만 추가 공간을 쓰게 되는 단점은 있는거 같습니다.

한쪽으로 쏠린 트리

이진 검색트리 의 시간복잡도는 O(log2N)O(log_2N) 이라 소개드렸는데, 사실 최악의 경우는 O(N)이 나오게 됩니다.

Q. 빅오 표기법은 최악의 시간인 경우로 표기해야 하므로, 이진 탐색트리 의 연산 시간복잡도는 O(N) 아닐까요? A. 엄밀히 따지자면 O(N)이 맞습니다. 빅오표기법의 약점은 빈번도를 체크 하지 못합니다. 100번에 1번 나오는 경우의 수 때문에 최악의 경우로 시간을 규정하는게 제대로 된 측정값 일까요? 그렇기에 퀵소트도 O(n2)O(n^2) 이라 하지 않고, 대체적으로 측정되는 O(Nlog2N)O(Nlog_2N) 이라고 표기합니다.

간단한 예로, 정렬된 순서로 요소가 들어가게 된다면 아래와 같은 트리 구조로 구성되게 됩니다.

1, 2, 3, 4, 5

해당 구조는 트리 라기보다, 연결 리스트 의 구조로 잡혀있습니다.

높이가 낮을 수록 성능에 유리한 이진 검색트리가 이렇게 개수만큼 높이가 쌓이게 되면, 검색/삽입/삭제는 선형 연산이 되버립니다. 선형 연산이기 때문에 시간 복잡도는 O(N) 이 됩니다.

5를 찾으려면 5번 탐색 이 필요하네요.

균형 이진 탐색트리

이진 탐색트리완전 이진트리 구조로 쌓여야, 요소 개수 N개의 대비 최소 높이가 됩니다.

그렇다면, 요소를 어떤 순서로 삽입 하더라도, 강제적으로 완전 이진트리 구조로 되게끔 규칙을 만들면 어떨까요!?

그러한 알고리즘을 추가한 이진 탐색트리균형 이진 탐색트리 라 부르며, 대표적은 구현체는 AVL 트리 , 레드 블랙 트리 등이 있습니다.

오늘 포스팅은 여기까지 입니다.

뇌피셜인 부분도 있으니, 주의해서 읽어주셨으면 합니다.

읽어주셔서 감사합니다.

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

[자료구조] B 트리  (0) 2021.07.29
[자료구조] 트리(Tree)  (0) 2021.07.10

포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee
Tree 란

안녕하세요.

오늘은 자료구조 트리(Tree)에 대해 포스팅 합니다.

필수적으로 알아야 할것들만 정리하였습니다.

트리구조에 대해 대략적으로 설명하므로, 특정 용어나 개념들은 따로 찾아봐야 합니다.

 

트리(Tree) 란

위 본문의 그림과 같이 노드간선으로 이루어진 것을 그래프(Graph) 라고 합니다.

트리는 이러한 그래프 의 일종 입니다. 아래의 규칙을 따르는 그래프트리라고 일컫습니다.

사실 규칙을 외울 필요는 없고, 이런 형태구나 정도로 기억하면 될 거 같습니다.

 

트리 규칙

  • 특정 노드는 오직 하나의 조상 노드와 N 개의 자식 노드와 연결됩니다.

    • 특정노드에서 조상노드, 자식노드는 레벨의 차이가 반드시 1이어야 합니다.
    • C 노드는 하나의 조상 노드인 A와 2개의 자식 노드인 G , H와 연결됩니다.
  • 여러 노드가 한 노드를 가리킬 수 없습니다.
  • 노드와 노드를 잇는 길(간선)이 하나만 존재합니다.

 

또한 트리는 다음과 같은 특징들을 가집니다.

높이

트리는 루트로 부터 레벨 을 셀 수가 있는데 이때 최대 레벨의 값을 높이라 부를 수 있습니다.

따라서, 예제의 트리의 높이는 3입니다.

 

말단 노드(리프 노드)

자식이 없는 노드를 말단 노드 또는 리프 노드라 일컫습니다.

 

형제 노드

같은 부모를 가지는 노드들을 형제(sibling)노드라 부릅니다.

 

서브 트리

트리안에서 특정 범위를 묶어 하나의 작은 트리를 구성 할 수 있습니다.

예제 트리의 빨간 박스, 파란 박스 와 같은 예시처럼 묶을 수 있으며 이를 서브 트리라 일컫습니다.

 

이진트리 (Binary Tree) 란

트리의 특정 노드는 자식의 개수를 제한 없이 가질 수 있는데요.

이때, 최대 2개의 자식 노드만 가질 수 있다 는 조건을 추가한것을 이진트리라고 부릅니다.

쉽게 말해, 노드가 왼쪽 자식오른쪽 자식을 갖는 특징을 가집니다.

예제의 트리도 이진트리라 부를 수 있습니다.

 

이진트리구조를 갖추면, 해당 구조가 제공하는 연산의 이점들이 존재합니다.

예를들면, 노드의 개수를 알면 높이를 알 수 있습니다.

레벨(N)당 노드의 개수는 2^N 이기 때문입니다.

 

대표적으로 사용되는 연산은 탐색입니다.

너무나도 유명한 이진 탐색 트리 를 구성할 수 있습니다.

 

특정 조건이 추가된 이진 트리들이 있습니다.

완전 이진트리

  • 모든 레벨에서의 노드가 꽉차 있어야 합니다.

  • 예외적으로, 마지막 레벨에서의 노드는 꽉차 있지 않아도 되지만 왼쪽에서 차례대로 채워져야만 합니다.

  • 예시 트리도 완전 이진트리 입니다.

  • 예시 트리에서 N 만 없애거나, (M, N) 을 없애거나, (L, M, N)을 없앤 케이스도 완전 이진트리입니다.

전 이진트리

  • 특정 노드의 자식이 0개 또는 2개로만 구성되어 있는 트리를 의미합니다.
  • 예시 트리에서 (G, H)를 없애거나, (E, F, K, L, M, N)을 없앤 케이스를 전 이진트리로 볼 수 있습니다.

 

포화 이진트리

  • 모든 말단 노드가 같은 높이에 있어야 합니다.

    • 예를들어, (E, F, K, L, M, N)을 없앤 케이스를 전 이진트리형태는 말단 노드인 B가 다른 말단 노드인 G와 레벨이 동일하지 않습니다.
  • 마지막 레벨에서 노드의 개수가 최대가 되어야 합니다.

  • 예시 트리도 포화 이진트리라 부를 수 있습니다.

 

이진 탐색트리

  • 노드의 자식은 아래의 규칙을 따릅니다.

    • 오른쪽 노드의 값 < 현재 노드의 값 < 왼쪽노드의 값
  • 값을 이용한 규칙을 통해 구조를 쌓으면 추후 이진 탐색을 사용할 수 있습니다.

다음 포스팅에서 자세히 다룰 예정입니다.

 

이진트리 순회

이진트리를 연산하기 위해서는 순회는 빼놓을 수 없습니다. 방문하지 않으면 값을 알 수 없기 때문입니다.

순회는 많이 알려진 방법인 너비우선탐색(BFS), 깊이우선탐색(DFS) 으로 나눌 수 있습니다.

 

너비우선탐색(BFS)

레벨순으로 차례대로 탐색합니다.

예시 트리를 기준으로 설명드리자면, 아래와 같이 탐색합니다.

Root > A > B > C > D > E > F > G > H > I > J > K > L > M > N

 

깊이우선탐색(DFS)

탐색 방법 3가지가 존재합니다.

기본적으로 왼쪽 자식 노드에서 오른쪽 자식 노드로 순회를 한다고 가정(B -> C)하고 현재 노드(A)가 방문되는 순서를 기억하면 좋습니다.

  • 전위 순회 (pre-order traversal)

    • 현재 노드(A)가 첫번째로 방문 됩니다.
    • B ->C는 고정이므로 첫번째로 방문되려면 맨 앞에 배치 되어야 합니다.
    • A -> B -> C
  • 중위 순회 (in-order traversal)

    • 현재 노드(A)가 중간에 방문 됩니다.
    • B -> A -> C
  • 후위 순회 (post-order traversal)

    • 현재 노드(A)가 마지막에 방문 됩니다.
    • B -> C -> A

 

위 예시는 서브 트리가 존재하지 않는 트리인 경우 입니다.

만약 서브 트리가 존재한다면 어떻게 방문할까요?

이때는 서브 트리를 하나의 노드라 생각하고 방문합니다.

다음의 트리를 이용하여 전위 순회로 알아봅시다.

 

해당 예시의 서브 트리는 (B, D, E) 입니다.

STEP1: 서브 트리를 하나의 노드(BB)로 치환하여 전위 순회를 진행합니다.(A -> BB -> C)

STEP2: BB도 트리이므로 내부적으로 순회를 똑같이 진행합니다. (B -> D -> E)

STEP3: 서브 트리 순회 결과를 치환한 노드BB에 반영합니다. (A -> B -> D -> E -> C)

 

최초 분문의 예시를 각 방법으로 순회 하면 다음과 같습니다. (ROOT의 위치를 살펴보세요.)

전위 순회 : ROOT > A > C > G > H > D > I > J > B > E > K > L > F > M > N

중위 순회 : G > C > H > A > I > D > J > ROOT > K > E > L > B > M > F > N

후위 순회 : G > H > C > I > J > D > A > K > L > E > M > N > F > B > ROOT

 

오늘 준비한 내용은 여기까지 입니다.

읽어주셔서 감사합니다.

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

[자료구조] B 트리  (0) 2021.07.29
[자료구조] 이진탐색트리  (1) 2021.07.12

포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

안녕하세요. 개발자 Mommoo 입니다.

오늘은 CheckedExceptionUnCheckedException에 대해 포스팅 합니다.

해당 내용에 대해 알아보는것도 의미가 있습니다만, 포스팅 하는 핵심 부분은 CheckedException 의 단점에 대해 알아보는 것입니다. 해당 주제는 면접에서도 많이 나와요.


ChecekdException 과 UnCheckedException이란?

CheckedException 처리는 컴파일 시점에 명시적으로 예외상황을 처리해야하는 예외 기법입니다. 자바 CLASS 중 Exception 을 상속받아 만들 수 있습니다.

명시적으로 예외를 처리한다 함은, 자바 언어 기준으로, try~catch 구문을 사용하는것을 의미합니다. 명시적인 예외를 처리하지 않는다면 컴파일 되지 않아, 코드 에디터에서 빨간 줄이 그어져 있는것을 확인하 실 수 있습니다.

UnCheckedException 처리는 런타임 시점에 예외상황을 처리하는 예외 기법입니다. 자바 CLASS 중 RuntimeException 을 상속받아 만들 수 있습니다.

런타임 시점이란, 컴파일이 된 후 프로그램이 실행될 때, 어느 특정 상황을 의미합니다.

위에서 살펴본 CheckedException 예외와는 다르게, 명시적으로 처리할 필요가 없습니다. 개발자가 필요시에 try~catch 구문을 이용하여 처리합니다.


ChecekdException의 특징

CheckedException 은 사실 현대적인 언어에서는 제공하지 않는 경우가 많습니다. CheckedException 은 사실상 필요없다는 의견도 많을 뿐더러, 프로그래밍에 있어서 큰 단점을 가지고 있기 때문입니다. 하지만 장점도 가지고 있는데요, 해당 내용을 알아봅시다.

CheckedException 의 최고 장점은 아무래도 안정적인 소프트웨어를 만들 수 있는 길을 제시해준다는 점입니다. CheckedException 을 제공하는 개발자는 빈번하게 발생할 수 있는 예외 케이스 를 제공해줌으로써, 해당 메서드를 사용하는 개발자로 하여금 UnHappyPath 에 대해 생각할 수 있는 여지를 제공해줍니다.

물론, UnCheckedException도 예외케이스를 제공해주지만 아무래도 명시적으로 작성해야 하는 효과가 주는 안정성이 있다고 생각합니다.

하지만, 큰 단점이 있는데요. 그것은 항상 명시적으로 작성해야 한다는 점 입니다.

예를들어, FileInputStream("파일이름") 객체를 만든다고 가정해봅시다. 해당 객체는 FileNotFoundException 이라는 CheckedException 처리를 해야합니다. 해당 파일이름으로 된 파일이 없는 경우를 대비하라는 의미이지요. 그런데 해당 파일이 항상 존재한다고 가정할 수 있는 경우에는 어떨까요? 해당 경우에는 불필요한 try~catch 구문이 들어가, 코드 가독성을 해치게 됩니다. 또한 해당 파일이 항상 존재한다는 상황의 표현도 되지 않는것도 큰 문제라 볼수 있습니다.

프로그래밍이란 것은 아무래도 흐름을 통해 특정 상황을 만드는 일이 빈번하다 보니, CheckedException은 프로그래밍에 있어서 짜증나는 상황으로 다가오는 경우가 많습니다.


UnChecekdException의 특징

UnCheckedExceptionCheckedException 의 명확한 단점을 극복할 수 있는 장점이 있지만, 반대로 단점은 명시적으로 예외를 정의할수 있게 유도하지 못한다는 점입니다.

하지만 이는 예외상황은 어쨌거나, 프로그래머가 신경써야 한다는 점에 있어서 큰 단점이 되지 않습니다. 특정 메서드를 사용할 때는 메서드를 탐색하면 어떤 예외가 있는지 확인할 수 있기 때문에 예외 대처에 있어 큰 문제가 되지 않습니다.

그렇기에 현대적인 언어에서는 예외처리를 UnCheckedException 으로 제공하는 경우가 많습니다.


마무리

개인적인 생각으로는 숙련된 프로그래머는 CheckedException 의 단점이 더 부각되고, 초보 프로그래머에게는 CheckedException의 장점이 더 부각되는 상황이 될거 같습니다.

그렇기에 프로그래밍을 많이 할 수록 예외 처리는 RuntimeException 을 상속받아 처리하는 부분으로 빈번하게 처리되는거 같습니다.

글에는 개인적인 의견도 포함됬기 때문에 틀린점이 있을 수 있습니다. 틀린부분에 대해 의견주시면 감사히 받겠습니다.

글 읽어주셔서 감사합니다. :)

'Java' 카테고리의 다른 글

[Java] 파일 경로 처리하기.  (2) 2019.01.24
JAVA - JNI 사용하기  (2) 2017.09.02
JAVA - [SWING] LinearLayout 사용하기.  (0) 2017.08.25
JAVA - DownCasting(다운캐스팅)  (25) 2016.08.27
JAVA - HashMap key 구하기.  (1) 2016.08.25

포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

Mysql 원격 접속 명령어는 다음과 같습니다.

mysql -u 유저이름 -h 호스트IP -p 비밀번호 --port 포트번호

--port 명령은 만약 포트번호가 3306 디폴트 포트라면 생략 가능합니다.

'데이터베이스' 카테고리의 다른 글

[데이터베이스] 인덱스(INDEX)  (0) 2021.07.31
트랜잭션(Transaction)이란?  (27) 2017.02.27
Mysql 외부접근 설정하기.  (0) 2015.11.16

포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee
Spring @Transactional 주의점

안녕하세요. 오늘은 Spring@Transactional 어노테이션을 사용할 때, 주의점에 대해 포스팅합니다.

 

Spring 에서 JPA 기술을 쓸 때 빼놓을 수 없는 기능중 하나는 @Transactional 입니다.

아래와 같이 많은 편리성을 우리에게 제공해주기 때문이지요.

  • transaction begin, commit을 자동 수행해준다.
  • 예외를 발생시키면, rollback 처리를 자동 수행해준다.

 

그렇기에, 많은 보일러코드를 줄 일 수 있기 때문에, 아래와 같이 편리하게 코드를 작성할 수 있습니다.

Note. 문제 상황을 억지로 만들기 위해 코드 효율성이 좋지 않을 수 있습니다.

 

문제는 예시에서 쓴 addBook 메서드를 Books 클래스 내부에서 사용할 때 발생 할 수 있습니다.

 

혹시, addBooks 메서드를 사용할 때, 어떤것이 문제가 될 수 있는지 예상이 가시나요?

문제는 addBooks 메서드 내부에서 호출하는 addBook 메서드의 @Transactional 어노테이션이 적용되지 않는 것입니다.

 

그렇기에, 해당 코드를 실행하더라도 Database에는 저장된 Book 정보에 Flag 컬럼에 정상적으로 업데이트 되지 않습니다.

bookRepositorysave 메서드는 자체적으로 @Transactional 어노테이션이 붙어있어, 정상적으로 코드가 수행이 되어 Database에 insert 를 수행합니다.

하지만, update 의 역할을 하는 book 객체의 변경감지가 동작하지 않아, Flag 컬럼에 업데이트가 되지 않습니다.

 

@Transactional 이 동작하지 않았는지는 Spring@Transactional 기능을 제공하는 방식에 대해 살펴볼 필요가 있습니다.

 

Spring @Transactonal 기능제공 방식

JPA 의 객체 변경감지는 transactoncommit 될 때, 작동합니다.

그렇기에 Spring@Transactonal 어노테이션을 선언한 메서드가 실행되기전, transaction begin 코드를 삽입하며

메서드가 실행된 후, transaction commit 코드를 삽입하여, 객체 변경감지를 수행하게 유도합니다.

 

Spring 의 코드 삽입 방법은 크게 2가지 방법이 있습니다.

  • 바이트 코드 생성 (CGLIB 사용)
  • 프록시 객체 사용

2가지 방법중 Spring 은 기본적으로 프록시 객체 사용 이 선택됩니다. 그렇기에 interface 가 반드시 필요합니다.

SpringBoot 는 기본적으로 바이트 코드 생성 이 선택됩니다. 그렇기에, 굳이 interface 가 필요없습니다.

만약 개발환경이 SpringBoot 라면 Books 인터페이스 없이 봐도 무방합니다.

 

원리는 이렇습니다. 프록시 객체로 우리가 만든 메서드를 한번 감싸서, 메서드 위 아래로 코드를 삽입 해줍니다.

 

그렇기에, 우리는 BooksImpl 자료형을 사용 할 때, 스프링이 제공하는 BooksProxy 객체를 사용하게 되며,

BooksProxy 객체가 제공하는 addBook 메서드를 사용해야만 transaction 처리가 수행되는것입니다.

 

@Transactonal 이 수행되지 않은 이유.

다시 아래의 예제를 살펴봅니다.

 

BooksProxyaddBooks 메서드를 수행하면, 아래와 같은 순서로 작동됩니다.

BooksProxy::addBooks -> BooksImpl::Book

 

즉, BooksImpl 내부의 코드(addBook) 가 수행 되기 때문에 해당 메서드는 프록시로 감싸진 메서드가 아니라는 점에서

@Transactonal 어노테이션 기능이 수행되지 않는다는 것입니다.

 

해결방법

사실 @Transactional 메서드를 내부적으로 사용하지 않는것이 근본적인 해결책입니다.

하지만 만약 굳이 사용해야 겠다면, 의존성 주입을 이용하여 Proxy 인스턴스를 자체적으로 가져와 사용하는 방법이 있습니다.

 

해당 코드는 Books 인터페이스를 이용하여 BooksProxy 인스턴스를 주입할 수 있도록 유도합니다.

생성자 주입을 사용하면, 순환 에러가 발생합니다. @Autowired 로만 해주세요. Autowired 싫다.

 

그 후, 자기 자신 즉 this 가 가지고 있는 순수한 addBook 메서드가 아니라 proxy 로 감싸진 addBook 메서드를 통해@Transactional 어노테이션 기능을 사용할 수 있게 됩니다.

 

정리하자면

  • @Transactonal 어노테이션 메서드는 클래스 내부적으로 사용하지 말고, 밖에서 사용하자. (프록시 객체 때문에)
  • 굳이 내부적으로 사용하려면, 자기 자신의 Proxy 객체를 사용하여 처리하자.

 

마무으리

혹시 틀린 내용이 있어서, 알려주신다면 감사하게 듣겠습니다. (_ _)

긴글 읽어주셔서 감사합니다!

 

포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee
퀵정렬 포스팅

Note.

해당 글은, 퀵정렬의 기본적인 개념은 알고 있다는 전제로 진행합니다.

또한 틀린 내용이 있다면, 가감없이 알려주시면 감사하겠습니다. (_ _)

 

오늘은 퀵정렬 알고리즘에 대해 포스팅합니다.

퀵정렬 알고리즘은 매우 유명해서 검색하면 많은 자료를 찾아볼 수 있을텐데요.

저도 오랜만에 퀵정렬을 공부하려고, 많은 자료를 찾아봤는데 비슷 비슷한 코드를 볼 수 있었습니다.

그런데 말입니다.(김상중 아찌톤) 코드를 왜 그렇게 구성했는지 한번에 이해가지 않았으며, 후에 분석해본 결과 틀린 코드들도 정말 많았습니다.

어쩌면, 퀵정렬 구현이 공식처럼 알려진게 아닌가 싶고 제대로 된 코드 이해없이 비슷하게만 따라하다가 틀린 코드를 작성하는 경우가 많을거 같다는 생각을 했습니다.

그렇기에, 퀵정렬 코드를 단번에 이해못하는 멍청한 저를 위해, 나름대로 개념을 뽀개 봤습니다.

 

해당 포스팅은 아래와 같이 쓸때없는(?) 것을 연구하고 도출한 결과를 설명 해볼까합니다.

  • 왜 많은 자료들의 코드는 pivot index를 항상 끝 방향에 두고 코딩을 하는걸까? 가운데다가 두고 하면 안돼는가?

  • 왜 어떤 코드는 탐색 할 때, pivot 의 값만 비교하고, 어떤 코드는 pivot 값과 왼쪽 index 와 오른쪽 index 크기 비교까지 하는 걸까?

  • 왜 다들 오른쪽 부터, 탐색하는 코드만 작성 하는가? 왼쪽부터 탐색하면 안되는가?

  • SWAP 작업을 마치고, 마지막에 pivot index를 옮길 때, 주의해야할점

 

Pivot을 끝방향(왼쪽 끝 또는 오른쪽 끝)에 두는 이유

저는 이상한 고집이 있어서, 남들이 pivot 을 끝에다가 두면 굳이 가운데다가 해보고싶은 욕구가 있습니다. 지식을 습득할 때, 당연하게 외우기 보다 왜 그렇게만 해야하는지에 대해 아는게 중요하다고 생각하기 때문이지요.


결론적으로, pivot 을 가운데다가 배치를 하게 되면, 코드를 작성하기가 매우 까다로워 집니다.

퀵정렬의 기본개념은, 왼쪽방향과 끝쪽방향에서 서로 접근하며 바꿀 수 있는 수가 나올때 SWAP 을 시도하는것과, SWAP 작업을 마친 후, pivot 자리를 찾아주는 것이 중요합니다. 그래야 pivot 기준으로 왼쪽, 오른쪽 배열로 쪼개서 분할정복 을 시도할 수 있기 때문이지요.


문제는 pivot 이 가운데에 끼여 있으면, pivotSWAP 될수도 있다는것 입니다. 그럴경우, pivot index를 업데이트를 꼭 해주어야 합니다.


그렇지 않으면, 마지막에 pivot 자리를 찾아주는 과정에서 pivot 이 아닌 다른 값을 배치하기 때문이지요.

정리하자면, pivot을 가운데 잡아서 SWAP 작업을 진행하느니, 맨끝쪽에 pivot을 배치하여 SWAP 작업으로부터 안전하게 빼면 index 업데이트를 할 필요가 없습니다.

 

탐색할 때 비교해야할 요소

코드는 짧고 명료하고 간단한것이 매력적입니다. 멍청해서, 어려운건 이해하가 힘들거든요. (나도 똑똑해지고 싶다!!!)

그래서 그런지, 아래 코드가 끌리긴 했습니다. 굳이 왼쪽 index 와 오른쪽 index 를 비교해야 하나 싶었지요.


결론적으로는, 비교를 해야 하는게 맞습니다.

만약 하지 않는다면, index 가 배열 밖으로 벗어날 위험이 있습니다.

아래와 같은 배열이 있다고 가정해봅시다. (pivot 은 맨 왼쪽입니다.)

위 코드를 실행하면, beginIndex 는 결국 배열 크기 밖으로 빠지면서, OutOfIndex 에러가 발생하게 됩니다.

이를 방지하기 위해, beginIndex 의 조건을 배열 크기 보다 작은 경우로 줄 수도 있겠습니다.


하지만 우리가 원하는 것은 beginIndexendIndex 가 만나는 순간이고, endIndex는 배열 크기 보다 무조건 작거나 같으므로 아래와 같이 더 적은 횟수를 수행하는 조건으로 작성할 수 있습니다.

생각보다 많은 자료의 코드들이 OutOfIndex 에러가 발생하는것을 목격할 수 있었습니다.

 

 

탐색 순서가 중요한 이유

아까도 말씀드렸다 시피, 저는 이상한 고집이 있어서... 오른쪽 부터 탐색하는 코드가 많으면, 굳이 왼쪽으로 해보고 싶어집니다.(삐뚤게 살거야ㅋㅋㅋ)

해봤더니, 코드가 제대로 동작하지 않았습니다.

이상합니다. 어떤 코드는 왼쪽부터 동작하게 짰었거든요. 그렇다면 탐색 순서가 중요하긴 한가 봅니다.

고민해본결과, 왼쪽 부터 먼저 탐색하는것과 오른쪽 부터 탐색하는 결과의 차이점은 다음과 같습니다.

 

다음 그림에서, pivot을 맨 왼쪽 으로 잡았습니다.

탐색하는 코드는 다음과 같이 왼쪽부터 수행한다고 가정 한다면,

beginIndex8endIndex 도 마찬가지로 8 에 위치하게 됩니다. (index 가 아니라 입니다.!!! )

beginIndexendIndex 가 만났으니 pivot SWAP 작업을 거쳐야 하는데, 자세히 살펴보면 해당 작업을 수행하면 정렬 이 깨져 버립니다.

85 를 바꾼 후, 살펴보면 pivot 값인 5를 기준으로 왼쪽에 작은수만 있어야 하는 8 이 있어버리는 바람에 정렬이 깨져버립니다.

 

그렇다면 오른쪽부터 수행하면 어떻게 될까요?

해보시면 알겠지만, beginIndex 도, endIndex5 에 도달하게 됩니다.

pivot SWAP 작업을 거치더라도, 정렬이 깨지지 않는걸 알 수 있죠.

 

정리하자면 이렇습니다.

왼쪽부터 탐색을 수행하면, pivot 값 보다 큰 값 으로 beginIndexendIndex 가 만나게 되며,

오른쪽 부터 탐색을 수행하면, pivot 값 보다 작은 값 으로 beginIndexendIndex 가 만나게 됩니다.

 

이를 통해, 아래와 같이 생각 할 수 있습니다.

이는, 만약 맨-왼쪽으로 pivot 값 을 잡는다면, SWAP 대상이 pivot 값 보다 작은 값 이여야 하며,

맨-오른쪽으로 pivot 값 을 잡는다면, SWAP 대상이 pivot 값 보다 큰 값 이여야 함을 알 수 있습니다.

즉... 왼쪽부터냐 오른쪽 부터냐는 취향대로 하면 안되고,

pivot의 위치에 따라 반대방향으로 탐색 순서를 잡아야 함을 알 수 있습니다.

 

 

pivot swap 할 때, 주의 해야할점.

SWAP 작업의 마지막은 pivot SWAP 으로 pivot 자리를 찾아주는 것으로, 이는 매우 중요한 작업입니다.

많은 자료의 코드들이 단순하게 beginIndexendIndex 가 만나는 지점의 indexSWAP 을 시켜주는데, 이는 잘못된 코드가 될 수 있습니다.

아래와 같은 배열이 있다고 가정해봅시다. (pivot 은 맨 왼쪽입니다.)

배웠드시, 맨 왼쪽 pivot 이므로 오른쪽 부터 탐색을 수행해야 합니다.

그렇게 되면 beginIndex 와, endIndex5 에 도달하게 됩니다.

 

이때, pivot SWAP 을 진행하고 살펴보면 정렬이 깨지는걸 알 수 있습니다.

이런 현상이 왜 발생하냐면, beginIndex 가 수행할 기회 없이, 일방적으로 endIndex 가 끝으로 와버렸기 때문입니다. 즉 합의에 의한 만남이 아니라 일방적인 만남인것이지요. (말이좀 이상하네)

이런 경우가 있을 수 있기 때문에, pivot SWAP 을 진행할 때는, 데이터 크기 비교 후 진행해야 합니다.

 

정리하자면,

  • pivot 은 가운데다가 둘 수 있지만, SWAP 작업에 영향을 받을 수 있으니, 안전한 장소인 양끝에 두는걸로 하자.
  • index 탐색 작업을 수행 할 때, 배열 크기를 넘을 수 있으니 반드시 left < right 조건과 같이 탐색을 진행하자.
  • pivot 을 오른쪽 끝에 둘거면, 탐색 순서를 왼쪽부터! pivot 을 왼쪽 끝에 둘거면, 탐색 순서를 오른쪽 부터 진행하자.
  • pivot SWAP 을 진행할 때는, 해당 요소의 크기 조건이 안맞는 경우도 있으니 크기 조건을 반드시 추가하자.

 

마무으리

위에 진행한 코드는 아래에 경로에서 볼 수 있습니다.

https://github.com/Mommoo/Programmers/blob/master/src/com/mommoo/practice/sort/QuickSort.java

코드를 봐도 이해가 안간다는건 참 속상한 일입니다.

이렇게 분석을 나름 열심히 했는데도 틀린 분석이라면 더 속상할 거 같습니다.

만약에 이 게시글을 보고 틀린점이 있다면, 꼭 알려주셨으면 합니다.

읽어주셔서 감사합니다.

 


포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee


터미널에서 Git commit 을 진행 하면 마지막에 commit message 를 작성하기 위한 vi 화면이 나옵니다.


문제는 # 문자로 시작하여 커밋 메시지를 작성하고 싶을 때 가 있습니다.


하지만 위 설명과 같이 # 으로 시작하는 문장은 Git 은 주석으로 인식하기 때문에 커밋 메시지가 정상적으로 적용되지 않습니다.


이를 해결 하기 위한 방법중 하나는 주석을 의미하는 힌트 문자를 바꾸는 방법이 있습니다.


아래와 같이 본인이 메시지 작성에 쓰이지 않는, 문자 하나를 주석 힌트 문자로 설정해주면 됩니다.


저는 * 로 하여, 주석의 시작이 * 로 바뀌었고, 무사히 # 문자를 commit message 에 작성할 수 있었습니다.

git config core.commentChar '*' (개인 프로젝트)
git config --global core.commentChar '*' (전체 프로젝트)


포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee


오늘 포스팅 할 내용은 IntelliJ IDEA 의 내장된 DB Client를 사용하는 방법에 대해 기술합니다.

사용한 환경은 대략적으로 아래와 같습니다.

  • Mac OS

  • IntelliJ IDEA Ultimate

  • H2 데이터베이스

  • Maven Project

H2 데이터베이스 실행하기

http://www.h2database.com/html/download.html

위 경로에서 H2 데이터베이스를 설치합니다.

다운받은 후, bin 경로의 h2.sh 스크립트를 실행합니다. (윈도우 사용자라면, h2.bat 을 실행하면 됩니다.)

실행하면, 아래 이미지와 같이 기본 설정된 브라우저로 데이터베이스 접속화면이 뜹니다.

Note. 브라우저의 접속화면이 계속 로딩 화면으로 유지 된다면, url을 localhost 로 변경 해보세요.

화면 상단 언어설정에서 한국어로 바꾼후,

위 이미지 처럼 설정 하되, 세부 설정은 아래의 설명을 참고해주세요.

JDBC URL 맨 끝에는 원하시는 데이터베이스 명을 넣으시면 됩니다.

ex) Database 이름을 Person으로 -> jdbc:h2:tcp://localhost/~/Person

사용자명비밀번호 는 개인적으로 맘에드는 것을 작성하면 되지만, 기억하셔야 합니다.

저는 사용자명sa비밀번호는 작성하지 않았습니다.

설정이 끝난 후 연결시험을 눌러 시험 성공 메시지가 뜨는지 확인해주세요.

성공 메시지가 정상적으로 떴다면, 연결 버튼을 눌러 연결을 해주세요.

의존성 준비하기 (FEAT. MAVEN)

필요한 의존성은 H2 Database 프로그램을 Java 프로젝트와 연동할 H2 Database Driver 라이브러리가 필요합니다.

pom.xml 의 dependency 설정부분에 아래와 같이 의존성을 추가해주세요.

<dependency>
 <groupId>com.h2database</groupId>
 <artifactId>h2</artifactId>
 <version>1.4.193</version>
</dependency>

INTELLIJ DB CLIENT 연결하기.

Action 검색으로 Database를 검색합니다. ( shift + shift )

Database 탭이 뜬 후, + 버튼을 눌러 DataSource > H2 를 누릅니다.

설정창이 뜰텐데요, 정보를 기입할때 몇가지 유념해야할 사항이 있습니다.

Connection Type

저희는 H2 Database 프로그램을 서버모드로 작동시켰습니다.

http://www.h2database.com/html/features.html#database_url

대응하는 Type은 Remote 입니다. 해당 Type을 선택해줍니다.

Driver

Maven으로 H2 Database Driver 모듈을 다운받았다면,

H2 로 설정되어 있습니다. 만약 안되어 있다면, 의존성을 다시 확인해주시고, 수동 설정을 해보시길 바랍니다.

Host

H2 Database 가 실행된곳의 서버정보를 입력하면 됩니다. (보통 localhost)

Port

port 는 조금 알아야하는 내용이 있습니다.

기입해야할 port H2 Database 웹 브라우저 접속에서 사용한 8082번 포트가 아닙니다.

8082 번 포트는 H2 Database의 웹 어플리케이션을 구동할때 사용하는 포트번호 입니다.

우리가 실제로 연결해야할 H2 Database의 프로그램 포트번호는 9092 번을 사용합니다.

따라서, 9092 번으로 기입합니다.

User , Password

H2 Database 웹 접속화면에서 작성한 아이디와 비밀번호를 입력하면 됩니다.

Database

우리가 작성한 JDBC URL 포맷은 ~ 를 사용하여 상대경로로 표현했습니다.

아쉽게도 IntelliJ IDEA 가 해당 표현을 인지를 못하는거 같아, 일단 공백으로 둡니다.

아래의 URL을 작성하는 곳에서 Database 명까지 한번에 입력할 예정입니다.

URL

지금 까지 설정대로 잘 작성했다면, 아래와 같은 패턴으로 자동완성되어 있습니다.

jdbc:h2:tcp://{host}:9092/

우리가 사용한 URL 포맷으로 아래와 같이 바꿉니다. (데이터베이스 명까지 한번에 작성합니다.)

jdbc:h2:tcp://{host}:9092/~/{데이터베이스 명}

마지막으로 TEST CONNECTION 버튼으로 연결 테스팅을 진행 후, 설정을 마칩니다.

마무리

IntelliJ IDEADatabase 탭 메뉴중 콘솔을 눌러서 잘되었는지 간단한 명령어로 확인해봅니다.

ex) show tables;

만약 스키마 목록이 보이지 않는다면, Database 탭 메인 화면에서 오른쪽에 조그만한 숫자를 눌러, 메뉴 표시를 하시면 됩니다.

오늘 포스팅은 여기까지 입니다.

읽어주셔서 감사합니다.

'IDE' 카테고리의 다른 글

[IntelliJ] 인텔리J에 이클립스 Tab 기능 설정하기.  (2) 2018.12.23
[Eclipse] 이클립스 폰트 설정  (0) 2016.04.08

포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

+ Recent posts