选择适当的数据结构存储关系矩阵,用C语言编程实现:A上的两个二元关

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了选择适当的数据结构存储关系矩阵,用C语言编程实现:A上的两个二元关相关的知识,希望对你有一定的参考价值。

选择适当的数据结构存储关系矩阵,用C语言编程实现:A上的两个二元关系的合成

参考技术A 1“ab中内容按增序排列”体现在输入上,比如A:12345就不要给自己找麻烦写成54321,不然还得排序;⑴.因为是增序,所以用指针遍历A、B链表,每次获取较小值加入C,注意重复筛选,可以在插入时多判断一下,也可以在最后遍历一次C表。⑵.以A表为主,每移动一次A表指针都要遍历一次B表至其表尾或(A->datadata)即此时B的值已经大于A。最后就是主函数中提供输入A、B表的提示语,还有3次printf。下面提供一个我写过的类似代码,忘记是什么题目了,好像就是(1)。#include#include/*链表节点定义*/typedefstructnodeintdata;structnode*next;node;node*head1,*head2,*Head;/*链表节点创建*/voidinitiatesl(node**h)*h=(node*)malloc(sizeof(node));(*h)->next=NULL;/*根据给定表长生成基础表1、2*/voidcreate_list(node*h,inti)intn=0;node*p,*q=h;for(;nnext=p;scanf("%d",&q->next->data);q=q->next;/*链表合成*/voidmerge(node*H,node*h1,node*h2)node*p=h1->next,*q=h2->next,*r=H,*s=H;for(;p!=NULL&&q!=NULL;r=r->next)initiatesl(&r->next);if(p->datadata)r->next->data=p->data;p=p->next;elseif(p->data>q->data)r->next->data=q->data;q=q->next;elser->next->data=p->data;p=p->next;q=q->next;if(p==NULL)for(;q!=NULL;q=q->next,r=r->next)initiatesl(&r->next);r->next->data=q->data;elsefor(;p!=NULL;p=p->next,r=r->next)initiatesl(&r->next);r->data=p->data;/*主函数及交互界面*/intmain(void)inta,b;node*s,*S=head1;initiatesl(&head1);/*创建三个头结点*/initiatesl(&head2);initiatesl(&Head);printf("Pleaseinputthelengthofthetwolist.(a,b)\n");/*给定长度输入样式*/scanf("%d,%d",&a,&b);printf("Inputlistone:");/*创建链表1、2*/create_list(head1,a);printf("Inputlisttwo:");create_list(head2,b);merge(Head,head1,head2);s=Head->next;printf("L:");for(;s!=NULL;s=s->next)printf("%d",s->data);printf("\n");return0;追问

大哥,确定这是我提问的问题吗…

参考技术B 大哥,这个问题解决了吗?我有同问

