트리 데이터 구조에 대해 알아야 할 모든 것

나무가 너무 아름답습니다. 내가 어렸을 때 만든 그림.

코딩을 처음 배우면 "주 데이터 구조"로 배열을 배우는 것이 일반적입니다.

결국 해시 테이블에 대해서도 배웁니다. 컴퓨터 과학 학위를 추구하는 경우 데이터 구조에 대한 수업을 들어야합니다. 또한 링크 된 목록, 대기열 및 스택에 대해 배웁니다. 이러한 데이터 구조는 모두 논리적 시작과 논리적 끝이 있기 때문에“선형”데이터 구조라고합니다.

나무와 그래프에 대해 배우기 시작하면 정말 혼란 스러울 수 있습니다. 우리는 선형 방식으로 데이터를 저장하지 않습니다. 두 데이터 구조 모두 특정 방식으로 데이터를 저장합니다.

이 글은 Tree Data Structure를 더 잘 이해하고 그에 대한 혼동을 명확히하기 위해 작성되었습니다.

이 기사에서는 다음을 배웁니다.

  • 나무 란?
  • 나무의 예
  • 용어와 작동 방식
  • 코드에서 트리 구조를 구현하는 방법

이 학습 여정을 시작합시다. :)

정의

프로그래밍을 시작할 때 트리 및 그래프와 같은 데이터 구조보다 선형 데이터 구조를 더 잘 이해하는 것이 일반적입니다.

트리는 비선형 데이터 구조로 잘 알려져 있습니다. 선형 방식으로 데이터를 저장하지 않습니다. 데이터를 계층 적으로 구성합니다.

실제 사례를 살펴 보겠습니다!

계층 적으로 말할 때 무엇을 의미합니까?

조부모, 부모, 자녀, 형제 자매 등 모든 세대의 관계를 가진 가계도를 상상해보십시오. 우리는 일반적으로 가계도를 계층 적으로 구성합니다.

내 가족 트리

위의 그림은 내 가계도입니다. Tossico, Akikazu, Hitomi 및 Takemi는 나의 조부모입니다.

Toshiaki와 Juliana는 부모님입니다.

TK, Yuji, Bruno 및 Kaio는 부모님 (나와 형제)의 자녀입니다.

조직의 구조는 계층의 또 다른 예입니다.

회사의 구조는 계층의 예입니다

HTML에서 DOM (Document Object Model)은 트리로 작동합니다.

문서 객체 모델 (DOM)

HTML 태그에는 다른 태그가 포함되어 있습니다. 헤드 태그와 바디 태그가 있습니다. 이러한 태그에는 특정 요소가 포함되어 있습니다. 헤드 태그에는 메타 및 제목 태그가 있습니다. body 태그에는 사용자 인터페이스에 표시되는 요소 (예 : h1, a, li 등)가 있습니다.

기술적 정의

트리는 노드라는 엔티티의 모음입니다. 노드는 가장자리로 연결됩니다. 각 노드에는 값 또는 데이터가 포함되며 자식 노드가 있거나 없을 수 있습니다.

트리의 첫 번째 노드를 루트라고합니다. 이 루트 노드가 다른 노드에 의해 연결된 경우 루트는 상위 노드이고 연결된 노드는 하위입니다.

모든 트리 노드는 에지라고하는 링크로 연결됩니다. 노드 사이의 관계를 관리하기 때문에 트리의 중요한 부분입니다.

잎은 나무의 마지막 노드입니다. 자식이없는 노드입니다. 진짜 나무처럼 뿌리, 가지, 그리고 마지막으로 나뭇잎이 있습니다.

이해해야 할 다른 중요한 개념은 높이와 깊이입니다.

나무의 높이는 잎까지 가장 긴 경로의 길이입니다.

노드의 깊이는 루트까지의 경로 길이입니다.

용어 요약

  • 루트는 트리의 최상위 노드입니다
  • 가장자리는 두 노드 사이의 링크입니다
  • 자식은 부모 노드가있는 노드입니다
  • 부모는 자식 노드에 가장자리가있는 노드입니다
  • 잎은 트리에 자식 노드가없는 노드입니다
  • 높이는 잎까지 가장 긴 경로의 길이입니다
  • 깊이는 루트까지의 경로 길이입니다

이진 나무

