- 왜 dto에서 항상 @Getter와 @NoArgsConstructor를 붙여줘야 할까? 2023.03.26
- join과 fetch join 개념, 스스로 짚고 가기 2023.03.25
- 지연로딩과 즉시로딩 개념, 스스로 짚고 가기 2023.03.25
- 왜 외래키가 있는 곳을 연관관계의 주인으로 잡을까? 2023.03.21
- javax에서 jakarta로 변경 2023.03.21
- 비트마스크(1) 2023.03.14
- 그래프와 인접행렬 2022.12.11
- Tree 말단 노드까지의 가장 짧은 경로(BFS) 2022.12.11
Web application 개발을 계속 하면서 request-POJO-response로 데이터가 흘러가는 것을 파악했습니다.
이를 위해서 dto에 항상 의무적으로 @Getter와 @NoArgsConstructor를 붙여가며 기능을 구현했습니다.
이렇게 json 데이터를 dto 객체로 변환하여 작업한 후에 처리된 데이터를 다시 json 형식으로 변환하는 일은 누가 하는지에 대해 궁금했습니다.
이런 일을 주로 처리하는 라이브러리는 SpringBoot starter에 포함되어 있는 jackson 라이브러리가 합니다.
Jackson라이브러리에 대해 좀 더 자세히 알아보고, 왜 @Getter와 @NoArgsConstructor를 붙여줘야 하는지 파악하려고 합니다.
jackson은 json 데이터 구조를 손쉽게 처리해주는 라이브러리입니다.
dto 객체를 json으로 변환하는 것을 직렬화라고 하고, json 데이터를 dto로 변환하는 것을 역직렬화라고 합니다.
@Getter
@NoArgsConstructor
public class ReviewUpdateDto {
private Long id;
private String review;
}
저희 프로젝트에서 dto객체의 property는 모두 private으로 선언했습니다.
이렇게 public이 아닌, private 또는 protected로 선언하는 경우에는 getter를 같이 설정함으로써 json 데이터와 매핑할 수 있습니다.
getter를 설정해줌으로써, {"id":"1", "review":"랄랄"} 이와 같이 getId(), getReview()메서드를 통해서 각각의 필드와 매핑이 되는 것입니다.
@Getter 어노테이션을 붙여줌으로써, json 데이터에 내보내는 필드명과 객체에 담긴 필드명을 손쉽게 일치시킬 수 있습니다.
만약 이 두 필드명이 불일치하다면, JsonProcessingException이 발생할 수도 있습니다.
또한 Getter 메서드를 기반으로 프로퍼티명을 확인하기 때문에,
postman을 사용하여 테스트할 경우에는 필드명이 일치하는지 꼭꼭 확인할 필요도 있습니다!(자주 발생했던 실수입니다)
만약 @Getter 어노테이션이 아닌 메서드를 직접 정의하고, 2번처럼 return 값을 고정시켜놨다고 가정해보겠습니다.
//1. 일반적인 getter 메서드
public int getAge(Entity entity) {
return this.age;
}
//2. 값을 고정한 getter 메서드
public int getAge(Entity entity) {
return 23;
}
이런 경우 json 데이터 변환작업이 이뤄진다면 json 데이터에도 {"age" : 23} 이렇게 담겨진다고 합니다.
이런 상황을 통해서 getter의 역할을 알 수 있습니다.
1. getter는 매핑관계를 설정해줄 수 있습니다.
2. 직렬화를 통해 json 데이터를 내보낼 때, getter의 return값을 데이터로 내보냅니다.
그렇기 때문에 json 변환에 있어서 getter는 빠져서는 안되는 필수적인 어노테이션이라는 것을 알 수 있습니다.
결론부터 말하자면, 저희 프로젝트의 변환작업에서 기본생성자는 있어도 되고, 없어도 되는 어노테이션이라고 생각했습니다.
ObjectMapper가 데이터 바인딩을 하기 위해서는,
1. 기본 생성자를 통해서 새 객체를 생성합니다.
2. getter, setter 혹은 public field 등을 통해서 프로퍼티명을 찾습니다.
3. java.lang.reflection 패키지를 통해 값을 주입합니다.
이런 과정을 거쳐야 합니다.
그렇기 때문에 @NoArgsConstructor를 통해서 기본 생성자를 만들어줬습니다.
그러나 기본 생성자가 없어도 이런 변환작업은 가능하다고 합니다.
ObjectMapper 내부에는 property와 생성자가 위임된 경우 그 정보를 이용해서 직렬화/역직렬화에 사용하는 로직이 있습니다.
위임 어노테이션에는 @JsonProperty, @JsonAutoDetect, @JsonCreator가 있습니다.
여기서 위임이라는 것은 어노테이션을 사용해 객체를 변환(직렬화/역직렬화)할 때 쓰일 정보를 직접 선언하는 것인데,
이 위임을 자동으로 해주는 jackson-datatype-jdk8이라는 라이브러리가 있습니다.
현재 저희의 프로젝트에도 포함되어 있습니다. 위의 3가지 모듈을 모두 지원해주고 있습니다.
여기에서 @JsonProperty 어노테이션을 통해서 jackson-module-parameter-names 모듈을 지원받을 수 있습니다.
이 모듈은 기본 생성자가 없어도 다른 생성자로 대체하여 역직렬화를 수행할 수 있다고 합니다.
SpringBoot에서는 jackson binding을 할 때 ObjectMapper에 이 모듈이 기본적으로 등록되어 있다고 합니다.
그래서 controller에 들어오는 request dto에 기본생성자가 없어도 역직렬화를 수행할 수 있습니다.
'개발 공부 기록 > 스프링부트 스터디' 카테고리의 다른 글
왜 외래키가 있는 곳을 연관관계의 주인으로 잡을까? (0) | 2023.03.21 |
---|---|
javax에서 jakarta로 변경 (0) | 2023.03.21 |
[SpringBoot]변수명으로 인한 mustache문제 해결 (0) | 2022.05.29 |
[SpringBoot] Spring Security와 OAuth 2.0(1) (0) | 2022.05.26 |
[SpringBoot] WSGI와 CGI (0) | 2022.05.08 |
여태까지 join보다 fetch join이 더 성능적으로 좋다고 해서 명확한 차이를 이해하지 못한채로 무작정 사용했습니다.
이번 기회에 join과 fetch join의 명확한 차이를 짚고가기 위해, 글을 작성했습니다.
이전 게시글에서 지연로딩과 즉시로딩에 대해 설명했습니다.
지연로딩에서도 피할 수 없는 N+1문제를 해결하고자 fetch join 개념이 나왔습니다.
먼저 N+1문제가 나오는 원인에 대해 한 문장으로 옮긴다면 다음과 같이 말할 수 있습니다.
JPA가 자동으로 먼저 생성해주는 Jpql을 통해서 우선적으로 쿼리를 만들다보니 연관관계가 걸려있어도 join이 바로 걸리지 않는다.
이는 근본 원인이자, 일반 join의 기능입니다.
일반 join을 사용하게 된다면 추가적인 쿼리가 생성이 되고, 바로 N+1 문제가 되는 것입니다.
join 방식을 다르게 한 jpql문을 예시로 들어보겠습니다.
1. 일반 join문
@Query("SELECT distinct t FROM Team t join t.members")
2. Fetch join문
@Query("SELECT distinct t FROM Team t join fetch t.members")
일반 join의 경우에는 member를 join 조건으로만 사용합니다. 실제 가져오는 데이터는 Team 테이블을 대상으로만 가져오게 됩니다.
fetch join의 경우에는 member를 join조건으로 사용하면서, member 테이블의 필요한 데이터와 team 테이블의 데이터를 같이 가져옵니다.
JPA측면에서 다시 join방식의 차이를 이야기해보자면,
- 일반 Join: 실제 쿼리에서 SELECT 하는 Entity는 오직 JPQL에서 조회하는 주체가 되는 Entity만 조회하여 영속화 (team만 영속화)
- Fetch Join: 일반 join과 달리, join이 걸리는 연관 Entity도 함께 select하여 모두 영속화(team, member 영속화)
라고 말할 수 있습니다.
Fetch join이 걸린 Entity를 모두 영속화하기 때문에 fetchType이 Lazy인 Entity를 참조하더라도 이미 영속성 컨텍스트에 들어있기 때문에 따로 쿼리가 실행되지 않고 참조할 수 있게 됩니다.
이제 지연로딩(fetchType = LAZY)과 즉시로딩(fetchType = EAGER) 개념에 대해 알아보도록 하겠습니다.
지연로딩과 즉시로딩은 join을 할 때, 객체를 가져오는 방식이라고 할 수 있습니다.
지연로딩과 즉시로딩은 연관 Entity 영속화를 언제 하느냐에 차이를 두고 있습니다. (영속화의 시점에 따라서 지연로딩과 즉시로딩 구분)
LAZY와 EAGER의 각각 개념에 대해 명시하자면 다음과 같습니다.
- 즉시로딩(EAGER): 최초 테이블 데이터를 모두 불러오고, 바로 연관관계가 있는 엔티티까지 싹 다 조회합니다.
- 지연로딩(LAZY): 최초 테이블 데이터를 모두 불러오고, 필요한 시점에만 연관관계가 있는 엔티티를 조회합니다.
- 지연로딩에서 처음 불려질 때에는 실제 객체가 아닌, 프록시(proxy)객체로 가져옵니다. 프록시 객체를 영속화합니다.
- 필요한 시점에 조회할 때에만 실제 객체를 가져옵니다. 이를 '프록시 객체를 초기화한다'고 합니다.
지연로딩이 왜 생겼는지 의문이 들 수도 있습니다. 처음부터 필요한 데이터를 다 깔아둔 다음, 입맛에 맞게 골라 쓰면 되지 않냐고 할 수 있습니다. 그러나 이는 단순한 엔티티 관계에서는 별 문제가 없어보일 수 있지만, 복잡한 관계가 설정된다면 많은 쿼리가 나가게 될 것이고 이는 성능 저하로 직결될 수도 있습니다.
그래서 지연로딩은 효과적으로 필요한 연관관계만 조회하도록 하는 것입니다.
지연로딩과 즉시로딩 개념을 공부하게 되면, N+1 문제를 흔히 접할 수 있습니다.
지연로딩을 적용하여 N+1문제를 해결할 수 있는 경우는 단순 조회의 경우에만 해결할 수 있습니다. 연관관계가 걸린 엔티티이더라도, 조회할 때에는 프록시 객체로 넘겨받기 때문에 N+1문제가 발생하지 않기 때문입니다.
조회를 넘어서 연관관계가 걸린 Entity 프록시 객체의 데이터값을 건드린다면, 바로 N+1문제가 발생합니다.
지연로딩 또한 N+1문제를 피하기 힘들고, 즉시로딩보다 발생시점을 늦춰주기만 할 뿐입니다.
이 때의 문제상황을 해결하기 위해서 fetch join 방식이 나오게 됩니다.
스프링부트 JPA 강의를 수강하면서, 왜 하필 외래키가 있는 곳을 연관관계의 주인으로 잡는지에 대한 의문이 생겼습니다.
결론부터 말하자면, 성능상의 이점이 가장 큰 것 같습니다.
일대다(1:N) 연관관계를 가지는 자동차와 바퀴 4개를 예시로 들어보겠습니다.
먼저, 외래키를 왜 다(N)에서 가져야 하는지에 대해 알아보겠습니다.
외래키는 일(1)이 가질수도 있고, 다(N)에서 가질수도 있습니다.
그러나 이런 경우는 외래키를 제외한 나머지 필드 데이터들의 중복 문제가 발생할 수도 있습니다.
이 외에도 다양한 문제점들이 있기 때문에, 일반적으로 다(N)에서 외래키를 가지게 하는 것입니다.
다(N)쪽이 외래키를 관리하게 된다면 위와 같은 중복 문제도 피할 수 있기 때문입니다.
'다(N)쪽이 외래키를 가지게 하는 것이 좋다'
이제 바퀴 테이블이 외래키를 가지고 있습니다.(외래키 = 자동차의 id)
다음으로 연관관계의 주인이 어떤 것인지 알아보겠습니다.
연관관계의 주인이란, 양방향 관계를 가진 두 개의 테이블이 있을 때 데이터의 변경을 책임지는 역할을 합니다.
즉 외래키 관리자라는 뜻입니다.
그래서 연관관계의 주인이 된다면, 등록/수정/삭제 작업이 일어날 때 직접 데이터를 관리할 수 있습니다.
반면 연관관계의 주인이 아닌 테이블은 직접적으로 관리하지 못하고, 오직 읽을수만 있게 됩니다.
자동차가 연관관계의 주인이라면, 바퀴의 값이 바뀌는 상황이 있을 때 바퀴 테이블을 자동차 테이블이 직접 관리해줘야 합니다.
이는 불필요하게 복잡해질 수도 있고, 외래키 무결성 제약 조건에 위반될 수도 있습니다.
바퀴가 연관관계의 주인이라면, 자신의 데이터를 직접 스스로 해결할 수 있게 됩니다.
'외래키를 가진 쪽이 연관관계의 주인이 되는 것이 좋다'
왜 외래키가 있는 곳을 연관관계의 주인으로 잡을까?에 대한 질문은 위의 내용을 토대로,
"다(N)쪽이 외래키를 가지는 것이 좋고, 외래키를 가진 쪽이 연관관계의 주인이 되어야 한다" 는 결론을 내릴 수 있을 것 같습니다.
아직 많은 테이블 설계를 해보지 못했고, 상황에 따라 달라지는 경우도 있다고 합니다.
그러나, 일반적인 사실이라는 것을 미뤄봤을 때 ERD의 기본적인 설계는 이를 토대로 해야겠다고 생각했습니다.
'개발 공부 기록 > 스프링부트 스터디' 카테고리의 다른 글
왜 dto에서 항상 @Getter와 @NoArgsConstructor를 붙여줘야 할까? (0) | 2023.03.26 |
---|---|
javax에서 jakarta로 변경 (0) | 2023.03.21 |
[SpringBoot]변수명으로 인한 mustache문제 해결 (0) | 2022.05.29 |
[SpringBoot] Spring Security와 OAuth 2.0(1) (0) | 2022.05.26 |
[SpringBoot] WSGI와 CGI (0) | 2022.05.08 |
스프링부트 버전을 대부분 2로 시작하는 버전만 사용하다가, 최근 3.0.4버전으로 프로젝트를 생성했습니다.
버전이 변경되도 별 차이가 없다고 생각했는데, @NotEmpty validation을 체크하려다가 변경된 사항을 확인했습니다.
기존에는 option + enter만 눌러도 바로 validation 라이브러리가 import가 되었지만, 변경된 버전에서는 build.gradle에 의존성을 따로 추가해줘야했습니다.
implementation 'org.springframework.boot:spring-boot-starter-validation'
이렇게 함으로써, validation 어노테이션들이 잘 적용되는 걸 확인했습니다.
Reference
Hibernate validator @NotEmpty not working spring boot and jackson
I have the following class which has errorRequests with @NotEmpty annotation. public class ErrorsRequests implements Serializable { private static final long serialVersionUID = -727308651190295...
stackoverflow.com
'개발 공부 기록 > 스프링부트 스터디' 카테고리의 다른 글
왜 dto에서 항상 @Getter와 @NoArgsConstructor를 붙여줘야 할까? (0) | 2023.03.26 |
---|---|
왜 외래키가 있는 곳을 연관관계의 주인으로 잡을까? (0) | 2023.03.21 |
[SpringBoot]변수명으로 인한 mustache문제 해결 (0) | 2022.05.29 |
[SpringBoot] Spring Security와 OAuth 2.0(1) (0) | 2022.05.26 |
[SpringBoot] WSGI와 CGI (0) | 2022.05.08 |
카카오 코테를 풀어보면서, DFS로 인해 복잡한 코드를 비트마스크를 통해서 간단하게 구현한 코드를 보게 되었습니다.
이전에 알고리즘 스터디를 할 때 스터디원 한 분이 비트마스크를 활용해서 풀었다고 했을 때, 배워봐야지 생각은 했는데 이제서야 학습하네요. 처음 개념 공부를 할 때에는 블로그를 찾기보다는 책으로 공부하는 편이기 때문에 '알고리즘 문제 해결 전략' 책을 읽고 정리한 것입니다.
처음 읽었을 때 이해가 안되서 두 번 계속 읽어본 결과, 여전히 낯설고 어려운 개념입니다 ㅎ
초반에는 읽고 바로 기초 문제들을 풀 수 있겠지 싶어서 호기롭게 도전했는데, 쉽지가 않네요..
아직도 약간은 기초 문제를 풀기조차 힘든 부족한 부분이 있는 것 같아요.
그래서 일단 정리를 해두고 다음주(~2023.03.21)까지의 목표는 비트마스크 문제를 집중적으로 공략해보기로 정했어요.
개념 정리
비트마스크의 도입 배경
현대의 모든 CPU는 이진수를 이용하여 모든 자료를 표현합니다.
덕분에 내부적으로 이진법 관련 연산을 빠르게 할 수 있는데요. 이런 특성을 활용해서 이진수 표현을 자료구조로 쓰는 기법을 비트마스크라고 부릅니다.
비트마스크를 도입함으로써 얻을 수 있는 장점에는,
1. 더 빠른 수행 시간
비트마스크 연산은 O(1)에 구현되는 것이 많습니다. 그래서 적절히 사용한다면, 훨씬 빨리 동작할 수 있어요.
그러나, 비트마스크를 사용할 수 있다는 건 원소의 수가 많지 않다는 뜻이기 때문에 눈에 띄는 속도 향상을 기대할 수는 없다고 합니다.
비트마스크 연산을 굉장히 여러 번 수행해야 할 경우에는 작은 최적화도 큰 속도 향상을 가져올 수 있습니다.
저는 이 부분에서 비트마스크 연산은 원소의 수가 많지 않을 때 사용할 수 있다는 인사이트를 얻을 수 있었습니다.
2. 더 간결한 코드
3. 더 작은 메모리 사용량
4. 연관 배열을 배열로 대체
이렇게 4가지의 장점을 제시할 수 있어요.
비트 마스크 용어 정리
비트(bit)란 이진수의 한 자리 말합니다.
비트는 0 혹은 1의 값을 가질 수 있고, 컴퓨터가 표현하는 모든 자료의 근간이 됩니다.
부호없는 8비트 정수형은 8자리의 이진수로서 표시할 수 있는 모든 정수를 표현할 수 있습니다.
최소값(0)부터 최대값(1111 1111(2) = 255)까지 값을 가질 수 있습니다.
위의 부호없는 8비트 정수형 예시처럼, 부호 없는 N비트 정수형 변수는 N자리의 이진수로 사용할 수 있습니다.
각 비트가 표현하는 값은 맨 오른쪽부터 2^0 ~ 2^(n-1)까지입니다.
2^(n-1)에 해당하는 비트를 최상위 비트(MSB, Most Significant Bit)라고 부르고,
2^0에 해당하는 비트를 최하위 비트(LSB, Least Significant Bit)라고 부릅니다.
정수를 이진수로 표현했을 때, 비트의 위치가 1이라면 해당 비트가 "켜져있다"라고 말하고,
비트의 위치가 0이라면 해당 비트가 "꺼져있다"라고 말합니다.
비트 연산자(AND, OR, XOR, NOT, 시프트)
두 정수 변수(a,b)를 비트별로 조작할 수 있는 비트 연산자를 사용해야 합니다.
AND는 모두 켜져있을 때만(=비교하는 비트들이 모두 1일 때만) 결과로 나타낼 비트를 킵니다. - a & b
OR은 두 비트 중 하나라도 켜져 있을 때만(=비교하는 비트들 중 최소 하나가 1일 때만) 비트를 킵니다. - a | b
XOR은 하나는 켜져있고, 하나는 꺼져있는 특수한 상황에서 비트를 킵니다. - a ^ b
NOT은 꺼져있는 비트는 켜고, 켜져있는 비트는 끕니다(1 -> 0, 0 -> 1) - ~a
시프트(shift)는 정수 a의 비트들을 왼쪽 또는 오른쪽으로 c비트만큼 움직입니다.
'알고리즘' 카테고리의 다른 글
프로그래머스 - 삼각 달팽이 (1) | 2024.06.09 |
---|---|
프로그래머스 - 교점에 별 만들기 (2) | 2024.06.08 |
그래프와 인접행렬 (0) | 2022.12.11 |
Tree 말단 노드까지의 가장 짧은 경로(BFS) (0) | 2022.12.11 |
Tree 말단 노드까지의 가장 짧은 경로(DFS) (0) | 2022.12.11 |
그래프란? 요소들이 서로 복잡하게 연결되어 있는 관계를 표현하는 자료구조를 말한다.
G = (E,V)라고 표현할 수 있다. 아래는 그림으로 노드(정점)와 간선을 나타낸 그림이다.
그래프는 인접행렬과 인접리스트로 구현 가능하며 이번 게시글에서는 인접행렬로 구현한 그래프에 대해서 설명할 것이다. 인접행렬이란 2차원 배열과 동일하다고 보면 된다.
노드의 개수가 많을수록 인접리스트로 구현하는 것이 효율적이다.
그래프의 종류에는 (1)무방향 그래프, (2)방향 그래프, (3)가중치 방향 그래프 가 있다.
(1) 무방향 그래프 - 이동하는 방향이 정해져 있지 않다.
이동하는 방향이 정해져 있지 않아서,
'1에서 2로' 라고 말할 수 있고, '2에서 1로' 라고도 말할 수 있다.
이 그래프에서 연결된 케이스를 모두 적어본다면 아래와 같다.
1 - 2
1 - 3
2 - 4
2 - 5
3 - 4
이를 그래프 인접행렬(2차원 배열)에 넣는다면 왼쪽의 그림에서 확인할 수 있다. graph[a][b] = 1은 노드a와 노드b가 연결되어있다는 것을 의미하게 된다. 반대로 값이 0이라면 연결되어 있지 않다는 것을 의미한다.
노드a와 노드b를 연결한 간선 정보를 담기 위해서는,
graph[a][b] = 1
graph[b][a] = 1
위의 코드들을 구현해줘야 한다.
(2)방향 그래프 - 이동하는 방향이 정해져 있다.
이동하는 방향이 정해져있기 때문에 지정된 방향으로만 이동이 가능하다. 연결된 이동 경로의 케이스를 모두 적으면 아래와 같다.
1 - 2
1 - 3
2 - 5
3 - 4
4 - 2
설명은 무방향 그래프와 동일하고, 다른 점이 있다면 지정한 방향에 대해서만 연결이 가능하다.
노드a와 노드b를 연결한 간선 정보를 담기 위해서는,
graph[a][b] = 1
한 줄의 코드만 구현하면 된다.
(3) 가중치 방향 그래프
방향 그래프에서 이동에 대한 가중치가 더해진 그래프이다.
연결된 이동 경로의 케이스에서 가중치 정보를 더해주면 된다.
1 - 2 : 2 (이동경로 : 가중치)
1 - 3 : 4
2 - 5 : 5
3 - 4 : 5
4 - 2 : 2
방향 그래프와 설명이 동일하다.
노드a와 노드b를 연결한 가중치 c인 간선의 정보를 담기 위해서는
graph[a][b] = c
와 같은 코드를 구현하면 된다.
'알고리즘' 카테고리의 다른 글
프로그래머스 - 교점에 별 만들기 (2) | 2024.06.08 |
---|---|
비트마스크(1) (0) | 2023.03.14 |
Tree 말단 노드까지의 가장 짧은 경로(BFS) (0) | 2022.12.11 |
Tree 말단 노드까지의 가장 짧은 경로(DFS) (0) | 2022.12.11 |
이진트리 순회(너비우선탐색 : 레벨탐색) (2) | 2022.12.10 |
BFS는 층별로 탐색을 한다. 각 층을 탐색하면서 말단 노드인 것을 확인하면 탐색하던 층을 반환한다. 그래서 DFS보다 최단 거리를 찾는 것에 더 효율적인 것이다.
이전 글에서 설명을 충분히 했다고 여겨서 바로 코드로 구현해볼 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | class Node{ int data; Node lt, rt; public Node(int root){ data = root; lt = rt = null; } } public class Practice { public static Node root; public static Queue<Node> q; public static int BFS(){ int L=0; q.offer(root); while(!q.isEmpty()){ int len = q.size(); for(int i=0; i<len; i++){ Node cur = q.poll(); if(cur.lt == null && cur.rt == null) return L; if(cur.lt != null) q.offer(cur.lt); if(cur.rt != null) q.offer(cur.rt); } L++; } return -1; } public static void main(String[] args){ q = new LinkedList<>(); root = new Node(1); root.lt = new Node(2); root.rt = new Node(3); root.lt.lt = new Node(4); root.lt.rt = new Node(5); q.offer(root); System.out.println(BFS()); } } | cs |
'알고리즘' 카테고리의 다른 글
비트마스크(1) (0) | 2023.03.14 |
---|---|
그래프와 인접행렬 (0) | 2022.12.11 |
Tree 말단 노드까지의 가장 짧은 경로(DFS) (0) | 2022.12.11 |
이진트리 순회(너비우선탐색 : 레벨탐색) (2) | 2022.12.10 |
부분집합 구하기(DFS) (0) | 2022.12.10 |