假设地图上有一片树林,坦克需要绕过树林,走到另一侧的军事基地,在无数条行进路线中,哪条才是最短的?

  这是典型的最短寻径问题,可以使用A*算法求解。A*搜索算法俗称A星算法,是一个被广泛应用于路径优化领域的算法,它的行为的能力基于启发式代价函数,在游戏的寻路中非常有用。

将地图表格化

  A*算法的第一个步是将地图表格化,具体来说是用一个大型的二维列表存储地图数据。这有点类似于像素画:

  画中的小狗是由一个个像素方格组成的,方格越小,图案越平滑。在坦克寻径问题中,坦克的个头远小于地图,因此我们把坦克作为一个像素,这样一来,地图就可以切分为一个个方格,其中S代表坦克的起点,E代表基地:

  我们把地图映射到二维列表上,每一方格都可以用唯一的二元组表示,元组的第一个维度是行号,第二个是列号,起点和终点的坐标分别是(3,2)和(5,7)。“找到坦克的最短路径”实际是在回答最短路径需要经过那些方格。

评估函数

  A*算法的核心是一个评估函数:F(n)=H(n)+G(n)。

  H(n)是距离评估函数,n代表地图上的某一个方格,H(n)的值是该方格到终点的距离。距离的计算方式有很多,选择不同的方式,计算的结果也不同:

  假设每个方格的边长都是1,如果用欧几里德距离S到计算S到E的距离,则:

  如果用曼哈顿距离计算,则:

  G(n)是从起点移动到n的代价函数,n离起点越远,付出的代价越高。起点达到n的路线有多条,每条路线的G值可能不同:

  坦克从S到T的路线有两条,S→A→B→C→T和S→D→T,第二条路线更短,付出的代价也更低。假设从一个方格移动到相邻方格的代价是1,则G(D)=G(A)=1。B的前一步是A,因此G(B)=G(A)+1=2。同理,G(C)=G(B)+1=3。对于G(T)来说,它的值取决于T的上一步,如果路线是S→A→B→C→T,则G(T)=G(C)+1=4;如果路线是S→D→T,则G(T)=G(D)+1=2。值得注意的是,代价函数并不是唯一的,具体如何定义,完全取决于你自己。

  某个位置的评估函数F仅仅是将该点的距离估值和代价值加起来。A*搜索的每一个寻径都会寻找评估值最小的点。

A*搜索的步骤

  A*搜索涉及到两个重要的列表,openList(开放列表,存储候选节点)和closeList(关闭列表,存储已经走过的节点)。算法先把起放入openList中,然后重复下面步骤:

  1. 遍历openList,找到F值最小的那个作为当前所在节点,用P表示;

  2. 把P加入closeList中,作为已经走过的节点;

  3. 探索P周围相邻且不在closeList中的每一个节点,记算它们的H值、G值和F值,并把P设置为这些方格的父节点,将这些节点作为待探索节点添加到Q中。当然,如何定义“相邻”也是你说的算。

  4. 如果Q中的节点不在openList中,将其加入到openList。Q中的节点已经存在于openList中,比较这些节点的F值和它们在openList中的F值哪个更小(F越小说明这条路径越短),如果openList中的F值更小或二者相等,不做任何改变,否则用Q中的节点替换掉openList中的节点。

  5. 如果终点在openList中,退出,最短路径就是从终点开始,沿着父节点移动直至起点;如果openList是空的,退出,此时意味着起点到终点没有任何路可走。

  似乎不那么直观,我们仍然以坦克移动的例子审视这个过程。

通向基地的最短路线

  在游戏开始之气,先要制定一些游戏规则。

  坦克可每一步都可以移动到与之相邻的八个方格中,我们指定每一个方格的边长是10,从一个方格移动到相邻方格的代价是这两个方格中心点的距离。如此一来,坦克上、下、左、右平移一格所花费的代价是10(这里之所以将边长定义为10而不是1,目的是为了避免向斜对角移动时产生小数),向斜对角移动的代价是

  下一步定义相邻的方格是否能够探索。如果坦克的相邻方格是障碍物,那么坦克无法移动到障碍物上,也无法贴着障碍物移动到斜对角的方格

  不能移动到×所在的方格

