Files
2020-02-12 02:12:35 +08:00

429 lines
11 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*==============================
* 集合
*
* 包含算法: 6.8、6.9、6.10、6.11
===============================*/
#include "MFSet.h"
/*
* 初始化一个由多个子集每个子集只包含单个成员构成的集合S。
*
*
*【备注】
*
* 教材中默认从控制台读取数据。
* 这里为了方便测试,避免每次运行都手动输入数据,
* 因而允许选择从预设的文件path中读取测试数据。
*
* 如果需要从控制台读取数据则path为NULL或者为空串
* 如果需要从文件中读取数据则需要在path中填写文件名信息。
*/
Status initial_mfset(MFSet* S, char* path) {
FILE* fp;
int readFromConsole; // 是否从控制台读取数据
// 如果没有文件路径信息,则从控制台读取输入
readFromConsole = path == NULL || strcmp(path, "") == 0;
if(readFromConsole) {
printf("请输入集合的元素信息...\n");
mfset(S, NULL);
} else {
// 打开文件,准备读取测试数据
fp = fopen(path, "r");
if(fp == NULL) {
return ERROR;
}
mfset(S, fp);
fclose(fp);
}
return OK;
}
/*
* 初始化集合的二元等价关系。
*/
Status initial_relation(Relation* R, char* path) {
FILE* fp;
int readFromConsole; // 是否从控制台读取数据
// 如果没有文件路径信息,则从控制台读取输入
readFromConsole = path == NULL || strcmp(path, "") == 0;
if(readFromConsole) {
printf("请输入集合的二元关系对...\n");
relation(R, NULL);
} else {
// 打开文件,准备读取测试数据
fp = fopen(path, "r");
if(fp == NULL) {
return ERROR;
}
relation(R, fp);
fclose(fp);
}
return OK;
}
/*
* ████████ 算法6.9 ████████
*
* 求两个互不相交的子集Si和子集Sj的并集。
*/
Status merge_mfset(MFSet* S, int i, int j) {
// 索引越界
if(i < 1 || i > S->n || j < 1 || j > S->n) {
return ERROR;
}
/*
* 相同的子集无需合并,直接返回
* 注:教材中没有此步,需要修正
*/
if(i == j) {
return OK;
}
S->nodes[i].parent = j;
return OK;
}
/*
* ████████ 算法6.8 ████████
*
* 在集合S中查找元素i当前所属的子集。
*/
int find_mfset(MFSet S, int i) {
int j;
// 索引越界
if(i < 1 || i > S.n) {
return -1;
}
// 查找i所属的子集的根结点
for(j = i; S.nodes[j].parent > 0; j = S.nodes[j].parent) {
// 从下往上查找
}
return j;
}
/*
* ████████ 算法6.10 ████████
*
* 求两个互不相交的子集Si和子集Sj的并集。
*
* 该算法是【算法6.9】的改进,改进之处在于:
* 1.子集根结点的绝对值为该子集元素的数量
* 2.坚持将规模小的集合合并到规模大的集合上
*/
Status mix_mfset(MFSet* S, int i, int j) {
// 索引越界
if(i < 1 || i > S->n || j < 1 || j > S->n) {
return ERROR;
}
/*
* 相同的子集无需合并,直接返回
* 注:教材中没有此步,需要修正
*/
if(i == j) {
return OK;
}
// Si所含成员数比sj少根结点的parent域是负数所以元素少的其值反而大
if(S->nodes[i].parent > S->nodes[j].parent) {
S->nodes[j].parent += S->nodes[i].parent;
S->nodes[i].parent = j;
} else {
S->nodes[i].parent += S->nodes[j].parent;
S->nodes[j].parent = i;
}
return OK;
}
/*
* ████████ 算法6.11 ████████
*
* 在集合S中查找元素i当前所属的子集。
*
* 该算法是【算法6.8】的改进,改进之处在于:
* 查找完成后,会对树的高度进行摊平,即尽量让元素直接挂载到子集根结点上。
*
*【注】
* 该操作会对集合造成改动
*/
int fix_mfset(MFSet* S, int i) {
int j, k, t;
// 索引越界
if(i < 1 || i > S->n) {
return -1;
}
// 查找i所属的子集的根结点
for(j = i; S->nodes[j].parent > 0; j = S->nodes[j].parent) {
// 从下往上查找
}
// 顺着结点i到其所在子集的根结点将路过的元素直接嫁接到子集的根上相当于摊平了树的高度
for(k = i; k != j; k = t) {
t = S->nodes[k].parent;
S->nodes[k].parent = j;
}
return j;
}
/*
* 根据等价关系R来整理/建立集合S中的元素(子集)。
*
*【注】
* 该方法与bix_relation方法目的一致
* 但该方法调用的是【算法6.8】和【算法6.9】,
* 是未优化的版本。
*/
Status build_mfset(MFSet* S, Relation R) {
int k;
int i, j;
for(k = 0; k < R.n; k++) {
// 先获取子集根结点
i = find_mfset(*S, R.pairs[k].i);
j = find_mfset(*S, R.pairs[k].j);
// 对两个互不相交的子集进行归并
if(!merge_mfset(S, i, j)) {
return ERROR;
}
}
return OK;
}
/*
* 根据等价关系R来整理/建立集合S中的元素(子集)。
*
*【注】
* 该方法与build_relation方法目的一致
* 但该方法调用的是【算法6.10】和【算法6.11】,
* 是优化后的版本。
*/
Status bix_mfset(MFSet* S, Relation R) {
int k;
int i, j;
for(k = 0; k < R.n; k++) {
// 先获取子集根结点
i = fix_mfset(S, R.pairs[k].i);
j = fix_mfset(S, R.pairs[k].j);
// 对两个互不相交的子集进行归并
if(!mix_mfset(S, i, j)) {
return ERROR;
}
}
return OK;
}
/*━━━━━━━━━━━━━━━━━━━━━━ 仅限内部使用的函数 ━━━━━━━━━━━━━━━━━━━━━━*/
// 创建集合的内部函数
static void mfset(MFSet* S, FILE* fp) {
int n, i;
S->r = 0;
if(fp == NULL) {
printf("请输入元素(子集)数量:");
scanf("%d", &n);
} else {
// 录入元素数量
ReadData(fp, "%d", &n);
}
if(n <= 0) {
return;
}
S->n = n;
S->r = 1;
// 子集编号默认从1开始计数
for(i = 1; i <= n; i++) {
S->nodes[i].parent = -1;
}
}
// 创建集合二元关系的内部函数
static void relation(Relation* R, FILE* fp) {
int n, k;
R->n = 0;
if(fp == NULL) {
printf("请输入二元关系对的数量:");
scanf("%d", &n);
if(n <= 0) {
return;
}
R->n = n;
for(k = 0; k < n; k++) {
printf("请输入第 %2d 对二元关系:", k + 1);
scanf("%d%d", &(R->pairs[k].i), &(R->pairs[k].j));
}
} else {
// 录入二元关系对的数量
ReadData(fp, "%d", &n);
if(n <= 0) {
return;
}
R->n = n;
for(k = 0; k < n; k++) {
ReadData(fp, "%d%d", &(R->pairs[k].i), &(R->pairs[k].j));
}
}
}
/*━━━━━━━━━━━━━━━━━━━━━━ 图形化输出 ━━━━━━━━━━━━━━━━━━━━━━*/
// 以图形化形式输出当前结构
void PrintSet(MFSet S) {
typedef struct Elem {
int x;
struct Elem* next;
} Elem;
int count; // 统计S中的子集数量
int k, i, cur;
Elem* set;
Elem* p;
if(S.n == 0) {
printf("\n");
return;
}
// 初次遍历,先确定子集数量
for(k = 1, count = 0; k <= S.n; k++) {
if(S.nodes[k].parent < 0) {
count++;
}
}
set = (Elem*) malloc((count + 1) * sizeof(Elem));
memset(set, 0, (count + 1) * sizeof(Elem));
// 0号单元的数据域存储子集数量
set[0].x = count;
// 再次遍历,确定每个子集下包含哪些元素
for(k = 1; k <= S.n; k++) {
// 遇到了子集根结点
if(S.nodes[k].parent < 0) {
// 在set中查找根结点位置如果找不到则定位到插入位置
for(i = 1; i <= set[0].x && set[i].x != 0; i++) {
if(set[i].next->x == k) {
break;
}
}
// 未找到该根结点,需要插入
if(set[i].x == 0) {
set[i].x++; // 0号单元依然计数
// 记录根结点信息
set[i].next = (Elem*) malloc(sizeof(Elem));
set[i].next->x = k;
set[i].next->next = NULL;
}
// 遇到子集中其他元素
} else {
for(cur = k; S.nodes[cur].parent > 0; cur = S.nodes[cur].parent) {
// 查找其所属的子集一定可以找到某个根结点cur
}
// 在set中查找根结点位置如果找不到则定位到插入位置
for(i = 1; i <= set[0].x && set[i].x != 0; i++) {
if(set[i].next->x == cur) {
break;
}
}
// 未找到该根结点,需要插入
if(set[i].x == 0) {
set[i].x++; // 0号单元依然计数
// 记录根结点信息
set[i].next = (Elem*) malloc(sizeof(Elem));
set[i].next->x = cur;
set[i].next->next = NULL;
}
// 插入当前结点到最尾部
set[i].x++;
for(p = set[i].next; p->next != NULL; p = p->next) {
// 查找链表尾部
}
p->next = (Elem*) malloc(sizeof(Elem));
p->next->x = k;
p->next->next = NULL;
}
}
printf("集合中共有 %d 个子集:\n", set[0].x);
for(i = 1; i <= set[0].x; i++) {
printf("子集 %2d 中共有 %2d 个元素:", set[i].next->x, set[i].x);
for(p = set[i].next; p != NULL; p = p->next) {
printf("%2d ", p->x);
}
printf("\n");
}
printf("存储结构:\n");
PrintFramework(S);
}
// 图形化输出树的排列结构,仅限内部测试使用
static void PrintFramework(MFSet S) {
int k;
if(S.n == 0) {
printf("\n");
return;
}
printf("+----------+\n");
printf("| i | p |\n");
printf("+----------+\n");
for(k = 1; k <= S.n; k++) {
printf("| %2d | %2d |\n", k, S.nodes[k].parent);
}
printf("+----------+\n");
}