이제 특정 유형의 트리에 대해 설명하겠습니다. 우리는 이것을 이진 트리라고 부릅니다.

"컴퓨터 과학에서 이진 트리는 각 노드에 최대 두 명의 자식이있는 트리 데이터 구조로 왼쪽 자식과 오른쪽 자식이라고합니다."— Wikipedia

이진 트리의 예를 살펴 보겠습니다.

이진 트리를 코딩하자

이진 트리를 구현할 때 가장 먼저 염두에 두어야 할 것은 노드 모음이라는 것입니다. 각 노드에는 value, left_child 및 right_child의 세 가지 속성이 있습니다.

이 세 가지 속성으로 초기화되는 간단한 이진 트리를 어떻게 구현합니까?

한 번 보자.

여기있어. 이진 트리 클래스.

객체를 인스턴스화 할 때 값 (노드의 데이터)을 매개 변수로 전달합니다. left_child와 right_child를보십시오. 둘 다 없음으로 설정되어 있습니다.

왜?

노드를 만들 때 자식이 없기 때문에 노드 데이터 만 있습니다.

테스트 해 봅시다 :

그게 다야

이진 트리 노드에 값으로 문자열 'a'를 전달할 수 있습니다. left_child 및 right_child 값을 인쇄하면 값을 볼 수 있습니다.

삽입 부분으로갑니다. 여기서 무엇을해야합니까?

오른쪽과 왼쪽에 새 노드를 삽입하는 방법을 구현합니다.

규칙은 다음과 같습니다.

  • 현재 노드에 왼쪽 자식이 없으면 새 노드를 만들어 현재 노드의 left_child로 설정하면됩니다.
  • 왼쪽 자식이 있으면 새 노드를 만들어 현재 왼쪽 자식 위치에 넣습니다. 이 왼쪽 자식 노드를 새 노드의 왼쪽 자식에 할당하십시오.

뽑아 봅시다. :)

코드는 다음과 같습니다.

다시 말하지만 현재 노드에 왼쪽 자식이 없으면 새 노드를 만들어 현재 노드의 left_child로 설정하면됩니다. 그렇지 않으면 새 노드를 만들어 현재의 왼쪽 자식 위치에 넣습니다. 이 왼쪽 자식 노드를 새 노드의 왼쪽 자식에 할당하십시오.

그리고 올바른 자식 노드를 삽입하기 위해 동일한 작업을 수행합니다.

끝난. :)

그러나 전부는 아닙니다. 우리는 여전히 그것을 테스트해야합니다.

다음과 같은 트리를 만들어 봅시다 :

이 트리의 그림을 요약하면 다음과 같습니다.

  • 노드는 이진 트리의 루트가됩니다
  • 왼쪽 자식은 b 노드입니다
  • 올바른 아이는 c 노드
  • b 오른쪽 자식은 d 노드입니다 (b 노드에는 왼쪽 자식이 없습니다)
  • c 왼쪽 자식은 e 노드입니다
  • c 오른쪽 자식은 f 노드입니다
  • e와 f 노드 모두 자식이 없습니다

트리 코드는 다음과 같습니다.

삽입이 완료되었습니다.

이제 나무 순회에 대해 생각해야합니다.

여기에는 깊이 우선 검색 (DFS)과 너비 우선 검색 (BFS)의 두 가지 옵션이 있습니다.

  • DFS는“트리 데이터 구조를 탐색하거나 검색하기위한 알고리즘입니다. 하나는 루트에서 시작하여 역 추적하기 전에 각 지점을 따라 가능한 한 멀리 탐색합니다.”— Wikipedia
  • BFS는“트리 데이터 구조를 탐색하거나 검색하기위한 알고리즘입니다. 나무 루트에서 시작하여 다음 수준의 이웃으로 이동하기 전에 먼저 이웃 노드를 탐색합니다.”— Wikipedia

각 트리 탐색 유형에 대해 알아 보겠습니다.

깊이 우선 검색 (DFS)

DFS는 역 추적하고 다른 경로를 탐색하기 전에 리프까지가는 경로를 탐색합니다. 이러한 유형의 순회를 예로 들어 보겠습니다.

이 알고리즘의 결과는 1–2–3–4–5–6–7입니다.

왜?

세분화합시다.

  1. 루트에서 시작하십시오 (1). 인쇄하십시오.

