Solving the first K short path--Yen algorithm, C++ implementation

1. The meaning of the top K short path

        The data structure or algorithm textbooks of various universities generally introduce the problem of solving the shortest path.

        There are generally two types of shortest path problems:

                Single-source shortest path, that is, the path from a specified point to other points;

                The shortest path between two pairs of vertices.

        The shortest path problem in a narrow sense can only solve one shortest path, but many times we need to know the second shortest, third shortest... and other suboptimal solutions and suboptimal solutions, and the Yen algorithm introduced in this article It is an algorithm to solve such problems.

         The Yen algorithm is named after the inventor. I believe that students who have read this article should also read other articles to learn about the origin of the Yen algorithm. The articles written by other big cows generally introduce the A* algorithm, heuristic functions, etc., but I am just an ordinary freshman student, and I will only introduce this algorithm to you in a relatively superficial way. The implementation method I gave And the various structures are also constructed by myself, so if you are a college student like me, this article may be more suitable for you to understand Yen's algorithm.

2. Some definitions that must be understood before understanding the algorithm

        

I use this diagram to illustrate the necessary concepts.

        1. Deviation point: the deviation point of path P3 relative to P1 is 2

        2. Deviation path: The deviation path of P3 relative to P1 is 2 4 5

3. Explain the Yen algorithm

        

For the numbers in the above figure, it is stipulated that the numbers corresponding to CDFEGH are 1, 2, 3, 4, 5, and 6, respectively.

Suppose we ask for k shortest paths from point C to point H, and suppose we have obtained the shortest path P1 between CH.

The idea of ​​Yen's algorithm is to generate deviation paths on the shortest path P1, and to obtain k shortest paths in turn.

Next, I use P1 to obtain P2 according to Yen's idea.

It is not difficult to find that the P1 path in the figure should be: CEFH.

Yen's algorithm requires us to treat every point on the P1 path except the end point as a deviation point in turn to solve,

First consider point C as a deviation point, then if we know the shortest path from point C to point H (of course, this path cannot coincide with P1), then this path is the path obtained from point C as a deviation point, I set for pk1.

Next, point E is regarded as a deviation point. Similarly, if we know the shortest path from point E to point H (not coincident with the existing ones),

Then the path obtained by point E as the deviation point is the path composed of the deviation path of point E + CE side, and I set it as pk2.

Then take point F as the deviation point, assuming that the shortest non-overlapping path from point F to point H is obtained, then the path obtained from point F as the deviation point is the deviation path of point F + CEF, and I set it as pk3.

So we got pk1 pk2 pk3, then I only need to select a path with the smallest total weight, which is the second shortest path after P1. I'll call it P2.

Then, in order to solve P3, you only need to bring P2 into the above description as the original P1 (that is, the paths on the path of P2 except the end point are sequentially used as deviation points to solve). The same is true for solving P4, P5....

However, we still have many problems left in the above description:

        1. How to ask for P1?

        2. How to find the non-overlapping shortest path from each deviation point to the end point?

        3. What details should be paid attention to when solving P3, P4, P5... and P2?

For 1:

        P1 can be obtained by directly calling the Dijkstra algorithm

For 2:

        It is still calling Dijkstra's algorithm, but we have to make some changes to Dijkstra's algorithm. The modification is reflected in the initialization of path and dist. From the above description, we know that every time we solve the path from the deviation point to the end point, there are some edges that cannot be used, so we only need to "delete" these edges from our graph during initialization.

        Taking point C as the deviation point as an example, we only need to set Path[number of vertex E]=-1; Dist[number of vertex E]=MAX;

For 3:

        In fact, the details that need to be paid attention to in question 3 are still on the "side that cannot be used".

For the process of solving P2 from P1, we only need to consider that the deviation path does not repeat with P1, but in fact, when we solve Pk+1 from Pk, our solved paths already have K, so we need to solve Pk+ At 1 o'clock, it must not coincide with the previous K key.

4. Code

1. Some structures defined by myself

class Path//用于描述路径的类
{
public:
	int VerList[Max];
	int Alleweight;
	int NodeNum;
	int Dist[Max];
}

class VectorForPath//用于存储已选路径和候选路径的类,我是用的小根堆存储
{
public:
	Path paths[Max];//下标取从1 到 n 即 Num为元素个数亦为数组下标
	int Num = 0;
	void Add(Path p);//入队
	void siftForAdd(int num);//为入队的调整函数

	Path SelectAndDelete();//出队
	void siftForDelete(int num);

	Path GetTop()
	{
		return paths[1];
	}
};

2. About Yen

I will first write out my process of building Yen's algorithm, and then give the code.

First of all, for the function of obtaining direct K short paths between two vertices, the desired output is: the set of the first K short paths

The necessary input is: the parameter K of the K short path, the two specified vertices vBegin vEnd

故有void Yen(int vBegin,int vEnd,int K,VectorForPath& VFP);

//Yen算法
	void Yen(int vBegin, int vEnd,VectorForPath& VFP,int K);//输出是k短路径的集合
	bool Yen_initial(int vBeign, int vEnd, VectorForPath& VFP);//对Yen进行初始化,返回值表示了指点两点间是否存在一条路径,若无则直接结束算法

	bool myDijkstra(int vID,int vEnd, VectorForPath VFP,Path& ptemp,Path Pk);
//自定义的Dijkstra
	void InitialFormyDijkstra(int path[], int dist[], int vID, VectorForPath VFP,Path Pk);
//为满足Yen的要求,对Dijkstra做特殊的初始化
	void dealDijkstra(int path[], int dist[],Path& p,int vEnd,Path Pk);
//对由Dijkstra算法得到的参数Path与Dist进行转化,将其转化为我自己构造的结构 Path里
    void SearchUnusedEdge(VectorForPath VFP, int arr[], int& num,int vID,Path Pk);
//用于找到不能使用的边的函数
	bool ifEgzist(Path p, VectorForPath VFP_Wait);
//用于新得到的候选路径是否与侯选路径集合里的路径重合

Next, the specific implementation of each function is given.

        (1)、Yen

//Yen
void GraphAdjMatrix::Yen(int vBegin, int vEnd, VectorForPath& VFP, int K)
{
    bool BOOL= Yen_initial(vBegin, vEnd, VFP);
    if (BOOL == false)return;
    Path p = VFP.GetTop();
    VectorForPath VFP_Wait;//VFP 是用堆存储的,其数组下标从1开始
    int k = 2;
    while (k <= K)
    {       
        for (int i = 0; i < p.NodeNum-1; i++)
        {
            Path ptemp = p;
            bool ifHasPath =myDijkstra(p.VerList[i],vEnd,VFP,ptemp,p);
            bool s = ifEgzist(ptemp, VFP_Wait);
            if (ifHasPath&&!ifEgzist(ptemp,VFP_Wait))VFP_Wait.Add(ptemp);
        }
        if (VFP_Wait.Num == 0)return;
        p = VFP_Wait.SelectAndDelete();
        VFP.Add(p);
        k++;
    }
}

(2)、Yen_initial

bool GraphAdjMatrix::Yen_initial(int vBegin, int vEnd, VectorForPath& VFP)
{
    Path p;
    bool ifHasPath=myDijkstra(vBegin,vEnd,VFP,p,p);
    if (ifHasPath)
    {
       // dealDijkstra(path, dist, p, vEnd);
        VFP.Add(p);
        return true;
    }
    else return false;
}

(3), my Dijkstra

bool GraphAdjMatrix::myDijkstra(int vID,int vEnd,VectorForPath VFP,Path& p,Path Pk)
{
   /*
   1、vID为第一个选定点 初始化 path dist 
   2、选取dist最小的一个边 将此边的邻接点变为已解点
   3、考虑新加入点的影响,对dist与path进行修改
   4、重复2、3
  //以上为原dijkstra算法
   而为满足Yen算法的要求 应做如下修改:
            考虑到Yen算法求解偏离点到汇点的最短路径时是建立在之前的偏离路径基础上的,故而对部分偏离点来说,可能会存在不能选的边
            在原有的初始化过后,要对path 与 dist 进行特殊的修改,把不能选的边从dist中把权值变为无穷大,从path中的前驱从vID变为-1

            而后再从剩余的dist中选取最小的一个边。将此边的邻接点变为已解顶点
            然后考虑新加入点的影响,对dist与path进行修改
            重复上述两句话,如此就得到了在Yen算法限制下的最短偏离路径
            不过实际上,上述的改变了的dijsktra算法只是求解出了一个偏离点的从偏离点到汇点的最短路径
            将此路径与从源点到偏离点的路径拼接起来方得到了点i作为偏离点是的路径p,将p放入侯选路径集合里

            当每个偏离点都进行完上述操作后,那么我们从侯选路径集合里找出Alleweight最小的一个路径,作为Pk短路径
            当然,要判断此次得到是否已经与候选集和了的重合
          */  
            
    //Part One-----initial

    int path[Max];
    int dist[Max];
    InitialFormyDijkstra(path, dist, vID, VFP,Pk); //满足Yen的初始化path dist
    for (int i = 0; i < p.NodeNum; i++)
    {
        if (p.VerList[i] != vID)
            solved[i] = true;
        else break;
    }
    dist[vID - 1] = 0;
    for (int i = 0; i < VerNum - 1; i++)
    {
        int minID = minSelect(dist);
        if (minID == -1)break;
        DP_change(path, dist, minID);
    }

    Path ptemp;
    dealDijkstra(path, dist, ptemp,vEnd,Pk);
    //这时的dist是偏离点到其余各点的最短路径
    //Pk 是当前轮次的主路径,当求P1时 Pk为None
    //求p1时 p1.dist被赋值为

    //经过deal ptemp:
        /*
            1、verList 里保存着偏离点到vEnd的路径
            2、eWeigth 是偏离点到vEnd的总权值
            3、ptemp.dist 里保存着偏离点到其余各点的最短距离
        
        */

    if (ptemp.VerList[ptemp.NodeNum - 1] != vEnd)return false;

    for (int i = 0; i < VerNum; i++)
    {
        p.Dist[i] = ptemp.Dist[i];
    }
    //把源点到偏离点路径与偏离点到汇点路径拼接起来
    //我想要返回的p是一个候选路径
    if (p.NodeNum != 0)
    {   
        int Pnum;
        for (Pnum = 0; Pnum < p.NodeNum; Pnum++)
        {
            if (Pk.VerList[Pnum] == ptemp.VerList[0])break;
        }
        p.Alleweight = Pk.Dist[vID-1]+ ptemp.Alleweight;
        int tempnum = p.NodeNum;
        p.NodeNum = Pnum+ ptemp.NodeNum;
        tempnum = (p.NodeNum > tempnum) ? p.NodeNum : tempnum;
        int j;
        for (j = 0; j < ptemp.NodeNum; Pnum++,j++)
        {
            p.VerList[Pnum] = ptemp.VerList[j];
        }
    }
    else
    {
        p = ptemp;
    }
    //TODO
    if (p.VerList[p.NodeNum - 1] != vEnd)return false;
    else return true;
}

(4)InitialFormyDijkstra

void GraphAdjMatrix::InitialFormyDijkstra(int path[], int dist[], int vID, VectorForPath VFP,Path Pk)
{
    //Part one normalInitial
    for (int i = 0; i < VerNum; i++)solved[i] = false;
    solved[vID - 1] = true;
    for (int i = 0; i < VerNum; i++)
    {
        if (AdjMatrix[vID - 1][i] != 0)
        {
            path[i] = vID;
            dist[i] = AdjMatrix[vID - 1][i];
        }
        else
        {
            path[i] = -1;
            dist[i] = MAX;
        }

    }
    dist[vID - 1] = 0;

    //Part two ForYen
    int arr[Max]; int num = 0;
    SearchUnusedEdge(VFP, arr, num,vID,Pk);//arr 里保存的是与vID相连的且不能使用的边的邻接点
    for (int i = 0; i < num; i++)
    {
        path[arr[i] - 1] = -1;
        dist[arr[i] - 1] = MAX;
    }

}

(5)、deal Dijkstra

void GraphAdjMatrix::dealDijkstra(int path[], int dist[], Path& p,int vEnd,Path Pk)
{
    //把path 与 dist 转化为 p里的verlist 、alleweight 、 nodenum

  
    Stackint s;
    int temp = vEnd;
   if(path[vEnd-1]!=-1) s.pushStack(vEnd);
    while (path[temp-1] != -1)
    {
        s.pushStack(path[temp - 1]);
        temp = path[temp - 1];
    }
    int num = 0;
    while (!s.StackEmpty())
    {
        s.popStack(p.VerList[num++]);
    }
    p.NodeNum = num; 
    p.Alleweight = dist[vEnd - 1];    
    
    int flag = 0;
    if (Pk.NodeNum != 0)
    { 
        for (int i = 0; i < VerNum; i++)
        {
            p.Dist[i] = Pk.Dist[i];
        }
        int vID = p.VerList[0];
        for (int i = 0; i < p.NodeNum; i++)
        {
            int vIDtemp = p.VerList[i];
            p.Dist[vIDtemp - 1] = dist[vIDtemp - 1] + Pk.Dist[vID - 1];
        }
    }
    else
    {
        for (int i = 0; i < VerNum; i++)
        {
            p.Dist[i] = dist[i];
        }
    }
  
}

(6)、searchUnusedEdge

void GraphAdjMatrix::SearchUnusedEdge(VectorForPath VFP, int arr[], int& num,int vID,Path Pk)
{
    for (int i = 1; i <= VFP.Num; i++)
    {
        Path p = VFP.paths[i];
        for (int j = 0; j < p.NodeNum; j++)
        {
            if (Pk.VerList[j] != p.VerList[j])break;
            if (p.VerList[j] == vID)
            {
                if (j + 1 < p.NodeNum)
                {
                    arr[num++] = p.VerList[j + 1];    
                }
                break;
            }

        }
    }
}

(7)ifEgzist

bool GraphAdjMatrix::ifEgzist(Path p, VectorForPath VFP_Wait)
{
    for (int i = 1; i <= VFP_Wait.Num; i++)
    {
        if (p == VFP_Wait.paths[i])return true;
    }
    return false;
}

------------------------------------------------------------------------------------------------------------

It's the first time I write a blog, and it's really difficult to write, and the writing is really bad. . . .

I thought I understood a lot of things well but it was really hard to express them.

This article is far from meeting the requirements of teaching.

make persistent efforts.

Guess you like

Origin blog.csdn.net/qq_52642915/article/details/118439855