当前位置: 首页 > news >正文

6.5.图的基本操作


一.图的基本操作:

1.判断图G是否存在弧<x,y>或边(x,y):

a.使用邻接矩阵来实现判断无向图G中是否存在边(x,y):

以上述图片的无向图为例,用邻接矩阵存储无向图时想要判断两个顶点之间是否有边是很方便的,

比如判断顶点B和D之间是否有边,只需要判断邻接矩阵中B行D列(D行B列也可以,因为无向图的边没有方向)的值是否为1即可,为1代表顶点B和D之间有直达的边,为0代表顶点B和D之间不存在直达的边,

因此使用邻接矩阵来实现判断无向图G中是否存在边(x,y)的时间复杂度为O(1)。

b.使用邻接表来实现判断无向图G中是否存在边(x,y):

以上述图片的无向图为例,

比如判断顶点B和D之间是否有边,在邻接表中顶点B在1索引上,顶点D在3索引上,由于顶点B对应的链表中没有索引3,所以顶点B和D之间没有直达的边(也可以判断顶点D对应的链表,原理相同),

对于时间复杂度,最好的时间复杂度就是在顶点对应的链表中第一个结点上就是要查找的顶点的索引,此时时间复杂度为O(1);假设有V个顶点,那么其中一个顶点最多关联|V|-1条边,最坏的时间复杂度为该顶点关联|V|-1条边(意味着该顶点对应的链表包含了除该顶点以外的所有顶点)且遍历完该顶点对应的整个链表后在最后一个结点上才找到了要查找的顶点的索引或者没有找到要查找的顶点的索引(此时意味着要查找的顶点不在图中),因此时间复杂度最坏为O(|V|-1),等价于O(|V|),

因此使用邻接表来实现判断无向图G中是否存在边(x,y)的时间复杂度为O(1)到O(|V|)。

显然在判断无向图G是否存在边(x,y)的操作中使用邻接矩阵比邻接表更高效一些。

c.使用邻接矩阵来实现判断有向图G中是否存在弧<x,y>:

以上述图片的有向图为例,

比如判断顶点A和B之间是否有弧,需要判断邻接矩阵中A行B列和B行A列,因为有向图的弧存在方向,顶点A到顶点B可能没有弧,但顶点B到顶点A可能就存在弧,A行B列的值为1,说明顶点A有指向顶点B的弧,B行A列的值为0,说明顶点B没有指向顶点A的弧,综上,顶点A和B之间有弧,

因此使用邻接矩阵来实现判断有向图G中是否存在弧<x,y>的时间复杂度为O(1)。

d.使用邻接表来实现判断有向图G中是否存在弧<x,y>:

以上述图片的有向图为例,

比如判断顶点A和B之间是否有弧,在邻接表中顶点A在0索引上,顶点B在1索引上,由于顶点B对应的链表中没有索引0,所以顶点B没有指向顶点A的弧,由于顶点A对应的链表中有索引1,所以顶点A有指向顶点B的弧,综上,顶点A和B之间有弧,

对于时间复杂度,最好的时间复杂度就是在顶点对应的链表中第一个结点上就是要查找的顶点的索引,此时时间复杂度为O(1);假设有V个顶点,那么其中一个顶点最多关联2(|V|-1)条弧(因为弧包括入弧和出弧),最坏的时间复杂度为该顶点关联2(|V|-1)条弧(意味着该顶点对应的链表包含了除该顶点以外的所有顶点)且遍历完该顶点对应的整个链表后在最后一个结点上才找到了要查找的顶点的索引或者没有找到要查找的顶点的索引(此时意味着要查找的顶点不在图中),因此时间复杂度最坏为O( 2(|V|-1) ),等价于O(2|V|-2),等价于O(|V|),

因此使用邻接表来实现判断有向图G中是否存在弧<x,y>的时间复杂度为O(1)到O(|V|)。

显然在判断有向图G是否存在弧<x,y>的操作中使用邻接矩阵比邻接表更高效一些。

2.列出图G中与结点x邻接的边:

a.无向图中使用邻接矩阵找和某一个顶点相连的所有的边:

以上述图片的无向图为例,

要列出图G中与某个顶点邻接的所有的边,只需要遍历某个顶点在邻接矩阵中的某一行或某一列即可(因为无向图的边不存在方向,所以行和列对称),判断哪个地方的元素是1,把元素是1的全部列举出来即可,比如找和顶点B邻接的所有的边,在B行中,只有B行A列、B行E列、B行F列的值为1,所以顶点B与顶点B、A之间的边,顶点B、E之间的边,顶点B、F之间的边邻接,

