蟻群演算法實現TSP(旅行商)問題(java)

mon seul發表於2016-05-22
        旅行商問題,即TSP問題(Traveling Salesman Problem)是數學領域中著名問題之一。假設有一個旅行商人要拜訪N個城市,他必須選擇所要走的路徑,路徑的限制是每個城市只能拜訪一次,而且最後要 回到原來出發的城市。路徑的選擇目標是要求得的路徑路程為所有路徑之中的最小值。TSP問題是一個NPC問題。這個問題一般是使用遺傳演算法去解,但是螞蟻演算法要更高效.對於c++不熟悉的我,用C語言是個噩夢,因此寫一個java程式碼,並且桌面視窗顯示結果的版本.

再來說說蟻群演算法

       各個螞蟻在沒有事先告訴他們食物在什麼地方的前提下開始尋找食物。當一隻找到食物以後,它會向環境釋放一 種揮發性分泌物pheromone (稱為資訊素,該物質隨著時間的推移會逐漸揮發消失,資訊素濃度的大小表徵路徑的遠近)來實現的,吸引其他的螞蟻過來,這樣越來越多的螞蟻會找到食物。有 些螞蟻並沒有像其它螞蟻一樣總重複同樣的路,他們會另闢蹊徑,如果另開闢的道路比原來的其他道路更短,那麼,漸漸地,更多的螞蟻被吸引到這條較短的路上 來。最後,經過一段時間執行,可能會出現一條最短的路徑被大多數螞蟻重複著。(實在不理解去度娘)


公共函式及變數


package com.seul.tsp;
/*
 * 公共函式以及變數
 */
public class PublicFun
{
	public static final double ALPHA=1.0;//資訊啟發式因子,資訊素的重要程度
	public static final double BETA=2.0;//期望啟發式因子, 城市間距離的重要程度
	public static final double ROU=0.5;//資訊素殘留係數
 
	public static  int N_ANT_COUNT=50;//螞蟻數量
	public static  int N_IT_COUNT=200;//迭代次數
	public static  int N_CITY_COUNT=15;//城市數量
 
	public static final double DBQ=100.0;//總資訊素
	public static final double DB_MAX=Math.pow(10,9);//一個標誌數,用來初始化一個比較大的最優路徑
	
	//兩兩城市間的資訊量
	public static double[][] g_Trial;
	
	//兩兩城市間的距離
	public static double[][] g_Distance;

	
	//返回指定範圍內的隨機整數
	public static int rnd(int nLow,int nUpper)
	{
		return (int) (Math.random()*(nUpper-nLow)+nLow);
	}
	//返回指定範圍內的隨機浮點數
	public static double rnd(double dbLow,double dbUpper)
	{
		return Math.random()*(dbUpper-dbLow)+dbLow;
	}
	
	
}


螞蟻類

package com.seul.tsp;
/*
 * 螞蟻類
 * 使用物件的複製,必須實現Cloneable介面,然後重寫Object中clone()方法
 */
public class Ant implements Cloneable
{
	public int[] m_nPath=new int[PublicFun.N_CITY_COUNT];//螞蟻走過的路徑
	public double m_dbPathLength;//螞蟻走過的路徑長度
	
	public int[] m_nAllowedCity=new int[PublicFun.N_CITY_COUNT];//螞蟻沒有去過的城市
	public int m_nCurCityNo;//當前所在城市的編號
	public int m_nMovedCityCount;//已經去過的城市數量
	
	
	
	/*
	 * 初始化函式,螞蟻搜尋前呼叫該方法
	 */
	public void Init()
	{
		for (int i = 0; i < PublicFun.N_CITY_COUNT; i++)
		{
			m_nAllowedCity[i]=1;//設定全部城市沒有去過
			m_nPath[i]=0;//螞蟻走過的路徑全部設定為0
		}
		m_dbPathLength=0.0; //螞蟻走過的路徑長度設定為0
		
		m_nCurCityNo=PublicFun.rnd(0,PublicFun.N_CITY_COUNT);//隨機選擇一個出發城市
		
		m_nPath[0]=m_nCurCityNo;//把出發城市儲存的路徑陣列中
		
		m_nAllowedCity[m_nCurCityNo]=0;//標識出發城市已經去過了
		
		m_nMovedCityCount=1;//已經去過的城市設定為1;
	}
	
