Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5월 17일] 트리 #13

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions [5월 17일] 트리/1068.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// [1068] 트리
// -> 틀린 코드인데요.. 대략적으로 이 아이디어 자체는 맞을까요..?
// 힌트 주시면 수정하겠습니다!

#include <iostream>
#include <map>
#include <vector>
#include <set>

using namespace std;

// 트리를 맵으로 생성 : 각각 부모노드 번호, set에 자식 노드 담을 용도
map<int, set<int>> tree;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p2. map도 부모 노드에 바로 접근할 수 있어 충분히 좋은 STL이지만, vector보다 메모리를 많이 차지하기 때문에 key값이 사람의 이름처럼 int형이 아닐 때 등에 사용하는 것이 좋습니다! 그리고, 입력에서 이미 '0번 노드부터 N-1번 노드'까지 순서대로 중복 없이 주어지기 때문에 set으로 저장해주지 않아도 됩니다! 반대로, set은 순서대로, 중복 검사가 필요할 때 써주시면 좋겠네요🙂! 또한, set의 검색은 O(logn) 의 시간복잡도를 가지기에 O(1)인 vector를 사용해서 구현해주실 수 있을 것 같습니다!
따라서, 이 문제는 각각 부모노드 번호가 int형이고 노드 번호가 중복 없이 정렬되어 있기 때문에 2차원 vector로 트리를 구현해주실 수 있을 것 같습니다 🤗🤗



// 노드 삭제하기 (트리, 삭제할 노드 번호)
void deleteNode(map<int, set<int>> tree, int del) {
vector<int> check;

// 자식 노드가 없을 때 리턴
if (tree[del].size() == 0) {
return;
}

// 삭제할 노드의 자식노드들 번호를 check 벡터에 넣어준다
for (auto it = tree[del].begin(); it != tree[del].end(); it++) {
check.push_back(*it);
}

// 노드 삭제
tree.erase(del);

// 삭제한 노드의 자식 노드들도 좌르륵 삭제하기..
for (int i = 0; i < check.size(); i++) {
deleteNode(tree, check[i]);
}
}


// 리프노드 개수 구하는 함수

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리프 노드는 자식의 개수가 0인 노드 즉, 트리의 끝이죠! 트리의 끝까지 탐색하기 위해선 어떤 알고리즘이 있었죠? 바로 한 노드에서 연결된 모든 노드를 탐색하는 dfs&bfs가 있었죠! 특히, dfs 탐색은 특정 루트가 정해지면 편하게 구할 수 있기 때문에, 리프 노드의 개수를 구할 땐 특정 노드(이 문제에선 부모가 -1인 루트)를 루트로 정하여 dfs로 구현해주시면 됩니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dfs 함수 작성은 어찌저찌 된 것 같은데 리프 노드 개수를 어떻게 세면 좋을지 모르겠습니다.. 아니면 아예 dfs 함수가 틀린 것 같기도 합니다..😭

int leafNode(map<int, set<int>> tree) {
int cnt = 0;

// 맵 순회하면서 자식노드가 0개이면 cnt를 1 늘려주는 방식으로
for (auto it = tree.begin(); it != tree.end(); it++) {
if (it->second.size() == 0) {
cnt++;
}
}

return cnt;
}

