任务
数据库关系模式中函数依赖的理论涉及不少算法,此次根据课程需要将求属性闭包(closure)和函数最小依赖集(basis)用代码实现。
思路
两个算法已有理论支撑,因此第一步是设计合适的数据结构,存储算法过程需要的变量,核心如下:
- 属性
- 函数依赖集
- 闭包集
- 最小函数依赖集
数据结构
全部用数组实现存储
//属性用数组储存(存在置为1)
int attributes[MAXSIZE];
//储存依赖某一项的左部X,对应右部Y,有X->Y
int leftDependencies[MAXNUM][MAXSIZE];
//储存依赖某一项的右部Y,对应左部,有X->Y
int rightDependencies[MAXNUM][MAXSIZE];
//储存求得某函数依赖集关于某属性集的闭包结果集
int closure[MAXSIZE];
//存储获取最小依赖集
int basisDependencies[MAXNUM];
//存储设定依赖项以便得到所有可能的最小依赖集
int controlDependencies[MAXNUM];
//存储可被删去的左部单个属性
int maskAttributes[MAXNUM][MAXSIZE];
考虑到这些数据需要频繁调用,为减少传参麻烦,我采用C++ 创建对象 form 存储变量与函数。
基本数据操作
为实现灵活处理,通过命令行显示菜单能够实现:
i.initAttributes //初始化属性个数
a.addDependency //新增函数依赖
s.setClosure //设定求属性闭包所需的初始属性
f.findClosure //寻找闭包
p.printInfo //打印基本信息
b.basisDependencies //打印所有的函数最小依赖集
算法实现
求属性闭包
算法如下(课讲来源,哈尔滨工业大学慕课 数据库系统(中)战德臣 教授):
通过观察例子能更直观的了解算法实现:
直白描述
核心:属性闭包需要两个必不可少的成分——属性集和函数依赖集。
含义:属性闭包是一个属性集通过一个函数依赖集能导出的所有属性(包括这个属性集在内)
我们的算法这样进行:
基础:给定一个属性集X和一组函数依赖F
将给定的属性集直接添加到闭包内,(直接通过setClosure()
实现)
遍历存储函数依赖的左部leftDependencies
,如果某一个左部的所有属性被提供的属性集所覆盖isConsistent == true
,说明右部的属性可以被添加至闭包
递归 调用函数,直至无法增添新的属性,返回。
主体函数
void form::findClosure()
{
bool isConsistent = true;
for (int i = 0; i < dependenciesNum;i++)
{
isConsistent = true;
for (int j = 0; j <attributesNum; j++)
{
if (leftDependencies[i][j]== 1 && closure[j] != 1)
{
isConsistent =false;
break;
}
}
if (isConsistent == true)
{
for (int j = 0; j <attributesNum; j++)
{
if(rightDependencies[i][j] == 1 && closure[j] != 1)
{
cout<< "add ";
printAttribute(j);
cout<< endl;
closure[j]= 1;
findClosure();
}
}
}
}
return;
}
求函数的最小依赖
相比于属性闭包容易操作的算法,求函数的最小依赖很难通过如下算法进行设计:
最终通过这篇博文中提供的例子找到方法
直白描述
归纳出的算法如下:
对于提供的函数依赖集,简化处理过程,输入时直接按照分解律把依赖X->A1A2...An
分解为X->A1
、 X->A2
、… 、X->An
一:
将一个函数依赖X->Y
除去,将其左部属性X
传递到闭包初始集closure
中,求解左部属性关于除去此依赖后的闭包结果closure
(本函数结果集与初始集是一个数组)中是否存在右部属性Y
,若存在,证明可除去此依赖。
遍厉所有的函数依赖,则得到第一步处理的结果
//依次删除一个依赖X'->Y',寻找新依赖集中能否推出Y'
//若能,则可被删除;否则不能被删除
//判断 closure 输出结果是否Y'为1
由于遍历的次序会导致不同的函数依赖结果,我通过设计算法得到所有的可能顺序的结果,具体算法为:
- 首先得到的一个最小函数依赖集, 结果储存在
basisDependencies
中,0代表函数依赖可除去,例如{0,1,0,1,1}表示第一和第三个函数依赖可除去- 我们将最后一个可除去的依赖(
basisDependencies[j] == 0
),如例中的第三个,通过控制数组controlDependencies
将代表其位置的值置为-1(意味不可再被寻找最小函数依赖集时除去),此时再重新寻找最小函数依赖集- 重复上述操作直至最后一个可除去的依赖已经是最后一个位置(没有必要再将
controlDependencies
置为-1 ,我们转而操作倒数第二个可除去的依赖,如例中的第一个,将其置为-1,再如第2步重新寻找最小函数依赖集- 重复上述操作直至不存在倒数第二个可除去的依赖,此时获得所有可能的结果.
二:
上一步得到消除了 冗余函数依赖的所有结果, 如下图所示
但对于下面的例子得出的结果能看出还存在问题:
将上个例子中函数依赖中左部的A
替换为AB
得到的结果如下所示:
因此下一步需要除去左部中属性的冗余,思路如下:
// 依次取上一步完成找到的basisDependency的左端的属性
// 若删去一个属性后
// 能通过除该依赖以外剩下的依赖得到这个属性,则可删去
// 通过maskAttributes[][]实现
// 若maskAttributes[i][j] = -1(可被删去)
// 则该属性被删去(不被计算闭包时考虑)
// 若maskAttributes[i][j] = 1(不可被删去,被处理后证明)
// 若maskAttributes[i][j] = 0(初始值)
主体函数
void form::findBasis()
{
//依次删除一个依赖X'->Y',寻找新依赖集中能否推出Y'
//若能,则可被删除;否则不能被删除
//需要函数:获取一个依赖集所有左边attributes
//closure 输出结果是否含有Y'
bool basisFound = false;
initbasisDependencies();
for (int i = 0; i < dependenciesNum; i++)
{
int targetValue = 0;
//必须在可以除去的依赖中
if (controlDependencies[i] != 1)
{
continue;
}
//假设除去该依赖,检查结果
basisDependencies[i] = 0;
targetValue = getRightDependency(i);
sendToClosure(i);//将除删除的依赖的左边的X'传给closure
findClosure(targetValue);//将目标值传入(meaningless)
//得到的闭包中不含Y'
if (closure[targetValue] != 1)
{
basisDependencies[i] = 1;//该依赖不可除去
}
else
{
basisFound = true;
}
}
if (basisFound == true)
{
//一旦找到一个,自身不再是最小的
ownIsBasis = false;
cout << "\r";
initMaskAttributes();
cout << "after step 1:" << endl;
printBasis();
//进一步对左边的属性进行处理
secondProcess();
cout << "after step 2:" << endl;
printBasis();
}
//若仍有可改变的值,递归获取新的可能,否则return
if (changeControl() == true)
{
findBasis();
}
else
{
if (ownIsBasis == true)
{
initMaskAttributes();
cout << "after step 1:" << endl;
printBasis();
//进一步对左边的属性进行处理
secondProcess();
cout << "after step 2:" << endl;
printBasis();
}
return;
}
}
运行结果
对于博客中的示例,结果如下:
对于博客中的示例,结果如下:
皆正确。
其他
为减少命令行输入的繁琐,增加接口函数void defaultSetting(form& sets)
可以直接设定属性和函数依赖集。
不足&需要改进
- 输入繁琐:
当前需要分别输入函数依赖集的左部和右部属性集
解决方法:
可增加读取函数切割输入字符串X->Y
,用更简洁的方式读取函数依赖集 - 输入受限
在输入右部时,目前的处理手段只能处理单个属性。
解决方法:
可以通过预留的split()
函数接口将右部的多个属性直接分割为单个属性 - 最小依赖函数集输出结果未处理完全
目前会存在这样的结果:
after step 2:
basis:
B->B
B->C
B->C
C->A
C->B
解决方法:
增加函数消除完全相同的依赖函数