	/*
	 * 覆蓋Object中的clone()方法
	 * 實現物件的複製
	 */
	protected Object clone() throws CloneNotSupportedException
	{
		return super.clone();
	}
	/*
	 * 選擇下一個城市
	 * 返回值為城市編號
	 */	
	public int ChooseNextCity()
	{
		
		int nSelectedCity=-1;//返回結果,初始化為-1
		
		//計算當前城市和沒去過城市的資訊素的總和
		double dbTotal=0.0;
		double[] prob=new double[PublicFun.N_CITY_COUNT];//用來儲存各個城市被選中的概率
		
		for (int i = 0; i < PublicFun.N_CITY_COUNT; i++)
		{
			if(m_nAllowedCity[i]==1)//城市沒去過
			{
				//該城市和當前城市的資訊素
				prob[i]=Math.pow(PublicFun.g_Trial[m_nCurCityNo][i], PublicFun.ALPHA)*Math.pow(1.0/PublicFun.g_Distance[m_nCurCityNo][i],PublicFun.BETA);
				dbTotal=dbTotal+prob[i];//累加資訊素
			}
			else//如果城市去過了 則被選中的概率為0;
			{
				prob[i]=0.0;
			}
			
		}
		
		//進行輪盤選擇
		
		double dbTemp=0.0;
		if(dbTotal>0.0)//如果總的資訊素大於0
		{
			dbTemp=PublicFun.rnd(0.0, dbTotal);//取一個隨機數
			
			for (int i = 0; i < PublicFun.N_CITY_COUNT; i++)
			{
				if(m_nAllowedCity[i]==1)//城市沒有去過
				{
					dbTemp=dbTemp-prob[i];//相當於輪盤
					
					if(dbTemp<0.0)//輪盤停止轉動,記下城市編號,跳出迴圈
					{
						nSelectedCity=i;
						break;
					}
				}
				
			}
		}
		
		/*
		 * 如果城市間的資訊素非常小 ( 小到比double能夠表示的最小的數字還要小 )  
		 * 那麼由於浮點運算的誤差原因,上面計算的概率總和可能為0  
		 * 會出現經過上述操作,沒有城市被選擇出來  
		 * 出現這種情況,就把第一個沒去過的城市作為返回結果  
		 */
		if(nSelectedCity==-1)
		{
			for (int i = 0; i < PublicFun.N_CITY_COUNT; i++)
			{
				if(m_nAllowedCity[i]==1)//城市沒有去過
				{
						nSelectedCity=i;
						break;
				}
			}
		}
		return nSelectedCity;
	}
	
	/*
	 * 螞蟻在城市間移動
	 */
	public void Move()
	{
		int nCityNo=ChooseNextCity();//選擇下一個城市
		m_nPath[m_nMovedCityCount]=nCityNo;//儲存螞蟻走過的路徑
		m_nAllowedCity[nCityNo]=0;//把這個城市設定為已經去過了
		m_nCurCityNo=nCityNo;//改變當前城市為選擇的城市
		m_nMovedCityCount++;//已經去過的城市加1
	}
	
	/*
	 * 螞蟻進行一次搜尋
	 */
	public void Search()
	{
		Init();//螞蟻搜尋前,進行初始化
		
		//如果螞蟻去過的城市數量小於城市數量,就繼續移動
		while (m_nMovedCityCount<PublicFun.N_CITY_COUNT)
		{
			Move();//移動
		}
		//完成搜尋後計算走過的路徑長度
		CalPathLength();
	}
	
	/*
	 * 計算螞蟻走過的路徑長度
	 */
	public void CalPathLength()
	{
		m_dbPathLength=0.0;//先把路徑長度置0
		int m=0;
		int n=0;
		
		for(int i=1;i<PublicFun.N_CITY_COUNT;i++)
		{
			m=m_nPath[i];
			n=m_nPath[i-1];
			m_dbPathLength=m_dbPathLength+PublicFun.g_Distance[m][n];
		}
		//加上從最後城市返回出發城市的距離
		n=m_nPath[0];
		m_dbPathLength=m_dbPathLength+PublicFun.g_Distance[m][n];
		m_dbPathLength=(Math.round(m_dbPathLength*100))/100.0;
	}
}