2. 왼쪽 아이에게갑니다 (2). 인쇄하십시오.

3. 그런 다음 왼쪽 아이 (3)로갑니다. 인쇄하십시오. (이 노드에는 자식이 없습니다)

4. 후퇴하고 올바른 자녀를 찾으십시오 (4). 인쇄하십시오. (이 노드에는 자식이 없습니다)

5. 루트 노드로 되돌아 가서 오른쪽 자식 (5)으로갑니다. 인쇄하십시오.

6. 왼쪽 아이에게 가십시오 (6). 인쇄하십시오. (이 노드에는 자식이 없습니다)

7. 역 추적하고 올바른 자녀 (7)로갑니다. 인쇄하십시오. (이 노드에는 자식이 없습니다)

8. 완료.

리프와 백 트랙으로 깊숙이 들어가면 이것을 DFS 알고리즘이라고합니다.

이제이 순회 알고리즘에 익숙해 졌으므로 DFS 유형 (사전 주문, 주문 및 주문)에 대해 논의 할 것입니다.

선주문

이것이 바로 위 예제에서 한 것입니다.

  1. 노드의 값을 인쇄하십시오.
  2. 왼쪽 자식으로 가서 인쇄하십시오. 이것은 왼쪽 자녀가있는 경우에만 해당됩니다.
  3. 올바른 자녀에게 가서 인쇄하십시오. 이것은 올바른 자녀가있는 경우에만 해당됩니다.

순서대로

이 트리 예제의 순서 알고리즘의 결과는 3–2–4–1–6–5–7입니다.

왼쪽, 중간, 오른쪽이 마지막입니다.

이제 코딩하겠습니다.

  1. 왼쪽 자식으로 가서 인쇄하십시오. 이것은 왼쪽 자녀가있는 경우에만 해당됩니다.
  2. 노드의 값을 인쇄
  3. 올바른 자녀에게 가서 인쇄하십시오. 이것은 올바른 자녀가있는 경우에만 해당됩니다.

주문 후

이 트리 예에 대한 주문 후 알고리즘의 결과는 3–4–2–6–7–5–1입니다.

왼쪽에서 첫 번째, 오른쪽에서 두 번째, 중간이 마지막입니다.

이것을 코딩합시다.

  1. 왼쪽 자식으로 가서 인쇄하십시오. 이것은 왼쪽 자녀가있는 경우에만 해당됩니다.
  2. 올바른 자녀에게 가서 인쇄하십시오. 이것은 올바른 자녀가있는 경우에만 해당됩니다.
  3. 노드의 값을 인쇄

너비 우선 검색 (BFS)

BFS 알고리즘은 레벨별로, 깊이별로 트리 수준을 통과합니다.

이 알고리즘을 더 잘 설명하는 데 도움이되는 예는 다음과 같습니다.

그래서 우리는 레벨별로 이동합니다. 이 예에서 결과는 1–2–5–3–4–6–7입니다.

  • 레벨 / 깊이 0 : 값이 1 인 노드 만
  • 레벨 / 깊이 1 : 값이 2 및 5 인 노드
  • 레벨 / 깊이 2 : 값이 3, 4, 6 및 7 인 노드

이제 코딩하겠습니다.

BFS 알고리즘을 구현하기 위해 큐 데이터 구조를 사용하여 도움을줍니다.

어떻게 작동합니까?

여기에 설명이 있습니다.

  1. 먼저 put 메소드를 사용하여 루트 노드를 큐에 추가하십시오.
  2. 큐가 비어 있지 않은 동안 반복하십시오.
  3. 대기열에서 첫 번째 노드를 가져온 다음 해당 값을 인쇄하십시오.
  4. 왼쪽 및 오른쪽 하위를 모두 큐에 추가하십시오 (현재 노드에 하위가있는 경우).
  5. 끝난. 큐 헬퍼를 사용하여 레벨별로 각 노드의 값을 인쇄합니다.

이진 검색 트리

“이진 검색 트리는 때때로 정렬 또는 정렬 된 이진 트리라고하며 값을 정렬 된 순서로 유지하므로 조회 및 기타 작업에서 이진 검색의 원리를 사용할 수 있습니다”— Wikipedia

이진 검색 트리의 중요한 속성은 이진 검색 트리 노드의 값이 왼쪽 자식의 자손 값보다 크지 만 오른쪽 자식의 자손 값보다 작다는 것입니다.”