int main() {
int n, node, del;

cin >> n;

// 입력 받기
for (int i = 0; i < n; i++) {
cin >> node;

// i번쨰 노드에 부모노드가 없으면 그대로 냅둔다..(?)
if (node == -1) {
continue;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p1. 부모노드가 없으면 그게 바로 트리의 루트인 거죠! 이 문제에서의 함정은 루트 노드가 항상 0은 아니라는 것이에요! 루트부터 시작하여 제거된 노드가 어디 있는지 탐색해야 하기 때문에 변수에 저장해두는 게 좋을 것 같아요!

// 부모 노드 없으면 트리 만들어주기..
tree[node].insert(i);
}

cin >> del;
deleteNode(tree, del);

cout << leafNode(tree);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

접근 아이디어 너무 좋아요!!
다만, deleteNode()를 해줄 필요가 있을까요?? leafNode() 내에서 리프노드 개수를 구할 때, 모~든 노드를 탐색할 필요는 없을 것 같지 않나요?? 🤗🤗 제거할 노드의 자식들을 탐색해줄 필요는 없을 것 같아요! 적절한 조건문을 넣어주신다면 더 간단하게 작성해주실 수 있을 것 같습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dfs 함수를 작성하는 부분하고 리프노드 개수를 구하는 부분이 아직 어렵네요ㅠㅠ 제거할 노드의 자식까지 탐색하지 않아도 된다는 조건문을 넣어보려 했는데 코드가 더 복잡해진 것 같습니다... 하하..🥲 어떤 방법일까요 ㅜㅜ


return 0;
}
61 changes: 61 additions & 0 deletions [5월 17일] 트리/5639.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

// [5639] 이진 검색 트리
// -> 틀린 코드입니다 ..
// -> 내용 질문이 좀 많은데 주석 읽어주시면 감사하겠습니다 !! :)

#include <iostream>
#include <map>
using namespace std;

// 루트 노드, left, right값
map<int, pair<int, int>> tree;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p1. 이렇게 된다면 left나 right에 연결된 자식 노드가 없을 때 어떻게 처리해주어야 할 지 애매해질 것 같아요🥲 물론 구현은 할 수 있습니다! 그렇지만 구조체로 해주는 건 어떨까요? 왼쪽, 오른쪽 연결은 포인터 변수인 주소로 연결을 해주는거죠! 구조체를 추천드리는 이유는 밑에 나옵니다!🤗🤗



// 이게.. 맞나요..?
void postorder(int v) {

// '정수형 = NULL' 이렇게 쓸 수 있는 건가요..?
if (v == NULL) {
return;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

쓸 수는 있는데, 보통 NULL은 0 주소를 의미하기 때문에 포인터 변수를 초기화할 때 쓰입니다.
int a = NULL : a 변수에 주소값 0(NULL) 을 넣는다.
int a = 0 : a 변수에 정수 0을 넣는다는 뜻 으로 의미는 같지만 정수형 변수를 초기화시에 NULL을 쓰는 일은 없지요!

그러니까, int인 v가 NULL인지 확인을 해야 하는 게 아니라 v 노드 즉, 노드의 데이터 타입인 map<int, pair<int, int>>를 참조하는 v가 null인지 확인을 해야 하는 것입니다! (이게 번거로워질 것 같아서 구조체로 선언하시는 걸 추천 드립니다!)🤗🤗

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

map 사용하는 것보다 구조체 이용하는 게 훠어얼씬 편리하네요 ㅠㅠ !! map 쓰면서도 너무 복잡하다 생각했는데.. 감사합니다 !😊

postorder(tree[v].first);
postorder(tree[v].second);
cout << v << '\n';
}


int main() {
int root, x;

// 맨 처음 입력값을 제일 부모노드라고 생각해서 먼저 하나 입력 받고
// 얘를 기준으로 크기를 비교하면서 트리를 채워나간다고 생각했습니다
cin >> root;


// 트리를 하나씩 채워나갈 때 비교해주기 위한 변수
int comp = root;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네! 맨 처음은 루트 노드로 정해주시는 접근이 맞습니다!



// 입력이 언제 종료될지 모를 때 어떻게 코드를 작성하면 좋을지 모르겠습니다..
// 다른 분이 올려주신 질문 참고해서 cin.eof() 검색해서 사용해보았는데도 아무 출력이 안 나왔습니다..
// 여기뿐만이 아니고 다른 데에서도 틀린 것 같은데 어디가 문젠지 잘 모르겠습니다..!

while (cin >> x) {
if (x == EOF) break;
Comment on lines +58 to +59

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

입력이 더 이상 주어지지 않을 때 프로그램을 종료하는 방법은 cin.eof()을 쓰는 것과 cin.eof()을 쓰지 않는 방법이 있습니다!

  • cin.eof() 쓰는 방법
    cin.eof() 은 bool타입을 가집니다! 만약 EOF(파일의 끝을 의미)을 읽게 되면 true로 바뀌게 됩니다. 그래서 보통 while (!cin.eof()) {cin >> n;} 이렇게 많이 쓰이죠! EOF는 콘솔 창에 수동으로 넣어주어야 하며, 윈도우 기준으로 ctrl+z 이 EOF 입니다!
  • cin.eof() 안 쓰는 방법
    그런데 while (cin >> x){...} 처럼 while 문의 조건문 안에 넣어버려도 됩니다! 이는 유효한 값이 들어올 때까지, ctrl+z 되기 전까지 계속 입력을 할 수 있습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아아 ctrl+z 를 입력했어야 하는군요ㅜㅜ🥲 감사합니다 !!


// 이런 식으로 트리를 생성해도 되나요..?
if (x < comp) {
tree[comp].first = x;
}
if (x > comp) {
tree[comp].second = x;
}
comp = x;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p1. 결론적으론 아닙니다! 오직 comp만 기준으로 왼쪽 오른쪽이 정해지기 때문이죠.
아래와 같은 트리가 있습니다. 현재 comp는 30 일 때, 60 이 들어온다고 합시다!

   50
  /
30

결과는 아래와 같이 나와야 하지만,

     50
    /  \
  30   60

현재 코드로는 오직 comp로만 비교를 했기 때문에 아래와 같이 나올 겁니다!

     50
    /
  30
     \
      60

따라서, 트리에 노드를 삽입하는 함수를 따로 작성해주셔야 합니다 🙂
삽입하려는 노드가 적절한 위치에 삽입되기 위해 트리의 루트인 root 로부터 작으면 왼쪽, 크면 오른쪽으로 내려가다가 결국엔 맨 아래에 도달해야 하기 때문에 부모노드와 연결된 자식노드를 탐색하기 위해선 dfs로 구현해주셔야겠네요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

너무 단순하게만 생각했었네요..!! 감사합니다 😀!!

}


// 함수 실행이 안 되고 있는 건지 어쩐건지 .. 뭐가 문젤까요..
postorder(root);

return 0;
}