python--数据结构--邻接表

# adj_list.py

"""
邻接表

:class VertexNode: 顶点结点类

:class ArcNode: 弧结点类

:class AdjList: 邻接表类

:method locate_vertex: 查找某个顶点结点在AdjList对象中的成员vertexs列表中的下标

:method create_adj_list: 创建一个邻接表对象

depth_first_search_recursion: 深度优先递归遍历

depth_first_search_nonrecursion: 深度优先非递归遍历

breadth_first_search_nonrecursion: 广度优先非递归遍历

:class TravVistor: 访问者类,对深度优先递归、深度优先非递归、广度优先非递归的访问

:method travers_graph: 遍历接口,设置不同参数利用TravVistor对象实现深度优先递归、深度优先非递归、广度优先非递归调用

:one_path: 寻找两个顶点间的简单路径

:method topo_sort: 拓扑排序

:method critical_path: 寻找关键路径

:method bfs_k_level: 从某一顶点开始找到距其路径长度为k以内的所有顶点

:method hamilton: 在图g中找出一条包含所有顶点的简单路径
"""


from typing import List, Tuple, Union
from collections import deque, namedtuple
from array import array


class VertexNode:
    def __init__(self, name: str):
        self.name = name  # 顶点结点名
        self.first_arc: ArcNode = None


class ArcNode:
    def __init__(self, arc_head_index: int, next_arc: VertexNode = None, weight: Union[int, float] = 0):
        self.arc_head_index = arc_head_index  # 该弧弧头顶点结点在已建好的邻接表的顶点结点列表中的下标
        self.next_arc = next_arc  # 与该弧弧尾顶点结点相同的下一个弧结点
        self.weight = weight  # 该弧的权重
        self.otherInfo = None


# 所创建的图的种类:DG表示有向图,DN表示有向网,UDG表示无向图,UDN表示无向图
graph_kind_ = {"DG": 0, "DN": 32768, "UDG": 0, "UDN": 32768}


class AdjList:
    def __init__(self):
        self.vertexs: List = []  # 邻接表的顶点结点列表
        self.vex_num: int = 0  # 顶点结点个数
        self.arc_num: int = 0  # 弧结点个数
        self.graph_king: str = ''  # 该邻接表表示的图的类型


def locate_vertex(adj_list: AdjList, vertex_node: VertexNode):
    """
    获取顶点的位置

    :param adj_list: 在该邻接表对象的顶点列表中查找顶点位置
    :param vertex_node: 所需查找位置的顶点
    :return: 顶点的索引
    """
    for index, vertex_node_item in enumerate(adj_list.vertexs):
        if vertex_node.name == vertex_node_item.name:
            return index


def create_adj_list(adj_list: AdjList, vertexs: List[VertexNode],
                    arcs: List[Union[Tuple[VertexNode, VertexNode, int], Tuple[VertexNode, VertexNode, float]]],
                    graph_kind: str = 'DN'):
    adj_list.vertexs = vertexs
    adj_list.vex_num = len(adj_list.vertexs)
    adj_list.arc_num = len(arcs)
    adj_list.graph_king = graph_kind
    for arc in arcs:
        arc_head_index = locate_vertex(adj_list, arc[1])
        arc_node = ArcNode(arc_head_index, weight=arc[2])

        arc_tail_index = locate_vertex(adj_list, arc[0])
        arc_node.next_arc = adj_list.vertexs[arc_tail_index].first_arc
        adj_list.vertexs[arc_tail_index].first_arc = arc_node


visited = array('i', [])  # 访问标志数组
trav_seq = deque()  # 访问队列,保存访问过的结点


def depth_first_search_recursion(adj_list: AdjList, vertex_node: VertexNode):
    """
    深度遍历图

    算法思想:
        (1) 访问出发点v0;
        (2) 依次以v0的未被访问的临界点为出发点,深度优先搜索图,直至图中所有与v0有路径相通的顶点都被访问。

    :param adj_list: 已创建好的邻接表对象
    :param vertex_node: 访问出发点v0
    :return: 深度遍历序列
    """
    vi_index = locate_vertex(adj_list, vertex_node)  # 获取当前顶点结点在ad_list中的下标
    trav_seq.append(adj_list.vertexs[vi_index])  # 访问当前顶点结点,放入访问对列中
    visited[vi_index] = 1  # 将访问过的当前顶点结点标志置为1,表示已访问过
    p = adj_list.vertexs[vi_index].first_arc  # 在邻接表中获取以当前顶点结点为弧尾的第一个弧结点
    while p:  # 判断当前结点是否存在
        if not visited[p.arc_head_index]:  # 判断弧的弧头结点是否已被访问
            # 若未被访问则进行递归调用深度遍历
            depth_first_search_recursion(adj_list, adj_list.vertexs[p.arc_head_index])
        p = p.next_arc  # 在邻接表中获取与该弧弧尾结点相同的下一个弧结点
    return trav_seq  # 返回以当前顶点结点为起始节点的深度遍历序列


