浙大陈越何钦铭数据结构07-图6 旅游规划【最小堆实现】

题目:

题目和浙大陈越何钦铭数据结构07-图6 旅游规划是一样的,不同的是用最小堆实现函数【FindMinDist】。

时间复杂度对比:
浙大陈越何钦铭数据结构07-图6 旅游规划:
创建图(CreateGraph):时间复杂度为O(N^2),因为需要使用两层循环初始化邻接矩阵。
插入边(InsertEdge):时间复杂度为O(1),因为只是将边的距离和代价插入到邻接矩阵中。
构建图(BuildGraph):时间复杂度为O(E),其中E为边的个数。需要进行E次的边的插入操作。
查找未被收录顶点中dist最小者(FindMinDist):时间复杂度为O(N),需要遍历所有未收录的顶点,查找其中dist最小的顶点。
Dijkstra算法主循环:时间复杂度为O(N^2),每次循环都需要找到未收录顶点中dist最小的顶点,并更新其周围顶点的dist和cost值。
综上所述,整个算法的时间复杂度为O(N^2)。

堆实现代码:
如果将 FindMinDist 函数使用最小堆实现,会使得 Dijkstra 算法的时间复杂度变为 O((N + E)logN),其中 N 为顶点数,E 为边数。
具体分析如下:
创建图(CreateGraph):时间复杂度仍为 O(N^2),与之前相同。
插入边(InsertEdge):时间复杂度仍为 O(1),与之前相同。
构建图(BuildGraph):时间复杂度仍为 O(E),与之前相同。
查找未被收录顶点中dist最小者(FindMinDist):使用最小堆实现后,每次查找最小值的时间复杂度为 O(logN),总共需要进行 N 次查找,因此时间复杂度为 O(NlogN)。
Dijkstra算法主循环:在每个节点更新最短路径时,需要将其邻接节点的信息插入最小堆中,插入一个节点的时间复杂度为 O(logN),总共需要插入 E 个节点,因此时间复杂度为 O(ElogN)。同时,在每个节点更新最短路径时,还需要进行一次堆操作,将堆中的最小值取出,时间复杂度为 O(logN),总共需要进行 N 次堆操作,因此时间复杂度为 O(NlogN)。
综上所述,使用最小堆实现的 Dijkstra 算法的时间复杂度为 O((N + E)logN)。相比于之前的 O(N^2),当图的规模较大时,使用最小堆可以提高算法的效率。

代码:

#include 
#include 
#include 

#define MAX_VERTEX_NUM 500
#define MAX_DIST 501
#define MAX_COST 501
#define ERROR -1
#define MIN_DATA -1000

typedef int ELEMENT_TYPE;
typedef int Vertex;

struct _MinHeap
{
    ELEMENT_TYPE *Elements;
    int Size;
    int Capacity;
};
typedef struct _MinHeap *MinHeap;

struct _Edge
{
    Vertex V, W;
    int dist, cost;
};
typedef struct _Edge *Edge;

