학생들의 인적사항이 주어졌을 때, 후보 키의 최대 개수를 구하라
후보 키의 조건은 다음과 같다.
유일성(uniqueness) : 릴레이션에 있는 모든 튜플에 대해 유일하게 식별되어야 한다.
최소성(minimality) : 유일성을 가진 키를 구성하는 속성(Attribute) 중 하나라도 제외하는 경우 유일성이 깨지는 것을 의미한다. 즉, 릴레이션의 모든 튜플을 유일하게 식별하는 데 꼭 필요한 속성들로만 구성되어야 한다.
먼저 후보 키의 조건을 이해해보자.
선택한 속성을 choice라는 문자열로 표현했다. ex) "023" = 학번, 전공, 학년, "0" = 학번
"0" 즉 학번을 선택하면 유일성을 만족한다. "1"은 apeach가 두 명이라서 유일성을 만족하지 않지만 "01"은 유일성을 만족한다. 하지만 "01"에서 "1"을 뺐을 때 "0"은 이미 유일성을 만족한다. 즉 "01"은 최소성을 만족하지 않는다.
"학번+이름"은 유일성은 만족하지만 "이름"을 빼고, "학번"만 사용해도 후보 키의 조건을 만족하기에 "학번+이름"은 최소가 아니라는 것이다.
후보 키를 이해했다면 풀이의 흐름은 다음과 같다.
#include <string>
#include <vector>
#include <set>
using namespace std;
int answer = 0;
int relationCnt;
int tupleCnt;
vector<vector<string>> rel;
vector<bool> visit;
set<string> uniq; // 유일성을 만족하는 choice들
bool check(string choice) {
set<string> countUniq;
// choice에 선택된 속성을 더하여 유일성 체크 ex) 첫 번째 튜플에서 choice: "01"이라면 tmp: "100ryan"
for (int i = 0; i < tupleCnt; i++) {
string tmp = "";
for (int j = 0; j < choice.size(); j++) {
int idx = choice[j] - '0';
tmp += relCopy[i][idx];
}
countUniq.insert(tmp);
}
if (countUniq.size() == tupleCnt) { // 유일성 만족
uniq.insert(choice);
if (choice.size() == 1) return true;
for (int i = 0; i < choice.size(); i++) {
string tmpChoice = choice;
tmpChoice.erase(tmpChoice.begin() + i);
if (uniq.count(tmpChoice) == 1) return false; // 최소성 만족 X
}
return true; // 최소성 만족
}
return false;
}
void comb(int cnt, int idx, int n) {
if (cnt == n) {
string choice = ""; // 선택된 인덱스 나열
for (int i = 0; i < visit.size(); i++) {
if (visit[i]) choice += to_string(i);
}
if (check(choice)) { // 유일성 최소성을 모두 만족하면
answer++;
return;
}
return;
}
for (int i = idx; i < relationCnt; i++) {
if (visit[i]) continue;
visit[i] = true;
comb(cnt + 1, i + 1, n);
visit[i] = false;
}
}
int solution(vector<vector<string>> relation) {
relationCnt = relation[0].size();
tupleCnt = relation.size();
visit.assign(relationCnt, false);
rel = relation;
for (int i = 1; i <= relationCnt; i++) {
comb(0, 0, i);
}
return answer;
}