def depth_first_search_nonrecursion(adj_list: AdjList, vertex_node: VertexNode):
    """
    深度非递归遍历图

    算法思想:
        (1) 首先将v0入栈
        (2) 只要栈不空,则重复下述处理:栈顶顶点出栈,如果未访问,则访问并置访问标志;然后将该顶点所有未访问的邻接点入栈。

    注意:
        (1) 对于同一个顶点结点栈中可能出现多次。
        (2) 访问既可以在入栈时访问也可以在出栈时访问,只不过都需在访问前进行判断,当顶点结点是否已被访问过。原因是注意(1)

    :param adj_list: 已创建好的邻接表对象
    :param vertex_node: 访问出发点v0
    :return: 深度遍历序列
    """
    temp_stack = deque()  # 栈
    temp_stack.append(vertex_node)  # 将起始顶点结点入栈
    while temp_stack:  # 循环直到栈为空为止
        vi_node = temp_stack.pop()  # 将栈顶顶点结点出栈成为当前结点
        vi_index = locate_vertex(adj_list, vi_node)  # 获取当前顶点结点在邻接表adj_list的顶点列表中的下标
        if not visited[vi_index]:  # 判断当前顶点结点是否被访问过,对于同一个顶点结点栈中可能出现多次,重复出现的在第一次出栈的时候已被访问过
            trav_seq.append(vi_node)  # 将当前顶点结点放入访问队列
            visited[vi_index] = 1  # 将当前顶点节点设置为1,表示已被访问
            p = vi_node.first_arc  # 获取以当前顶点结点的为弧尾的第一个弧结点
            while p:  # 将所有以当前顶点结点为弧尾顶点结点的的弧的弧头结点入栈
                if not visited[p.arc_head_index]:  # 判断获取到的顶点结点是否非访问过,被访问过的不能入栈
                    temp_stack.append(adj_list.vertexs[p.arc_head_index])
                p = p.next_arc  # 获取与该弧结点弧尾结点相同的下一个弧结点
    return trav_seq  # 返回以当前顶点结点为起始节点的深度遍历序列


def breadth_first_search_nonrecursion(adj_list: AdjList, vertex_node: VertexNode):
    """
    广度非递归遍历图

    算法思想:
        (1) 首先将v0入对
        (2) 只要对不空,则重复下述处理:对头顶点出对,如果未访问,则访问并置访问标志;然后将该顶点所有未访问的邻接点入对。

    注意:
        (1) 对于同一个顶点结点队中可能出现多次。
        (2) 访问既可以在入队时访问也可以在出队时访问,只不过都需在访问前进行判断,当顶点结点是否已被访问过。原因是注意(1)

    :param adj_list: 已创建好的邻接表对象
    :param vertex_node: 访问出发点v0
    :return: 广度遍历序列
    """
    temp_queue = deque()  # 队列
    temp_queue.append(vertex_node)  # 将起始顶点结点入对
    while temp_queue:  # 循环直到队列为空为止
        vi_node = temp_queue.popleft()  # 将栈顶顶点结点出对成为当前结点
        vi_index = locate_vertex(adj_list, vi_node)  # 获取当前顶点结点在邻接表adj_list的顶点列表中的下标
        if not visited[vi_index]:  # 判断当前顶点结点是否被访问过,对于同一个顶点结点对中可能出现多次,重复出现的在第一次出对的时候已被访问过
            trav_seq.append(vi_node)  # 将当前顶点结点放入访问队列
            visited[vi_index] = 1  # 将当前顶点节点设置为1,表示已被访问
            p = adj_list.vertexs[vi_index].first_arc  # 获取以当前顶点结点的为弧尾的第一个弧结点
            while p:   # 将所有以当前顶点结点为弧尾顶点结点的的弧的弧头结点入对
                if not visited[p.arc_head_index]:  # 判断获取到的顶点结点是否非访问过,被访问过的不能入对
                    temp_queue.append(adj_list.vertexs[p.arc_head_index])
                p = p.next_arc  # 获取与该弧结点弧尾结点相同的下一个弧结点
    return trav_seq  # 返回以当前顶点结点为起始节点的深度遍历序列


class TravVistor:
    """
    遍历访问者

    访问者设计模式:使用同一个方法对不同对象访问具有不同的行为
    对不同的遍历方法使用同一个方法traver_graph进行遍历
    """
    __recursion_nonrecursion = {"r": "recursion", "nr": "nonrecursion"}  # 递归遍历与非递归遍历
    __depth_breadth = {"d": "depth", "b": "breadth"}

    def __init__(self, adj_list, vertex_node, depth_breadth: str, recursion_nonrecursion: str):
        self.adj_list = adj_list
        self.vertex_node = vertex_node
        self.depth_breadth = depth_breadth
        self.recursion_nonrecursion = recursion_nonrecursion

    def traver_graph(self):
        func_name = f'{self.__depth_breadth[self.depth_breadth]}_first_search_{self.__recursion_nonrecursion[self.recursion_nonrecursion]}'
        func_obj = getattr(self, func_name, None)
        return func_obj()

    def depth_first_search_recursion(self):
        return depth_first_search_recursion(self.adj_list, self.vertex_node)

    def depth_first_search_nonrecursion(self):
        return depth_first_search_nonrecursion(self.adj_list, self.vertex_node)

    def breadth_first_search_nonrecursion(self):
        return breadth_first_search_nonrecursion(self.adj_list, self.vertex_node)