위의 그림은 다음과 같습니다.

  • A가 반전됩니다. 하위 트리 7–5–8–6은 오른쪽에 있어야하고 하위 트리 2–1–3은 왼쪽에 있어야합니다.
  • B가 유일한 올바른 옵션입니다. 이진 검색 트리 속성을 충족시킵니다.
  • C에는 하나의 문제가 있습니다. 값이 4 인 노드는 5보다 작기 때문에 루트의 왼쪽에 있어야합니다.

이진 검색 트리를 코딩하겠습니다!

이제 코딩 할 차례입니다!

여기서 무엇을 볼 수 있습니까? 새 노드를 삽입하고 값을 검색하고 노드를 삭제하고 트리의 균형을 조정합니다.

시작하자.

삽입 : 트리에 새 노드 추가

빈 트리가 있고 50, 76, 21, 4, 32, 100, 64, 52의 순서로 새 노드를 추가한다고 가정합니다.

가장 먼저 알아야 할 것은 50이 나무의 뿌리인지입니다.

이제 노드별로 삽입을 시작할 수 있습니다.

  • 76이 50보다 크므로 오른쪽에 76을 삽입하십시오.
  • 21이 50보다 작으므로 왼쪽에 21을 삽입하십시오.
  • 값이 50 인 노드에는 왼쪽 하위 21이 있습니다. 4가 21보다 작으므로이 노드의 왼쪽에 삽입하십시오.
  • 32는 50보다 작습니다. 값이 50 인 노드에는 왼쪽 하위 21이 있습니다. 32가 21보다 크므로이 노드의 오른쪽에 32를 삽입하십시오.
  • 값이 50 인 노드에는 오른쪽 하위 76이 있습니다. 100이 76보다 크므로이 노드의 오른쪽에 100을 삽입하십시오.
  • 값이 50 인 노드에는 오른쪽 하위 76이 있습니다. 64가 76보다 작으므로이 노드의 왼쪽에 64를 삽입하십시오.
  • 값이 50 인 노드는 오른쪽 하위 76을 갖습니다. 52가 76보다 작으므로 값 76을 갖는 노드는 왼쪽 하위 64를 갖습니다. 52는 64보다 작으므로이 노드의 왼쪽에 54를 삽입하십시오.

여기에 패턴이 있습니까?

세분화합시다.

  1. 새 노드 값이 현재 노드보다 크거나 작습니까?
  2. 새 노드의 값이 현재 노드보다 큰 경우 오른쪽 하위 트리로 이동하십시오. 현재 노드에 올바른 자식이 없으면 거기에 삽입하거나 1 단계로 되돌아갑니다.
  3. 새 노드의 값이 현재 노드보다 작 으면 왼쪽 하위 트리로 이동하십시오. 현재 노드에 왼쪽 자식이 없으면 거기에 삽입하거나 1 단계로 되돌아갑니다.
  4. 여기서는 특별한 경우를 다루지 않았습니다. 새 노드의 값이 노드의 현재 값과 같으면 규칙 번호 3을 사용하십시오. 서브 트리의 왼쪽에 동일한 값을 삽입하십시오.

이제 코딩하겠습니다.

매우 간단 해 보입니다.

이 알고리즘의 강력한 부분은 9 번째 줄과 13 번째 줄에있는 재귀 부분입니다. 두 코드 줄 모두 insert_node 메서드를 호출하고 각각 왼쪽 및 오른쪽 자식에 사용합니다. 11 행과 15 행은 각 아동에 대해 삽입을하는 것입니다.

노드 값을 검색합시다…

우리가 지금 만들 알고리즘은 검색을하는 것입니다. 주어진 값 (정수)에 대해서는 이진 검색 트리에 해당 값이 있는지 없는지를 말할 것입니다.

주목해야 할 중요한 항목은 트리 삽입 알고리즘을 정의한 방법입니다. 먼저 루트 노드가 있습니다. 모든 왼쪽 하위 트리 노드는 루트 노드보다 작은 값을 갖습니다. 그리고 모든 오른쪽 하위 트리 노드는 루트 노드보다 큰 값을 갖습니다.

예를 살펴 보겠습니다.

이 나무가 있다고 상상해보십시오.

이제 값 52를 기반으로 노드가 있는지 알고 싶습니다.

