精华内容
下载资源
问答
  • 自适应大邻域搜索算法

    千次阅读 2020-06-16 20:50:10
    自适应大邻域搜索算法(Adaptive Large Neighborhood Search)是由Ropke与Pisinger在2006年提出的一种启发式方法,其在邻域搜索的基础上增加了的对算子的作用效果的衡量,使算法能够自动选择好的算子对解进行破坏与...

    自适应大邻域搜索算法(Adaptive Large Neighborhood Search)是由Ropke与Pisinger在2006年提出的一种启发式方法,其在邻域搜索的基础上增加了的对算子的作用效果的衡量,使算法能够自动选择好的算子对解进行破坏与修复,从而有一定几率得到更好的解。在邻域搜索算法中,有的算法可以只使用一种邻域,如模拟退火算法,因此它仅仅搜索了解空间的一小部分,找到全局最优的概率较小,它的优势之一是可以避免陷入局部最优;而有的算法可以使用多种算子,如变邻域搜索算法,它通过在当前解的多个邻域中寻找更满意的解,能够大大提高算法在解空间的搜索范围,但是它在使用算子时盲目地将每种算子形成的邻域结构都搜索一遍,缺少了一些启发式信息的指导。而自适应大邻域搜索算法就弥补了这种不足,这种算法根据算子的历史表现与使用次数选择下一次迭代使用的算子,通过算子间的相互竞争来生成当前解的邻域结构,而在这种结构中有很大概率能够找到更好的解。

    自适应大邻域搜索算法的步骤如下:
    1.生成初始解X,最优解X’ = X,初始化算法参数。
    2.重复如下步骤直到停止准则:
    2.1轮盘赌方法根据算子权重选择破坏与修复算子对当前解进行操作得到新解X’’。
    2.2更新算子使用次数
    2.3如f(X’’) < f(X),则X = X’’。
    2.4如f(X’’) < f(X’),则X’ = X’’。
    2.5如f(X’’) > f(X),则以一定的概率接受该解作为当前解。
    2.5更新算子的分数。
    2.6更新算子的权重。权重更新式子如下:
    在这里插入图片描述
    式中,Wd为算子权重,Sd为算子分数,Ud为算子的使用次数。
    3.输出最优解。

    对于TSP问题,ALNS往往能在较短时间内找到满意解:

    # Adaptive Large Neighborhood Search
    import numpy as np
    import random as rd
    import copy
    
    distmat = np.array([[0,350,290,670,600,500,660,440,720,410,480,970],
                     [350,0,340,360,280,375,555,490,785,760,700,1100],
                     [290,340,0,580,410,630,795,680,1030,695,780,1300],
                     [670,360,580,0,260,380,610,805,870,1100,1000,1100],
                     [600,280,410,260,0,610,780,735,1030,1000,960,1300],
                     [500,375,630,380,610,0,160,645,500,950,815,950],
                     [660,555,795,610,780,160,0,495,345,820,680,830],
                     [440,490,680,805,735,645,495,0,350,435,300,625],
                     [720,785,1030,870,1030,500,345,350,0,475,320,485],
                     [410,760,695,1100,1000,950,820,435,475,0,265,745],
                     [480,700,780,1000,960,815,680,300,320,265,0,585],
                     [970,1100,1300,1100,1300,950,830,625,485,745,585,0]])
    
    def disCal(path):             # calculate distance
        distance = 0
        for i in range(len(path) - 1):
            distance += distmat[path[i]][path[i + 1]]
        distance += distmat[path[-1]][path[0]]
        return distance
    
    def selectAndUseDestroyOperator(destroyWeight,currentSolution):    # select and use destroy operators
        destroyOperator = -1
        sol = copy.deepcopy(currentSolution)
        destroyRoulette = np.array(destroyWeight).cumsum()
        r = rd.uniform(0, max(destroyRoulette))
        for i in range(len(destroyRoulette)):
            if destroyRoulette[i] >= r:
                if i == 0:
                    destroyOperator = i
                    removedCities = randomDestroy(sol)
                    destroyUseTimes[i] += 1
                    break
                elif i == 1:
                    destroyOperator = i
                    removedCities = max3Destroy(sol)
                    destroyUseTimes[i] += 1
                    break
        return sol,removedCities,destroyOperator
    
    def selectAndUseRepairOperator(repairWeight,destroyedSolution,removeList):    # select and use repair operators
        repairOperator = -1
        repairRoulette = np.array(repairWeight).cumsum()
        r = rd.uniform(0, max(repairRoulette))
        for i in range(len(repairRoulette)):
            if repairRoulette[i] >= r:
                if i == 0:
                    repairOperator = i
                    randomInsert(destroyedSolution,removeList)
                    repairUseTimes[i] += 1
                    break
                elif i == 1:
                    repairOperator = i
                    greedyInsert(destroyedSolution,removeList)
                    repairUseTimes[i] += 1
                    break
        return destroyedSolution,repairOperator
    
    def randomDestroy(sol):    # randomly remove 3 cities
        solNew = copy.deepcopy(sol)
        removed = []
        removeIndex = rd.sample(range(0, distmat.shape[0]), 3)
        for i in removeIndex:
            removed.append(solNew[i])
            sol.remove(solNew[i])
        return removed
    
    def max3Destroy(sol):      # remove city with 3 longest segments
        solNew = copy.deepcopy(sol)
        removed = []
        dis = []
        for i in range(len(sol) - 1):
            dis.append(distmat[sol[i]][sol[i + 1]])
        dis.append(distmat[sol[-1]][sol[0]])
        disSort = copy.deepcopy(dis)
        disSort.sort()
        for i in range(3):
            if dis.index(disSort[len(disSort) - i - 1]) == len(dis) - 1:
                removed.append(solNew[0])
                sol.remove(solNew[0])
            else:
                removed.append(solNew[dis.index(disSort[len(disSort) - i - 1]) + 1])
                sol.remove(solNew[dis.index(disSort[len(disSort) - i - 1]) + 1])
        return removed
    
    def randomInsert(sol,removeList):     # randomly insert 3 cities
        insertIndex = rd.sample(range(0, distmat.shape[0]), 3)
        for i in range(len(insertIndex)):
            sol.insert(insertIndex[i],removeList[i])
    
    def greedyInsert(sol,removeList):     # greedy insertion
        dis = float("inf")
        insertIndex = -1
        for i in range(len(removeList)):
            for j in range(len(sol) + 1):
                solNew = copy.deepcopy(sol)
                solNew.insert(j,removeList[i])
                if disCal(solNew) < dis:
                    dis = disCal(solNew)
                    insertIndex = j
            sol.insert(insertIndex,removeList[i])
            dis = float("inf")
    
    T = 100
    a = 0.97
    b = 0.5
    wDestroy = [1 for i in range(2)]     # weights of the destroy operators
    wRepair = [1 for i in range(2)]      # weights of the repair operators
    destroyUseTimes = [0 for i in range(2)]     #The number of times the destroy operator has been used
    repairUseTimes = [0 for i in range(2)]      #The number of times the repair operator has been used
    destroyScore = [1 for i in range(2)]      # the score of destroy operators
    repairScore = [1 for i in range(2)]       # the score of repair operators
    solution = [i for i in range(distmat.shape[0])] # initial solution
    bestSolution = copy.deepcopy(solution)  # best solution
    iterx, iterxMax = 0, 100
    
    if __name__ == '__main__':
        while iterx < iterxMax:       # while stop criteria not met
            while T > 10:
                destroyedSolution,remove,destroyOperatorIndex = selectAndUseDestroyOperator(wDestroy,solution)
                newSolution,repairOperatorIndex = selectAndUseRepairOperator(wRepair,destroyedSolution,remove)
    
                if disCal(newSolution) <= disCal(solution):
                    solution = newSolution
                    if disCal(newSolution) <= disCal(bestSolution):
                        bestSolution = newSolution
                        destroyScore[destroyOperatorIndex] += 1.5     # update the score of the operators
                        repairScore[repairOperatorIndex] += 1.5
                    else:
                        destroyScore[destroyOperatorIndex] += 1.2
                        repairScore[repairOperatorIndex] += 1.2
                else:
                    if rd.random() < np.exp(- disCal(newSolution)/ T):  # the simulated annealing acceptance criteria
                        solution = newSolution
                        destroyScore[destroyOperatorIndex] += 0.8
                        repairScore[repairOperatorIndex] += 0.8
                    else:
                        destroyScore[destroyOperatorIndex] += 0.6
                        repairScore[repairOperatorIndex] += 0.6
    
                wDestroy[destroyOperatorIndex] = wDestroy[destroyOperatorIndex] * b + (1 - b) * \
                                                 (destroyScore[destroyOperatorIndex] / destroyUseTimes[destroyOperatorIndex])
                wRepair[repairOperatorIndex] = wRepair[repairOperatorIndex] * b + (1 - b) * \
                                               (repairScore[repairOperatorIndex] / repairUseTimes[repairOperatorIndex])
                # update the weight of the operators
    
                T = a * T 
            iterx += 1
            T = 100
    
        print(bestSolution)
        print(disCal(bestSolution))
    
    展开全文
  • 邻域搜索算法matlab代码
  • 邻域搜索算法matlab代码
  • 邻域搜索算法matlab代码
  • 目录广义邻域搜索算法传统邻域搜索算法:广义邻域搜索算法:广义邻域搜索算法的六要素:广义邻域搜索算法的统一结构:优化算法的性能评价指标: 传统邻域搜索算法: 即利用邻域结构进行逐步优化的局部搜索算法。 ...

    广义邻域搜索算法

    传统邻域搜索算法:

    即利用邻域结构进行逐步优化的局部搜索算法。
    算法从一个初始解出发,然后利用状态发生器持续的在解x的邻域中搜索比它好的解,然后替代x成为新的当前解,直至算法结束。

    广义邻域搜索算法:

    算法从若干初始解出发,在算法参数控制下由当前状态的邻域中产生出若干个候选解,并以某种策略在当前解和候选解中确定新的当前状态。伴随控制参数的调节,重复执行上述搜索过程,直至满足算法终止准则,结束搜索过程并输出优化结果。

    对传统算法加以改进得到广义邻域搜索算法:
    改进途径1、采用并行搜索结构
    改进途径2、设计复杂的邻域函数
    改进邻域3、替换贪婪策略等
    广义邻域搜索算法的栗子:模拟退火算法、遗传算法、进化规划、混沌搜索等,这些算法在优化流程上呈现出很大的共性。

    广义邻域搜索算法的六要素:

    1、搜索机制的选择(搜索解的方式、如何产生新的候选解的方式)
    2、搜索方法的选择(即每代有多少解参与优化。并行搜索、串行搜索
    3、邻域函数的设计(如基于路径编码的TSP优化中可利用互换、逆序和插入等多种邻域结构)
    4、状态更新方式的设计(指以何种策略在新旧状态中确定新的当前状态。基于确定性的容易陷入局部极小,基于随机性的探索性更高)
    5、控制参数的修改准则和方式的设计(当前控制参数难以使算法性能取得较大提高时,应考虑修改参数)
    6、算法终止准测的设计(应兼顾算法的优化质量和搜索效率等多方面,如给定最大步数、最优解的最大凝滞步数和最小偏差阈值等)

    广义邻域搜索算法的统一结构:

    对优化过程作两方面分解处理:
    方面1、基于优化空间的分层(原问题分解为子问题求解,最后将各子问题的解逆向综合为原问题的解)
    方面2、基于优化进程的分层(进程层次分为若干阶段,各阶段采用不同的搜索算法或邻域函数进行优化)

    目前混合算法的结构类型主要可归纳为串行镶嵌并行及混合结构
    串行结构:
    在这里插入图片描述

    镶嵌结构:
    在这里插入图片描述

    并行结构(又分为同步式并行、异步式并行、网络结构):
       同步式:各子算法相对独立但与主过程的通讯必须同步
       异步式:子算法与主过程的通讯不受其他子算法的限制
       网络结构:各算法分别在独立的存储器上执行独立的搜索,算法间的通信是通过网络相互传递的在这里插入图片描述

    优化算法的性能评价指标:

    1、优化性能指标(用以衡量算法对问题的最佳优化度,其值越小意味着算法的优化性能越好)
    2、时间性能指标(用以衡量算法对问题解的搜索快慢程度即效率)
    3、鲁棒性指标(用以衡量在随机初值下对最优解的逼近程度)

    基于上述三个性能指标,优化算法的综合性能指标即为它们的加权组合。综合性能指标值越小表明算法的综合性能越好。

    展开全文
  • 邻域搜索算法

    2012-10-28 00:07:01
    有阈值的四邻域搜索算法 用c#代码编写的 可直接运行 不需要改动
  • 邻域搜索算法

    千次阅读 2020-05-26 11:12:58
    邻域搜索算法,于1997年由Hansen和Mladenovi首次提出,已经成为国内外的一个研究热点。作为一种经典的启发式算法,其在众多领域涌现了大量的研究成果。它的基本思想是在搜索过程中系统地改变邻域结构集来拓展搜索...

    变邻域搜索算法(Variable Neighborhood Search,VNS),于1997年由Hansen和Mladenovi首次提出,已经成为国内外的一个研究热点。作为一种经典的启发式算法,其在众多领域涌现了大量的研究成果。它的基本思想是在搜索过程中系统地改变邻域结构集来拓展搜索范围, 获得局部最优解, 再基于此局部最优解重新系统地改变邻域结构集拓展搜索范围找到另一个局部最优解的过程。
    启发式方法一般用于生成NP-hard问题的初始解,好的启发式算法不仅能较快找出当前解周围的局部最优解,更能够跳出局部最优的桎梏,去更广阔的的天地寻找更满意的解,从而收敛于全局最优。VNS就是这样一种算法,它能够在当前邻域中找到最优解,又能跳出当前邻域寻找更好的解,因此,只要邻域结构集设置得较为合适,它有很大概率能够收敛于全局最优解。

    VNS算法的步骤如下:
    1.初始化 选择邻域结构集Nk (k=1, …, kmax) 和停止准则, 并给出初始解x。

    2.重复如下步骤直到满足停止准则:

    2.1 设置k=1;

    2.2 直到k=kmax, 重复如下步骤:

    2.2.1 随机搜索 在x的第k个邻域结构中随机产生x′ (x′∈Nk (x) ) ;

    2.2.2 局部搜索 以x′为初始解, 应用一些局部搜索方法获得的局部最优解, 对应局部最优解为x*l。

    2.2.3 更新 如果局部最优解优于当前最优解, 设置x=x*l, 继续在邻域结构N1内搜索;否则设置k=k+1。

    对于经典的NP-hard问题–TSP问题,VNS算法是对其进行求解的一个有效方法。

    #变邻域搜索解TSP问题
    import numpy as np
    import random as rd
    import copy
    
    def disCal(path):
        dis = 0
        for i in range(len(path)-1):
            dis += distmat[path[i]][path[i+1]]
        dis += distmat[path[0]][path[-1]]
        return dis
    
    def shaking(solution):
        solutioni = []
        for i in range(0, 12, 3):
            lis = solution[i:i+3]
            solutioni.append(lis)
        sequence = [i for i in range(4)]
        rd.shuffle(sequence)
        sol = []
        for se in sequence:
            sol += solutioni[se]
        return sol
    
    def variableNeighborhoodDescent(solution):
        i = 0
        dis , k = float("inf") , -1
        while i < 3:
            if i == 0:
                neiborSolution = neighborhoodOne(solution)
            elif i == 1:
                neiborSolution = neighborhoodTwo(solution)
            elif i == 2:
                neiborSolution = neighborhoodThree(solution)
            for j in range(len(neiborSolution)):
                if disCal(neiborSolution[j]) < dis:
                    dis = disCal(neiborSolution[j])
                    k = j
            if dis < disCal(solution):
                solution = neiborSolution[k]
                i = 0
            else:
                i += 1
        return disCal(solution),solution
    
    def neighborhoodOne(sol):      #swap算子
        neighbor = []
        for i in range(len(sol)):
            for j in range(i+1,len(sol)):
                s = copy.deepcopy(sol)
                x = s[j]
                s[j] = s[i]
                s[i] = x
                neighbor.append(s)
        return neighbor
    
    def neighborhoodTwo(sol):    #two_opt_swap算子
        neighbor = []
        for i in range(len(sol)):
            for j in range(i + 3, len(sol)):     #这里j从i+3开始是为了不产生跟swap算子重复的解
                s = copy.deepcopy(sol)
                s1 = s[i:j+1]
                s1.reverse()
                s = s[:i] + s1 + s[j+1:]
                neighbor.append(s)
        return neighbor
    
    def neighborhoodThree(sol):  #two_h_opt_swap算子
        neighbor = []
        for i in range(len(sol)):
            for j in range(i+1,len(sol)):
                s = copy.deepcopy(sol)
                s = [s[i]] + [s[j]] + s[:i] + s[i+1:j] + s[j+1:]
                neighbor.append(s)
        return neighbor
    
    distmat = np.array([[0,350,290,670,600,500,660,440,720,410,480,970],
                     [350,0,340,360,280,375,555,490,785,760,700,1100],
                     [290,340,0,580,410,630,795,680,1030,695,780,1300],
                     [670,360,580,0,260,380,610,805,870,1100,1000,1100],
                     [600,280,410,260,0,610,780,735,1030,1000,960,1300],
                     [500,375,630,380,610,0,160,645,500,950,815,950],
                     [660,555,795,610,780,160,0,495,345,820,680,830],
                     [440,490,680,805,735,645,495,0,350,435,300,625],
                     [720,785,1030,870,1030,500,345,350,0,475,320,485],
                     [410,760,695,1100,1000,950,820,435,475,0,265,745],
                     [480,700,780,1000,960,815,680,300,320,265,0,585],
                     [970,1100,1300,1100,1300,950,830,625,485,745,585,0]])
    
    if __name__ == '__main__':
        currentSolution = [i for i in range(distmat.shape[0])]
        rd.shuffle(currentSolution)                #随机产生初始解
        shorterDisrance = disCal(currentSolution)
        iterx, iterxMax = 0, 5
        while iterx < iterxMax:
            currentSolution = shaking(currentSolution)
            currentDistance, currentSolution = variableNeighborhoodDescent(currentSolution)
            if currentDistance < shorterDisrance:
                shorterDisrance = currentDistance
                iterx = 0
            else:
                iterx += 1
        print(shorterDisrance)
        print(currentSolution)
    
    展开全文
  • 邻域搜索算法matlab代码
  • 邻域搜索算法matlab代码
  • 邻域搜索算法的多样性和研究成果的分散性阻碍了算法的系统化研究与发展,因此建立统一的结构框架很有必要。论文通过对邻域搜索算法的流程和主要环节的分析与归纳,基于空间分解和进程分解的思路提出了邻域搜索算法的一...
  • 邻域搜索算法matlab代码
  • 邻域搜索算法matlab代码
  • 邻域搜索算法matlab代码FVNS MATLAB中的FVNS算法(林洪涛,王忠,机场关闭后航班重新调度的可变邻域搜索,IEEE ACCESS,正在审查中)代码 选择算法 运行FVNS.m以解决FVNS的飞机恢复问题。 运行SALS.m,以解决SALS...
  • 可变邻域搜索算法(VNS)是一种优化算法,它基于邻域的系统变化,同时在下降和扰动阶段搜索给定问题的最优解。 可变邻域搜索算法(VNS)算法是一种基于元启发式算法的全局优化技术。 它探讨了邻域变化的概念,既...
  • 邻域搜索算法matlab代码
  • 提出了解决无等待流水线调度问题的变邻域搜索调度算法。采用基于自然数编码的工件序列表达问题的解,采用多重Insert移动邻域和多重Swap移动邻域作为变邻域搜索的两种邻域结构...仿真实验证明了变邻域搜索算法的有效性。
  • 邻域搜索算法matlab代码
  • 邻域搜索算法matlab代码
  • 定义:变邻域搜索算法(VNS)就是一种改进型的局部搜索算法。它利用不同的动作构成的邻域结构进行交替搜索,在集中性和疏散性之间达到很好的平衡。其思想可以概括为“变则通”。 变邻域搜索算法依赖于以下事实: 1)...

    定义:变邻域搜索算法(VNS)就是一种改进型的局部搜索算法。它利用不同的动作构成的邻域结构进行交替搜索,在集中性和疏散性之间达到很好的平衡。其思想可以概括为“变则通”。

    变邻域搜索算法依赖于以下事实
    1)一个邻域结构的局部最优解不一定是另一个邻域结构的局部最优解。
    2)全局最优解是所有可能邻域的局部最优解。

    变邻域搜索算法主要由以下两个部分组成

    1. VARIABLE NEIGHBORHOOD DESCENT (VND)
    2. SHAKING PROCEDURE

    邻域:邻域就是指对当前解进行一个操作(这个操作可以称之为邻域动作)可以得到的所有解的集合。那么不同邻域的本质区别就在于邻域动作的不同了。

    邻域动作:邻域动作是一个函数,通过这个函数,对当前解s,产生其相应的邻居解集合。例如:对于一个bool型问题,其当前解为:s = 1001,当将邻域动作定义为翻转其中一个bit时,得到的邻居解的集合N(s)={0001,1101,1011,1000},其中N(s) ∈ S。同理,当将邻域动作定义为互换相邻bit时,得到的邻居解的集合N(s)={0101,1001,1010}。

    VARIABLE NEIGHBORHOOD DESCENT (VND)
    VND其实就是一个算法框架,它的过程描述如下:

    1. 给定初始解S; 定义m个邻域,记为N_k(k = 1, 2, 3…m);i = 1。

    2. 使用邻域结构N_i(即 N_i(S))进行搜索,如果在N_i(S)里找到一个比S更优的解S′,则令S=S′, i=1 。

    3. 如果搜遍邻域结构N_i仍找不到比S更优的解,则令i++。

    4. 如果i≤m ,转步骤2。

    5. 输出最优解S。
      在这里插入图片描述

    6. 当在本邻域搜索找不出一个比当前解更优的解的时候,我们就跳到下一个邻域继续进行搜索。如图中虚黑线所示。

    7. 当在本邻域搜索找到了一个比当前解更优的解的时候,我们就跳回第一个邻域重新开始搜索。如图中红线所示。

    shaking procedure
    是一个扰动算子,类似于邻域动作的这么一个东西。通过这个算子,可以产生不同的邻居解。扰动、抖动、邻域动作这几个本质上还是没有什么区别的。都是通过一定的规则,将一个解变换到另一个解而已。

    本文转载自:
    https://mp.weixin.qq.com/s?__biz=MzU0NzgyMjgwNg==&mid=2247484621&idx=1&sn=f2e92f44c2306b58034cf158647bc737&chksm=fb49c974cc3e406228737e1a986c73368131bc7f0c0251d82b1e64266220df59134ab0a9def1&mpshare=1&scene=1&srcid=0828EK2xkjxczX8dMch3ud66&sharer_sharetime=1566963711577&sharer_shareid=054592193644de509623829748e83807&key=d4d627468bf29e729a37d96e9420f75ae8ee07d92976b165df3762d4a0bf2e01a06e3591d4793592a5980676c1d1668f96fe3e822792af675cd35f1752c460bc5edec3bfc0549fe8ec7273a4243355f3&ascene=1&uin=MjYzMDA1MzAyMQ%3D%3D&devicetype=Windows+10&version=62060834&lang=zh_CN&pass_ticket=mKdxom8osJJZb4ixH2eELB08AKAMdUQR7xMY0nJhEcpBIeHmtdQToh3Nk47Aacno

    展开全文
  • 邻域搜索算法matlab代码
  • 邻域搜索算法matlab代码
  • 邻域搜索算法(VNS)就是一种改进型的局部搜索算法。它利用不同的动作构成的邻域结构进行交替搜索,在集中性和疏散性之间达到很好的平衡 目标函数:用来判断解的优劣。 邻域的定义:根据不同问题,有着不同的邻域...
  • 阅读本文大概需要 10分钟。算法介绍自适应大邻域搜索算法(Adaptive Large Neighborhood Search),简称(ALNS),是由Ropke与Pisinger在20...
  • 快速的局部邻域搜索算法,用于无等待流水车间调度,总流水时间达到最小
  • 邻域搜索算法matlab代码
  • 基于python语言,实现经典自适应大邻域搜索算法(ALNS)对车辆路径规划问题(CVRP)进行求解。
  • 该算法在分析路径寻优问题的局部特性的基础上,利用变邻域搜索算法VNS(Variable Neighbourhood Search)对路径空间进行“局部探索”,结合变异机制对路径空间进行“全局开采”,最后根据近邻优先原则将动态路径片段...
  • 邻域搜索算法matlab代码STYRENE是一个黑盒优化问题,作为无导数优化社区的基准案例。 它模拟了苯乙烯的生产过程,被视为不存在衍生物的黑盒模拟。 目的是使净现值最大化,但要遵循多个过程和经济约束条件。 该代码...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 809
精华内容 323
关键字:

邻域搜索算法