对于时间复杂度,如果此时有V个顶点,那么时间复杂度就是O( |V| ),因为总共要遍历V个元素。

b.无向图中使用邻接表找和某一个顶点相连的所有的边:

以上述图片的无向图为例,

要列出图G中与某个顶点邻接的所有的边,只需要遍历某个顶点在顺序表中对应的链表即可,

对于时间复杂度,最好的情况是当前的顶点只邻接一条边或者没有邻接任何边,此时的时间复杂度为O(1),最坏的情况就是如果当前图中有V个顶点,某个顶点邻接了除该顶点外每一个顶点上的边,最多会邻接|V|-1条边,此时遍历完链表需要O( |V|-1 )的时间复杂度,等价于O( |V| )。

显然在列出无向图G中与结点x邻接的边的操作中使用邻接表比邻接矩阵更高效一些。

c.有向图中使用邻接矩阵找和某一个顶点相连的所有的边:

以上述图片的有向图为例,

假设此时图中共有V个顶点,要想找到某个顶点的出边,只需要遍历该顶点在邻接矩阵中对应的那一行即可,需要遍历|V|个顶点;要想找到某个顶点的入边,只需要遍历该顶点在邻接矩阵中对应的那一列即可,需要遍历|V|个顶点,找入边和出边的这两个遍历是遍历完其中一个再遍历另一个,所以

综上,有向图中使用邻接矩阵找和某一个顶点相连的所有的边的时间复杂度为O( |V| )。

d.有向图中使用邻接表找和某一个顶点相连的所有的边:

以上述图片的有向图为例,

要列出图中与某个顶点邻接的所有的弧,只需要遍历该顶点所在邻接表中所有的链表即可,

要找某个顶点的出边,只需要遍历邻接表中该顶点对应的链表即可->假设此时图中共有V个顶点,最好的时间复杂度就是当前顶点没有出边即不需要遍历链表,此时时间复杂度为O(1),如果当前顶点指向除了当前顶点外的所有顶点,就意味着要遍历|V|-1个顶点,此时达到最坏时间复杂度为O( |V|-1 ),等价于O( |V| ),所以找出边的时间复杂度是O(1)到O( |V| );

要找指向某个顶点的弧即该顶点的入边,就需要遍历邻接表中所有的链表,因为只有把这些链表都遍历完,才能找全入边,假设图中有E条弧,遍历完所有链表就意味着要判断图中所有的弧,那么找入边的时间复杂度为O( |E| ),

{邻接表中顶点A在0索引上,顶点B在1索引上,顶点A对应的链表中只有1索引,说明顶点A只有指向顶点B的1条弧;顶点E在4索引上,顶点B在1索引上,顶点C在2索引上,顶点E对应的链表中有1索引和2索引,说明顶点E有指向顶点B的弧和指向顶点C的弧,其他同理}

显然在列出有向图G中与结点x邻接的弧的操作中使用邻接矩阵比邻接表更高效一些,但这个不绝对,如果当前的图是稀疏图,意味着弧很少,此时O( |E| )的数量级就很小,此时邻接表的效果也很好。

3.在图G中插入顶点x:

要插入的顶点x刚开始时和图G中的任何顶点都不相连,所以插入操作很方便。

a.使用邻接矩阵来实现在无向图G中插入顶点x:

在上述图片中的无向图中插入顶点x,只需要在存储顶点的顺序表中添加顶点x的数据即可,在邻接矩阵中新添加的顶点x的这一行和这一列一开始都为0,表示顶点x起初不和任何顶点相连:

上述图片的邻接矩阵中看起来要插入许多个0,实际上化0的操作在邻接矩阵初始化时就已经处理过了,所以使用邻接矩阵在无向图中插入顶点x的时间开销只有在存储顶点的顺序表中添加顶点x的数据,因此时间复杂度为O(1)。

b.使用邻接表来实现在无向图G中插入顶点x:

如上述图片所示,只需要在存储顶点的顺序表中插入新顶点x的数据即可,由于新顶点x起初不和任何顶点相连,所以x顶点对应的链表的内容为NULL,因此时间复杂度为O(1)。

c.d.有向图类似,时间复杂度都是O(1)。

4.从图G中删除顶点x:

a.用邻接矩阵表示在无向图G中删除顶点x:

上述图片中,如果删除C顶点,只需要把邻接矩阵中第C行和第C列的数据全部清空即可,清空这些数据以后,比较容易想到的一个方法就是,我们可以让顺序表中C顶点后面的数据依次前移,对于邻接矩阵而言,就需要把断开的部分拼接起来,这么操作的话就意味着会有大量的数据元素需要移动,显然移动这么多的元素开销会很大,所以该方案不科学:

->如果删除C顶点,意味着C顶点不和任意一个顶点相连,对于邻接矩阵而言,只需要把C行和C列的数据都改为0即可(0代表此时的两个顶点互不相连),在顶点的结构体中可以增加一个布尔类型的变量,用于表示某个顶点是否是一个不和任何顶点相连的顶点即可,用该方式实现删除顶点显然要比移动大量元素好很多。由于每删除一个顶点需要修改邻接矩阵中一行和一列的数据,还需要修改表示顶点是否连接顶点的布尔型变量,所以时间复杂度为O( |V| )+O(1),等价于O( |V| ),V为图的顶点个数。

b.用邻接表表示在无向图G中删除顶点x:

上述图片中,如果删除C顶点,顺序表中要删除C顶点的数据以及C顶点对应的链表,其他和C顶点相连的顶点中,对应的链表中需要把关于C顶点的信息删除,如下图:(C顶点在顺序表中的索引为2,所以顺序表中2索引对应的数据删除,再把顺序表中所有链表中关于2的都删除)

邻接表的删除操作中,对于时间复杂度:

最好的情况是当前要删除的C顶点没有连接任何的顶点,这种情况只需要把顺序表里2索引上的C顶点删除即可,时间复杂度为O(1),

最坏的情况就是当前要删除C顶点和除C顶点以外的所有顶点都相连,所以当删除掉顺序表中2索引上的C顶点和对应的链表后,还需要依次遍历其他和C顶点相连的顶点以及对应的链表,链表中最坏的情况是要删除的2索引上的C顶点是在每一条链表的最后一个位置,那么就意味着要遍历所有链表而且要把所有链表都遍历完才能把和C顶点有关的内容删除完,所以最坏的时间复杂度为O( |E| ),E为图G的边,因为要把每一条边都遍历一遍,

所以时间复杂度是O(1)到O( |E| ):

c.用邻接矩阵表示在有向图G中删除顶点x:

思路和用邻接矩阵表示在无向图G中删除顶点x类似。

d.用邻接表表示在无向图G中删除顶点x:

以上述图片为例,删除有向图G中的C顶点,C顶点在顺序表中的2索引上,

如果要删除C顶点的出边,只需要把顺序表中2索引上的C顶点对应的链表中的数据全部删除即可,因为链表里记录的是C顶点指向哪些顶点,最好的情况是C顶点没有出边,无需删除,此时时间复杂度为O(1),最坏的情况是C顶点指向除了C顶点以外的所有顶点,假设此时图G中有V个顶点,那么时间复杂度为O( |V-1| ),等价于O( |V| ),所以删除出边的时间复杂度是O(1)到O( |V| ),

如果要删除C顶点的入边即指向C顶点的边,此时就只能遍历整个邻接表,把除了C顶点对应的链表以外的所有链表中关于2的数据删除,因为C顶点在顺序表中的2索引上,意味着要遍历所有的边,假设图G中有E条边,那么时间复杂度就是O( |E| )。如果是稀疏图的话,边比较少,O( |E| )也不会很大。

5.向图G中添加边:

a.使用邻接矩阵实现在无向图G中添加边:

以上述图片为例,此时要添加无向边(A,E)即添加A和E之间的边,此时只需要把邻接矩阵中A行E列和E行A列的值全部赋值为1即可,时间复杂度为O(1)。

b.使用邻接表实现在无向图G中添加边:

以上述图片为例,此时要添加无向边(C,F)即添加C和F之间的边,由于顺序表中C顶点在2索引上,F顶点在5索引上,所以只需要在顺序表中C顶点对应的链表中末尾添加数据5和F顶点对应的链表中末尾添加数据2即可:

这里采用了链表的尾插法,其实更高效的方法是采用链表的头插法,就是把新的边的信息添加到链表的头部,此时直接在链表头部插入数据即可,所以时间复杂度为O(1),如下:

最坏的情况就是在顶点对应的链表中添加数据时指定了要添加的位置,此时需要遍历链表(详情见"单链表的插入和删除"),假设V为图G中顶点的个数,所以时间复杂度为O( |V| )