세분화합시다.

  1. 루트 노드를 현재 노드로 시작합니다. 주어진 값이 현재 노드 값보다 작습니까? 그렇다면 왼쪽 하위 트리에서 검색합니다.
  2. 주어진 값이 현재 노드 값보다 큽니까? 그렇다면 오른쪽 하위 트리에서 검색합니다.
  3. 규칙 # 1과 # 2가 모두 거짓이면 현재 노드 값과 주어진 값이 같은 경우 비교할 수 있습니다. 비교가 참이면 우리는“그렇습니다! 우리의 나무는 주어진 가치를 지니고 있습니다. 그렇지 않으면“아뇨, 그렇지 않습니다”라고 말합니다.

이제 코딩하겠습니다.

코드를 부리 보자.

  • 8 행과 9 행은 규칙 # 1에 속합니다.
  • 10 행과 11 행은 규칙 # 2에 속합니다.
  • 13 번 줄은 규칙 # 3에 속합니다.

우리는 그것을 어떻게 테스트합니까?

값 15로 루트 노드를 초기화하여 이진 검색 트리를 만들어 봅시다.

이제 많은 새로운 노드를 삽입 할 것입니다.

삽입 된 각 노드마다 find_node 메소드가 실제로 작동하는지 테스트합니다.

예, 주어진 값에 작동합니다! 이진 검색 트리에 존재하지 않는 값을 테스트 해 봅시다.

오 예.

검색이 완료되었습니다.

삭제 : 제거 및 구성

삭제는 다른 경우를 처리해야하므로 더 복잡한 알고리즘입니다. 주어진 값에 대해이 값으로 노드를 제거해야합니다. 이 노드에 대해 다음 시나리오를 상상해보십시오. 하위 노드가 없거나 단일 하위 또는 두 개의 하위가 있습니다.

  • 시나리오 # 1 : 하위가없는 노드 (리프 노드).

삭제하려는 노드에 자식이 없으면 간단히 삭제합니다. 알고리즘은 트리를 재구성 할 필요가 없습니다.

  • 시나리오 # 2 : 하나의 자식 (왼쪽 또는 오른쪽 자식) 만있는 노드.

이 경우 알고리즘은 노드의 부모가 자식 노드를 가리 키도록해야합니다. 노드가 왼쪽 자식 인 경우 왼쪽 자식의 부모가 자식을 가리 키도록합니다. 노드가 부모의 오른쪽 자식이면 오른쪽 자식의 부모가 자식을 가리 킵니다.

  • 시나리오 # 3 : 두 자녀가있는 노드.

노드에 자식이 2 개 있으면 노드의 오른쪽 자식부터 최소값을 가진 노드를 찾아야합니다. 제거하려는 노드 대신이 노드를 최소값으로 설정합니다.

