1.题目:假设有两个表A和B,分别是m1行、n1列和m2行、n2列。它们的简单自然连接结果C=A▷◁B,其中i表示表A中的列号,j表示表B中的列号,C为A和B的笛卡尔积中满足指定连接条件的所有记录组,该连接条件为表A的第i列与表B的第j列相等。
2.数据组织:
由于每个表的行数不确定,所以采用单链表作为表的存储结构,每行作为一个数据节点,也称为行节点。另外,每行中元素的个数也是不确定的,但由于需要提供随机查找行中的数据元素,所以每行的数据元素采用顺序存储结构,这里用长度为MaxCol的数组data存储每行的数据。因此该单链表中数据节点的类型声明如下:
#define MaxCol 10 //最大列数
typedef struct Node1 //定义数据节点类型
{
ElemType data[MaxCol]; //存放一行的数据
struct Node1 *next; //指向后继数据节点
}DList; //行节点类型
另外,需要指定每个表的行数和列数,为此将单链表的头结点类型声明如下:
typedef struct Node2
{
int Row,Col; //行数和列数
DList *next; //指向第一个数据节点
}HList; //头结点类型
注意:在这里,头结点和数据节点两者的类型是不同的。
3.设计运算算法:
通过对本求解问题进行分析,发现需要设计以下4个基本运算算法。
·CreateTable(&h):采用交互方式建立单链表h。
·DestroyTable(&h):销毁单链表h。
·DispTable(h):输出单链表h。
·LinkTable(h1,h2,&h):由h1和h2连接产生结果单链表h。
1)采用交互方式建立单链表的算法
采用尾插法建表的方法创建存储一个表的单链表,用户先输入表的行数和列数,然后输入各行的数据。在采用尾插法建表时需要设置一个尾节点指针r,一般尾插法是先让r指向头结点,但这里头结点和数据节点的类型不同,且头结点只要一个,而数据节点有若干个,所以只让r指向数据节点。对应的建表算法如下:
void CreateTable(HList *&h)
{
int i,j;
DList *r,*s;
h=(HList *)malloc(sizeof(HList)); //创建头结点
printf("表的行数,列数:");
scanf("%d%d",&h->Row,&h->Col); //输入表的行数和列数
for(i=0;i<h->Row;i++) //输入所有行的数据
{
printf("第%d行:",i+1);
s=(DList *)malloc(sizeof(DList)); //创建数据节点s
for(j=0;j<h->Col;j++) //输入一行的数据
scanf("%d",&s->data[j]);
if(h->next==NULL) h->next=s; //插入第一个数据节点的情况
else r->next=s; //将s插入r节点之后
r=s; //r始终指向尾节点
}
r->next=NULL; //将尾节点的next域置空
}
显然该算法的时间复杂度为O(m×n),其中m为表的行数,n为表的列数
2)销毁单链表的算法
该算法和前面销毁单链表的算法类似,只是要针对头结点和数据节点类型不相同的情况进行相应修改。对应的算法如下:
void DestroyTable(HList *&h)
{
DList *pre=h->next,*p=pre->next; //将pre指向第一个数据节点,p指向第二个数据节点
while(p!=NULL) //while循环条件为第二个数据节点非空
{
free(pre); //释放第一个数据节点
pre=p; //将指针p,pre分别往后移一个数据节点
p=p->next;
}
free(pre); //不论是否执行循环while,均释放pre指向的数据节点
free(h); //释放头结点
}
分为两种情况:
①若只有一个行节点,则直接销毁行节点和头结点
②若有多个行节点,则先销毁第一个行节点,再将p和pre指针均向后移一个单位,不断销毁pre指向的节点,将所有行节点销毁后,再销毁头结点。
该算法的时间复杂度为O(m),其中m为表的行数。
3)输出单链表的算法
对应的输出单链表的算法如下:
void DispTable(HList *h)
{
int j;
DList *p=h->next; //p指向开始行节点
while(p!=NULL) //遍历所有行
{
for(j=0;j<h->Col;j++) //输出一行的数据
printf("%4d",p->data[j]);
printf("\n");
p=p->next; //p指向下一个行节点
}
}
该算法的时间复杂度为O(m×n),其中m为表的行数,n为表的列数。
4)表连接运算算法
为了实现两个表h1和h2的简单自然连接,先要输入两个表连接的列序号i和j,然后用p指针遍历单链表h1,对于h1的每个数据节点,都用q指针从头至尾遍历单链表h2的所有数据节点,若自然连接条件成立,即h1的p所指节点和h2的q所指节点满足连接条件p->data[i-1]=q->data[j-1],则建立一个连接节点s并添加到结果单链表h中。结果单链表h也是采用尾插法建表方法创建的。实现两个表h1和h2的简单自然连接并生成结果单链表h的算法如下:
void LinkTable(HList *h1,HList *h2,HList *&h)
{
int i,j,k;
DList *p=h->next,*q,*s,*r;
printf("连接字段是:第1个表序号,第2个表序号:");
scanf("%d%d",&i,&j);
h=(HList *)malloc(sizeof(HList)); //创建结果表头结点
h->Row=0; //置行数为0
h->Col=h1->Col+h2->Col; //置列数为表1和表2的列数和
h->next=NULL; //置next域为NULL
while(p!=NULL) //遍历表1
{
q=h2->next; //q指向表2的首节点
while(q!=NULL) //遍历表2
{
if(p->data[i-1]==q->data[j-1]) //对应字段值相等
{
s=(DList *)malloc(sizeof(DList)); //创建一个数据节点s
for(k=0;k<h1->Col;k++) //复制表1的当前行
s->data[k]=p->data[k];
for(k=0;k<h2->Col;k++) //复制表2的当前行
s->data[h1->Col+k]=q->data[k];
if(h->next==NULL)
h->next=s; //若插入的是第一个数据节点,将s节点插入头结点之后
else r->next=s; //将s节点插入节点r之后
r=s; //r始终指向尾节点
h->Row++; //表的行数增1
}
q=q->next; //表2后移一个节点
}
p=p->next; //表1后移一个节点
}
r->next=NULL; //表尾节点的next
}
4.设计求解程序
在设计好4个基本运算算法以后,设计以下主函数调用这些算法完成求解任务:
int main()
{
HList *h1,*h2,*h;
printf("表1:\n");
CreateTable(h1); //创建表1
printf("表2:\n");
CreateTable(h2); //创建表2
LinkTable(h1,h2,h); //连接两个表
printf("连接结果表:\n");
DispTable(h); //输出连接结果
DestroyTable(h1); //销毁单链表h1
DestroyTable(h2); //销毁单链表h2
DestroyTable(h); //销毁单链表h
return 1;
}