-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: main
Are you sure you want to change the base?
The head ref may contain hidden characters: "\uD2B8\uB9AC"
[5월 17일] 트리 #13
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
// | ||
// [1068] 트리 | ||
// -> 말씀해주신대로 수정 최대한 해보았습니다..! 알려주신대로 하니까 코드 방향은 확실히 알겠어요!! | ||
// 그래도 구현하는 데 있어서 여전히 모르는 부분이 있어서요 ㅠㅠ 흑흑 | ||
|
||
#include <iostream> | ||
#include <map> | ||
#include <vector> | ||
|
||
using namespace std; | ||
|
||
// 이차원 벡터로 -> 인덱스가 부모노드, 노드 번호는 벡터로 저장 | ||
vector<vector<bool>> tree; | ||
|
||
// 방문체크용 | ||
vector<bool> visited; | ||
|
||
// 리프노드 담는.. | ||
vector<int> leaf; | ||
|
||
// 리프노드 개수 구하는 함수 | ||
// 이 부분을 잘 모르겠습니다.. dfs탐색 부분이 아직 약한 것 같습니다..ㅜ | ||
void dfsLeafNode(int n, vector<vector<bool>> &tree, int curr, int del) { | ||
if (visited[curr]) { | ||
return; | ||
} | ||
|
||
visited[curr] = true; | ||
|
||
|
||
for (int next = 0; next < n; next++) { | ||
|
||
// 자식노드 아닌 것의 개수를 세려고 설정은 했습니다.. | ||
int cnt = 0; | ||
|
||
if (visited[next]) continue; | ||
|
||
if (next == del) { | ||
tree[curr][next] = false; | ||
} | ||
|
||
|
||
if (tree[curr][next]) { | ||
dfsLeafNode(n, tree, next, del); | ||
} | ||
// next가 curr의 자식 노드가 아니면 cnt를 하나 늘려주기..? | ||
else cnt++; | ||
|
||
// 마지막까지 자식노드가 하나도 없으면 리프노드 벡터에 넣어주려는 용도입니다.. | ||
if (next == n - 1 && cnt == n) leaf.push_back(curr); | ||
} | ||
|
||
// Q. 제거할 노드를 제외하고 탐색할 수 있는 조건문을 어떻게 추가하면 좋을지 잘 모르겠습니다..ㅜㅜ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이거 증말 어렵죠!!!!!😭 그런데 잠깐! 생각해보자면, 루트에서부터 연결된 자식 노드들을 탐색하며 내려오다가 지운 노드를 만났을 때에 지운 노드의 자식들을 탐색할 필요가 있을까요?? 지운 노드에 연결된 자식 노드들도 어차피 지워진 노드이기 때문에 탐색할 필요가 없죠! 그래서 지운 노드를 만났을 때 더 이상 나아가서 연결되어 있는 자식 노드들을 탐색하지 않도록 해주시면 됩니다!🤗🤗 이것 또한 더 이상 탐색을 하지 않는 경우이기 때문에 마지막까지 간 경우이겠죠. 이 말인 즉슨!! 지운 노드를 만나는 조건이 기저 조건이라는 거겠죠 ㅎㅎㅎ??😆 |
||
} | ||
|
||
|
||
int main() { | ||
int n, node, del; | ||
int root; | ||
|
||
cin >> n; | ||
|
||
// 0 ~ n-1까지 노드 있음, false로 방문 초기화 | ||
visited.assign(n, false); | ||
|
||
tree.assign(n, vector<bool>(n, false)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 방문확인을 위해 bool로 해주셨군요! 물론 이 방법도 충분히 구현하는 데 문제는 없을 것 같지만, 효율성과 메모리 면에서 한번 봐볼까요!
연결된 건지 아닌 지를 저장해주기 위해 5x5 꽉 채워서 false, true가 저장되겠네요! 연결된 자식들만 저장해준다면 5x5를 채우지 않고 동적으로 저장해줄 수 있지 않을까요??🙂 |
||
|
||
// 입력 받기 | ||
for (int i = 0; i < n; i++) { | ||
cin >> node; | ||
|
||
// 루트노드 root에 저장! | ||
if (node == -1) { | ||
root = i; | ||
continue; | ||
} | ||
// 부모 노드가 node이고, i를 자식으로 넣어주기 | ||
else { | ||
tree[node][i] = true; | ||
} | ||
} | ||
|
||
cin >> del; | ||
|
||
dfsLeafNode(n, tree, root, del); | ||
|
||
cout << leaf.size(); | ||
|
||
|
||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
#include <iostream> // 표준입출력 헤더 | ||
#include <vector> // 벡터 헤더 | ||
|
||
using namespace std; | ||
|
||
// subtree 개수 세기 -> 루트 번호에 대해, subtree 개수 저장 | ||
vector<int> subtree_cnt; | ||
|
||
// 루트 번호, graph 벡터를 매개변수로 | ||
int treeDp(int cur, vector<vector<int>>& graph) { | ||
if (subtree_cnt[cur] != -1) { //이미 탐색했던 상태 | ||
// 바로 현재 루트에 대해 subtree_cnt 리턴 | ||
return subtree_cnt[cur]; | ||
} | ||
|
||
//서브트리에 속한 정점의 수를 세고, 저장 | ||
subtree_cnt[cur] = 0; | ||
|
||
// 루트노드 1개는 항상 포함이므로 노드 개수 1로 지정 | ||
int node = 1; | ||
|
||
// 현재 노드에서 연결된 자식노드들에 대해 | ||
for (int i = 0; i < graph[cur].size(); i++) { | ||
// treeDp 연산 해서 node 결과에 갱신시켜주기 | ||
node += treeDp(graph[cur][i], graph); | ||
} | ||
|
||
// 현재 subtree_cnt는 node로 저장되어 리턴 | ||
return subtree_cnt[cur] = node; | ||
} | ||
|
||
/** | ||
* PPT 트리의 정점의 수 구하기 응용 | ||
* | ||
* [트리와 쿼리] | ||
* | ||
* 1. 루트에서부터 dfs 탐색 | ||
* 2. 각 노드를 루트로 하는 서브트리의 정점 수를 재귀적으로 메모리제이션하며 계산 | ||
* - 서브트리에 속한 정점의 개수를 저장하는 dp 배열의 초기화를 -1로 해주고, | ||
* dfs 탐색 시 현재 정점의 dp 값을 0으로 설정함으로써 자식 노드만 탐색할 수 있도록 함 (부모 노드에 0이 저장되어 있으므로 바로 리턴) | ||
* | ||
*/ | ||
|
||
int main() { | ||
// 입출력 속도 향상 | ||
ios::sync_with_stdio(false); | ||
cin.tie(NULL); | ||
|
||
// 정점 개수, 루트번호, 쿼리 수, u-v 간선 정보(방향x), | ||
int n, r, q, u, v, input; | ||
|
||
//입력 | ||
cin >> n >> r >> q; | ||
|
||
// 이차원벡터 선언 | ||
vector<vector<int>> graph(n + 1, vector<int>(0)); | ||
|
||
// 모든 노드들에 대해 일단 subtree에 속한 정점 개수 -1로 초기화 | ||
subtree_cnt.assign(n + 1, -1); | ||
|
||
|
||
// n-1회 반복 | ||
while (--n) { //무방향 그래프 | ||
// 간선 정보 | ||
cin >> u >> v; | ||
|
||
// 무방향이므로 각각 u,v에 대해 push_back 해줘야 | ||
graph[u].push_back(v); | ||
graph[v].push_back(u); | ||
} | ||
|
||
//연산 | ||
treeDp(r, graph); | ||
|
||
//출력 (쿼리 개수 q만큼 반복) | ||
while (q--) { | ||
// 입력 받고 | ||
cin >> input; | ||
|
||
// input을 루트 노드로 하는 subtree_cnt 개수 출력 | ||
cout << subtree_cnt[input] << '\n'; | ||
} | ||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
#include <iostream> // 표준 입출력 헤더 | ||
#include <vector> // 벡터 헤더 파일 | ||
#include <map> // 맵 헤더 파일 | ||
#include <algorithm> // 알고리즘 헤더 파일 -> min_element | ||
|
||
using namespace std; | ||
|
||
// pair형 이름 지정 | ||
typedef pair<int, int> ci; | ||
|
||
|
||
// 비교함수 (min_element용) | ||
bool cmp(const pair<int, ci>& c1, const pair<int, ci>& c2) { | ||
// 후보자들의 추천횟수가 다르면 | ||
if (c1.second.first != c2.second.first) { | ||
// 추천 횟수가 작은 걸 삭제 | ||
return c1.second.first < c2.second.first; | ||
} | ||
|
||
// 게시 시간이 더 오래된 걸 삭제 | ||
return c1.second.second < c2.second.second; | ||
} | ||
|
||
/** | ||
* [후보 추천하기] | ||
* | ||
* 1. 비어있는 사진틀이 없는 경우, 가장 추천수가 작은 학생 중 게시 시간이 오래된 학생을 삭제 | ||
* -> min_element() 함수를 활용해서 조건을 만족하는 학생 찾기 | ||
* -> min_element(x.begin(), x.end(), cmp): x 컨테이너 내에서 최솟값을 찾아주는 함수로 정렬과 비슷하게 cmp함수 써서 조건 설정 가능! | ||
* | ||
* 2. 후보 학생을 바로 찾기 위해 본 풀이는 map 컨테이너를 사용해 구현 | ||
* | ||
* !주의! 게시 시간 정보 저장 시, 후보로 올라간 가장 첫 시간을 저장. 이미 후보에 있는데 게시 시간이 갱신되지 않도록 주의. | ||
*/ | ||
|
||
int main() { | ||
// 사진틀, 추천횟수, 추천받은 학생 번호 | ||
int n, m, input; | ||
|
||
//입력 & 연산 | ||
cin >> n >> m; | ||
|
||
map<int, ci> candidate; //first: 후보 학생 번호, second: {추천 횟수, 게시 시간} | ||
|
||
// 추천횟수 m만큼 입력받기 | ||
for (int i = 0; i < m; i++) { | ||
// 추천받은 학생 누군지 | ||
cin >> input; | ||
|
||
//비어있는 사진틀이 없는 경우 | ||
// 즉 후보자들이 n명 && 방금 입력받은 후보자가 첫 게시이면 | ||
if (candidate.size() == n && candidate.find(input) == candidate.end()) { | ||
|
||
// min_element 함수 : candidate의 처음부터 끝까지 돌면서 비교함수 cmp 조건에 맞는 후보자를 삭제 | ||
candidate.erase(min_element(candidate.begin(), candidate.end(), cmp)); | ||
} | ||
//첫 게시라면 | ||
if (candidate.find(input) == candidate.end()) { | ||
// 게시 시간 저장 | ||
candidate[input].second = i; | ||
} | ||
candidate[input].first++; //추천 횟수 증가 | ||
} | ||
|
||
//출력 (후보학생 번호) | ||
for (auto iter = candidate.begin(); iter != candidate.end(); iter++) { | ||
// 반복자 이용해서 출력 | ||
cout << iter->first << ' '; | ||
} | ||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
#include <iostream> // 표준입출력 헤더 | ||
#include <vector> // 벡터 헤더 | ||
|
||
using namespace std; | ||
|
||
// pair형 선언 | ||
typedef pair<int, int> ci; | ||
|
||
ci dfs(int node, int parent, vector<vector<ci>>& graph) { | ||
int pos = node, len = 0; //지름을 구성하는 노드 중 하나, 그 노드까지의 거리 | ||
for (int i = 0; i < graph[node].size(); i++) { | ||
// 다음 노드 -> 현재 노드의 자식노드로 지정 | ||
int next_node = graph[node][i].first; | ||
// 만약 다음 노드가 매개변수로 주어진 parent이면 다음 반복 | ||
if (next_node == parent) { | ||
continue; | ||
} | ||
|
||
ci dfs_search = dfs(next_node, node, graph); //자식 노드에 대해 dfs 탐색 | ||
if (graph[node][i].second + dfs_search.second > len) { //기존 거리보다 길다면 갱신 | ||
// len 갱신 (지름 길이로), second는 가중치 | ||
len = graph[node][i].second + dfs_search.second; | ||
// pos 갱신 (dfs 탐색 결과 나온 pos로) | ||
pos = dfs_search.first; | ||
} | ||
} | ||
// 노드 하나와 거리 리턴 | ||
return { pos, len }; | ||
} | ||
|
||
/** | ||
* [트리의 지름] | ||
* | ||
* 1. 지름을 이루는 두 점은 모두 리프 노드 | ||
* 2. 임의의 한 노드에서 가장 멀리 있는 노드(리프 노드)는 지름을 이루는 노드 중 하나 | ||
* 3. 지름을 이루는 노드에서 가장 멀리 있는 노드는 지름을 이루는 다른 노드 | ||
* | ||
* 부모->자식의 방향만 저장하면 리프 노드에서 다른 리프 노드로 탐색할 수 없으므로 무방향 그래프로 저장 | ||
*/ | ||
|
||
int main() { | ||
// 노드 개수, 부모노드, 자식노드, 가중치 | ||
int n, p, c, w; | ||
|
||
//입력 | ||
cin >> n; | ||
|
||
// 그래프 벡터로 선언 | ||
vector<vector<ci>> graph(n + 1, vector<ci>(0)); | ||
|
||
// n-1회만큼 간선정보 입력받기 | ||
for (int i = 1; i < n; i++) { //무방향 그래프로 만들기 | ||
// 부모, 자식, 가중치 | ||
cin >> p >> c >> w; | ||
|
||
// 무방향 그래프이므로 p와 c가 연결되어있고 가중치가 w임을 표시 | ||
graph[p].push_back({ c, w }); | ||
graph[c].push_back({ p, w }); | ||
} | ||
|
||
//연산 | ||
ci first_node = dfs(1, -1, graph); //지름을 구성하는 노드 하나 찾기 | ||
ci second_node = dfs(first_node.first, -1, graph); //지름을 구성하는 다른 노드 찾기 | ||
|
||
//출력 (다른 노드의, 거리값) | ||
cout << second_node.second; | ||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// | ||
// [5639] 이진 검색 트리 | ||
// -> 맵 말고 구조체, 포인터 이용해서 트리 생성 !! | ||
|
||
#include <iostream> | ||
|
||
using namespace std; | ||
|
||
// 구조체 : 루트 노드, left, right값 -> 계속 노드로 연결 | ||
struct Node { | ||
int data; | ||
Node* left; | ||
Node* right; | ||
}; | ||
|
||
// 트리 만드는 함수 : 리턴형 void로 하면 까다롭.. | ||
Node* makeTree(Node* tree, int x) { | ||
// 맨 처음거 메인 함수에서 초기화했다고 이 부분 안 넣어주면 안됐음 -> left, right 노드들은 안 채워졌을 거니까! | ||
if (tree == NULL) { | ||
tree = new Node(); | ||
tree->data = x; | ||
tree->left = tree->right = NULL; | ||
} | ||
Comment on lines
+18
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🥰🥰최고에용🥰🥰!!! |
||
|
||
// 'else' if.. | ||
else if (x <= tree->data) { | ||
tree->left = makeTree(tree->left, x); | ||
} | ||
else tree->right = makeTree(tree->right, x); | ||
|
||
return tree; | ||
} | ||
|
||
// 후위순회 | ||
void postorder(Node* tree) { | ||
if (tree->left != NULL) { | ||
postorder(tree->left); | ||
} | ||
if (tree->right != NULL) { | ||
postorder(tree->right); | ||
} | ||
|
||
cout << tree->data << '\n'; | ||
} | ||
|
||
|
||
int main() { | ||
int root, x; | ||
|
||
cin >> root; | ||
|
||
// 맨 처음 초기화 -> 루트노드 설정 | ||
Node* tree = new Node(); | ||
tree->data = root; | ||
tree->left = tree->right = NULL; | ||
|
||
// ctrl + z 입력해야 ! | ||
while (cin >> x) { | ||
if (x == EOF) break; | ||
Comment on lines
+58
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 입력이 더 이상 주어지지 않을 때 프로그램을 종료하는 방법은 cin.eof()을 쓰는 것과 cin.eof()을 쓰지 않는 방법이 있습니다!
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아아 ctrl+z 를 입력했어야 하는군요ㅜㅜ🥲 감사합니다 !! |
||
|
||
// 계속 트리 갱신 | ||
tree = makeTree(tree, x); | ||
} | ||
|
||
// 후위순회 | ||
postorder(tree); | ||
|
||
return 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 노드가 리프노드인지를 확인하기 위해 자식 개수를 세어주셨군용! 맞아요! 자식 노드가 없다라는 리프노드의 특징을 잘 캐치 해주셨어요!🤗 그런데 원진님께서 써주신 대로 '마지막까지 자식노드가 하나도 없으면' 에서 알 수 있듯, 우린 탐색을 하다가 마지막에 도달했을 때 리프노드인지 확인을 해주어야 하는 거에요! 이 말인 즉슨, 리프노드인지 확인하는 조건이 dfs의 기저 조건 이라는 거 아닐까요?🤗
또한, 리프노드가 되는 경우가 하나 더 있어요! 만약 지우는 노드가 해당 부모 노드의 유일한 자식 노드였을 경우 해당 노드를 지우면 부모 노드가 리프 노드가 된다는 것을 뜻하죠! 함께 고려해주셔야 합니다 😯😯!