코딩 할 시간입니다.

  1. 첫째 : 매개 변수 값과 부모를 기록하십시오. 이 값을 가진 노드를 찾고 싶습니다. 노드의 부모는 노드를 제거하는 데 중요합니다.
  2. 둘째 : 반환 값을 기록해 둡니다. 우리의 알고리즘은 부울 값을 반환합니다. 노드를 찾아서 제거하면 True를 리턴합니다. 그렇지 않으면 False를 반환합니다.
  3. 2 행에서 9 행까지 : 찾고있는 값을 가진 노드를 검색하기 시작합니다. 값이 현재 nodevalue보다 작 으면 재귀 적으로 왼쪽 하위 트리로 이동합니다 (현재 노드에 왼쪽 자식이있는 경우에만). 값이 크면 재귀 적으로 오른쪽 하위 트리로 이동하십시오.
  4. 10 행 : 제거 알고리즘에 대해 생각하기 시작합니다.
  5. 11 번째 줄부터 13 번째 줄까지 : 우리는 자식이없는 노드를 덮으며 부모의 왼쪽 자식입니다. 부모의 왼쪽 자식을 없음으로 설정하여 노드를 제거합니다.
  6. 14, 15 행 : 자녀가없는 노드를 다루며 부모로부터 올바른 자녀입니다. 부모의 오른쪽 자식을 없음으로 설정하여 노드를 제거합니다.
  7. 클리어 노드 방법 : 아래에 clear_node 코드를 보여 드리겠습니다. left child, right child 및 해당 값을 None으로 설정합니다.
  8. 16 번째 줄에서 18 번째 줄까지 : 우리는 단지 하나의 자식 (왼쪽 자식)으로 노드를 덮으며 부모의 왼쪽 자식입니다. 부모의 왼쪽 자식을 노드의 왼쪽 자식 (만있는 자식)으로 설정합니다.
  9. 19 행에서 21 행까지 : 우리는 단지 하나의 자식 (왼쪽 자식)으로 노드를 덮으며 부모에서 오른쪽 자식입니다. 부모의 오른쪽 자식을 노드의 왼쪽 자식 (만있는 자식)으로 설정합니다.
  10. 22 번째 줄부터 24 번째 줄까지 : 우리는 단지 하나의 자식 (오른쪽 자식)으로 노드를 덮으며 부모에서 왼쪽 자식입니다. 부모의 왼쪽 자식을 노드의 오른쪽 자식 (만있는 자식)으로 설정합니다.
  11. 25 번째 줄에서 27 번째 줄까지 : 우리는 단지 하나의 자식 (오른쪽 자식)으로 노드를 덮으며 부모로부터 올바른 자식입니다. 부모의 오른쪽 자식을 노드의 오른쪽 자식으로 설정합니다 (유일한 자식).
  12. 28 행에서 30 행까지 : 왼쪽 및 오른쪽 자식으로 노드를 덮습니다. 가장 작은 값을 가진 노드 (코드는 아래에 표시됨)를 가져 와서 현재 node의 값으로 설정합니다. 가장 작은 노드를 제거하여 완료하십시오.
  13. 32 행 : 찾고있는 노드를 찾으면 True를 반환해야합니다. 11 행에서 31 행까지이 사례를 처리합니다. True 만 반환하면됩니다.
  • clear_node 메소드를 사용하려면 다음과 같이 None 값을 세 속성 모두로 설정하십시오 (value, left_child 및 right_child).
  • find_minimum_value 메소드를 사용하려면 왼쪽으로 이동하십시오. 더 이상 노드를 찾을 수 없으면 가장 작은 노드를 찾았습니다.

이제 테스트 해 봅시다.

이 트리를 사용하여 remove_node 알고리즘을 테스트합니다.

값이 8 인 노드를 제거합시다. 자식이없는 노드입니다.

이제 값이 17 인 노드를 제거하겠습니다. 자식이 하나 뿐인 노드입니다.

마지막으로 두 명의 자식이있는 노드를 제거합니다. 이것이 우리 나무의 뿌리입니다.

테스트가 완료되었습니다. :)

지금은 여기까지입니다!

우리는 여기서 많은 것을 배웠습니다.

이 고밀도 컨텐츠를 완성한 것을 축하합니다. 우리가 모르는 개념을 이해하기는 정말 어렵습니다. 하지만 했어요 :)

이것은 알고리즘과 데이터 구조를 학습하고 마스터하기위한 여정에서 한 단계 더 발전한 것입니다. 르네상스 개발자 간행물에서 나의 완전한 여정에 대한 문서를 볼 수 있습니다.

즐겁게 배우고 코딩을 계속하십시오.

이 콘텐츠가 마음에 드셨기를 바랍니다. Ko-Fi 작업 지원

내 트위터 & Github. ☺

추가 자료

  • mycodeschool의 트리 데이터 구조 소개
  • Wikipedia의 나무
  • 재능있는 Vaidehi Joshi가 나무에 쓰러지지 않는 방법
  • 나무 소개, 조나단 코헨 교수의 강의
  • 데이비드 슈미트 교수의 강의, 나무 소개
  • 빅터 아담 칙 교수의 강의, 나무 소개
  • Gayle Laakmann McDowell와 나무
  • TK의 이진 트리 구현 및 테스트
  • Coursera Course : University of California, San Diego의 데이터 구조
  • Coursera Course : University of California, San Diego의 데이터 구조 및 성능
  • Paul Programming의 이진 검색 트리 개념 및 구현
  • TK의 이진 검색 트리 구현 및 테스트
  • 위키 백과의 Tree Traversal
  • 이진 검색 트리 GeeksforGeeks에 의해 노드 알고리즘 제거
  • 이진 검색 트리 알고리즘 별 노드 알고리즘 제거
  • 제로에서 영웅으로 파이썬 배우기