探索最短路线

  定义了游戏规则后就可以开始移动坦克。

  我们定义地图是一个8×8的小地图,使用曼哈顿距离作为距离评估函数。以探索起点正上方的方格为例,它的位置是(4,2),到起点的代价是G=10。

  对于任意方格到终点的距离,我们不考虑障碍物,仅仅是简单的根据曼哈顿距离的公式计算。起点到终点的距离:

H(n) = H(4,2) = (|4 - 5| + |2 - 7|) * 10 = 60

  这里乘以了系数10,这是由于我们在游戏规则中定义了方格的单位长度是10。

  这有点类似于手机导航中的红色连线,这条连线仅仅连接了车标和终点,并不考虑中间是否有阻碍物:

  起点的G值是0,F=G+H=70。在待探索的八个方格中,我们设置从上到下的三个数值分别代表G、H、F,使用一个箭头指向是它的parent,箭头的指向不同,G值也可能不同:

 将S周围的方格设置为待探索方格

  由于openList是空的,所以把 Q 中的8个待探索节点都放入openList中。此时的openList中,F(4,3) 最小,因此选择(4,3)作为下一个到达的位置,并把它从openList移至closeList

  有八个方格与(4,3)相邻,其中(3,2)已经在closeList中,将它排除,(5,4)是障碍物,也排除,现在还剩六个,把它们都放入Q中:

将(4,3)相邻的可探索方格放到Q中

  在Q的六个点中,(5,2),(5,3),(4,4),(3,4)是第一次探索,直接加入到openList中;(4,2),(3,3)已经存在于openList中,表示二者曾经被探索过。由于是从(4,3)探索(4,2)和(3,3),因此二者的G值与从S点探索时的G值不同,即GQ(4,2)≠GopenList(4,2),GQ(3,3)≠GopenList(3,3),并且它们的父节点也不同。很明显,对于从S到(4,2)的两条路径来说,S→(4,3) →(4,2)要比S→ (4,2)更长,移动的代价更高,即GQ(4,2)> GopenList(4,2);同理,GQ(3,3)>GopenList(3,3)。此时保留(4,2)和(3,3)在openList中的的数值和箭头指向:

保持openList中的(4,2)和(3,3)不变

  现在,openList中(5,3)和(4,4)的F值都是64,选择哪个都无所谓,这完全取决你自己制定的选取规则。这里我们用“胡乱选一个”的规则选择了(4,4)作为下一个目的地。与(4,4)相邻的八个方格中,四个是障碍物,一个在closeList中,还剩下(5,3),(3,3),(3,4)。根据游戏的规则,坦克无法“贴着障碍物移动到斜对角的方格”,因此(5,3)也要从待探索方格中去掉:

从(4,4)出发,可探索(3,3)和(3,4)

  Q中的F(3,3)和F(3,4)都大于OpenList中的F(3,3)和F(3,4),因此保留openList的元素不变:

保留openList的(3,3)和(3,4)

  现在,openList的最小F值是F(5,3)=64,而(5,3)并不在Q中,说明对于路径S→(4,3)→(4,4)的探索失败了,但这并不妨碍我们从openList中挑选最小值F(5,3)=64。根据游戏规则,(5,3)周围有4个可供探索的方格:

从(5,3)出发,可探索(4,2), (5,2), (6,2), (6,3)

  类似地,Q中的F(4,2)和F(5,2)都小于openList中的F(4,2)和F(5,2),因此保持openList中的元素不变,将Q中的另外两个元素(6,2)和(6,3)移至openList中:

保持openList中的(4,2)和(5,2)不变,添加(6,2)和(6,2)

  在openList中,(4,2)是最佳选择,而(4,2)并没有指向(5,3),说明通过S→(4,3)→(5,3)并不能产生最佳路径。

  这个结论不妨碍继续执行A*搜索,再一次从openList中选择F值最小的元素(4,2)继续探索

从(4,2)出发,可探索(3,1),(4,1),(5,1),(5,2)

  在这一次探索中,Q中的最小F值F(5,2)=70已经小于openList中的F(5,2)=78,因此用Q中的(5,2)替换openList中的(5,2),这将重新改变(5,2)的评估值和父节点:

用Q中的(5,2)替换openList中的(5,2)

  接下来从openLIst中选择(5,2)作为出发点,它周围可探索(4,1),(5,1),(6,1),(6,2),(6,3)这5个方格:

从(5,2)出发,可探索(4,1),(5,1),(6,1),(6,2),(6,3)

  这次openLIst中的最小F值是F(3,3)=70。选择(3,3)后将会继续选择(3,4),此时我们将又一次面对openLIst中有多个最小F值相等的情况:

openList中多个最小F值相等,F(4,1)=F(5,1) = F(6,3)=F(2,4)=F(2,3) =84

  无论选择哪一个,最终都将得到同样的最短路径,假设(6,3)是这几个方格中最后选择的,则最终的结果:

  从终点开始向前遍历,可以发现A*算法找到的最短路径是S→(4,3) →(5,3) →(6,3) →(6,4) →(6,5) →(6,6) →E。

  可以看出,A*搜索和广度优先搜索十分类似,二者的候选集相同,它们的主要区别在于,广度优先搜索的选择是盲目的,而A*搜索是优先选择出代价最小的那个,利用启发的方式,使得每一步都更接近于最优解。

构建数据模型

  地图上的每一个方格都是一个节点,我们将节点信息映射为Node类:

class Node:
def __init__(self, x, y, parent, g=0, h=0):
self.x = x # 节点的行号
self.y = y # 节点的列号
self.h = h
self.g = g
self.f = g + h
self.parent = parent # 父节点 def get_G(self):
'''
当前节点到起点的代价
:param parent:
:return:
'''
if self.g != 0:
return self.g
elif self.parent is None:
self.g = 0
# 当前节点在parent的垂直或水平方向
elif self.parent.x == self.x or self.parent.y == self.y:
self.g = self.parent.get_G() + 10
# 当前节点在parent的斜对角
else:
self.g = self.parent.get_G() + 14
return self.g def get_H(self, end):
'''
节点到终点的距离估值
:param end: 终点坐标(x,y)
:return:
'''
if self.h == 0:
self.h = self.manhattan(self.x, self.y, end[0], end[1]) * 10
return self.h def get_F(self, end):
'''
节点的评估值
:param: end 终点坐标
:return:
'''
if self.f == 0:
self.f = self.get_G() + self.get_H(end)
return self.f def manhattan(self, from_x, from_y, to_x, to_y):
''' 曼哈顿距离 '''
return abs(to_x - from_x) + abs(to_y - from_y)

  每个节点都能够计算出自己的G值、H值和F值。在get_G()中,计算G值需要使用parent.get_G(),这是一种递归调用,为了避免递归的无用功,如果当前节点的G值已经计算过了,get_G()将直接返回结果。

  接下来可以编写坦克寻径的代码,先来看一些基础结构:

class Tank_way:
''' 使用A*搜索找到坦克的最短移动路径 '''
def __init__(self, start, end, map2d, obstruction=1):
'''
:param start: 起点坐标(x,y)
:param end: 终点坐标(x,y)
:param map: 地图
:param obstruction: 障碍物标记
'''
self.start_x, self.start_y = start
self.end = end
self.map2d = map2d
self.openlist = {}
self.closelist = {}
# 垂直和水平方向的差向量
self.v_hv = [(-1, 0), (0, 1), (1, 0), (0, -1)]
# 斜对角的差向量
self.v_diagonal = [(-1, 1), (1, 1), (1, -1), (-1, -1)]
self.obstruction = obstruction # 障碍物标记
self.x_edge, self.y_edge = len(map2d), len(map2d[0]) # 地图边界
self.answer = None def is_in_map(self, x, y):
''' (x, y)是否中地图内 '''
return 0 <= x < self.x_edge and 0 <= y < self.y_edge def in_closelist(self, x, y):
''' (x, y) 方格是否在closeList中 '''
return self.closelist.get((x, y)) is not None def upd_openlist(self, node):
''' 用node 替换 openlist中的对应数据 '''
self.openlist[(node.x, node.y)] = node def add_in_openlist(self, node):
''' 将node添加到 openlist '''
self.openlist[(node.x, node.y)] = node def add_in_closelist(self, node):
''' 将node添加到 closelist '''
self.closelist[(node.x, node.y)] = node def pop_min_F(self):
''' 弹出openlist中F值最小的节点 '''
key_min, node_min = None, None
for key, node in self.openlist.items():
if node_min is None:
key_min, node_min = key, node
elif node.get_F(self.end) < node_min.get_F(self.end):
key_min, node_min = key, node
# 将node_min从openlist中移除
if key_min is not None:
self.openlist.pop(key_min)
return node_min

  我们使用二维列列表存储地图上的每一个方格,用1表示障碍物,0表示可走的道路。openList和closeList使用字典代替列表,key是方格的坐标,value是表方格的节点,这将比列表更便于执行中A*搜索中的相关操作。

  注意到这里并没有像5.5.1那样用一个列表存储八个方向的差向量,而是将斜对角的向量拆分出来,这样做的目的是便于应对游戏规则中“无法贴着障碍物移动到斜对角的方格”这一规则。假设某个方格的坐标是(x,y),现在想要移动到左上方的(x’,y’)。能够移动的前提是(x,y)附近的两个方格都不是障碍物,可以用(x,y’)和(x’,y)来定位它们:

  这种方法的好处是,只要知道(x,y)和(x’,y’),就可以判断是否存在阻挡移动的障碍物,而无需关心(x’,y’)具体在什么方向:

  根据这种思路编写用于寻找待探索节点的方法:

    def get_Q(self, P):
''' 找到P周围可以探索的节点 '''
Q = {}
# 将水平或垂直方向的相应方格加入到Q
for dir in self.v_hv:
x, y = P.x + dir[0], P.y + dir[1]
# 如果(x,y)不是障碍物并且不在closelist中,将(x,y)加入到Q
if self.is_in_map(x, y) \
and self.map2d[x][y] != self.obstruction \
and not self.in_closelist(x, y):
Q[(x, y)] = Node(x, y, P)
# 将斜对角的相应方格加入到Q
for dir in self.v_diagonal:
x, y = P.x + dir[0], P.y + dir[1]
# 如果(x,y)不是障碍物,且(x,y)能够与P联通,且(x,y)不在closelist中,将(x,y)加入到Q
if self.is_in_map(x, y) \
and self.map2d[x][y] != self.obstruction \
and self.map2d[x][P.y] != self.obstruction \
and self.map2d[P.x][y] != self.obstruction \
and not self.in_closelist(x, y):
Q[(x, y)] = Node(x, y, P)
return Q

实现A*搜索

  A*搜索的完整代码:

class Node:
def __init__(self, x, y, parent, g=0, h=0):
self.x = x # 节点的行号
self.y = y # 节点的列号
self.h = h
self.g = g
self.f = g + h
self.parent = parent # 父节点 def get_G(self):
'''
当前节点到起点的代价
:param parent:
:return:
'''
if self.g != 0:
return self.g
elif self.parent is None:
self.g = 0
# 当前节点在parent的垂直或水平方向
elif self.parent.x == self.x or self.parent.y == self.y:
self.g = self.parent.get_G() + 10
# 当前节点在parent的斜对角
else:
self.g = self.parent.get_G() + 14
return self.g def get_H(self, end):
'''
节点到终点的距离估值
:param end: 终点坐标(x,y)
:return:
'''
if self.h == 0:
self.h = self.manhattan(self.x, self.y, end[0], end[1]) * 10
return self.h def get_F(self, end):
'''
节点的评估值
:param: end 终点坐标
:return:
'''
if self.f == 0:
self.f = self.get_G() + self.get_H(end)
return self.f def manhattan(self, from_x, from_y, to_x, to_y):
''' 曼哈顿距离 '''
return abs(to_x - from_x) + abs(to_y - from_y) class Tank_way:
''' 使用A*搜索找到坦克的最短移动路径 '''
def __init__(self, start, end, map2d, obstruction=1):
'''
:param start: 起点坐标(x,y)
:param end: 终点坐标(x,y)
:param map: 地图
:param obstruction: 障碍物标记
'''
self.start_x, self.start_y = start
self.end = end
self.map2d = map2d
self.openlist = {}
self.closelist = {}
# 垂直和水平方向的差向量
self.v_hv = [(-1, 0), (0, 1), (1, 0), (0, -1)]
# 斜对角的差向量
self.v_diagonal = [(-1, 1), (1, 1), (1, -1), (-1, -1)]
self.obstruction = obstruction # 障碍物标记
self.x_edge, self.y_edge = len(map2d), len(map2d[0]) # 地图边界
self.answer = None def is_in_map(self, x, y):
''' (x, y)是否中地图内 '''
return 0 <= x < self.x_edge and 0 <= y < self.y_edge def in_closelist(self, x, y):
''' (x, y) 方格是否在closeList中 '''
return self.closelist.get((x, y)) is not None def upd_openlist(self, node):
''' 用node 替换 openlist中的对应数据 '''
self.openlist[(node.x, node.y)] = node def add_in_openlist(self, node):
''' 将node添加到 openlist '''
self.openlist[(node.x, node.y)] = node def add_in_closelist(self, node):
''' 将node添加到 closelist '''
self.closelist[(node.x, node.y)] = node def pop_min_F(self):
''' 弹出openlist中F值最小的节点 '''
key_min, node_min = None, None
for key, node in self.openlist.items():
if node_min is None:
key_min, node_min = key, node
elif node.get_F(self.end) < node_min.get_F(self.end):
key_min, node_min = key, node
# 将node_min从openlist中移除
if key_min is not None:
self.openlist.pop(key_min)
return node_min def get_Q(self, P):
''' 找到P周围可以探索的节点 '''
Q = {}
# 将水平或垂直方向的相应方格加入到Q
for dir in self.v_hv:
x, y = P.x + dir[0], P.y + dir[1]
# 如果(x,y)不是障碍物并且不在closelist中,将(x,y)加入到Q
if self.is_in_map(x, y) \
and self.map2d[x][y] != self.obstruction \
and not self.in_closelist(x, y):
Q[(x, y)] = Node(x, y, P)
# 将斜对角的相应方格加入到Q
for dir in self.v_diagonal:
x, y = P.x + dir[0], P.y + dir[1]
# 如果(x,y)不是障碍物,且(x,y)能够与P联通,且(x,y)不在closelist中,将(x,y)加入到Q
if self.is_in_map(x, y) \
and self.map2d[x][y] != self.obstruction \
and self.map2d[x][P.y] != self.obstruction \
and self.map2d[P.x][y] != self.obstruction \
and not self.in_closelist(x, y):
Q[(x, y)] = Node(x, y, P)
return Q def a_search(self):
while True:
# 找到openlist中F值最小的节点作为探索节点
P = self.pop_min_F()
# openlist为空,表示没有通向终点的路
if P is None:
break
# P加入closelist
self.add_in_closelist(P)
# P周围待探索的节点
Q = self.get_Q(P)
# Q中没有任何节点,表示该路径一定不是最短路径,重新从openlist中选择
if Q == {}:
continue
# 找到了终点, 退出循环
if Q.get(self.end) is not None:
self.answer = Node(self.end[0], self.end[1], P)
break # Q中的节点与openlist中的比较
for item in Q.items():
(x, y), node_Q = item[0], item[1]
node_openlist = self.openlist.get((x, y))
# 如果node_Q不在openlist中,直接将其加入openlist
if node_openlist is None:
self.add_in_openlist(node_Q)
# node_Q的F值比node_openlist更小,则用node_Q替换node_openlist
elif node_Q.get_F(self.end) < node_openlist.get_F(self.end):
self.upd_openlist(node_Q) def start(self):
node_start = Node(self.start_x, self.start_y, None)
self.openlist[(self.start_x, self.start_y)] = node_start
self.a_search() def paint(self):
''' 打印最短路线 '''
node = self.answer
while node is not None:
print((node.x, node.y), 'G={0}, H={1}, F={2}'.format(node.g, node.h, node.get_F(self.end)))
node = node.parent if __name__ == '__main__':
map2d = [[0] * 8 for i in range(8)]
map2d[5][4] = 1
map2d[5][5] = 1
map2d[4][5] = 1
map2d[3][5] = 1
map2d[2][5] = 1
start, end = (3, 2), (5, 7)
a_way = Tank_way(start, end, map2d)
a_way.start()
a_way.paint()

  运行结果:

代价因子

  坦克寻径的故事并没有结束,还可以额外考虑游戏中的两种典型的情况。一种是我们之前定义的“无法贴着障碍物移动到斜对角的方格”并不那么准确,如果障碍物只是占据了单元格的一部分位置,坦克也许可以挤过去:

  另一个情况在游戏中更为常见,坦克其实是可以穿过树林的,只不过在树林中行进远远慢于在大路上行进。这类似于电视中的桥段:大路远但好走,小路近而难行,至于最终哪个更省力,全靠运气——也许小路由于刚下过一场雨导致更加难走,克服困难的成本远大于原计划节省的成本。为了应对这种情况,可以为每个方格添加一个代价因子,一个方格的代价因子越高,移动到这里的代价越大。例如某一点(x,y)的G值是G(x,y)=100,向垂直和水平方向的相邻方格移动一步的代价是10;左侧方格(x1,y1)是树林,移动因子是2;右侧方格(x2,y2)是平地,移动因子是1,此时:

  


   作者:我是8位的

  出处:http://www.cnblogs.com/bigmonkey

  本文以学习、研究和分享为主,如需转载,请联系本人,标明作者和出处,非商业用途!

  扫描二维码关注公众号“我是8位的”

A*搜索详解(1)——通往基地的最短路线的更多相关文章

  1. Elastic Stack 笔记(六)Elasticsearch5.6 搜索详解

    博客地址:http://www.moonxy.com 一.前言 Elasticsearch 主要包含索引过程和搜索过程. 索引过程:一条文档被索引到 Elasticsearch 之后,默认情况下 ES ...

  2. Solr系列五:solr搜索详解(solr搜索流程介绍、查询语法及解析器详解)

    一.solr搜索流程介绍 1. 前面我们已经学习过Lucene搜索的流程,让我们再来回顾一下 流程说明: 首先获取用户输入的查询串,使用查询解析器QueryParser解析查询串生成查询对象Query ...

  3. Lucene系列六:Lucene搜索详解(Lucene搜索流程详解、搜索核心API详解、基本查询详解、QueryParser详解)

    一.搜索流程详解 1. 先看一下Lucene的架构图 由图可知搜索的过程如下: 用户输入搜索的关键字.对关键字进行分词.根据分词结果去索引库里面找到对应的文章id.根据文章id找到对应的文章 2. L ...

  4. elasticsearch最全详细使用教程:入门、索引管理、映射详解、索引别名、分词器、文档管理、路由、搜索详解

    一.快速入门1. 查看集群的健康状况http://localhost:9200/_cat http://localhost:9200/_cat/health?v 说明:v是用来要求在结果中返回表头 状 ...

  5. A*搜索详解(2)——再战觐天宝匣

    书接上文.在坦克寻径的,tank_way中,A*算法每一步搜索都是选择F值最小的节点,步步为营,使得寻径的结果是最优解.在这个过程中,查找最小F值的算法复杂度是O(n),这对于小地图没什么问题,但是对 ...

  6. 算法进阶面试题01——KMP算法详解、输出含两次原子串的最短串、判断T1是否包含T2子树、Manacher算法详解、使字符串成为最短回文串

    1.KMP算法详解与应用 子序列:可以连续可以不连续. 子数组/串:要连续 暴力方法:逐个位置比对. KMP:让前面的,指导后面. 概念建设: d的最长前缀与最长后缀的匹配长度为3.(前缀不能到最后一 ...

  7. 【蓝桥杯真题】地宫取宝(搜索->记忆化搜索详解)

    链接 [蓝桥杯][2014年第五届真题]地宫取宝 题目描述 X 国王有一个地宫宝库.是 n x m 个格子的矩阵.每个格子放一件宝贝.每个宝贝贴着价值标签. 地宫的入口在左上角,出口在右下角. 小明被 ...

  8. 搜索引擎(Elasticsearch搜索详解)

    学完本课题,你应达成如下目标: 掌握ES搜索API的规则.用法. 掌握各种查询用法 搜索API 搜索API 端点地址 GET /twitter/_search?q=user:kimchy GET /t ...

  9. elasticsearch系列四:搜索详解(搜索API、Query DSL)

    一.搜索API 1. 搜索API 端点地址 从索引tweet里面搜索字段user为kimchy的记录 GET /twitter/_search?q=user:kimchy 从索引tweet,user里 ...

随机推荐

  1. Delphi 对ini文件的操作

    界面如图: 代码如下: unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Vari ...

  2. (2018 Multi-University Training Contest 2)Problem G - Naive Operations

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6315 题目大意:告诉你a,b两个数组,a数组初始化为0,b数组告诉你长度和具体值,接下来有q次操作,a ...

  3. 利用 Eclipse IDE 的强大功能远程调试 Java 应用程序

    II. Eclipse 连接套接字模式下的 VM 调用示例(具体引用实践) 说明:不管采用哪种方式,调试的源代码都在eclipse的环境下 一.调试方式一(将目标应用程序作为调试的服务器,eclips ...

  4. chrome console.log失效

    把红框里的内容去掉就可以了 那个框是过滤..

  5. 使用restTemplate来访问https

    1.maven: <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId& ...

  6. MAVEN项目不扫描mybatis的mapper.xml问题

    在使用maven+mybatis+spring在开发的时候,遇到问题,总是找不到mapper.xml文件里定义的方法.检查后发现maven编译后并没有将xml文件打包到输出路径,导致bean创建失败. ...

  7. 读取Excel,单元格内容大于255个字符自动被截取的问题

    DataSet ds = new DataSet(); cl_initPage.v_DeBugLog("ExcelDataSource进入"); string strConn; s ...

  8. 手动调用run方法和普通方法调用没有区别

    手动调用run方法和普通方法调用没有区别

  9. WebStorm 安装及使用

    WebStrom 插件安装 File(文件) -> settings(设置) -> Plugins 即可调出设置中的插件选项. 或者 按 快捷键ctrl + alt + s也可调出设置菜单 ...

  10. TiDB初步概念

    阅读官方文档画以下路线图: 储存: rockDB用于单机数据固化:完全理解 raft用于分布式数据同步:完全理解 最终对外展示一整个完全有序的Key-Value序列:完全理解 重点:有序,就可以随机访 ...