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

[5월 17일] 트리 #13

wants to merge 5 commits into from

Conversation

wonjinyoo
Copy link
Collaborator

@wonjinyoo wonjinyoo commented May 23, 2022

내용 & 질문

두 문제 다 일단 틀린 문제입니다..😭 힌트 주시면 수정하겠습니다! 나머지 문제는 이후에 제출하겠습니다

기존 제출했던 두 문제 중에 한 문제가 아직 해결이 안 됐는데 오늘 자정까지도 해결을 못할 것 같아서 4문제 추가제출하겠습니다..!

<기존 제출>

1068, 5639

<추가 제출>

1713, 1967, 15681, 양궁대회

Copy link

@jaeseo222 jaeseo222 left a comment

Choose a reason for hiding this comment

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

선택 0문제, 필수 0문제 확인했습니다!
접근 아이디어가 너무 좋아서 아이디어 보충+구현에 대한 피드백 남겼습니다!
좀.. 많이,.,난잡하게 드렸는데...! 혹시 설명이 필요하시거나, 수정 하셨다면 저 리뷰어로 불러주세요! 감사합니다 🥰🥰

Comment on lines 12 to 13
// 트리를 맵으로 생성 : 각각 부모노드 번호, 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로 트리를 구현해주실 수 있을 것 같습니다 🤗🤗

Comment on lines 64 to 66
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은 아니라는 것이에요! 루트부터 시작하여 제거된 노드가 어디 있는지 탐색해야 하기 때문에 변수에 저장해두는 게 좋을 것 같아요!

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

}


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

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 함수가 틀린 것 같기도 합니다..😭

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

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 를 입력했어야 하는군요ㅜㅜ🥲 감사합니다 !!

Comment on lines 30 to 36
// 맨 처음 입력값을 제일 부모노드라고 생각해서 먼저 하나 입력 받고
// 얘를 기준으로 크기를 비교하면서 트리를 채워나간다고 생각했습니다
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.

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

Comment on lines 10 to 11
// 루트 노드, 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에 연결된 자식 노드가 없을 때 어떻게 처리해주어야 할 지 애매해질 것 같아요🥲 물론 구현은 할 수 있습니다! 그렇지만 구조체로 해주는 건 어떨까요? 왼쪽, 오른쪽 연결은 포인터 변수인 주소로 연결을 해주는거죠! 구조체를 추천드리는 이유는 밑에 나옵니다!🤗🤗

Comment on lines 46 to 53
// 이런 식으로 트리를 생성해도 되나요..?
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.

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

Comment on lines 17 to 20
// '정수형 = 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 쓰면서도 너무 복잡하다 생각했는데.. 감사합니다 !😊

@wonjinyoo
Copy link
Collaborator Author

원래 제출해야 하는 자정보다 시간이 늦었습니다!ㅜㅜ 죄송합니다
게다가 1068번은 아직도 해결을 못했습니다..🥲 dfs 부분이나, 리프 노드 개수 구하는 부분 조금 힌트 주시면 오늘 자정 내로 꼭 해결하겠습니다 !!

@wonjinyoo wonjinyoo requested a review from jaeseo222 May 23, 2022 17:26
Copy link

@jaeseo222 jaeseo222 left a comment

Choose a reason for hiding this comment

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

안녕하세요!! 너무 늦게 와서 죄송합니다 😭😭!!
혼란스러우셨을 저의 설명에도 ,,,포기하지 않고 끝까지 해내시는 모습에 백번만번 감동먹는 중입니다༼;´༎ຶ ۝ ༎ຶ༽༼;´༎ຶ ۝ ༎ຶ༽ 저도 dfs 처음 봤을 때 와 닿지 않았는데, 이게 진짜 어느 순간에 아!!!! 하는 때가 오더라구요! 정말 응원합니다!! 끝까지 함께 할 거니까요오오오!!!

1068제외 선택 2문제, 필수 2문제 확인했습니다.
우리 1068 정복해봅시다!! 샘플코드 참고해주셔도 됩니다 ! 물론 참고 안 하시는 게 좋지만요!!🤗🤗
질문 있으시거나 수정하셨다면 저 리뷰어로 불러주세요! 감사합니다.🥰

Comment on lines +18 to +23
// 맨 처음거 메인 함수에서 초기화했다고 이 부분 안 넣어주면 안됐음 -> left, right 노드들은 안 채워졌을 거니까!
if (tree == NULL) {
tree = new Node();
tree->data = x;
tree->left = tree->right = NULL;
}

Choose a reason for hiding this comment

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

🥰🥰최고에용🥰🥰!!!