TSP問題類

package com.seul.tsp;
/*
 * Tsp問題類
 */
public class Tsp
{
	//螞蟻陣列
	public Ant[] m_antAry=new Ant[PublicFun.N_ANT_COUNT];
	public Ant[] m_betterAnts=new Ant[PublicFun.N_IT_COUNT];//定義一組螞蟻,用來儲存每一次搜尋中較優結果,不參與搜尋
	public Ant m_bestAnt;//定義一個螞蟻變數,用來儲存最終最優結果,不參與搜尋
	
	/*
	 * 初始化資料
	 */
	public void InitData() throws CloneNotSupportedException
	{
		//初始化所有螞蟻
		
		PublicFun.g_Distance=new double[PublicFun.N_CITY_COUNT][PublicFun.N_CITY_COUNT];
		PublicFun.g_Trial=new double[PublicFun.N_CITY_COUNT][PublicFun.N_CITY_COUNT];
		m_bestAnt=new Ant();
		for (int i = 0; i <PublicFun.N_ANT_COUNT; i++)
		{
			m_antAry[i]=new Ant();
		}
		for (int i = 0; i < PublicFun.N_IT_COUNT; i++)
		{
			m_betterAnts[i]=new Ant();
			m_betterAnts[i].m_dbPathLength=PublicFun.DB_MAX;//把較優螞蟻的路徑長度設定為一個很大值
		}
		
		//先把最優螞蟻的路徑長度設定為一個很大值
		m_bestAnt.m_dbPathLength=PublicFun.DB_MAX;
		
		//隨機生成兩城市之間的距離
		for(int i=0;i<PublicFun.N_CITY_COUNT;i++)
		{
			for(int j=i;j<PublicFun.N_CITY_COUNT;j++)
			{
				if(i==j)
					PublicFun.g_Distance[i][j]=0.0;//同一個城市為0
				else
				{
					PublicFun.g_Distance[i][j]=PublicFun.rnd(20.0,100.0);//i-j的距離與j-i的距離相等
					PublicFun.g_Distance[j][i]=PublicFun.g_Distance[i][j];
				}
			}
		}
		//初始化資訊素
		for(int i=0;i<PublicFun.N_CITY_COUNT;i++)
		{
			for(int j=0;j<PublicFun.N_CITY_COUNT;j++)
			{
				PublicFun.g_Trial[i][j]=1.0;
			}
		}
		
		Iterator();//開始迭代
		
	}
	
	/*
	 * 更新環境資訊素
	 */
	