->所以使用邻接表实现在无向图G中添加边的时间复杂度为O(1)到O( |V| ),V为图G中顶点的个数:

c.有向图类似。

6.求图G中顶点x的第一个邻接点,若有则返回该邻接点在顺序表中的索引;若顶点x没有邻接点或图G中不存在顶点x,则返回-1:

a.使用邻接矩阵实现求无向图G中顶点x的第一个邻接点,若有则返回该邻接点在顺序表中的索引;若顶点x没有邻接点或无向图G中不存在顶点x,则返回-1:

以上述图片为例,例如要找C顶点的第一个邻接点,可以扫描C行的数据(也可以扫描C列的数据,因为此时是无向图,邻接矩阵是对称的),直到找到第一个1,就找到了第一个和C顶点邻接的点,此时第一个1对应的顶点是A顶点,所以顶点C的第一个邻接点是A顶点。最好的情况就是扫描的第一个顶点就是当前顶点的邻接点,此时的时间复杂度为O(1),最坏的情况就是扫描完一整行(意味着把所有顶点都扫描了)都没有和当前顶点相邻接的点,假设图G中有V个顶点,此时时间复杂度为O( |V| ),所以时间复杂度是O(1)到O( |V| )。

b.使用邻接表实现求无向图G中顶点x的第一个邻接点,若有则返回该邻接点在顺序表中的索引;若顶点x没有邻接点或无向图G中不存在顶点x,则返回-1:

以上述图片为例,例如要找C顶点的第一个邻接点,C顶点在顺序表的2索引上,只需要知道C顶点对应的链表的第一个结点中的数据是多少,此时是0,意味着C顶点的第一个邻接点为顺序表中0索引上的顶点即A顶点,易知时间复杂度为O(1)。如果C顶点没有对应的链表,说明C顶点不和任何顶点邻接。

c.使用邻接矩阵实现求有向图G中顶点x的第一个邻接点,若有则返回该邻接点在顺序表中的索引;若顶点x没有邻接点或有向图G中不存在顶点x,则返回-1:

以上述图片为例,例如C顶点,

找C顶点的出边中C顶点的第一个邻接点需要扫描第C行,直到找到第一个1,就找到了顶点C的出边中第一个和C顶点邻接的点,此时第一个1对应的顶点是A顶点,所以顶点C的出边中第一个邻接点是A顶点,

找C顶点的入边中C顶点的第一个邻接点需要扫描第C列,直到找到第一个1,就找到了顶点C的入边中第一个和C顶点邻接的点,此时第一个1对应的顶点是E顶点,所以顶点C的入边中第一个邻接点是E顶点。

无论是在出边中找还是在入边中找,最好的情况都是扫描的第一个顶点就是当前顶点的邻接点,此时的时间复杂度为O(1),最坏的情况就是扫描完一整行或列(意味着把所有顶点都扫描了)都没有和当前顶点相邻接的点,假设图G中有V个顶点,此时时间复杂度为O( |V| ),所以时间复杂度是O(1)到O( |V| )。

d.使用邻接表实现求有向图G中顶点x的第一个邻接点,若有则返回该邻接点在顺序表中的索引;若顶点x没有邻接点或有向图G中不存在顶点x,则返回-1:

以上述图片为例,例如C顶点,

找C顶点的出边中C顶点的第一个邻接点只需要知道C顶点对应的链表的第一个结点中的数据是多少,此时是0,意味着C顶点的出边中C顶点的第一个邻接点为顺序表中0索引上的顶点即A顶点,易知时间复杂度为O(1)。如果C顶点没有对应的链表,说明C顶点的出边中C顶点不和任何顶点邻接。

找C顶点的入边中C顶点的第一个邻接点需要遍历顺序表中除了C顶点对应的链表以外的所有链表,链表中第一个出现2的链表对应的顶点就是C顶点的入边中C顶点的第一个邻接点,此时链表中第一个出现2的链表对应的顶点是E顶点,所以C顶点的入边中C顶点的第一个邻接点是E顶点->对于时间复杂度,最好的情况是遍历顺序表中第一条链表的第一个元素时就找到了对应的邻接点,此时的时间复杂度为O(1),最坏的情况是遍历完顺序表中所有的链表都没有对应的邻接点,假设图G中有E条边,那么时间复杂度就是O( |E| )。所以时间复杂度就是O(1)到O( |E| )。

更常用的是找某个顶点的出边中该顶点的第一个邻接点。

