SMALL
  • BFS의 개념
  • BFS에 대한 간단한 코드 설명 
  • BFS의 장단점
  • LeetCode 예시 문제 및 참고 코드

 

BFS ( Breadth First Search )는 너비 우선 탐색이라 불리는 알고리즘이다.

해당 알고리즘은 보통 DFS ( Depth First Search)와 많이 비교 된다. 

두 알고리즘은 모두 그래프를 탐색하는데 많이 사용된다.

 

그래프 탐색이란 - 하나의 정점으로부터 시작하여 차례대로 모든 정점들을 한 번씩 방문하는 것을 말한다.

예를 들어, 특정 도시에서 다른 도시로 이동을 할 수 있는지 탐색하는 것이 될 수 있다.

 

BFS는 그래프를 탐색시, 너비를 우선 해서 탐색을 하겠다는 전략이다. 

즉, 루트 노드에서 시작해서 인접한 노드를 먼저 탐색하는 방법을 말한다.

시작 정점으로부터 가까운 정점을 먼저 방문하고 멀리 떨어져 있는 정점을 나중에 방문하는 순회 방법이다.

깊게(deep) 탐색하기 전에 넓게(wide) 탐색하는 것이다.

사용하는 경우는 보통  노드 사이의 최단 경로 혹은 임의의 경로를 찾고 싶을 방법을 선택한다.

 

출처 : https://coding-factory.tistory.com/612

위의 설명을 코드로 나타낸 간단한 의사 코드를 보면 아래와 같을 수 있다. 

void search(Node root) {
  Queue queue = new Queue();
  root.marked = true; // (방문한 노드 체크)
  queue.enqueue(root); // 큐에 추가

  // 큐가 소진될 때까지 반복한다.
  while (!queue.isEmpty()) {
    Node r = queue.dequeue(); // 큐에서 노드 추출
    r.marked = true; // (방문한 노드 체크)
    
    // 큐에서 꺼낸 노드와 인접한 노드들을 모두 차례로 방문한다.
    foreach (Node n in r.adjacent) {
      if (n.marked == false) {
        queue.enqueue(n); // 큐에 추가
      }
    }
  }
}

BFS의 장단점은 아래와 같을 수 있다

  • 장점
    • 출발 노드로 부터 목표 노드까지의 최단 경로를 보장해 준다.
  • 단점
    • BFS에 비해 코드 구현이 까다롭다.
    • Queue에 노드를 저장하다 보니, 저장공간이 준비되어있어야 한다. 

예시 문제 

1161 - Maximum Level Sum of a Binary Tree ( 이진트리의 합이 가장 큰 레벨 )

joomn11.tistory.com/37

 

513 - Find Bottom Left Tree Value ( 트리의 가장 밑의 왼쪽 값 찾기 )

joomn11.tistory.com/39

 

429 - N-ary Tree Level Order Traversal ( N-ary 트리의 레벨 순서 순회 )

joomn11.tistory.com/38

LIST
SMALL
  • DFS의 개념
  • DFS에 대한 간단한 코드 설명
  • DFS의 장단점
  • LeetCode 예시 문제 및 예시 코드

DFS는 깊이 우선 탐색이라고 불리는 알고리즘이다.

해당 알고리즘은 보통 그래프 탐색을 하는 경우에 사용된다.

 

그래프 탐색이란 - 하나의 정점으로부터 시작하여 차례대로 모든 정점들을 한 번씩 방문하는 것을 말한다.

예를 들어, 특정 도시에서 다른 도시로 이동을 할 수 있는지 탐색하는 것이 될 수 있다.

 

그래프 탐색 방식 중의 하나로 DFS ( 깊이 우선 탐색)은 

그래프를 탐색 할때 , 깊이를 우선적으로 탐색하겠다는 전략이다. 

시작점부터 다음 분기로 넘어가기 전에 해당 분기를 완벽하게 탐색하고 넘어가는 방법이다.

스택이나 재귀함수를 통해서 구현할 수 있는데 재귀 함수가 구현이 간편하기에 대부분 재귀 함수로 구현한다.

출처: https://ko.wikipedia.org/wiki/%EA%B9%8A%EC%9D%B4_%EC%9A%B0%EC%84%A0_%ED%83%90%EC%83%89

