Varios algoritmos para resolver el problema del viajante de comercio

1. Descripción del problema

El problema del viajante de comercio es que el viajero quiere viajar n ciudades, y requiere que cada ciudad sea visitada una y solo una vez y luego regrese a la ciudad de origen, y se requiere que la distancia recorrida sea la más corta.

Primero, mediante un grafo no dirigido dado, es decir, n vértices, m aristas no dirigidas, cada arista tiene un peso que representa la distancia entre dos puntos, y se requiere pasar por cada punto y volver al origen, encontrar el camino más corto.

2. Análisis de problemas

(1) Análisis: Hay (n-1)! rutas de viaje que parten del punto inicial, que es igual al número de permutación de n-1 nodos excepto el nodo inicial, por lo que el problema del viajante de comercio es un problema de permutación. Al enumerar (n-1)! rutas de viaje, encuentre una ruta de viaje con el algoritmo de costo mínimo y su tiempo de cálculo sea O(n!).

(2) Complete la solución: puede evitar el problema combinando el gráfico no dirigido dado en la pregunta, como: Figura 1, es decir, el punto de la ciudad y la distancia requerida a otras ciudades en una matriz bidimensional, y asegúrese de que la distancia a sí misma se establece en infinito En el esquema aparece el esquema de la ciudad original, como por ejemplo: Figura 2. Luego resuelva el algoritmo correspondiente a través de programación dinámica, método codicioso, ramificación y límite y otros algoritmos.

(3) Resultados esperados: muestra los resultados de la mejor solución después de que se ejecuta el programa.

Figura 1 Gráfico no dirigido

Figura 2 La matriz correspondiente al grafo no dirigido

3. Diseño y análisis de algoritmos

1. Entrada y salida del algoritmo

(1) Entrada del algoritmo: el número de ciudades, la distancia a otras ciudades (la distancia a sí mismo se establece en infinito);

(2) Salida del algoritmo: el valor mínimo de la ruta total requerida para pasar por todas las ciudades

2. Descripción del algoritmo

(1) Método codicioso: el algoritmo codicioso siempre hace la mejor elección en la actualidad. El algoritmo codicioso no considera la optimización general, y la elección que hace es solo una elección óptima local en cierto sentido. Aunque el algoritmo voraz no puede obtener la solución óptima general para todos los problemas, puede producir la solución óptima general para muchos problemas. Por ejemplo, el problema del camino más corto de fuente única, el problema del árbol de expansión mínimo, etc. En algunos casos, incluso si el algoritmo voraz no puede obtener la solución óptima general, el resultado final es una buena aproximación de la solución óptima.

(2) Método de programación dinámica: el algoritmo de programación dinámica es similar al método divide y vencerás, y su idea básica es descomponer el problema a resolver en varios subproblemas; sin embargo, los subproblemas obtenidos a través de la descomposición a menudo no son independientes unos de otros. El número de subproblemas diferentes a menudo es solo de orden polinomial. Al resolver por divide y vencerás, algunos subproblemas se recalculan muchas veces. Si puede guardar las respuestas a los subproblemas resueltos y encontrar las respuestas cuando las necesite, puede evitar muchos cálculos repetidos y obtener un algoritmo de tiempo polinomial.

(3) Método de ramificación y límite: método de ramificación y límite: adopte la amplitud primero para generar los nodos del árbol de espacio de estado y use el método de función de poda. "Branch" utiliza una estrategia de ancho primero para generar todas las ramas (es decir, nodos secundarios) del nodo extendido a su vez.

"Límite" calcula el límite superior (o límite inferior) del nodo durante el proceso de expansión del nodo y lo recorta durante la búsqueda para mejorar la eficiencia de la búsqueda.

3. Descripción del algoritmo

(1) Método codicioso: adopte la estrategia del punto vecino más cercano, comience desde cualquier punto, vaya al más cercano de los puntos que no se han pasado cada vez, hasta que se pasen todos los puntos, y finalmente regrese al punto de partida. Es decir, si actualmente se encuentra en el vértice u, tome el punto p más cercano al vértice u.

(2) Método de programación dinámica: la suma de la ruta más corta de un punto i de regreso al punto de inicio sin pasar por ningún punto es mp[i][0] (el punto inicial predeterminado es 0), dp[i][0]= pf[i][0];(1<=i<n). La suma del camino más corto desde el punto i a través del conjunto S (el número en representación binaria es j) es el valor mínimo del conjunto S-{k} a partir del punto i después de pasar por un cierto punto k en el conjunto S. dp[i][j]=min{mp[i][k]+dp[k][j-(1<<(k-1))};

(3) Método de ramificación y límite: primero, el valor resuelto por el método codicioso se usa como límite superior, y la mitad de la suma de los dos lados más cercanos de cada punto se usa como límite inferior. El método de bifurcación y límite establece la función objetivo y toma el nodo con el valor más pequeño de la función objetivo de la cola de prioridad cada vez. Primero juzgue si se han pasado n-1 puntos. Si se han pasado n-1 puntos, entonces la suma de la ruta más corta se puede calcular directamente y comparar con los valores de la función objetivo de otros nodos en la cola. Si la suma de la ruta es más alto que Los valores de la función objetivo de todos los demás nodos en la cola son pequeños, entonces cambiar la ruta y la suma es la solución al problema. De lo contrario, continúe calculando otros nodos en la cola de prioridad.

4. Análisis de algoritmos

(1) Método codicioso: Comenzando desde el punto de partida, cada vez encuentre el punto más cercano al punto actual entre los puntos que no han sido pasados, como el punto a atravesar en el siguiente paso, hasta que todos los puntos hayan sido atravesados , y volver al punto de partida. Aplicable a la búsqueda de límites superiores.

(2) Método de programación dinámica: suponiendo que se comienza desde el vértice s, sea d(i, V') el comienzo del vértice i y el paso por cada vértice en V' (una colección de puntos) una y solo una vez, y finalmente se regresa al punto de partida s Longitud de ruta más corta. Aplicable a menos ciudades.

(3) Método de ramificación y límite: primero defina la estructura del nodo, la cola de prioridad, la matriz de adyacencia del gráfico de almacenamiento y lea los datos. Calcule la distancia mínima para cada ciudad y la suma de las distancias mínimas para todas las ciudades. Comience a atravesar desde el nodo inicial, buscando en el árbol del espacio de permutación. Saque el nodo y expándalo. Si encuentra el nodo principal del nodo hoja, intente actualizar la solución óptima y finalice el recorrido si encuentra el nodo hoja. El camino para obtener la solución óptima de los nodos hoja. Aplicable a muchas ciudades.

4. Diagrama de flujo del algoritmo

(1) Método codicioso: consulte la Figura 3 a continuación.

Figura 3 Diagrama de flujo del algoritmo codicioso

(2) Método de programación dinámica: consulte la Figura 4 a continuación.

Fig. 4 Diagrama de flujo del algoritmo del método de programación dinámica

(3) Método de ramificación y límite: consulte la Figura 5 a continuación.

Fig. 5 Diagrama de flujo del algoritmo del método de ramificación y límite

5. Datos de prueba y análisis de resultados.

(1) Método codicioso: los datos de prueba se muestran en el siguiente código y los resultados de la ejecución se muestran en la Figura 6.

Análisis: primero use la función de visita para juzgar si pasa por una ciudad, luego use la función clostCityDistance para encontrar la siguiente ciudad más cercana a la ciudad actual y finalmente use la función TSP para sumar la salida.

Figura 6 Resultados de la ejecución del método Greedy

#include<iostream>
#define n 4
using namespace std;
int s[n] = { -1,-1,-1,-1 };// 记录已经访问过的城市, 初始化为-1,表示未访问
int distance[n][n] = { {10000,3,6,5},// 城市间距离数组,10000表示自己到自己的距离
                      {3,10000,5,4},
                      {6,5,10000,2},
                      {5,4,2,10000} };
bool visit(int k) {//判断城市k是否被访问过 
    for (int i = 0; i < n; i++)
        if (s[i] == k) return 1;
    return 0;
}

void clostCityDistance(int currentCity) { //查找距离当前城市最近的下一个城市       
    int Dtemp = 10000;//Dtemp暂时存储当前最小路径的值 
    for (int i = 0; i < n; i++)
    {
        if (visit(i) == 0 && ::distance[i][s[currentCity]] < Dtemp)
        {
            Dtemp = ::distance[i][s[currentCity]];
            s[currentCity + 1] = i;
//若该城市没有被访问过,且距离当前城市最短,则将访问该城市,存入s[]数组中 
        }
    }

    for (int i = 0; i < n; i++)
    {//查找是否还有未访问的城市 
        if (s[i] == -1)
            clostCityDistance(s[currentCity + 1]);
    }
}
void TSP() {
    int sum = 0;// 最短路径之和 
    s[0] = 2;//从第2个城市出发 ,初始化出发的城市,可在0,1,2,3中任意一个 
    clostCityDistance(s[0]);//寻找距离2城市最近的城市   
    for (int i = 0; i < n; i++) {
        if (i == n - 1) {
            printf("%d", s[i]);
            printf("->%d 距离为:%d\n", s[0], ::distance[s[n - 1]][s[0]]);
            printf("总距离是  %d\n", sum += ::distance[s[n - 1]][s[0]]);
            break;
        }
        printf("%d->%d 距离为:%d \n", s[i], s[i + 1], ::distance[s[i]][s[i + 1]]);
        sum += ::distance[s[i]][s[i + 1]];
    }
}
int main() {
    TSP();
    return 0;
}

(2) Método de programación dinámica: los datos de prueba se muestran en el siguiente código y los resultados de ejecución se muestran en la Figura 7.

Análisis: primero inicialice la matriz bidimensional a través de la función Tsp tsp(city_number), luego imprima la ciudad a través de la función tsp.printCity, luego calcule a través de la función tsp.printProcess y finalmente encuentre el camino más corto a través de la función tsp.getShorestDistance .

Fig.7 Resultado de ejecución del método de programación dinámica

#include<iostream>
#include<iomanip>
#include<cmath>
using namespace std;
#define MAX_IN 10

class Tsp
{
private:
    int city_number;        //城市个数
    int** distance;            //城市距离矩阵
    int** process;            //求最短路径的过程矩阵
public:
    Tsp(int city_number);        //构造函数
    void correct();            //矫正输入的城市代价矩阵
    void printCity();        //打印城市的距离矩阵
    void getShoretstDistance();    //动态规划法求最短路径
    void printProcess();        //打印过程矩阵

};

//构造函数
Tsp::Tsp(int city_num)
{
    int i = 0, j = 0;
    city_number = city_num;

    //初始化城市距离矩阵
    distance = new int* [city_number];
    cout << "请输入" << city_number << "个城市之间的距离" << endl;
    for (i = 0; i < city_number; i++)
    {
        distance[i] = new int[city_number];
        for (j = 0; j < city_number; j++)
            cin >> distance[i][j];
    }

    //生成过程矩阵
    process = new int* [city_number];
    for (i = 0; i < city_number; i++)
    {
        process[i] = new int[1 << (city_number - 1)];
    }


}

//纠正用户输入的城市代价矩阵
void Tsp::correct()
{
    int i;
    for (i = 0; i < city_number; i++)
    {
        distance[i][i] = 0;
    }
}

//打印城市距离
void Tsp::printCity()
{
    int i, j;
    //打印代价矩阵
    cout << "您输入的城市距离如下" << endl;
    for (i = 0; i < city_number; i++)
    {
        for (j = 0; j < city_number; j++)
            cout << setw(3) << distance[i][j];
        cout << endl;
    }
}

//动态规划法求最短路径
void Tsp::getShoretstDistance()
{
    int i, j, k;
    //初始化第一列
    for (i = 0; i < city_number; i++)
    {
        process[i][0] = distance[i][0];
    }
    //初始化剩余列
    for (j = 1; j < (1 << (city_number - 1)); j++)
    {
        for (i = 0; i < city_number; i++)
        {
            process[i][j] = 0x7ffff;//设0x7ffff为无穷大

            //对于数字x,要看它的第i位是不是1,通过判断布尔表达式 (((x >> (i - 1) ) & 1) == 1的真值来实现

            if (((j >> (i - 1)) & 1) == 1)
            {
                continue;
            }
            for (k = 1; k < city_number; k++)
            {
                //不能达到k城市
                if (((j >> (k - 1)) & 1) == 0)
                {
                    continue;
                }
                if (process[i][j] > distance[i][k] + process[k][j ^ (1 << (k - 1))])
                {
                    process[i][j] = distance[i][k] + process[k][j ^ (1 << (k - 1))];
                    //cout<<i<<"行"<<j<<"列为:"<<process[i][j]<<endl;
                }
            }
        }
    }
    cout << "最短路径为" << process[0][(1 << (city_number - 1)) - 1] << endl;
}
//打印过程矩阵
void Tsp::printProcess()
{
    int i, j;
    for (j = 0; j < 1 << (city_number - 1); j++)
    {
        cout << setw(3) << j;
    }
    cout << endl;
    for (i = 0; i < city_number; i++)
    {
        for (j = 0; j < 1 << (city_number - 1); j++)
        {
            if (process[i][j] == 0x7ffff)
                process[i][j] = -1;
            cout << setw(3) << process[i][j];
        }
        cout << endl;

    }
}
//主函数
int main(void)
{
    int city_number;
    while (cin >> city_number)
    {
        Tsp tsp(city_number);        //初始化城市代价矩阵
        tsp.correct();                    //纠正用户输入的代价矩阵
        tsp.printCity();                //打印城市
        tsp.getShoretstDistance();        //求出最短路径
        tsp.printProcess();            //打印计算矩阵
    }
    return 0;
}

(3) Método de bifurcación y límite: los datos de prueba se muestran en el siguiente código y el resultado de la ejecución se muestra en la Figura 8.

Análisis: primero inicialice la matriz bidimensional a través de la función principal, luego calcule la ruta de viaje y la ruta más corta a través de la función Viajar, y finalmente imprima la ruta más corta a través de la función de impresión.

Fig. 8 Resultado de ejecución del método de bifurcación y límite

#include<iostream>
using namespace std;
#define NoEdge -1
#define MAX 20
int G[MAX][MAX];
int ans[MAX], x[MAX];
int bestc, cc;
void init(int n)
{
    int i, j, len;
    memset(G, NoEdge, sizeof(G));
    while (cin >> i >> j)
    {
        if (i == 0 && j == 0) break;cin >> len;

        G[i][j] = len;
        G[j][i] = len;
}
    for (i = 1;i <= n;i++) x[i] = i;bestc = 0x7fffff;

    cc = 0;
}
void Swap(int& i, int& j)
{
    int t = i;
    i = j;
    j = t;
}
void Traveling(int i, int n)
{
    int j;

    if (i == n + 1)
    {
        if (G[x[n - 1]][x[n]] != NoEdge && G[x[n]][1] != NoEdge && (cc + G[x[n]][1] < bestc))
        {
            for (j = 1;j <= n;j++)ans[j] = x[j];

            bestc = cc += G[x[n]][1];
        }
    }
    else {
        for (j = i;j <= n;j++) {

            if (G[x[i - 1]][x[j]] != NoEdge && (cc + G[x[i - 1]][x[j]] < bestc))
            {
                Swap(x[i], x[j]);

                cc += G[x[i - 1]][x[i]];Traveling(i + 1, n);cc -= G[x[i - 1]][x[i]];Swap(x[i], x[j]);
            }
        }
    }
}
void print(int n)
{
    cout << "最小的旅行费用为: " << bestc << endl;
    cout << "最佳路径是: ";
    for (int i = 1;i <= n;i++)
        cout << ans[i] << "->";
    cout << ans[1] << endl;
}
int main()
{
    int n;

    cout << "请输人需要旅行多少个城市: " << endl;
    while (cin >> n && n) {

        cout << "輸人丙个城市之同的距高,例如1 2 20,輸人00結束" << endl;
        init(n);
        Traveling(2, n);
        print(n);
    }
    return 0;

Supongo que te gusta

Origin blog.csdn.net/weixin_58351753/article/details/129428437
Recomendado
Clasificación