	public void UpdateTrial()
	{
		//臨時陣列,儲存各只螞蟻在兩兩城市間新留下的資訊素
		double[][] dbTempAry=new double[PublicFun.N_CITY_COUNT][PublicFun.N_CITY_COUNT];
		
		//全部設定為0;
		for (int i = 0; i <PublicFun.N_CITY_COUNT; i++)
		{
			for (int j = 0; j < PublicFun.N_CITY_COUNT; j++)
			{
				dbTempAry[i][j]=0.0;
			}
		}
		//計算新增加的資訊素,儲存到臨時變數
		int m=0;
		int n=0;
		for(int i=0; i<PublicFun.N_ANT_COUNT;i++)
		{
			for (int j = 1; j < PublicFun.N_CITY_COUNT; j++)
			{
				m=m_antAry[i].m_nPath[j];
				n=m_antAry[i].m_nPath[j-1];
				dbTempAry[n][m]=dbTempAry[n][m]+PublicFun.DBQ/m_antAry[i].m_dbPathLength;
				dbTempAry[m][n]=dbTempAry[n][m];
			}
			
			//最後城市與開始城市之間的資訊素
			n=m_antAry[i].m_nPath[0];
			dbTempAry[n][m]=dbTempAry[n][m]+PublicFun.DBQ/m_antAry[i].m_dbPathLength;
			dbTempAry[m][n]=dbTempAry[n][m];
		}
		
		//更新環境資訊素
		for (int i = 0; i < PublicFun.N_CITY_COUNT; i++)
		{
			for (int j = 0; j < PublicFun.N_CITY_COUNT; j++)
			{
				//最新的環境資訊素 = 留存的資訊素 + 新留下的資訊素  
				PublicFun.g_Trial[i][j]=PublicFun.g_Trial[i][j]*PublicFun.ROU+dbTempAry[i][j];
			}
		}
	}
	
	
	/*
	 * 迭代
	 */
	public void Iterator() throws CloneNotSupportedException
	{
		//迭代次數內進行迴圈

		for (int i = 0; i < PublicFun.N_IT_COUNT; i++)
		{
			//每隻螞蟻搜尋一遍
			for(int j=0;j<PublicFun.N_ANT_COUNT;j++)
			{
				m_antAry[j].Search();
			}
			
			//儲存較優結果
			for(int j=0;j<PublicFun.N_ANT_COUNT;j++)
			{
				if (m_antAry[j].m_dbPathLength < m_betterAnts[i].m_dbPathLength)
				{
					m_betterAnts[i] = (Ant) m_antAry[j].clone();
				}
			}
			UpdateTrial();
		}

		//找出最優螞蟻
		for(int k=0;k<PublicFun.N_IT_COUNT;k++)
		{
			if(m_betterAnts[k].m_dbPathLength<m_bestAnt.m_dbPathLength)
			{
				m_bestAnt=m_betterAnts[k];
			}
		}
		
	//	getBetterAnt();//輸出每次的較優路徑
//		getBestAnt();//輸出最佳路徑
		
	}
	/*
	 * 輸出最佳路徑到控制檯.(暫不使用,但保留)
	 */
	public void getBestAnt()
	{
		System.out.println("最佳路徑:");
		System.out.println( "路徑:"+getAntPath(m_bestAnt)+"長度:"+getAntLength(m_bestAnt));
	}
	
	/*
	 * 輸出每次的較優路徑到控制檯.(暫不使用,但保留)
	 */
	public void getBetterAnt()
	{
		System.out.println("每一次的較優路徑:");
		for (int i = 0; i < m_betterAnts.length; i++)
		{
			 System.out.println("("+i+") 路徑:"+getAntPath(m_betterAnts[i])+"長度:"+getAntLength(m_betterAnts[i]));
		}
	}

	/*
	 * 返回螞蟻經過的路徑
	 */
	public String getAntPath(Ant ant)
	{
		String s="";
		for(int i=0;i<ant.m_nPath.length;i++)
		{
			s+=ant.m_nPath[i]+"-";
		}
		s+=ant.m_nPath[0];  //螞蟻最後要回到開始城市
		return s;
	}
	/*
	 * 返回螞蟻經過的長度
	 */
	public double getAntLength(Ant ant)
	{
		return ant.m_dbPathLength;
	}
	
	
}









介面初始化


package com.seul.tsp;

import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
/*
 * 介面實現
 */
@SuppressWarnings("serial")
public class Frame extends JFrame
{
	JLabel headLabel;
	JLabel antLabel;
	JLabel itLabel;
	JLabel cityLabel;
	JLabel betterAntLabel;
	JLabel bestAntLabel;
	JTextField antTF;
	JTextField itTF;
	JTextField cityTF;
	JTextArea betterAntTA;
	JTextArea bestAntTA;
	JButton beginBtn;
	JScrollPane betterAntScroll;
	JScrollPane bestAntScroll;
	