struct _MGraph
{
    int Nv, Ne;
    int dist[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
    int cost[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
};
typedef struct _MGraph *MGraph; /* 以邻接矩阵存储的图的类型  */

void InsertEdge(MGraph G, Edge E); // 插入边
MGraph CreateGraph(int vertexNum); // 初始化图
MGraph BuildGraph();

bool isEmpty(MinHeap H);
bool isFull(MinHeap H);
void PercUp(MinHeap H, int p, int dist[]);
ELEMENT_TYPE DelMin(MinHeap H);
void FreeHeap(MinHeap H);
MinHeap CreateHeap(int MaxSize);
void BuildMinHeap(MinHeap H, int dist[]);

Vertex FindMinDist(MinHeap H, int dist[], bool collected[]);
void Dijkstra(MGraph G, int dist[], int cost[], Vertex S);

Vertex src, dst;
// 对于全局的int数组自动初始化为0,bool数组初始化为false
int dist[MAX_VERTEX_NUM];
int cost[MAX_VERTEX_NUM];
bool collected[MAX_VERTEX_NUM];

/*
07-图6 旅游规划

难度:3颗星

4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20

3 40
 */

int main()
{
    MGraph G = BuildGraph();
    Dijkstra(G, dist, cost, src);
    printf("%d %d\n", dist[dst], cost[dst]);

    return 0;
}

MGraph CreateGraph(int vertexNum)
{
    MGraph G = (MGraph)malloc(sizeof(struct _MGraph));
    G->Nv = vertexNum;
    G->Ne = 0;
    Vertex V, W;

    for (V = 0; V < vertexNum; V++)
    {
        for (W = 0; W < vertexNum; W++)
        {
            G->dist[V][W] = MAX_DIST;
            G->cost[V][W] = MAX_COST;
        }
    }

    return G;
}

void InsertEdge(MGraph G, Edge E)
{
    /* 插入边 */
    G->dist[E->V][E->W] = E->dist;
    G->cost[E->V][E->W] = E->cost;

    /* 若是无向图则要反向也插入 */
    G->dist[E->W][E->V] = E->dist;
    G->cost[E->W][E->V] = E->cost;
}

MGraph BuildGraph()
{
    MGraph G;
    Edge E;
    int Nv, Ne;
    scanf("%d %d %d %d", &Nv, &Ne, &src, &dst);
    G = CreateGraph(Nv);
    if (Ne)
    {
        G->Ne = Ne;
        E = (Edge)malloc(sizeof(struct _Edge));

        for (int i = 0; i < G->Ne; i++)
        {
            scanf("%d %d %d %d", &E->V, &E->W, &E->dist, &E->cost);
            InsertEdge(G, E);
        }

        free(E);
    }

    return G;
}

Vertex FindMinDist(MinHeap H, int dist[], bool collected[])
{
    Vertex minV = ERROR;

    while (H->Size > 0)
    {
        // 从堆中取出最小值,并维护最小堆的有效性。
        minV = DelMin(H);
        break;
    }

    return minV;
}

void Dijkstra(MGraph G, int dist[], int cost[], Vertex S)
{
    Vertex V, W;

    /* 初始化:此处默认邻接矩阵中不存在的边用INFINITY表示 */
    for (V = 0; V < G->Nv; V++)
    { // dist和cost分别保存的是源点到顶点V的距离和开销
        dist[V] = G->dist[S][V];
        cost[V] = G->cost[S][V];
    }
    /* 先将起点收入集合 */
    dist[S] = 0;
    cost[S] = 0;
    collected[S] = true;

    // 根据dist对未收录顶点创建最小堆
    MinHeap H = CreateHeap(MAX_VERTEX_NUM);

    for (V = 0; V < G->Nv; V++)
    {
        if (collected[V] == false)
        { // H->Elements保存的是未收集顶点的编号,本例依次是1,2,3
            H->Elements[++H->Size] = V;
        }
    }

    BuildMinHeap(H, dist);

    while (1)
    {
        /* V = 未被收录顶点中dist最小者 */
        V = FindMinDist(H, dist, collected);
        if (V == ERROR)      /* 若这样的V不存在 */
            break;           /* 算法结束 */
        collected[V] = true; /* 收录V */

        for (W = 0; W < G->Nv; W++) /* 对图中的每个顶点W */
            /* 若W是V的邻接点并且未被收录 */
            if (collected[W] == false && G->dist[V][W] < MAX_DIST)
            {
                if (G->dist[V][W] < 0) /* 若有负边 */
                    return;            /* 不能正确解决,返回错误标记 */
                /* 若收录V使得dist[W]变小 */
                if (dist[V] + G->dist[V][W] < dist[W])
                {
                    dist[W] = dist[V] + G->dist[V][W]; /* 更新dist[W] */
                    cost[W] = cost[V] + G->cost[V][W];
                }
                else if (dist[V] + G->dist[V][W] == dist[W] &&
                         cost[V] + G->cost[V][W] < cost[W])
                {
                    cost[W] = cost[V] + G->cost[V][W];
                }
            }
    } /* while结束*/

    FreeHeap(H);
    free(G);
}

bool isEmpty(MinHeap H)
{
    return H->Size == 0;
}

bool isFull(MinHeap H)
{
    return H->Size == H->Capacity;
}

ELEMENT_TYPE DelMin(MinHeap H)
{
    if (!isEmpty(H))
    {
        ELEMENT_TYPE min, last;
        int parent, child;

        min = H->Elements[1];
        last = H->Elements[H->Size--];
        for (parent = 1; 2 * parent <= H->Size; parent = child)
        {
            child = 2 * parent;
            if ((child != H->Size) && (dist[H->Elements[child]] > dist[H->Elements[child + 1]]))
            {
                child++;
            }
            if (dist[last] <= dist[H->Elements[child]])
            {
                break;
            }
            else
            {
                H->Elements[parent] = H->Elements[child];
            }
        }
        H->Elements[parent] = last;

        return min;
    }
    else
    {
        return ERROR;
    }
}

void PercUp(MinHeap H, int p, int dist[])
{ // 根据dist的值,决定堆中保存顶点的位置。
    int parent, child;
    ELEMENT_TYPE X;
    X = H->Elements[p];

    for (parent = p; 2 * parent <= H->Size; parent = child)
    {
        child = 2 * parent;
        if ((child != H->Size) && (dist[H->Elements[child]] > dist[H->Elements[child + 1]]))
        {
            child++;
        }
        if (dist[X] <= dist[H->Elements[child]])
        {
            break;
        }
        else
        {
            H->Elements[parent] = H->Elements[child];
        }
    }

    H->Elements[parent] = X;
}

void FreeHeap(MinHeap H)
{
    if (H != NULL)
    {
        free(H->Elements);
        free(H);
    }
}

MinHeap CreateHeap(int MaxSize)
{
    MinHeap H = (MinHeap)malloc(sizeof(struct _MinHeap));
    H->Elements = (ELEMENT_TYPE *)malloc((MaxSize + 1) * sizeof(ELEMENT_TYPE));
    H->Elements[0] = MIN_DATA;
    H->Size = 0;
    H->Capacity = MaxSize;

    return H;
}

void BuildMinHeap(MinHeap H, int dist[])
{ // p表示顶点在堆中的位置
    int p;
    for (p = H->Size / 2; p > 0; p--)
    {
        PercUp(H, p, dist);
    }
}

小结:
本题的最小堆比用循环的方式实现FindMinDist要难一些,主要是要理解和修改堆的几个实现,核心是构造和维护最小堆要根据dist的值,来维护对应的顶点。

你可能感兴趣的:(数据结构,Dijkstra,多权重,算法)