def travers_graph(adj_list: AdjList, deepth_breadth: str = "d", recursion_nonrecursion: str = "nr"):
    """
    遍历图

    算法思想:
        若是非连通图,则途中一定还有顶点未被访问,需要从途中另选一个未被访问过的顶点作为起始顶点,重复深度优先搜索过程,
        直至图中所有的顶点均被访问过为止。

    :param adj_list: 已创建好的邻接表对象
    :param deepth_breadth: 选用深度deepth_breadth = 'd', 选用广度deepth_breadth = 'b'
    :param recursion_nonrecursion: 选用递归way = 'r' ,选用非递归way = 'nr'
    :return: 返回多个连通子图的遍历序列
    """
    travers_list = deque()
    for vi_index in range(adj_list.vex_num):  # 初始化visited
        visited.append(0)
    for vi_index in range(adj_list.vex_num):  # 循环调用遍历方法来遍历连通子图的操作,若图adj_list是连通图,则此调用只执行一次
        if not visited[vi_index]:
            trav_vistor = TravVistor(adj_list, adj_list.vertexs[vi_index], deepth_breadth, recursion_nonrecursion)  # 创建一个遍历访问者对象
            travers_list.append(trav_vistor.traver_graph().copy())  # 将遍历结果添加到travers_list
        trav_seq.clear()  # 清空trav_seq,以便下一轮的使用
    return travers_list  # 返回多个连通子图的遍历序列


def one_path(adj_list: AdjList, vertex_start: VertexNode, vertex_end:VertexNode):
    for i in range(adj_list.arc_num):
        visited.append(0)

    def dfs_path(adj_list_, vertex_start_, vertex_end_):
            """
            深度遍历搜索两个指定顶点之间的简单路径

            算法思想:
                从顶点vertex_start_开始,进行深度(广度)优先遍历,如果能搜索到顶点vertex_end_,则表明从顶点vertex_start_
                到顶点vertex_end_有一条路径。由于在搜索过程中,每个顶点只访问一次,所以这条路径必定是一条简单路径。因此,
                只要在搜索过程中,把搜索的线路记录下来,并在搜索到顶点vj时退出搜索过程,就可得到从u到v的一条简单路径。

                (1) 访问出发点vertex_start_;
                (2) 依次以vertex_start_的未被访问的邻接点为出发点,深度优先搜索图,直至图中所有与vertex_start_有路径相通
                的顶点都被访问。

            :param adj_list_: 已创建好的邻接表对象
            :param vertex_start_: 访问出发结点
            :param vertex_end_: 终止结点
            :return: 深度遍历路径
            """
            vi_index = locate_vertex(adj_list_, vertex_start_)  # 获取当前顶点结点在ad_list中的下标
            trav_seq.append(adj_list_.vertexs[vi_index])  # 访问当前顶点结点,放入访问对列中
            visited[vi_index] = 1  # 将访问过的当前顶点结点标志置为1,表示已访问过
            # print(vertex_start_.name)
            p = adj_list_.vertexs[vi_index].first_arc  # 在邻接表中获取以当前顶点结点为弧尾的第一个弧结点
            while p:  # 循环获取以当前顶点结点为弧尾顶点结点的弧结点
                if not visited[p.arc_head_index]:  # 判断弧的弧头结点是否已被访问
                    # 若未被访问则进行深度遍历
                    if adj_list_.vertexs[p.arc_head_index].name == vertex_end_.name:
                        # 当前弧结点的弧头顶点结点是所要找的终止顶点结点
                        vj_index = locate_vertex(adj_list_, adj_list_.vertexs[p.arc_head_index])  # 获取当前顶点结点在ad_list中的下标
                        trav_seq.append(adj_list_.vertexs[vj_index])  # 访问当前顶点结点,放入访问对列中
                        visited[vj_index] = 1  # 将访问过的当前顶点结点标志置为1,表示已访问过
                        return 1  # 找到时返回,只是最深层返回
                    elif dfs_path(adj_list_, adj_list_.vertexs[p.arc_head_index], vertex_end_):
                        return 1  # 找到时返回,避免接着递归遍历,除了最深层其它层进行返回。
                p = p.next_arc  # 在邻接表中获取与该弧弧尾结点相同的下一个弧结点

    dfs_path(adj_list, vertex_start, vertex_end)
    return trav_seq


def find_id(adj_list: AdjList, indegree: array):
    for i in range(adj_list.vex_num):
        indegree.append(0)
    for arc_tail_node in adj_list.vertexs:
        arc = arc_tail_node.first_arc
        while arc:
            arc_head_index = arc.arc_head_index
            indegree[arc_head_index] += 1
            arc = arc.next_arc
    return indegree


def topo_sort(adj_list: AdjList):
    """
    拓扑排序:邻接表

    AOV-网:
        用顶点表示活动,用有向弧表示活动间的优先关系

    拓扑排序:
        在有向图G=(V, {E})中,V中顶点的线性序列(vi1,vi2,vi3,...,vin)称为拓扑序列。如果此序列满足条件:对序列中任意两个顶点
        vi,vj,在G中有一条从vi到vj的路径,则在序列中vi必排在vj之前。

    拓扑排序基本思想:
        (1) 从有向图中选一个无前驱的结点输出;
        (2) 将此结点和以它为起点的边删除
        (3) 重复(1)(2),直到不存下来无前驱的结点
        (4) 若此时输出的结点数小于有向图的顶点数,则说明有向图中存在回路,否则输出的顶点顺序即为一个拓扑序列

    基于邻接表的存储结构:
        此时入度为0的顶点即没有前驱的顶点,因此可以附设一个存放各顶点入度的数组indegree[],于是有:
            (1) 找G中无前驱的顶点--查找indegree[i]为零的顶点i;
            (2) 删除以i为起点的所有弧--对链在顶点i后面的所有临界顶点k,将对应的indegree[k]减1
            为了避免重复检测入读为0的顶点,可以再设置一个辅助栈,若某一顶点的入度减为0,则将它入栈,每当输出
            某一顶点时,便将它从栈种删除。

    算法思想:
        (1) 首先求出各顶点的入度,并将入度为0的顶点入栈。
        (2) 只要栈不空,则重复下面处理:
            a. 将栈顶顶点i出栈并打印
            b. 将栈顶顶点i的每一个邻接点k的入度减1,如果顶点k的入度变为0,则将顶点k入栈。

    算法时间复杂度:
        若有向无环图有n个顶点和e条弧,则在拓扑排序的算法中,for循环需要执行n次,时间复杂度为O(n);对于while循
        环,由于每一顶点必定出栈一次,出一次栈,其时间复杂度为O(e);故该算法的时间复杂度为O(n+e)
    :param adj_list:  # 需要拓扑排序的邻接表
    :return:  # 拓扑排序的结点序列
    """
    indegree = array('i', [])  # 入度计数
    temp_stack = deque()  # 辅助栈,避免重复检测入度为0的顶点
    vex_seq = deque()  # 用于保存排好序的顶点结点
    counter = 0  # 用于记录顶点个数,便于最后判断AOV网中是否存在回路
    indegree = find_id(adj_list, indegree)
    for index, value in enumerate(indegree):  # 初始化temp_stack
        if value == 0:
            temp_stack.append(index)
    while temp_stack:  # 循环直到栈为空为止
        arc_tail_index = temp_stack.pop()
        vertex_node = adj_list.vertexs[arc_tail_index]  # 栈顶顶点出栈
        vex_seq.append(vertex_node)  # 访问当前顶点,进行保存
        counter += 1  # 计数,当前顶点是序列中的第几个顶点
        arc = vertex_node.first_arc  # 获取以当前顶点为弧尾顶点结点的第一条弧
        while arc:  # 更新indegree
            arc_head_index = arc.arc_head_index
            indegree[arc_head_index] -= 1
            if indegree[arc_head_index] == 0:
                # 表示adj_list.vertexs[arc_head_index]该顶点结点已没有前驱结点需对其入栈
                temp_stack.append(arc_head_index)
            arc = arc.next_arc
    if counter < adj_list.vex_num:  # 判断AOV网中是否存在回路
        return "ERROR"  # 表示AOV网中存在回路
    return vex_seq  # 成功返回拓扑序列


def topo_sort_(adj_list: AdjList):
    """
    算法思想:
        (1) 首先求出各顶点的入度,并将入度为0的顶点入栈temp_stack;
        (2) 将各顶点的最早发生时间vexs_early[i]初始化为0
        (3) 只要栈tempa_stack不空,则重复下面处理;
            a. 将栈顶顶点j出栈并压入栈T(生成逆拓扑序列)
            b. 将顶点j的每一个邻接点k的入度减1,如果顶点k的入度变为0,则将顶点k入栈;
            c. 根据顶点j 的最早发生时间vexs_early[j]和弧<j,k>的权值,更新顶点k的最早发生时间ve[k]。
    :param adj_list:
    :return:
    """
    indegree = array('i', [])  # 入度计数
    temp_stack = deque()  # 辅助栈,避免重复检测入度为0的顶点
    vex_seq = deque()  # 用于保存排好序的顶点结点,为逆拓扑做准备
    counter = 0  # 用于记录顶点个数,便于最后判断AOE网中是否存在回路
    vexs_early = array('i', [])  # 用于保存每个顶点所表示的事件最早发生事件
    indegree = find_id(adj_list, indegree)
    for index, value in enumerate(indegree):  # 初始化temp_stack
        if value == 0:
            temp_stack.append(index)
    for i in range(adj_list.vex_num):  # 初始化顶点所表示事件列表最早发生时间
        vexs_early.append(0)
    while temp_stack:  # 循环直到栈为空为止
        arc_tail_index = temp_stack.pop()
        vertex_node = adj_list.vertexs[arc_tail_index]  # 栈顶顶点出栈
        vex_seq.append(arc_tail_index)  # 访问当前顶点,进行保存
        counter += 1  # 计数,当前顶点是序列中的第几个顶点
        arc = vertex_node.first_arc  # 获取以当前顶点为弧尾顶点结点的第一条弧
        while arc:  # 更新indegree
            arc_head_index = arc.arc_head_index
            indegree[arc_head_index] -= 1
            if indegree[arc_head_index] == 0:
                # 表示adj_list.vertexs[arc_head_index]该顶点结点已没有前驱结点需对其入栈
                temp_stack.append(arc_head_index)
            if vexs_early[arc_tail_index] + arc.weight > vexs_early[arc_head_index]:  # 按拓扑顺序计算事件最早发生时间
                vexs_early[arc_head_index] = vexs_early[arc_tail_index] + arc.weight
            arc = arc.next_arc
    if counter < adj_list.vex_num:  # 判断AOV网中是否存在回路
        return "ERROR"  # 表示AOV网中存在回路
    return vex_seq, vexs_early  # 成功返回拓扑序列


def critical_path(adj_list: AdjList):
    """
    关键路径:邻接表

    AOE-网:
        用顶点表示事件,用弧表示活动,弧的权值表示活动所需要的时间

    关键路径:从源点到汇点的最长路径的长度即为完成整个工程任务所需的时间,该路径称为关键路径。关键路径上的活动称为关键活动

    求关键路径的基本步骤:
        (1) 对图中顶点进行拓扑排序,在排序过程中按拓扑序列求出每个事件的最早发生时间vexs_early(i);
        (2) 逆拓扑序列求每个事件的最晚发生时间vexs_last(i)
        (3) 求出每个活动ai的最早开始时间arcs_early(i)和最晚发生时间arcs_last(i);
        (4) 找出arcs_early(i) == arcs_last(i)的活动ai, 即为关键活动。
        下面使用拓扑排序的方法,对torpo_sort进行修改为torpo_sort_,只有细微差别

    算法思想:
        (1) 首先调用修改后的拓扑排序算法,求出每个事件的最早发生时间和逆拓扑序列栈T。
        (2) 将各顶点的最晚发生时间vexs_last[i]初始化为汇点的最早发生时间;
        (3) 只要vex_seq不空,则重复下面处理:
            a.将栈顶顶点j出栈;
            b.对于顶点j的每一个邻接点k,根据顶点k的最晚发生时间vexs_last和弧<j, k>的权值,更新顶点j的最晚发生时间vexs_last[j]。
        (4) 扫描每一条弧,计算其最早发生时间arcs_early[i]和最晚发生时间arcs_early[i],如果arcs_early[i]等于arcs_early[i]则
            falg = 1,表示该弧是关键活动。

    :param adj_list: 创建好的邻接表
    :return: 返回所有的活动及其相关信息

    """
    vex_seq, vexs_early = topo_sort_(adj_list)  # 获取拓扑序列与顶点的最早发生时间
    if not vex_seq:
        return "ERROR"
    vexs_last = array('i', [])  # 每个顶点的最迟发生时间
    for i in range(adj_list.vex_num):  # 初始化vexs_last
        vexs_last.append(vexs_early[adj_list.vex_num-1])  # 将各顶点时间的最迟发生时间初始化为汇点的最早发生时间
    while vex_seq:  # 按逆拓扑顺序求各顶点的最晚发生时间vexs_early[i]
        arc_tail_index = vex_seq.pop()
        arc = adj_list.vertexs[arc_tail_index].first_arc
        while arc:  # 求当前顶点结点(事件)的最晚发生事件
            arc_head_index = arc.arc_head_index
            if vexs_last[arc_tail_index] > vexs_last[arc_head_index] - arc.weight:
                vexs_last[arc_tail_index] = vexs_last[arc_head_index] - arc.weight
            arc = arc.next_arc

    critical_path_seq = deque()
    Active = namedtuple('Active', ['active_tail', 'active_head', 'weight', 'active_early', 'active_last', 'flag'])
    for arc_tail_index in range(adj_list.vex_num):  # 求弧所表示的活动的最早发生时间和最晚发生时间
        arc = adj_list.vertexs[arc_tail_index].first_arc  # 获取以当前顶点结点(事件)为弧尾顶点结点的第一条弧(活动)
        while arc:  # 循环以当前顶点结点(事件)为弧尾顶点结点的所有弧(活动)结点
            arc_head_index = arc.arc_head_index  # 获取以当前顶点结点为弧尾顶点结点的弧的弧头顶点结点在adj_list.vertexs的下标
            arcs_early = vexs_early[arc_tail_index]  # 弧(活动)的最早发生时间=弧尾顶点结点(事件)的最早发生事件
            arcs_last = vexs_last[arc_head_index] - arc.weight  # 弧(活动)的最晚发生事件=弧头顶点结点(事件)的最晚发生事件-弧(活动)的权重
            # print((arc_early, arc_last))
            if arcs_early == arcs_last:   # 如果弧(活动)的最早发生时间==其最晚发生时间,则为关键活动
                flag = 1  # 标记活动为关键活动
            else:
                flag = 0
            active = Active(adj_list.vertexs[arc_tail_index].name,
                            adj_list.vertexs[arc_head_index].name, arc.weight, arcs_early, arcs_last, flag)
            critical_path_seq.append(active)  # 保存活动及其相关信息
            arc = arc.next_arc  #
    return critical_path_seq  # 返回所有的活动及其相关信息


def bfs_k_level(adj_list: AdjList, vertex_node: VertexNode, k: int):
    """
    求距离顶点v0的最短路径长短为k的所有顶点

    已知无向图g,设计算法求距离顶点v0的最短路径长度为k的所有顶点,要求尽可能节省时间。

    【问题分析】:
            由于题目要求找出最短路径长度为k的所有顶点,故从顶点v0开始进行广度优先搜索,将一步、两步可达的.....直
        至k步可达的所有顶点记录下来,同时用一个队列记录所有顶点的层号,输出第k+1层的所有结点即为所求。

    此算法适用于无向图和有向图

    :param adj_list:  创建好的邻接表
    :param vertex_node:  起始顶点
    :param k:  路径长度
    :return:  成功返回k步可达的所有结点及相应层号列表,失败返回"ERROR"
    """
    vex_seq = deque()  # 用于保存k步可达的所有结点及相应层号
    temp_index_queue = deque()  # 遍历过程中所需用到的队列,保存结点在
    temp_level_queue = deque()  # 遍历过程中所需用到的队列,保存结点所在的层号adj_list.vertexs中的下标
    visited = array('i', [])  # 访问标志数组
    for i in range(adj_list.vex_num):  # 初始化访问标志数组
        visited.append(0)
    vertex_index = locate_vertex(adj_list, vertex_node)  # 查找起始结点在adj_list.vertexs中的下标
    if vertex_index is None:  # 未找到返回"ERROR"
        return "ERROR"
    temp_index_queue.append(vertex_index)  # 起始结点下标入栈
    temp_level_queue.append(1)  # 起始结点层号入栈
    visited[vertex_index] = 1  # 起始结点访问标志域置位1,表示已访问过
    vex_seq.append((adj_list.vertexs[vertex_index], 1))  # 保存到返回队列中
    while temp_index_queue:  # 直到栈为空为止
        arc_tail_index = temp_index_queue.popleft()  # 下标栈栈顶出栈
        arc_tail_level = temp_level_queue.popleft()  # 层号栈栈顶出栈
        arc_head_level = arc_tail_level + 1  #
        if arc_head_level > k+1:  # 当访问到k+2层时返回。k+1层已访问完成
            return vex_seq
        arc = adj_list.vertexs[arc_tail_index].first_arc  # 获取当前顶点结点的第一个弧结点
        while arc:  # 获取当前顶点结点的其余弧结点
            arc_head_index = arc.arc_head_index
            if not visited[arc_head_index]:
                temp_index_queue.append(arc_head_index)
                temp_level_queue.append(arc_head_level)
                visited[arc_head_index] = 1
                vex_seq.append((adj_list.vertexs[arc_head_index], arc_head_level))
            arc = arc.next_arc


counter_node = 0  # 记录路径上的顶点数


def dfs_g_path(adj_list: AdjList, index: int):
    """
        用递归的深度优先搜索法在图g中寻找一条包含所有顶点的简单路径。从当前结点adj_list.vex_num[index]成功找
    到时返回1,否则返回0
    :param adj_list: 传递进来的adj_list
    :param index: 起始结点
    :return: 成功返回1,失败返回0
    """
    global counter_node, trav_seq, visited
    visited[index] = 1  # 设置访问标志
    counter_node += 1  # 递归进层时,路径上定点数加1
    trav_seq.append(adj_list.vertexs[index])  # 保存路径上的顶点
    if counter_node == adj_list.vex_num:
        return 1  # 成功返回1
    arc = adj_list.vertexs[index].first_arc  # 找第1个弧结点
    while arc:
        arc_head_index = arc.arc_head_index  # 找第一个邻接点
        if not visited[arc_head_index] and dfs_g_path(adj_list, arc_head_index) == 1:
            return 1  # 从邻接点adj_list.vertexs[arc_head_index]往下搜索,且成功返回
        arc = arc.next_arc
    visited[index] = 0  # 递归退层时,取消访问标志,以便重新遍历该顶点
    counter_node -= 1  # 递归退层时,路径上顶点数减1
    trav_seq.pop()  # 递归退层时,将当前顶点退出
    return 0  # 从当前结点v0出发,无法找到解路径,失败返回


def hamilton(adj_list: AdjList):
    """
    找出在图g中找出一条包含所有顶点的简单路径

        从任一顶点开始进行深度优先搜索,同时记录当前路径结点序列和当前路径结点数目。当递归进层时结点数目加1,
    当递归退层时结点数目减1,当节点数目等于结点总数时,输出当前路径结点序列,成功返回。

    :param adj_list: 已创建好的邻接表
    :return: 简单路径序列
    """
    for i in range(adj_list.vex_num):
        visited.append(0)  # 初始化访问标志数组
    for i in range(adj_list.vex_num):
        if not visited[i] and dfs_g_path(adj_list, i) == 1:  # 一旦找到一条简单路径则返回
            return trav_seq
# test_adj_list.py

from graph.adj_list import VertexNode, ArcNode, AdjList, create_adj_list, travers_graph, dfs_path, \
    topo_sort, topo_sort_, critical_path, bfs_k_level, hamilton


if __name__ == '__main__':
    # v1 = VertexNode('v1')
    # v2 = VertexNode('v2')
    # v3 = VertexNode('v3')
    # v4 = VertexNode('v4')
    # v5 = VertexNode('v5')
    # v6 = VertexNode('v6')
    #
    # arc1 = (v1, v2, 5)
    # arc2 = (v1, v4, 7)
    # arc3 = (v2, v3, 4)
    # arc4 = (v3, v1, 8)
    # arc5 = (v3, v6, 9)
    # arc6 = (v4, v3, 5)
    # arc7 = (v4, v6, 6)
    # arc8 = (v5, v4, 5)
    # arc9 = (v6, v1, 2)
    # arc10 = (v6, v5, 1)
    #
    # adj_list = AdjList()
    # vertexs = [v1, v2, v3, v4, v5, v6]
    # arcs = [arc1, arc2, arc3, arc4, arc5, arc6, arc7, arc8, arc9, arc10]
    # create_adj_list(adj_list, vertexs, arcs, 'DN')
    # print(adj_martix.arcs_matrix)
    # travs_list = travers_graph(adj_list, 'b', 'nr')
    # for trav_seq in travs_list:
    #     for vertex_node in trav_seq:
    #         print(vertex_node.name)
    # path = dfs_path(adj_list, v1, v3)
    # for node in path:
    #         print(node.name)

    # 测试拓扑排序算法topo_sort
    # v1 = VertexNode('v1')
    # v2 = VertexNode('v2')
    # v3 = VertexNode('v3')
    # v4 = VertexNode('v4')
    # v5 = VertexNode('v5')
    # v6 = VertexNode('v6')
    #
    # arc1 = (v1, v2, 1)
    # arc2 = (v1, v3, 1)
    # arc3 = (v1, v4, 1)
    # arc4 = (v4, v5, 1)
    # arc5 = (v6, v4, 1)
    # arc6 = (v6, v5, 1)
    # arc7 = (v3, v5, 1)
    # arc8 = (v3, v2, 1)
    #
    # adj_list = AdjList()
    # vertexs = [v1, v2, v3, v4, v5, v6]
    # arcs = [arc1, arc2, arc3, arc4, arc5, arc6, arc7, arc8]
    # create_adj_list(adj_list, vertexs, arcs, 'DG')
    # topo_seq = topo_sort(adj_list)
    # topo_seq = ",".join([vertex_node.name for vertex_node in topo_seq])
    # print(topo_seq)

    # 测试关键路径算法critical_path
    # v0 = VertexNode('v0')
    # v1 = VertexNode('v1')
    # v2 = VertexNode('v2')
    # v3 = VertexNode('v3')
    # v4 = VertexNode('v4')
    # v5 = VertexNode('v5')
    # v6 = VertexNode('v6')
    # v7 = VertexNode('v7')
    # v8 = VertexNode('v8')
    #
    # arc1 = (v0, v1, 6)
    # arc2 = (v0, v2, 4)
    # arc3 = (v0, v3, 5)
    # arc4 = (v1, v4, 1)
    # arc5 = (v2, v4, 1)
    # arc6 = (v3, v5, 2)
    # arc7 = (v4, v6, 9)
    # arc8 = (v4, v7, 7)
    # arc9 = (v5, v7, 4)
    # arc10 = (v6, v8, 2)
    # arc11 = (v7, v8, 4)
    #
    # adj_list = AdjList()
    # vertexs = [v0, v1, v2, v3, v4, v5, v6, v7, v8]
    # arcs = [arc1, arc2, arc3, arc4, arc5, arc6, arc7, arc8, arc9, arc10, arc11]
    # create_adj_list(adj_list, vertexs, arcs, 'DN')
    # # topo_seq, vex_early = topo_sort_(adj_list)
    # # print(topo_seq)
    # # print(vex_early)
    # critical_path_ = critical_path(adj_list)
    # for item in critical_path_:
    #     print(item)

    # 测试 从某一顶点开始找到距其路径长度为k以内的所有顶点算法bfs_k_level
    # v0 = VertexNode('v0')
    # v1 = VertexNode('v1')
    # v2 = VertexNode('v2')
    # v3 = VertexNode('v3')
    # v4 = VertexNode('v4')
    # v5 = VertexNode('v5')
    # v6 = VertexNode('v6')
    # v7 = VertexNode('v7')
    # v8 = VertexNode('v8')
    #
    # arc1 = (v0, v1, 6)
    # arc2 = (v0, v2, 4)
    # arc3 = (v0, v3, 5)
    # arc4 = (v1, v4, 1)
    # arc5 = (v2, v4, 1)
    # arc6 = (v3, v5, 2)
    # arc7 = (v4, v6, 9)
    # arc8 = (v4, v7, 7)
    # arc9 = (v5, v7, 4)
    # arc10 = (v6, v8, 2)
    # arc11 = (v7, v8, 4)
    #
    # adj_list = AdjList()
    # vertexs = [v0, v1, v2, v3, v4, v5, v6, v7, v8]
    # arcs = [arc1, arc2, arc3, arc4, arc5, arc6, arc7, arc8, arc9, arc10, arc11]
    # create_adj_list(adj_list, vertexs, arcs, 'DN')
    # vex_seq = bfs_k_level(adj_list, v0, 3)
    # print([(vertex_node[0].name, vertex_node[1]) for vertex_node in vex_seq])

    # 测试在图g中找出一条包含所有顶点的简单路径算法hamilton
    v1 = VertexNode('v1')
    v2 = VertexNode('v2')
    v3 = VertexNode('v3')
    v4 = VertexNode('v4')
    v5 = VertexNode('v5')
    v6 = VertexNode('v6')

    arc1 = (v1, v2, 1)
    arc2 = (v2, v5, 1)
    arc3 = (v5, v6, 1)
    arc4 = (v6, v4, 1)
    arc5 = (v1, v1, 1)
    arc6 = (v1, v3, 1)
    arc7 = (v2, v3, 1)
    arc8 = (v5, v3, 1)
    arc9 = (v6, v3, 1)
    arc10 = (v4, v3, 1)

    adj_list = AdjList()
    vertexs = [v1, v2, v3, v4, v5, v6]
    arcs = [arc1, arc2, arc3, arc4, arc5, arc6, arc7, arc8, arc9, arc10]
    create_adj_list(adj_list, vertexs, arcs, 'NDG')
    trav_seq = hamilton(adj_list)
    print(','.join([vertex_node.name for vertex_node in trav_seq]))
邻接表是一种用于表示图的数据结构,它通过将每个顶点与其相邻的顶点列表关联起来来表示图。在Python中,可以使用类来实现邻接表。 首先,我们需要创建一个Vertex类来存储顶点信息。该类包含一个data属性用于存储顶点的数据,以及一个firstEdge属性用于指向该顶点的第一条边。\[3\] 接下来,我们需要创建一个Edge类来存储边的信息。该类包含一个adjVex属性用于存储相邻顶点的索引,以及一个next属性用于指向下一条边。\[2\] 然后,我们可以创建一个LinkedGraph类来构造邻接表。在构造函数中,我们需要传入顶点列表和边列表作为参数。在构造过程中,我们可以根据边列表的信息来构建邻接表。\[1\] 最后,我们可以创建一个测试类来输出邻接表。在测试类中,我们可以创建一个LinkedGraph对象,并调用其print方法来输出邻接表。\[1\] 以下是一个示例代码: ```python class Vertex(object): def __init__(self, data): self.data = data self.firstEdge = None class Edge(object): def __init__(self, adjVex): self.adjVex = adjVex self.next = None class LinkedGraph(object): def __init__(self, vers, edges): self.vertexList = \[\] for v in vers: self.vertexList.append(Vertex(v)) for e in edges: v1 = self.vertexList\[vers.index(e\[0\])\] v2 = self.vertexList\[vers.index(e\[1\])\] edge1 = Edge(vers.index(e\[1\])) edge2 = Edge(vers.index(e\[0\])) edge1.next = v1.firstEdge edge2.next = v2.firstEdge v1.firstEdge = edge1 v2.firstEdge = edge2 def print(self): for v in self.vertexList: print(v.data, end=": ") edge = v.firstEdge while edge: print(self.vertexList\[edge.adjVex\].data, end=" ") edge = edge.next print() if __name__ == "__main__": vers = \['A', 'B', 'C', 'D', 'E', 'F', 'G'\] edges = \[\['A', 'C'\], \['A', 'D'\], \['B', 'E'\], \['B', 'F'\], \['C', 'E'\], \['C', 'G'\], \['D', 'F'\]\] g = LinkedGraph(vers, edges) g.print() ``` 这段代码会输出以下邻接表: ``` A: C D B: E F C: A E G D: A F E: B C F: B D G: C ``` 这就是使用邻接表构造无向图的Python代码。希望对你有帮助! #### 引用[.reference_title] - *1* *2* *3* [Python邻接表(数据结构)](https://blog.csdn.net/wn2001/article/details/117513377)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值