	JLabel descLabel;
	public Frame()
	{
		Init();
	}
	/*
	 * 介面初始化函式
	 */
	private void Init()
	{
		String descStr = "<html><font color=green>說明:<br/>本測試程式兩兩城市間距離為隨機生成20.0~100.0之間的隨機浮點數<font/><br/><br/><br/><br/><font color=black>&copy;2016 Seul</font></html>";
		headLabel=new JLabel("蟻群演算法解決TSP問題演示");
		antLabel=new JLabel("請輸入螞蟻數量:");
		itLabel=new JLabel("請輸入迭代次數:");		
		cityLabel=new JLabel("請輸入城市數量:");
		betterAntLabel=new JLabel("每一次迭代的較優路徑以及長度");
		bestAntLabel=new JLabel("最佳路徑以及長度");
		antTF=new JTextField("50");
		itTF=new JTextField("200");
		cityTF=new JTextField("15");
		betterAntTA = new JTextArea();
		bestAntTA=new JTextArea();
		beginBtn=new JButton("開始執行");
		betterAntScroll=new JScrollPane(betterAntTA);
		bestAntScroll=new JScrollPane(bestAntTA);
		
		descLabel=new JLabel(descStr);
		descLabel.setBounds(20,220,150,150);
		
		setBounds(300, 150, 600, 400);
		setTitle("蟻群演算法解決TSP問題演示程式");
		setLayout(null);
		setResizable(false);
		
		headLabel.setBounds(0, 0, 600, 50);
		headLabel.setHorizontalAlignment(SwingConstants.CENTER);
		headLabel.setFont(new Font("隸書", Font.BOLD, 20));
		
		antLabel.setBounds(20, 60, 100, 20);
		cityLabel.setBounds(20, 100, 100, 20);
		itLabel.setBounds(20, 140, 100, 20);
		
		antTF.setBounds(125, 60, 50, 20);
		cityTF.setBounds(125, 100, 50, 20);
		itTF.setBounds(125, 140, 50, 20);
		
		beginBtn.setBounds(40, 180, 110, 25);
		
		betterAntLabel.setBounds(200, 60, 200, 20);
		betterAntScroll.setBounds(200, 80, 380, 200);
		
		bestAntLabel.setBounds(200, 300, 200, 20);
		bestAntLabel.setForeground(Color.red);
		bestAntScroll.setBounds(200, 320, 380, 40);
		bestAntTA.setBackground(Color.CYAN);
		bestAntTA.setFont(new Font("微軟雅黑", Font.BOLD, 13));
		
		getContentPane().add(headLabel);
		getContentPane().add(antLabel);
		getContentPane().add(antTF);
		getContentPane().add(itLabel);
		getContentPane().add(itTF);
		getContentPane().add(cityLabel);
		getContentPane().add(cityTF);
		getContentPane().add(beginBtn);
		getContentPane().add(betterAntLabel);
		getContentPane().add(betterAntScroll);
		getContentPane().add(bestAntLabel);
		getContentPane().add(bestAntScroll);
		getContentPane().add(descLabel);
		
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		
		beginBtn.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				try
				{
					Begin();
				} catch (CloneNotSupportedException e1)
				{
					e1.printStackTrace();
				}
			}
		});
		setVisible(true);
	}
	
	/*
	 * 開始運算
	 */
	public void Begin() throws CloneNotSupportedException
	{
		betterAntTA.setText("");
		bestAntTA.setText("");
		String antCountStr=antTF.getText().replaceAll("[^\\d]", "");//去除所有非數字字元
		String itCountStr=itTF.getText().replaceAll("[^\\d]", "");
		String cityCountStr=cityTF.getText().replaceAll("[^\\d]", "");
		
		if(!antCountStr.equals("")&&!itCountStr.equals("")&&!cityCountStr.equals(""))//不為"";
		{
			PublicFun.N_ANT_COUNT=Integer.parseInt(antCountStr);// 轉換為數字
			PublicFun.N_IT_COUNT=Integer.parseInt(itCountStr);
			PublicFun.N_CITY_COUNT=Integer.parseInt(cityCountStr);
		}
		
		/*
		 * 例項化Tsp 
		 */
		
		Tsp tsp=new Tsp();  
		tsp.InitData();//開始執行
		
		/*
		 * 把結果顯示在視窗中
		 */
		for (int i = 0; i < tsp.m_betterAnts.length; i++)
		{
			 betterAntTA.append("("+(i+1)+") 路徑:"+tsp.getAntPath(tsp.m_betterAnts[i])+"長度:"+tsp.getAntLength(tsp.m_betterAnts[i])+"\n");
		}
		betterAntTA.setCaretPosition(0);
		bestAntTA.append("路徑:"+tsp.getAntPath(tsp.m_bestAnt)+"長度:"+tsp.getAntLength(tsp.m_bestAnt));
		bestAntTA.setCaretPosition(0);


	}
}


程式入口


package com.seul.tsp;

/*
 * 入口類
 */
public class Main
{
	public static void main(String[] args) throws CloneNotSupportedException
	{
		new Frame();
	}
}

程式執行開始介面



程式執行介面


相關文章