// 0 ~ n-1까지 노드 있음, false로 방문 초기화
visited.assign(n, false);

tree.assign(n, vector<bool>(n, false));

Choose a reason for hiding this comment

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

방문확인을 위해 bool로 해주셨군요! 물론 이 방법도 충분히 구현하는 데 문제는 없을 것 같지만, 효율성과 메모리 면에서 한번 봐볼까요!

   0 1 2 3 4
0  .
1    .
2      .
3        .
4          .

연결된 건지 아닌 지를 저장해주기 위해 5x5 꽉 채워서 false, true가 저장되겠네요! 연결된 자식들만 저장해준다면 5x5를 채우지 않고 동적으로 저장해줄 수 있지 않을까요??🙂
또한, 만약 1에 연결된 자식들이 무엇인지 탐색하기 위해선 5번을 꼭 돌면서 확인을 해야 하겠어요🥲 연결된 자식들만 저장해준다면 연결된 자식들만 확인하면 되니까 5번을 다 돌지 않아도 되지 않을까요?😆
그러니까 연결된 건지 아닌 지를 저장해주는 것이 아닌, 어떤 것이 연결되었는지를 저장해주기 위해 int형 2차원 벡터는 어떨까요~?🤗

Comment on lines 49 to 50
// 마지막까지 자식노드가 하나도 없으면 리프노드 벡터에 넣어주려는 용도입니다..
if (next == n - 1 && cnt == n) leaf.push_back(curr);

Choose a reason for hiding this comment

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

해당 노드가 리프노드인지를 확인하기 위해 자식 개수를 세어주셨군용! 맞아요! 자식 노드가 없다라는 리프노드의 특징을 잘 캐치 해주셨어요!🤗 그런데 원진님께서 써주신 대로 '마지막까지 자식노드가 하나도 없으면' 에서 알 수 있듯, 우린 탐색을 하다가 마지막에 도달했을 때 리프노드인지 확인을 해주어야 하는 거에요! 이 말인 즉슨, 리프노드인지 확인하는 조건이 dfs의 기저 조건 이라는 거 아닐까요?🤗

또한, 리프노드가 되는 경우가 하나 더 있어요! 만약 지우는 노드가 해당 부모 노드의 유일한 자식 노드였을 경우 해당 노드를 지우면 부모 노드가 리프 노드가 된다는 것을 뜻하죠! 함께 고려해주셔야 합니다 😯😯!

if (next == n - 1 && cnt == n) leaf.push_back(curr);
}

// Q. 제거할 노드를 제외하고 탐색할 수 있는 조건문을 어떻게 추가하면 좋을지 잘 모르겠습니다..ㅜㅜ

Choose a reason for hiding this comment

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

이거 증말 어렵죠!!!!!😭 그런데 잠깐! 생각해보자면, 루트에서부터 연결된 자식 노드들을 탐색하며 내려오다가 지운 노드를 만났을 때에 지운 노드의 자식들을 탐색할 필요가 있을까요?? 지운 노드에 연결된 자식 노드들도 어차피 지워진 노드이기 때문에 탐색할 필요가 없죠! 그래서 지운 노드를 만났을 때 더 이상 나아가서 연결되어 있는 자식 노드들을 탐색하지 않도록 해주시면 됩니다!🤗🤗 이것 또한 더 이상 탐색을 하지 않는 경우이기 때문에 마지막까지 간 경우이겠죠. 이 말인 즉슨!! 지운 노드를 만나는 조건기저 조건이라는 거겠죠 ㅎㅎㅎ??😆

@wonjinyoo
Copy link
Collaborator Author

피드백 정말X100 감사합니다!!🥰 저야말로 코드를 이도저도 아니게 써놔서 이를 이해하고 피드백 주시기 어려우셨을텐데.. 제가 긴가민가했던 부분이 덕분에 해결됐습니다 !! 그럼에도 구현하는 연습을 더 해야겠습니다..ㅜㅜ 어제 계속 고민하다가 도무지 답이 안 나와서 샘플 코드를 약간 보고 말았습니다..😭 스스로 또 해보겠습니다!!

튜터님 근데 질문이 하나 있습니다ㅜㅜ! (이번이 마지막 질문입니다😭) 튜터링 시간에도 그렇고 피드백 주신 부분에도 그렇고 튜터분들께서 '기저조건' 이라는 말씀을 자주 하시던데 제가 아직 이게 정확히 무슨 의민지 감이 안 와서요.. 정확히 어떤 조건을 일컫는지 여쭤봐도 될까요??

@wonjinyoo wonjinyoo requested a review from jaeseo222 May 26, 2022 02:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants