離散實驗——關系閉包運算


【實驗目的】掌握求關系閉包的方法。

【實驗內容】編程求一個關系的閉包,要求傳遞閉包用warshall方法。

例 :A={8、6、4、2},A上的關系R={<8,6><6,8><6,4><4,2>}

結果:

自反閉包:
{ <8,8>,<8,6>,<6,8>,<6,6>,<6,4>,<4,4>,<4,2>,<2,2> }
1 1 0 0
1 1 1 0
0 0 1 1
0 0 0 1
對稱閉包:
{ <8,6>,<6,8>,<6,4>,<4,6>,<4,2>,<2,4> }
0 1 0 0
1 0 1 0
0 1 0 1
0 0 1 0
傳遞閉包:
{ <8,8>,<8,6>,<8,4>,<8,2>,<6,8>,<6,6>,<6,4>,<6,2>,<4,2> }
1 1 1 1
1 1 1 1
0 0 0 1
0 0 0 0

【源程序及解析】

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#define N 100
int n;   // 
int r[N][N];  // 關系矩陣
int c[N][N];  // 可以初始化為關系矩陣, 最后用來存放 閉包矩陣
int set[N];   // 存放 集合中的元素
void initc(int t[][N])  // 將 閉包矩陣 化成  其他的矩陣
{
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            c[i][j] = t[i][j];
}
void show()
{
    int flag = 0;
    printf("{ ");
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
        {
            if (c[i][j])  // 這個 三目運算 用的挺秀的
            {
                printf(flag ? ",<%d,%d>" : "<%d,%d>", set[i], set[j]);
                flag = 1;
            }
        }
    printf(" }\n");
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
            printf("%d ", c[i][j]);
        puts("");
    }
}
void funR()   // 求 自反閉包
{
    initc(r);
    for (int i = 0; i < n; i++)
        c[i][i] = 1;
    printf("自反閉包:\n");
    show();
}
void funS()  // 求 對稱閉包
{
    initc(r);
    for (int i = 0; i < N; i++)
    {
        for (int j = 0; j < N; j++)
        {
            if (c[i][j])
                c[j][i] = 1;
        }
    }
    printf("對稱閉包:\n");
    show();
}
void funT1(void)  // 求 傳遞閉包第一種方法
{
    initc(r);
    int b[N][N];
    for (int m = 1; m < n; m++)  // 求 矩陣的 n-1 次方
    {
        for (int i = 0; i < n; i++)  
        {
            for (int j = 0; j < n; j++)
            {
                b[i][j] = 0;
                for (int k = 0; k < n; k++)
                    b[i][j] += c[i][k] * r[k][j];  // 矩陣運算

                c[i][j] += b[i][j];   // 將求得的 矩陣的 m 次方與前面的累 並
                if (c[i][j])
                    c[i][j] = 1;
            }
        }
    }
    printf("傳遞閉包:\n");
    show();
}
void funT2(void)   // 求 傳遞閉包第二種方法
{
    initc(r);
    for (int k = 0; k < N; k++)   // 枚舉途徑 某中間頂點 k 的任兩個頂點對 i 和 j ,進而通過判斷 k->j 是否連接,來判斷 是否將 i->j 連接
    {
        for (int i = 0; i < N; i++)
            if (c[i][k])
                for (int j = 0; j < N; j++)
                {
                    /* 有兩種情況  
                       ①,若 i->j 已連接,則 k->j 是否連接 並無影響
                       ②  若 i->j 沒有連接,則 k->j 連接了,則將 i->j 連接
                                           則 k->j 沒有連接,則不必將 i->j 連接      
                    */  
                    c[i][j] = c[i][j] + c[k][j];   
                    if (c[i][j])
                        c[i][j] = 1;
                }
    }
    printf("傳遞閉包:\n");
    show();
}
int main(void)
{
    printf("請輸入集合的元素個數:");
    scanf("%d", &n);
    printf("請輸入集合內容:\n");
    for (int i = 0; i < n; i++)
    {
        scanf("%d", &set[i]);
    }

    printf("請輸入二元關系的序偶個數:");
    int lon; scanf("%d", &lon);
    printf("請輸入集合上的二元關系:\n");
    for (int i = 0; i < lon; i++)
    {
        char s1, s2;
        int a, b; scanf(" %c%d,%d %c", &s1, &a, &b, &s2);
        for (int i = 0; i < n; i++)    // 將有序對第一元素 變成 鄰接矩陣的橫坐標
            if (set[i] == a)
            {
                a = i;
                break;
            }
        for (int i = 0; i < n; i++)   // 將有序對第二元素 變成 鄰接矩陣的縱坐標
            if (set[i] == b)
            {
                b = i;
                break;
            }
        r[a][b] = 1;
    }
    puts("");
    funR();
    funS();
    funT1();
    funT2();

    system("pause");
    return 0;
}
/*
測試數據 1:
4
8 6 4 2
4
<8,6> <6,8> <6,4> <4,2>
測試數據 2:
3
1 2 3
3
<1,2> <2,3> <3,1>

*/
View Code

 

一,求自反閉包

r(R) = R U IA  (IA 為 R 上的恆等關系)

 所以只需要將 關系矩陣 的 主對角線  全部變成 1,就可以了.

void funR()   // 求 自反閉包
{
	initc(r);
	for (int i = 0; i < n; i++)
		c[i][i] = 1;
	printf("自反閉包:\n");
	show();
}

 

 

 

二,求對稱閉包

s(R) = R U R'  (這里的 R‘ 是指 R 的逆)

所以只需要在 關系矩陣 的基礎上,若 r(ij) = 1, 則 r(ji) = 1 

 

void funS()  // 求 對稱閉包
{
	initc(r);
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < N; j++)
		{
			if (c[i][j])
				c[j][i] = 1;
		}
	}
	printf("對稱閉包:\n");
	show();
}

  

三,求傳遞閉包

這里有兩種方法。

①  t(R) = R U R2 U R3 U ... U Rn  (這里 Rn 指的是 R的n次冪,n 指的是 R 的 邊數,代碼中是 點數)

所以只需要  進行 矩陣的 乘法運算 和 布爾運算 就可以了

void funT1(void)  // 求 傳遞閉包第一種方法
{
	initc(r);
	int b[N][N];
	for (int m = 1; m < n; m++)  // 求 矩陣的 n-1 次方
	{
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < n; j++)
			{
				b[i][j] = 0;
				for (int k = 0; k < n; k++)
					b[i][j] += c[i][k] * r[k][j];  // 矩陣運算

				c[i][j] += b[i][j];   // 將求得的 矩陣的 m 次方與前面的累 並
				if (c[i][j])
					c[i][j] = 1;
			}
		}
	}
	printf("傳遞閉包:\n");
	show();
}

 

② 用 warshall方法

即 枚舉途徑 某中間頂點 k 的任兩個頂點對 i 和 j ,進而通過判斷 k->j 是否連接,來判斷 是否將 i->j 連接

 然后  ① 若 i->j 已連接,則 k->j 是否連接 並無影響
          ② 若 i->j 沒有連接,則 k->j 連接了,則將 i->j 連接

                                        , 則 k->j 沒有連接,則不必將 i->j 連接

void funT2(void)   // 求 傳遞閉包第二種方法
{
	initc(r);
	for (int k = 0; k < N; k++)   // 枚舉途徑 某中間頂點 k 的任兩個頂點對 i 和 j ,進而通過判斷 k->j 是否連接,來判斷 是否將 i->j 連接
	{
		for (int i = 0; i < N; i++)
			if (c[i][k])
				for (int j = 0; j < N; j++)
				{
					/* 有兩種情況
					①,若 i->j 已連接,則 k->j 是否連接 並無影響
					②  若 i->j 沒有連接,則 k->j 連接了,則將 i->j 連接
					                    則 k->j 沒有連接,則不必將 i->j 連接
					*/
					c[i][j] = c[i][j] + c[k][j];
					if (c[i][j])
						c[i][j] = 1;
				}
	}
	printf("傳遞閉包:\n");
	show();
}

  

 

 四,補充內容

1,集合與關系矩陣的對應關系

通過 一個 set 數組,按順序對應關系矩陣.

2, 打印閉包的方法

其中,遇到的一個問題是 ,沒有算完,不知道 “,” 有幾個

所以巧妙運用 flag 進行處理

3,initc() 

因為 原先的 關系矩陣 不能 更改。所以需要一個新的 關系矩陣 來

存 閉包的關系,所以  數組c 就是 這個新的矩陣。

另外將 c  初始化為 R的關系矩陣 這樣處理比較方便,所以 initc() 就是這樣一個函數。

 

 

=========== ========= ======== ======= ====== ===== ==== === == =

誡子書  諸葛亮(三國) 

 
夫君子之行,靜以修身,儉以養德。非淡泊無以明志,非寧靜無以致遠。
夫學須靜也,才須學也,非學無以廣才,非志無以成學。淫慢則不能勵精,險躁則不能治性。
年與時馳,意與日去,遂成枯落,多不接世,悲守窮廬,將復何及!

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM