• Nenhum resultado encontrado

Pseudocódigo do algoritmo construtivo semi-guloso com tamanho

algConstrSemiGulosoTamAleatório()

Entrada: -

Saída: solução sol; 1 2 3 4 5 6 7 8 9 10 11 12 13 flag = 1; ENQUANTO flag == 1:

PARA cada vértice j sol:

potentialProfit[j] = d[j] – c[path(sol)[i]][j] – o[path(sol)[i]][j][0];

SE não existir nenhum potentialProfit[] > 0: flag = 0;

SENÃO:

Q = n melhores vértices em potentialProfit[]; g = vértice aleatório de Q;

sol = sol {g}

carregamentoMíope(0, 0, 0, sol, sol, {0..0});

RETORNAR sol;

Algoritmo 5. Pseudocódigo do algoritmo construtivo semi-guloso com tamanho guloso

A partir do algoritmo construtivo semi-guloso de tamanho aleatório foram criados outros dois algoritmos semi-gulosos. O primeiro, denominado tamanho aleatório com roleta, substitui o conjunto de elite por um sorteio de roleta. O segundo, denominado tamanho aleatório com peso baseado em passageiros, leva em consideração os passageiros no cálculo do lucro em potencial.

A primeira modificação consiste em trocar o conjunto de elite por um sorteio de roleta. Os vértices são ordenados de acordo com o seu lucro em potencial, onde aquele que tiver o maior lucro terá o maior peso na roleta. O lucro em potencial é calculado da mesma maneira que o lucro utilizado para a seleção do conjunto de elite: o seu valor é bônus do vértice subtraído do custo da aresta e do custo do pedágio com 0 passageiros.

A segunda modificação é alterar o cálculo do lucro em potencial (o mesmo cálculo utilizado nos algoritmos anteriores) de cada vértice. O cálculo passa a ser multiplicado por um peso (superior a 1) de acordo com a quantidade de passageiros que, no vértice a ser calculado, estejam dentro das suas janelas de tempo, e que o seu vértice destino ainda não tenha sido inserido na solução. Dessa forma, os vértices que tiverem a maior quantidade

de passageiros que tem a possibilidade de serem embarcados terão um peso maior.

5.3. Operadores de busca local

Uma das maneiras de melhorar uma solução qualquer é analisar todas as soluções que possam ser consideradas próximas a solução original, e dentre elas escolher aquela que tenha a sua qualidade superior as demais, e superior à solução original. Para esse fim, são utilizados operadores de busca local (também podem ser chamados apenas de buscas locais). Uma busca local é um método heurístico que realiza modificações em uma solução, gerando um conjunto de soluções resultantes e retornando aquela que possua a melhor qualidade dentro do conjunto, e que possua uma qualidade superior à solução original.

O conjunto de soluções que podem ser alcançadas a partir de uma iteração da busca local em uma solução original é denominado o espaço de busca da solução (do operador de busca local utilizado). No problema tratado, uma solução ter uma qualidade superior à outra significa que aquela possui um lucro superior à esta.

Foram implementadas 7 buscas locais: 2-exchange, 1-shift, 2-opt, 2- interchange, add, remove e switchservice. Também foram implementadas versões exaustivas, onde a busca local realizará uma busca exaustiva na solução, versões exaustivas gulosas, onde a busca exaustiva será interrompida caso uma solução com lucro superior à solução original seja encontrada, e versões exaustivas limitadas, onde será feita uma busca exaustiva em apenas um espaço delimitado da solução.

Figura 2. Exemplo da busca local 2-exchange

A busca local 2-exchange (Figura 2) consiste em escolher um vértice como pivô e gerar soluções resultantes trocando o vértice pivô de posição com todos vértices que pertencem à solução (com exceção do vértice pivô).

A versão exaustiva da busca local (2-exchange-exh) utiliza múltiplas iterações da busca local 2-exchange de tal forma que cada vértice pertencente à solução, com exceção do vértice base, tenha sido utilizado como vértice pivô exatamente uma vez.

A versão exaustiva gulosa da busca local (2-exchange-exhgreedy) adiciona uma condição de parada à versão exaustiva, retornando como resultado a primeira solução resultante que tenha um lucro superior à solução original.

A versão exaustiva limitada (2-exchange-exhlim), utilizada pelo Algoritmo Transgenético, aplica a versão exaustiva em apenas um subconjunto dos vértices pertencentes à solução, onde os vértices estão dispostos na solução em posições sequenciais e dentro do intervalo de posições estipulado. Apenas os vértices pertencentes ao subconjunto poderão ser vértices pivôs ou trocados por um.

Figura 3. Exemplo da busca local 1-shift

A busca local 1-shift (Figura 3) consiste em escolher um vértice como pivô e gerar as soluções da vizinhança trocando o vértice pivô pelos vértices com os quais aquele possui uma aresta na solução. Essa operação é denominada de shifting, já que similarmente à operação de bit shifting, o vértice tem sua posição mudada o mínimo possível em uma direção. O shifting é aplicado até que, a partir da posição original do vértice pivô, este tenha sido posicionado em todas as posições possíveis da solução original.

A versão exaustiva da busca local (1-shift-exh) utiliza múltiplas iterações da busca local 1-shift de tal forma que cada vértice pertencente à solução, com exceção do vértice base, tenha sido utilizado como vértice pivô exatamente uma vez.

A versão exaustiva limitada da busca local (1-shift-exhlim), utilizada pelo Algoritmo Transgenético, aplica a versão exaustiva em apenas um subconjunto dos vértices pertencentes à solução, onde os vértices estão dispostos na solução em posições sequenciais e dentro do intervalo de posições estipulado. Apenas os vértices pertencentes ao subconjunto poderão ser vértices pivôs ou trocados por um.

Figura 4. Exemplo da busca local 2-opt

A busca local 2-opt (Figura 4) criada por Croes [11], é um operador que remove duas arestas da solução e adiciona as arestas agora disponíveis. Na representação em vértices, isso é obtido escolhendo 2 vértices (denominados de pontas), representando as arestas antes e depois dos vértices, respectivamente, e realiza uma inversão dos vértices entre as duas pontas. O resultado final é o mesmo que remover as arestas e adicionar outras. Nesta implementação, o 2-opt escolhe uma ponta fixa (pivô), e realiza múltiplas trocas utilizando todos os outros vértices como a segunda ponta. No final das operações, serão feitas múltiplas inversões com a ponta fixa e a ponta móvel.

A versão exaustiva da busca local (2-opt-exh) realiza a busca utilizando todos os vértices como pontas para a inversão. Dessa forma, o algoritmo garante que todos os pares de vértices foram selecionados para a inversão.

A versão exaustiva limitada da busca local (2-opt-exhlim) aplica o mesmo procedimento da busca local exaustiva, porém limitando a escolha dos vértices de troca a um subconjunto de vértices sequenciais, de tamanho definido por parâmetro e posição escolhida anteriormente.

Figura 5. Exemplo da busca local 2-interchange

A busca local 2-interchange (Figura 5) escolhe um vértice pertencente à solução como pivô e gera soluções resultantes a partir da troca do pivô com todos vértices não pertencentes à solução. A versão exaustiva da busca local (2-interchange-exh) aplica múltiplas iterações da busca local, de tal forma que cada vértice pertencente à solução tenha sido escolhido como pivô exatamente uma vez.

Figura 7. Exemplo da busca local add2

As buscas locais de adição tem como objetivo inserir novos vértices à uma solução e comparar as soluções resultantes com a solução original. Para isso, foram implementados múltiplas variações, utilizando diferentes métodos de realizar essas inserções.

A busca local add1 (Figura 6) sorteia um vértice que não pertença à solução e tenta inseri-lo em todas as posições possíveis (entre todos os pares sequenciais de vértices na solução), guardando a melhor solução resultante e comparando-a com a solução original.

A busca local add2 (Figura 7) sorteia uma posição entre dois vértices sequenciais na solução e tenta inserir todos os vértices que não pertencem à solução original. A melhor solução resultante é comparada à solução original.

A busca local addExh une o funcionamento das duas buscas locais anteriores e realiza uma busca exaustiva na solução. Para cada posição entre dois vértices sequenciais na solução, a busca local insere todos os vértices que não pertencem à solução. No final da busca, a melhor solução resultante é comparada à solução original.

A busca local addWeighted funciona de forma similar à busca local add1, porém a seleção do vértice a ser inserido é feita a partir de uma lista de

pesos, informada inicialmente à busca local. Diferente das outras buscas locais, esta necessita de uma informação externa (a tal lista) para funcionar.

Figura 8. Exemplo da busca local remove

A busca local remove (Figura 8) gera as soluções da vizinhança tentando remover cada vértice da solução original. Cada solução resultante é comparada com a solução original, e caso aquela seja melhor que esta, a original é substituída pela resultante.

A versão exaustiva (removeFirst) da busca local funciona da mesma maneira que a busca local remove, com a adição de uma nova condição de parada: caso a solução original seja substituída por uma solução resultante, a busca local é interrompida e a nova solução original é retornada.

A busca local serviceswitch escolhe um vértice aleatório da solução e modifica o serviço. Caso o vértice na solução original iria realizar o serviço, ela deixará de realiza-lo, e caso o vértice na solução original não iria realizar o serviço, ela passará a realiza-lo.

5.4. Operadores de cruzamentos

Os operadores de cruzamento tem como objetivo receber duas ou mais soluções, chamadas de soluções pai, e gerar uma ou mais soluções, chamadas de soluções-filho. As soluções-filho são criadas utilizando informações contidas nas soluções pais recebidas. Os algoritmos de cruzamentos são cruciais não só para a metáfora e funcionamento dos Algoritmos Genéticos, mas também para outras metáforas que não utilizam a ideia de pai-filho, como algoritmos baseados em bactérias e os Algoritmos Meméticos.

Foram implementados 4 cruzamentos para serem utilizados pelo Algoritmo Genético e Algoritmo Memético: 1-ponto, n-ponto, aleatório e SCX. Para resolver o problema de soluções-filho inviáveis, também foi implementado um algoritmo para reparação de soluções, que será explicado após os cruzamentos. Dos 4 cruzamentos, o SCX não utiliza tal algoritmo, já que ele garante que as suas soluções-filho serão viáveis.

Os algoritmos de cruzamento se encarregam de mesclar apenas as informações da rota e dos serviços, ignorando os passageiros. Para isso, os passageiros são removidos das soluções pai antes de realizar cada operação, e as soluções-filho resultantes são recarregadas por meio de um algoritmo de carregamento.

cruzamento1ponto()

Entrada: solução sol1, solução sol2; Saída: solução sol;

1 2 3 4 5 6

rPoint1 = rand(0, sizeof(sol1); rPoint2 = rand(0, sizeof(sol2);

PARA i de 0 a rPoint1: path(sol)[i] = path(sol1)[i]; PARA i de 0 a rPoint2: path(sol)[i+rPoint1] = path(sol2)[i]; sol = reparação(sol); RETORNAR sol;

Documentos relacionados