数据结构与算法:终于可以用三种语言(C,C#,JavaScript)把图的广度优先遍历讲清楚了(推荐收藏)


邻接矩阵存储图的广度优先遍历过程分析

对图1这样的无向图,要写成邻接矩阵,则就是下面的式子
在这里插入图片描述在这里插入图片描述一般要计算这样的问题,画成表格来处理是相当方便的事情,实际中计算机处理问题,也根本不知道所谓矩阵是什么,所以画成表格很容易帮助我们完成后面的编程任务。在我们前面介绍的内容中,有不少是借助着表格完成计算任务的,如Huffman树。

在这里插入图片描述
为了记录那些顶点是已经走过的,还要设计一个表来标记已经走过的顶点,在开始,我们假设未走过的是0,走过的是1,于是有:

在这里插入图片描述
对广度优先遍历,还需要补充一个队列、来记录一个顶点可以抵达到的其他顶点。

广度优先遍历过程如下:

在这里插入图片描述
结果分析

从上面的过程可以看出:仅仅就顶点访问到的次序而言,图1的广度优先遍历结果是:

V1->V2->V3>V4->V5->V6->7->V8

但实际执行过程中我们可以发现:所谓图的广度优先遍历、其结果应该是一个树:

在这里插入图片描述
在C语言中,显示这个结果并不容易,所以大多C语言的教材中并不会给出这样的结果。

C语言实现队列编程

根据上面的分析,我们可以知道:要广度优先遍历图,首先要一个队列系统。

队列在C语言上只能自己构造,好在我们前面有链表、有顺序表,我们可以复制过来一个链表程序构造一个队列,于是从链表程序中复制过来b5.c或者b6.c即可,我们分析队列的ADT可知,最需要的队列功能需求是:

QueueInit()、EnQueue、DeQueue()、QueueEmpty()这4个函数,于是有以下队列定义:

struct Queue 
{
	struct LinkedList * LinkQueue;
	int Count;
};

由于我们已经确定使用链表做队列,所以队列实际就是链表的换名包装,所以我们可以理解为队列就是链表的另一种应用,表3的程序就是这样的做法,所以对队列的初始化,就是:

struct Queue * QueueInit()
{
	struct Queue *q;
	q=(struct Queue *)malloc(sizeof(struct Queue));
	q->LinkQueue=LinkedListInit(); 
	q->Count=0; 
	return q;
}

有了队列的初始化,则进入队列、实际相当于给这个链表追加一条记录,就是Append()的另类包装:

int EnQueue(struct Queue *Q,struct ElemType *E)
{
	if(Q==NULL) return -1;
	if(E==NULL) return -2;
	Append(Q->LinkQueue,E);
	Q->Count++; 
	return 0;
}

注意数据出队列,出队列总是把链表中第一个结点的数据给出来、并删除第一个结点,所以出队列就是:

int DeQueue(struct Queue *Q,struct ElemType *E)
{
	struct ElemType *pE;
	if(Q==NULL) return -1;
	if(E==NULL) return -2;
	pE=LinkedListGet(Q->LinkQueue,1);
	ElemCopy(pE,E); 
	LinkedListDel(Q->LinkQueue,1); 
	Q->Count--;
	return 0;
}

出队列函数总是把第一个结点删除掉,注意队列完全可能数据出完后再次有数据进入队列,则原来的结点删除函数有Bug,这在程序开发中很正常,修改后就是:

int LinkedListDel(struct LinkedList *L,int n)
{
	int i;
	struct Node *p0,*p1;
	if(L==NULL) return -1;
	if(n<0||n>L->Count) return -2;
	p0=L->Head;
	for(i=0;i<n-1;i++) 
		p0=p0->next;
	p1=p0->next;
	p0->next=p1->next;
	free(p1);
	L->Count--;
	if(L->Count==0) L->Tail=L->Head;  
	return 0;
}

修改的这个链表结点函数、仅仅加了第14行,在过去,所以结点删除后,最后的尾巴结点指针Tail所指的存储空间被释放,导致这个指针变量不可用,现在在结点个数为0的情况下,再次让尾结点指向头结点,保证下次进入链表的数据依然正确。

而判断队列是否为空则相对简单的多,就是:

int QueueEmpty(struct Queue *Q)
{
	if(Q==NULL) return -1;
	return !(Q->Count); 
}

补充main()函数,测试多批次进入队列、出队列,全部程序见B0.c

在我们的图遍历应用中,我们对队列的数据仅仅要求一个整数即可,而这个程序进出队列的数据有三列数据,为加强该程序可靠行,修改ElemType(),就是:

void ElemCopy(struct ElemType *s,struct ElemType *d)
{
	d->sNo=s->sNo;
	//strcpy(d->sName,s->sName);
	//d->sAge=s->sAge;  
}

在一个系统中,类似这样的修改很正常,使用已有的程序完成自己的工作,会大大加快编程的进度,使得编程工作更加流畅。
而这一切都需要自己有足够的积累,有这个积累后完成这样的工作才有基础,所谓技术水平,就是不断积累的过程。

下面,在图的处理中会再次体现这样的过程。

程序中加入图的处理函数

我们的队列系统完成后,记着再复制一个文件,加入图的邻接矩阵读数据程序,我们这里这个程序名称是b1.c。对邻接矩阵数据的读取、并构造图的过程,在深度优先遍历程序中已完成,所以直接复制过来即可,回顾广度优先遍历算法,就是把第一个顶点先无条件装进队列,所以编写遍历BFSM函数如下:

四、程序中加入图的处理函数
我们的队列系统完成后,记着再复制一个文件,加入图的邻接矩阵读数据程序,我们这里这个程序名称是b1.c。对邻接矩阵数据的读取、并构造图的过程,在深度优先遍历程序中已完成,所以直接复制过来即可,回顾广度优先遍历算法,就是把第一个顶点先无条件装进队列,所以编写遍历BFSM函数如下:
void BFSM(struct Graph *G)
{
	int i,n;
	struct Queue *Q;
	struct ElemType *p,E,e;
	Q=QueueInit();	
	E.sNo=0;           // 设置0进队列
	EnQueue(Q,&E);
	G->Visited[0]=1;     // 设置0号顶点已被访问
	p=&e;
	while(!QueueEmpty(Q))
	{
    //待补充
	}
}

从第11行开始,则进入真正的遍历。

有这么个函数后,我们可以补充main()的测试函数就是:

main()
{
	struct Graph *G;
	G=GraphCreat("p176G719.txt");
	BFSM(G);
}

main()很短,也很简单,如有不明白的回顾下深度优先遍历函数。

回顾一下:就是队列Q里出队列,然后找与该顶点相连的所有顶点、在进队列,就是:

void BFSM(struct Graph *G)
{
	int i,n;
	struct Queue *Q;
	struct ElemType *p,E,e;
	Q=QueueInit();	
	E.sNo=0;
	EnQueue(Q,&E);
	G->Visited[0]=1; 
	p=&e;
	while(!QueueEmpty(Q))
	{
		DeQueue(Q,p);
		n=p->sNo;
		printf("%s\\n",G->pV[n]);
		for(i=0;i<G->num;i++)
			if(G->pA[n][i]==1&&G->Visited[i]==0)
			{
				G->Visited[i]=1; 
				E.sNo=i;
				EnQueue(Q,&E);
			}
	}
}

运行这个程序、就会打印出这个图的广度优先遍历结果。

结果的再次分析

有了这个函数后,构造main()开始从第0个顶点遍历图1,就是:

进一步测试该函数,按图1的数据仔细分析下它的执行过程,如有图的连接分量不为1,则会在第一个连接分量遍历完成后终止。如下图4,在B1.C中是无法全部遍历完成的。这个图的文件在G4.TXT,修改表23中第5行,从G4.TXT中读数据,则会发现这个程序仅仅遍历了A、B、C、D,而没有到达过E、F、G这三个顶点。

在这里插入图片描述
这个图该如何遍历呢?请同学们自己修改程序,完成这个图的遍历。
广度优先遍历到此结束。

C#语言实现图的广度优先遍历、并显示广度优先遍历生成树

在C#文件夹中可以找到“Graph0.cs”,这个文件中包含着深度优先遍历、广度优先遍历等程序中的所有图类程序,现在,我们就要在这个类中补充新的方法。
首先复制这个类到Graph.cs,然后用C#建立一个Windows应用程序,然后在资源管理器中添加这个类,这个类和在深度优先遍历中的类完全一致,但去掉了命名空间说明,这样,这个类就可以使用在其他工程中了。

首先是再次熟悉这个类中的变量定义:

private int[,] A         //邻接矩阵
private string[] V       //顶点矩阵
private int[] Visited    //顶点访问表
private TreeNode[] T     //遍历生成树
private int num          //顶点个数
private int ConnComp     //连通分量

找到这个类中的最后一个方法:DSFTraverse(),然后开始在这个方法后补充新方法:DFS(),由于算法和C语言完全一致,此处算法问题不在介绍。

private void BFS(int N)
{
            int n;
            Queue<int> Q = new Queue<int>();
            Q.Enqueue(N);
            Visited[N] = 1; 
            while (Q.Count != 0)
            {
                n = Q.Dequeue();
                for (int i = 0; i < num; i++)
                    if (A[n, i] == 1 && Visited[i] == 0)
                    {
                        T[n].Nodes.Add(T[i]);  
                        Visited[i] = 1; 
                        Q.Enqueue(i);
                    }
            }
}

这个方法可以从第N个顶点开始遍历,同前面涉及的问题一样,考虑到多次遍历、以及多连通分量的图,我们还要补充下面的方法:

        public int BFSTraverse()
        {
            int i;
            ConnComp = 0;
            for (i = 0; i < num; i++)
            {
                T[i] = new TreeNode(V[i]);
                Visited[i] = 0;
            }
            for (i = 0; i < num; i++)
                if (Visited[i] == 0)
                {
                    BFS(i);
                    ConnComp++;
                }
            return ConnComp; 
        }

补充完类Graph中两个方法补充后、就可以进行界面设计,设计界面如下:

在这里插入图片描述
根据图1的界面设计,则广度优先遍历程序中连通分量为1的图在button1下,于是有:

private void button1_Click(object sender, EventArgs e)
{
            int m;
            int[,] A = {
                            {0, 1, 1, 0, 0, 0, 0, 0},
                            {1, 0, 0, 1, 1, 0, 0, 0},
                            {1, 0, 0, 0, 0, 1, 1, 0},
                            {0, 1, 0, 0, 0, 0, 0, 1},
                            {0, 1, 0, 0, 0, 0, 0, 1},
                            {0, 0, 1, 0, 0, 0, 1, 0},
                            {0, 0, 1, 0, 0, 1, 0, 0},
                            {0, 0, 0, 1, 1, 0, 0, 0}
                         };
            string[] V = { "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8" };
            Graph G = new Graph(8);
            G.Arc = A; G.Vertex = V;
            m = G.BFSTraverse(); 
            treeView1.Nodes.Clear();
            treeView1.Nodes.Add(G.DFSResult);
            textBox1.Text = "该图连接分量为" + m.ToString(); 
}

由于类设计中、广泛使用了原有的代码,所以这段程序看起来和深度优先遍历的测试代码差别很小。同理,在有多个连通分量的情况下,在button2下的代码是:

        private void button2_Click(object sender, EventArgs e)
        {
            int m;
            int[,] A = {
                        {0, 1, 1, 0, 0, 0, 0},
                        {1, 0, 0, 1, 0, 0, 0},
                        {1, 0, 0, 1, 0, 0, 0},
                        {0, 1, 1, 0, 0, 0, 0},
                        {0, 0, 0, 0, 0, 1, 1},
                        {0, 0, 0, 0, 1, 0, 1},
                        {0, 0, 0, 0, 1, 1, 0}
                       };
            string[] V = { "A", "B", "C", "D", "E", "F", "G" };
            Graph G = new Graph(7);
            G.Arc = A; G.Vertex = V;
            m = G.BFSTraverse(); 
            treeView1.Nodes.Clear();
            G.AddInTreeView(treeView1);
            textBox1.Text = "该图连接分量为" + m.ToString(); 
        }

请自行补充button3下的代码。

程序运行结果就是:
在这里插入图片描述图的广度优先遍历到此结束。通过上述编程我们可以发现:大量使用已有的代码,可以大大简化编程的复杂程度。

问题:

我们在C#的程序中、并没有使用类似C语言那样的技术:在数据文件中保存图的数据,这首先是基于我们对C#的使用方式造成的,C#最重要的应用场合是连接数据库服务器和前端的用户浏览器,这个场合下C#提供一个正确的运算类就足够了,其数据要来自于数据库,而结果要给到浏览器上的程序。浏览器下的程序就是JavaScript,这样的情况下C#不做数据文件读取、而要做的是数据库上数据读取,至于送到JavaScript,这个对C#、就要通过一种叫WebService的技术,而在JavaScript上、则要用到一种叫Ajax技术读写这些数据,而这些都是下学期的重要实验任务。

JavaScript语言实现图的广度优先遍历、并显示广度优先遍历生成树

对JavaScript而言,是没有队列类的,尽管数组的类型直接泛型,但仅有栈而无队列。我们需要最低代价完成一个队列系统,所以要再次查看JavaScript数组的所有方法和属性:

其中:FF: Firefox, IE: Internet Explorer

在这里插入图片描述
而这个对象提供的属性,则如下表:FF: Firefox, IE: Internet Explorer

在这里插入图片描述
回顾栈和队列的差异,一个是先进后出、一个是先进先出,查找上述数组的方法,有个方法是reverse(),含义是颠倒数组元素的次序,很显然:

如果进队列是数组的push()操作,那么出队列则就是颠倒数组次序、然后pop()操作,有这个思路,按这个算法构造队列类就是:

		function Queue()
		{
		this.Q=new Array();
		this.EnQueue=function(E)
			{
			this.Q.push(E);
			}
		this.DeQueue=function()
			{
			var E;
			this.Q=this.Q.reverse();
			E=this.Q.pop();
			this.Q=this.Q.reverse();
			return E;
			}
		this.Count=function()
			{
			return this.Q.length;
			}
		}

一定注意这个类的第13行,颠倒次序出栈后一定要再次颠倒这个数组的次序,保证进栈数据的次序。这样,我们就用最小代价完成了一个队列系统,然后补充多次进出队列的测试网页,就是:

<html>
	<head数据结构 图的基本操作要C语言的完整代码!!

存储结构与邻接矩阵,深度优先和广度优先遍历及Java实现

图的理解:存储结构与邻接矩阵

图(邻接矩阵)

数据结构与算法:终于可以用三种语言(C,C#,JavaScript)把图的广度优先遍历讲清楚了(推荐收藏)

数据结构与算法:终于可以用三种语言(C,C#,JavaScript)把图的广度优先遍历讲清楚了(推荐收藏)