해당 설명을 코드로 표현한 간단한 의사코드를 나타내면 아래와 같을 수 있다.

void search(Node root) {
  if (root == null) return;
  
  visit(root); // 1. root 노드 방문 (방문한 노드를 표시)
  
  // 2. root 노드와 인접한 정점을 모두 방문
  for each (Node n in root.adjacent) {
    if (n.visited == false) { // 3. 방문하지 않은 정점을 찾는다.
      search(n); // 4. root 노드와 인접한 정점 정점을 시작 정점으로 DFS를 시작
    }
  }
}

 

DFS의 장단점은 아래와 같을 수 있다.

 

장점 

  • 현재 경로(branch)상의 노드들만 기억하면 되므로, 저장 공간의 수요가 비교적 적음 
  • 코드가 직관적이고 상대적으로 구현하기 간단하다

단점

  • 재귀함수를 이용하기 때문에, 함수 호출 비용이 추가적으로 들어간다
  • 재귀 깊이가 지나치게 깊어지면 stack 메모리 비용을 예측하기 어렵다
  • 최단 경로를 알 수 없다 ( 단지 해당 노드를 방문할 수 있냐 없냐 만 판단할 때 쓰기 적합하다)

예시문제

1026 Maximum Difference Between Node and Ancestor ( 조상과 노드 사이의 최대 차이값 )

joomn11.tistory.com/36

 

1448 Count Good Nodes in Binary Tree ( 이진트리의 Good 노드 갯수 )

joomn11.tistory.com/35

 

1038 Binary Search Tree to Greater Sum Tree ( BST를 GreaterSumTree로 변환 )

joomn11.tistory.com/33

 

LIST
SMALL

Hateoas에 대한 정의 

REST API에 대한 정의 

Hateoas 사용 예시


Hateoas에 대한 정의 

Hypermedia As The Engine Of Application State의 약자로, 기본적인 아이디어는 

하이퍼미디어를 애플리케이션의 상태를 관리하기 위한 메커니즘으로 사용한다는 것입니다.

 

위의 정의 내용을 이해하고, 구글링을 하다 보면 보이는 정의 내용을 다시 보면 조금은 더 이해하는데 도움이 된다.

Hateoas란 REST Api를 사용하는 클라이언트가 전적으로 서버와 동적인 상호작용이 가능하도록 하는 것을 의미합니다. 이러한 방법은 클라이언트가 서버로부터 어떠한 요청을 할 때, 요청에 필요한 URI를 응답에 포함시켜 반환하는 것으로 가능하게 할 수 있습니다.

 

REST API에 대한 정의 

Hateoas는 REST API를 잘 설계하기 위해 나온 개념이다. 그렇다면 REST API는 무엇인가 간단히 알아보자.

REST API는 Representational State Transfer API의 약자로,

“웹 애플리케이션이 제공하는 각각의 데이터를 리소스, 즉 자원으로 간주하고 각각의 자원에 고유한 URI(Uniform Resource Identifier)를 할당함으로써 이를 표현하는 API를 정의하기 위한 소프트웨어 아키텍처 스타일이다.

 

잘 설계된 REST API를 구현하기 위한 단계가 존재하는데 , 그 마지막 단계가

Hypermedia Controls (하이퍼미디어 컨트롤) - HATEOAS라는 개념을 통해, 자원에 호출 가능한 API 정보를 자원의 상태를 반영하여 표현하는것이다.

(자세한 구현 단계에 대한 설명은 다른 포스팅을 통해서 소개하도록 하겠습니다)