7.假设图G中顶点y是顶点x的一个邻接点,返回除顶点y之外顶点x的下一个邻接点的顶点号,若顶点y是顶点x的最后一个邻接点,则返回-1:

a.使用邻接矩阵实现假设图G中顶点y是顶点x的一个邻接点,返回除顶点y之外顶点x的下一个邻接点的顶点号,若顶点y是顶点x的最后一个邻接点即顶点y之后顶点x没有邻接点,则返回-1:

以上述图片为例,例如要找C顶点除当前邻接点的下一个邻接点,可以扫描C行的数据(也可以扫描C列的数据,因为此时是无向图,邻接矩阵是对称的),直到找到除当前邻接点的下一个1,就找到了C顶点除当前邻接点的下一个邻接点,假设C顶点当前的邻接点为A顶点,邻接矩阵中A顶点之后的下一个为1的数据对应的顶点是E顶点,所以顶点C除当前邻接点A的下一个邻接点是E顶点。最好的情况就是扫描一次就是除当前邻接点的下一个邻接点,此时的时间复杂度为O(1),最坏的情况就是扫描完一整行(意味着把所有顶点都扫描了)都没有除当前邻接点的下一个邻接点,假设图G中有V个顶点,此时时间复杂度为O( |V| ),所以时间复杂度是O(1)到O( |V| )。

b.使用邻接表实现假设图G中顶点y是顶点x的一个邻接点,返回除顶点y之外顶点x的下一个邻接点的顶点号,若顶点y是顶点x的最后一个邻接点即顶点y之后顶点x没有邻接点,则返回-1:

以上述图片为例,例如要找C顶点除当前邻接点的下一个邻接点,只需要操作C顶点在顺序表中对应的链表即可,假设C顶点此时邻接点是A顶点,A顶点在顺序表中的索引为0,C顶点的链表中数据0的下一个结点中的数据为4,所以C顶点除当前邻接点A的下一个邻接点是顺序表中4索引上的顶点E,其中不需要遍历,所以时间复杂度为O(1)。

有向图类似。

8.获取图G中边(x,y)或弧<x,y>对应的权值,设置图G中边(x,y)或弧<x,y>对应的权值为v:(权值就是边或弧的含义,如权值可以是距离)

对于实现图G中边(x,y)或弧<x,y>的获取权值和设置权值,主要的时间开销就是来源于找边或弧,因为只有获取到边或弧,才能进行获取权值或设置权值,所以实现获取权值和设置权值的这两个操作是和判断某一条边或弧是否存在这个基本操作的时间开销是一样的。


二.总结:


http://www.dtcms.com/a/113232.html

相关文章:

  • YOLOX 检测头以及后处理
  • 联网汽车陷入网络安全危机
  • 贪心算法之任务选择问题
  • mmap函数的概念和使用方案
  • 爬楼梯问题-动态规划
  • 3536 矩形总面积
  • leetcode4.寻找两个正序数组中的中位数
  • 类 和 对象 的介绍
  • 2024 .11-2025.3 一些新感悟
  • 【33期获取股票数据API接口】如何用Python、Java等五种主流语言实例演示获取股票行情api接口之沪深A股当天逐笔交易数据及接口API说明文档
  • 【2020】【论文笔记】相变材料与超表面——
  • 使用Cusor 生成 Figma UI 设计稿
  • 数据库并发控制问题
  • 麒麟系统桌面版本v10安装教程
  • 【动手学深度学习】卷积神经网络(CNN)入门
  • 低代码开发平台:飞帆画 echarts 柱状图
  • pygame里live2d的使用方法(live2d-py)
  • 人工智能与计算机技术赋能高中教育数字化教学模式的构建与实践
  • Git 分布式版本控制工具
  • 【ROS2】〇、ROS2的安装
  • 神经网络与深度学习:案例与实践——第三章(2)
  • 3D图像重建中Bundle Adjustment的推导与实现
  • Shell脚本笔记
  • Java第三节:新手如何用idea创建java项目
  • #SVA语法滴水穿石# (004)关于 ended 和 triggered 用法
  • Java HttpURLConnection修仙指南:从萌新到HTTP请求大能的渡劫手册
  • #SVA语法滴水穿石# (005)关于 问号表达式(condition ? expr1 : expr2)
  • Arduino示例代码讲解:ADXL3xx 加速传感器
  • Java 类型转换和泛型原理(JVM 层面)
  • 论定制开发开源 AI 智能名片 S2B2C 商城小程序源码在零售变革中的角色与价值