转载声明:这篇文章是从网上好多文章总结摘抄来的,所以也不算是我写的,没法标出原转载网址;
1.局部搜索
通常考察一个算法的性能通常用局部搜索能力和全局收敛能力这两个指标。局部搜索是指能够无穷接近最优解的能力,而全局收敛能力是指找到全局最优解所在大致位置的能力。局部搜索能力和全局搜索能力,缺一不可。向最优解的导向,对于任何智能算法的性能都是很重要的。
定义:
- 局部搜索是解决最优化问题的一种启发式算法。对于某些计算起来非常复杂的最优化问题,比如各种NP完全问题,要找到最优解需要的时间随问题规模呈指数增长,因此诞生了各种启发式算法来退而求其次寻找次优解,是一种近似算法(Approximate algorithms),以时间换精度的思想。局部搜索就是其中的一种方法。
- 邻域动作是一个函数,通过这个函数,对当前解s,产生其相应的邻居解集合。例如:对于一个bool型问题,其当前解为:s = 1001,当将邻域动作定义为翻转其中一个bit时,得到的邻居解的集合N(s)={0001,1101,1011,1000},其中N(s) ∈ S。同理,当将邻域动作定义为互换相邻bit时,得到的邻居解的集合N(s)={0101,1001,1010}.
描述算法时需用到领域的概念,所谓领域,简单的说即是给定点附近其他点的集合。在距离空间中,领域一般被定义为以给定点为圆心的一个圆;而在组合优化问题中,领域一般定义为由给定转化规则对给定的问题域上每结点进行转化所得到的问题域上结点的集合。公式描述如下:
设D为问题定义域,若存在一映射N,使得:
N:S∈D→N(S)∈2^D
则称N(S)为N的领域,A∈2^D为N的邻居。
局部搜索(Local Search)的一般过程是:
- 随机选择一个初始的可能解x,计算P=N(x)为x在映射N下的领域。
- 如果满足结束条件则goto (9),其中结束条件包括规定的循环次数、P为空。
- Begin
- 选择P的一个子集,y为此子集的一个最优解。
- 如果f(y) < f(x),则将y改为临时的最优解,并将P=N(y),其中f为一指标函数。
- 否则,从P中减去刚才选择的子集.
- goto (2).
- End
- 输出计算结果
- 结束
原始的局部搜索算法存在三个改进的算法,分别从三个不同的侧面对其进行了优化。
局部最优问题:
即考虑到算法在搜索过程中陷入到局部极值点而结束的情况。设想我们去攀登一座山群的最高峰,而此山群有很多的小山峰,且我们对此山群一无所知,那么当我们按照算法的步骤来到一座小山峰(局部极值点)时,我们会错误的判断这就是最高峰,事实上这有可能是一个很糟糕的解(即与最高峰还差很远)。
步长问题:
为便于理解,我们考虑用此局部搜索算法寻找一开口向下的抛物线的顶点:设此顶点的x坐标为10,求领域的映射N定义为N:x∈R,N(x)=x±3(即给定点x的领域仅有在其两边相距为3的两个点),指标函数f(x)=-y(x)(为使指标函数值小的解为较优解,我们让其取相反数);那么当我们所选取的初始解为3时,无论如何算法都将不能在顶点(最优解)处结束。根本原因就是我们的步长固定,所以能够搜索到的也仅为一些固定的点。解决此问题可以在搜索的过程中改变步长(本质为改变映射函数N)。
起始点问题:
在上面步长问题的讨论中,我们发现起始点的选择也对问题的求解有很大的影响,选择不好可能会导致得不出最优解的情况。一种很自然的解决方案就是随机的选择一些可能解,分别以这些可能解为初始解进行搜索,最后将这些搜索得到的解进行比较从而选出最优解。
2.局部搜索案例与求解方法
一般认为,NP完全问题的算法复杂性是指数级的。当问题规模达到一定程度时,局部搜索算法、模拟退火算法和遗传算法等引入了随机因素,不一定能找到最优解,但一般能快速找到满意的解。
组合优化问题举例
- TSP问题:从某个城市出发,经过n个指定的城市,每个城市只能且必须经过一次,最后回到出发城市,如何安排旅行商的行走路线以使总路程最短?
- 约束机器排序问题:n 个加工量为di(i=1,2,… n)的产品在一台机器上加工,机器在第t个时段的工作能力为ct,完成所有产品加工的最少时段数。
- 指派问题: 一家公司经理准备安排N名员工去完成N项任务,每人一项。由于各员工的特点不同,不同的员工去完成同一项任务时获得的回报是不同的。如何分配工作方案可以获得最大收益?
- 0-1背包问题: 设有一个容积为b的背包,n个体积分别为ai(i=1,2,… n),价值分别为ci ( i=1,2,… n)的物品,如何以最大的价值装包?
- 装箱问题: 如何用个数最少的尺寸为1的箱子装进n个尺寸不超过1的物品?
- SAT问题:称 判定一个公式是否存在一个模型的问题为可满足性问题(以后简称为SAT问题)。如果一个公式存在模型,则称该公式是可满足的,否则称为不可满足的。
- 皇后问题: 在n×n的国际象棋棋盘上,摆放n个皇后,使得n个皇后之间不能相互“捕捉”?
局部搜索算法
局部搜索算法是从爬山法改进而来的。爬山法:在没有任何有关山顶的其他信息的情况下,沿着最陡的山坡向上爬。局部搜索算法的基本思想:在搜索过程中,始终选择当前点的邻居中与离目标最近者的方向搜索。
- 爬山算法
1, n := s;
2, LOOP: IF GOAL(n) THEN EXIT(SUCCESS);
3, EXPAND(n) →{mi}, 计算h(mi), next n=min {h(mi)}
4, IF h(n) < h(next n) THEN EXIT(Fail);
5, n : =next n;
6, GO LOOP;
该算法在单峰的条件下,必能达到山顶。
- 局部搜索算法
(1)随机选择一个初始的可能解x0 ∈D,xb=x0,P=N(xb);
//D是问题的定义域, xb用于记录到目标位置的最优解,P为xb的邻域。
(2)如果不满足结束条件,则: //结束条件为循环次数或P为空等
(3)Begin
(4)选择P的一个子集P’, xn为P’的最优解
// P’可根据问题特点,选择适当大小的子集。可按概率选择
(5)如果f(xn) < f(xb),则xb=xn,P=N(xb),转(2)
// 重新计算P,f(x)为指标函数
(6)否则P=P-P’,转(2)
(7)End
(8)输出计算结果
(9)结束
局部最优问题(或叫局部峰值局部陷井)
现实问题中,f在D上往往有多个局部的极值点。
一般的局部搜索算法一旦陷入局部极值点,算法就在该点处结束,这时得到的可能是一个糟糕的结果。
解决的方法就是每次并不一定选择邻域内最优的点,而是依据一定的概率,从邻域内选择一个点。
指标函数优的点,被选中的概率大,指标函数差的点,被选中的概率小。
考虑归一化问题,使得邻域内所有点被选中的概率和为1。
局部搜索算法2——可变步长
(1)随机选择一个初始的可能解x0属于D,xb=x0,P=N(xb);
//D是问题的定义域,xb用于记录到目标位置的最优解,P为xb的邻域。
(2)如果不满足结束条件,则: //结束条件为循环次数或P为空等
(3)Begin
(4)选择P的一个子集P’,xn为P’的最优解
(5)如果f(xn) < f(xb),则xb=xn
(6)按某种策略改变步长,计算P=N(xb),转(2) 继续
(7)否则P=P-P’,转(2)
(8)End
(9)输出计算结果
(10)结束
起始点问题
一般的局部搜索算法是否能找到全局最优解,与初始点的位置有很大的依赖关系。
解决的方法就是随机生成一些初始点,从每个初始点出发进行搜索,找到各自的最优解。再从这些最优解中选择一个最好的结果作为最终的结果。
起始点位置影响搜索结果示意图
局部搜索算法3——多次起始点
(1)k=0
(2)随机选择一个初始的可能解x0属于D,xb=x0,P=N(xb);
(3)如果不满足结束条件,则:
(4)Begin
(5)选择P的一个子集P‘,xn为P’的最优解
(6)如果f(xn) < f(xb),则xb=xn,P=N(xb),转(3)
(7)否则P=P-P’,转(3)
(8)End
(9)k=k+1
(10)如果k达到了指定的次数,则从k个结果中选择一个最好的结果,否则转(2)
(11)输出结果
(12)结束
3.八皇后局部搜索示例
/*********************************************************************************** 模块:QueenSearch.cpp 目的:解决经典的八皇后问题 思想:局部搜索 ***********************************************************************************/
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#define N 8 // 皇后数目,
////////////////////////////////////////////////////////////////////////////////////
// 皇后
typedef struct _Queen
{
unsigned int x;
unsigned int y;
}QUEEN, *pQUEEN, **ppQUEEN;
// 棋格
typedef struct _Grid
{
pQUEEN pQueen;
}GRID, *pGRID, **ppGRID;
////////////////////////////////////////////////////////////////////////////////////
/* * 函数声明提前 */
// 初始化棋盘,使每行、每列上仅出现一位皇后
bool InitChessBroad(GRID ppChessBroad[][N], pQUEEN pN_Queen, unsigned n);
// 两皇后冲突则返回true,否则返回false
bool isConflict(pQUEEN pQueen_1, pQUEEN pQueen_2);
// 计算给定一组皇后中发生冲突的数目
unsigned CountOfConflict(pQUEEN pN_Queen, unsigned n);
// 随机交换两位皇后的行、列号,若能使冲突数减少则返回true,若任意可能交换都不能够使冲突数目减少则返回false
bool ChangeTwoQueen(GRID ChessBroad[][N], pQUEEN pN_Queen, unsigned n);
// 打印输出
void PrintChessBroad(GRID ppChessBroad[][N], unsigned n);
////////////////////////////////////////////////////////////////////////////////////
bool InitChessBroad(GRID ppChessBroad[][N], pQUEEN pN_Queen, unsigned n)
{
int *pavCols; // 可用的行列
unsigned i, j;
int r;
pavCols = new int[n];
/* 考虑到rand()函数的特性,即以静态变量为基础按照一定的规则变化此变量,然而每当我们 程序启动时静态变量的数值是一样的,因此不能说成是随机的,只能说是以特定初始值按照 特定规则变化的一组数值,在此,我们以系统当前启动时间为基准让rand()运行一段时间。 */
DWORD dwTickCount = GetTickCount() % 100;
while (dwTickCount--)
rand();
for (i = 0; i < n; i++)
pavCols[i] = i;
// 扫描每一行,在每一行的合适位置放入一皇后
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
ppChessBroad[i][j].pQueen = NULL;
while (1)
{
r = rand() % n;
if (pavCols[r] != -1)
{
pavCols[r] = -1;
break;
}
}
//
ppChessBroad[i][r].pQueen = pN_Queen + i;
pN_Queen[i].x = i;
pN_Queen[i].y = r;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////////
bool isConflict(pQUEEN pQueen_1, pQUEEN pQueen_2)
{
if (pQueen_1 == pQueen_2)
return false;
if ((pQueen_1->x == pQueen_1->y) && (pQueen_2->x == pQueen_2->y) // 同在主对角线上
|| ((pQueen_1->x + pQueen_1->y == N - 1) && (pQueen_2->x + pQueen_2->y == N - 1))) // 同在副对角线上
return true;
return (pQueen_1->x == pQueen_2->x || pQueen_1->y == pQueen_2->y)? true:false; // 判定同行同列
}
////////////////////////////////////////////////////////////////////////////////////
unsigned CountOfConflict(pQUEEN pN_Queen, unsigned n)
{
unsigned i, j, Count = 0;
for (i = 0; i < n - 1; i++)
for (j = i + 1; j < n; j++)
if (isConflict(pN_Queen + i, pN_Queen + j))
Count++;
return Count;
}
///////////////////////////////////////////////////////////////////////////////////
bool ChangeTwoQueen(GRID ChessBroad[][N], pQUEEN pN_Queen, unsigned n)
{
int tmp, h1, h2;
unsigned i, j, Count = 0; // 冲突数目
bool *isavH_1, *isavH_2; // 指向一维数组,用来标识两层循环中可用的皇后
isavH_1 = new bool[n];
isavH_2 = new bool[n];
for (i = 0; i < n; i++)
isavH_1[i] = isavH_2[i] = true; // 初始均可用
Count = CountOfConflict(pN_Queen, n);
// 两层循环产生从n个元素中选择个的组合数
for (i = 0; i < n - 1; i++)
{
//随机选择一个可用的皇后作为第一组合数h1
while (1)
{
h1 = rand() % n;
if (isavH_1[h1])
{
isavH_1[h1] = false;
// 第二组合数从尚未选为第一组合数集合中选择
for (j = 0; j < n; j++)
isavH_2[j] = isavH_1[j];
break;
}
}
for (j = i + 1; j < n; j++)
{
//随机选择一个可用的皇后作为第二组合数h2
while (1)
{
h2 = rand() % n;
if (isavH_2[h2])
{
isavH_2[h2] = false;
break;
}
}
// 交换标号为h1,h2皇后的x坐标
tmp = pN_Queen[h1].x;
pN_Queen[h1].x = pN_Queen[h2].x;
pN_Queen[h2].x = tmp;
if (Count > CountOfConflict(pN_Queen, n))
{
// 更改相应的棋格
ChessBroad[pN_Queen[h2].x][pN_Queen[h1].y].pQueen = NULL;
ChessBroad[pN_Queen[h1].x][pN_Queen[h2].y].pQueen = NULL;
ChessBroad[pN_Queen[h1].x][pN_Queen[h1].y].pQueen = pN_Queen + h1;
ChessBroad[pN_Queen[h2].x][pN_Queen[h2].y].pQueen = pN_Queen + h2;
return true;
}
else
{
tmp = pN_Queen[h1].x;
pN_Queen[h1].x = pN_Queen[h2].x;
pN_Queen[h2].x = tmp;
}
// 交换标号为h1、h2皇后的y坐标
tmp = pN_Queen[h1].y;
pN_Queen[h1].y = pN_Queen[h2].y;
pN_Queen[h2].y = tmp;
if (Count > CountOfConflict(pN_Queen, n))
{
// 更改相应的棋格
ChessBroad[pN_Queen[h1].x][pN_Queen[h2].y].pQueen = NULL;
ChessBroad[pN_Queen[h2].x][pN_Queen[h1].y].pQueen = NULL;
ChessBroad[pN_Queen[h1].x][pN_Queen[h1].y].pQueen = pN_Queen + h1;
ChessBroad[pN_Queen[h2].x][pN_Queen[h2].y].pQueen = pN_Queen + h2;
return true;
}
else
{
tmp = pN_Queen[h1].y;
pN_Queen[h1].y = pN_Queen[h2].y;
pN_Queen[h2].y = tmp;
}
}
}
// 不存在这样的交换则返回false
return false;
}
////////////////////////////////////////////////////////////////////////////////////
void PrintChessBroad(GRID ppChessBroad[][N], unsigned n)
{
unsigned i, j;
printf("\n");
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if (ppChessBroad[i][j].pQueen == NULL)
printf("*\t");
else
printf("H\t");
}
printf("\n\n\n");
}
}
////////////////////////////////////////////////////////////////////////////////////
int _tmain(int argc, _TCHAR* argv[])
{
GRID ChessBroad[N][N];
QUEEN nQueen[N];
while (1)
{
InitChessBroad(ChessBroad, nQueen, N);
while (1)
{
// 完成皇后的布局
if (0 == CountOfConflict(nQueen, N))
{
PrintChessBroad(ChessBroad, N);
return 1;
}
if (!ChangeTwoQueen(ChessBroad, nQueen, N))
break;
}
}
return 0;
}