REST 구현 단계 ( 출처 : https://images.app.goo.gl/tp6yVbK2Lrs16dcB8)
REST API 구현 단계별 예시 (출처 : https://grapeup.com/blog/how-to-build-hypermedia-api-with-spring-hateoas/)

Hateoas 사용 예시

이제 대략적으로 Hateoas, REST API가 무엇인지 알았으니, 해당 개념에 대한 예제를 보고 더욱 구체적으로 이해해 보자.

 

계좌번호가 "12345"인 계좌의 정보를 조희 하는 경우에 해당 계좌의 상태 ( 잔여 금액 등등..)에 따라 접근 가능한 추가 API들이 LINKS라는 이름으로 제공된다.

 

기존의 전형적인 REST API 응답

 

Hateoas가 적용된 응답 ( URI 정보를 응답에 추가)

위와 같이 Hateoas를 사용하게 되면 누릴 수 있는 장점은 아래와 같다 

  • Client 사이드에서는 "rel"의 이름으로 요청 URI를 사용하기 때문에 URI 수정이 발생하더라도 Client 사이드는 수정이 이루어지지 않는다. 

 

LIST
SMALL
  • JWT에 대한 간단한 정의
  • 기존 서버 기반 인증의 문제점 ( MSA 환경 )
  • JWT의 세부 구조 

 

JWT는 JSON Web Token의 약자로 

클라이언트와 서버, 서비스와 서비스 사이 통신 시 권한 인가를 위해 사용하는 토큰이다. 

URL에 대해 안전한 문자열로 구성되어 있어서 HTTP 어디든 ( URL, Header.. ) 토큰 정보가 위치할 수 있다.

 

기존의 권한 인증 인가는 대부분 서버 기반으로 이루어져 있었다. 

서버 기반의 권한 인증 인가는 MSA ( MicroService Architecture)에 적합하지 않아 그의 대안으로 JWT가 등장하였다.


기존 서버 기반 인증의 문제점

사용자 수가 늘어날수록 세션에 담아야 할 정보가 함께 증가하기 때문에 메모리 과부하 문제가 발생한다.

많은 트래픽을 감당하기 위해 프로세스를 늘리거나 서버 장비를 추가하는 경우 확장이 쉽지 않다.

쿠키는 단일 도메인 및 서브 도메인에서만 작동하기 때문에 여러 도메인에서 관리하는 것은 번거롭다.

즉, 서버 기반 인증인 세션과 쿠키는 MSA환경에 적합하지 않다.

 


JWT 인증 과정에 대한 간략 Flow

  • 사용자 로그인
  • 서버 : 사용자 정보 인증 -> 맞는 경우 클라이언트에게 signed token 발급 
    ( signed tocken : 정상 토큰인지 증면된 signature를 담고 있는 토큰) 
  • 클라이언트 : 서버로부터 받은 토큰 저장, 서버에 요청할 때마다 토큰을 서버에 전달
  • 서버 : 요청이 올 때마다 함께 전달된 토큰 검증 

JWT의 구조 

header, payload, signature로 이루어져 있고, 각각의 정보는 "."을 통해서 구분된다.

각각의 정보는 Base64 URL-Safe 인코딩 된 문자열이다.

header . payload . signature 

 

Header에는 JWT를 어떻게 검증하는가에 대한 내용을 담고 있다. 

{
    "type" : "JWT", // 토큰의 타입
    "ALG" : "H256" // 서명 시 사용하는 알고리즘 ( HMAC SHA256, RSA ) 
}

headerStr = Base64.encodeBase64URLSafeString(objectMapper.writeValueAsBytes(header));

Payload에는 토큰에 담을 정보가 포함되어있다.

토큰은 클레임으로 이루어져 있으며 "name : value" 쌍으로 이루어져 있다.

토큰 안에는 여러 개의 클레임을 넣을 수 있으며 크게 세 분류로 나눌 수 있다.

  • registered claim : 서비스에서 필요한 정보들이 아닌, 토큰에 대한 정보들을 담기 위해 이름이 이미 정해진 클레임이다.
  • public claim : 사용자 정의 클레임으로 공개용 정보를 위해 사용된다. 충돌 방지를 위해 URL 포맷을 이용해야 한다.
  • private claim : 서버와 클라이언트 사이에 임의로 지정한 정보를 저장하기 위해 만들어진 사용자 지정 클레임
{
    "iss": "joojoo.com",
    "exp": "1485270000000",
    "https://joojoo.com/jwt_claims/is_student": true,
    "userId": "11025454527102",
    "username": "joojoo"
}

payloadStr = Base64.encodeBase64URLSafeString(objectMapper.writeValueAsBytes(payload));

 

Signature에는 헤더의 인코딩 값, payload의 인코딩 값을 합친 후 주어진 비밀키로 해쉬를 하여 생성된 값이 들어있다. 

HMACHASH256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secretKey)

 

 

 

 

LIST

+ Recent posts