1. Введение
Поиск может быть как сложным, так и простым. Когда не известна (или только частично известна) как сама цель, так и способ её достижения, случайность важна
Целью исследования статьи будет сравнение способов нахождения цели как подвижной (жёлтый объект), так и неподвижной.
Эти способы:
- Случайный поиск (красный объект)
- Случайный поиск с памятью (синий объект)
- Случайный поиск с памятью и иерархией (зелёный объект)
- Поиск первого маршрута (фиолетовый объект)
- Поиск короткого маршрута (коричневый объект)
На рис.1 эти объекты показаны. Полностью код программы выложен на github
2. Основная часть
2.1. Класс P – случайный поиск
Конструктор инициализирует атрибуты и переменные класса: главное окно, цвет, координату по y и x, счётчик, словарь посещённых, цель, словарь иерархии, словарь соседей. Метод init_3_dict заполняет три словаря. Метод show закрашивает клетку в нужный цвет. Метод update обновляет объект. Метод top_show создаёт окно верхнего уровня и показывает сколько шагов сделано до цели.
class P(): def __init__(self, root, color, node, target, maps, ban): self.root = root self.color = color self.y = node[0] self.x = node[1] P.target = target self.count = 0 self.visit = {} P.hierarchy = {} P.neighbor = {} self.init_3_dict(maps, ban)
В методе init_3_dict объявляется функция is_en, которая проверяет, чтобы координата не была забанина и не выходила за рамки карты.
def init_3_dict(self, maps, ban): def is_en(yx): if yx[0] < 0 or yx[0] > len(maps)-1: return if yx[1] < 0 or yx[1] > len(maps[0])-1: return if yx in ban: return return True
В цикле в словарь посещённых с ключом координата записываем начальное значение ноль. В словарь иерархии с таким же ключом записываем иерархическое имя этой координаты. Потом заполняем словарь соседей, вызывая функцию is_en.
for y in range(len(maps)): for x in range(len(maps[0])): self.visit[(y,x)] = 0 P.hierarchy[(y,x)] = maps[y][x] n = [] if is_en((y-1,x)): n.append((y-1,x)) if is_en((y+1,x)): n.append((y+1,x)) if is_en((y,x-1)): n.append((y,x-1)) if is_en((y,x+1)): n.append((y,x+1)) P.neighbor[(y,x)] = n
Метод show закрашивает клетку с координатами y,x в нужный цвет.
def show(self, y, x, color): lb = Label(text=" ", background = color) lb.configure(text=P.hierarchy[(y,x)] ) lb.grid(row=self.y, column=self.x, ipadx=10, ipady=5, padx=1, pady=1)
Метод move передвигает объект. В переменные y, x записываем координату соседа, которая выбрана случайно из списка соседей. Потом перерисовываем с новыми координатами.
Увеличиваем счётчик. Проверяем достигнута ли цель. Если цель достигнута, то в переменную класса J.disable записываем верно. Вызываем метод top_show().
def move(self): v = [] for i in P.neighbor[(self.y, self.x)]: v.append(i) y,x = random.choice(v) self.show(self.y, self.x, 'white') self.y = y self.x = x self.show(y, x, self.color) self.count +=1 if P.target == P.hierarchy[(self.y, self.x)]: J.disable = True self.top_show()
Метод update вызывает move и обновляет через каждые пол секунды.
def update(self): self.move() self.root.after(500, self.update)
Метод top_show выводит результаты.
def top_show(self): top = Toplevel() lbt = Label(top, text=self.color + " = " + str(self.count)) lbt.pack() top.mainloop()
2.2. Класс М – случайный поиск с памятью
Конструктор вызывает конструктор класса родителя и увеличивает значение той клетки поля, куда идём. Метод move передвигает объект. Метод choice возвращает координату, которую выбрал алгоритм случайного поиска с памятью.
В методе move вызываем метод choice. В остальном его работа аналогична методу родителя.
def move(self): yx = self.choice((self.y, self.x))
Метод choice перебирает координаты соседей и добавляет в список v кортежи. Первым элементом кортежа будет координата соседа, вторым — сколько раз она была посещена. Потом сортируем от маленького до большого. Перебираем все кортежи и в списке v оставляем только те, которые встречались столько раз, сколько записано в первом кортеже. Случайно выбираем любой из них.
def choice(self, yx): v = [] for i in P.neighbor[yx]: v.append((i, self.visit[i])) v.sort(key = lambda x: x[1], reverse = False) v = [i for i in v if v[0][1] == i[1]] v = random.choice(v) self.visit[v[0]] += 1 return v[0]
2.3. Класс N – случайный поиск с памятью и иерархией.
Метод choice отбирает те кортежи, которые больше всего совпадают с иерархическим именем цели. Если это совпадение одинаково, то отбираются те координаты, которые меньше всего были посещены.
Конструктор вызывает конструктор родителя и инициализирует атрибут coincidence количество посимвольных совпадений иерархического имени с целью.
def __init__(self, root, color, node, target, maps, ban): super().__init__(root, color, node, target, maps, ban) self.coincidence = 0
Метод choice в цикле записывает иерархическое имя текущей координаты соседа. Переменная r хранит количество совпадений этой переменной c целью. Если количество совпадений r больше, то coincidence перезаписывается. В этом случае в список d записывается кортеж (координаты, количество посещений) из списка v. Если же r равно coincidence, то в d добавляем кортеж из v.
def choice(self, yx): v = [] for i in P.neighbor[yx]: v.append((i, self.visit[i])) d = [] for l in v: c = P.hierarchy[l[0]] r = 0 for i in range(len(P.target)): if c[i] == P.target[i]: r +=1 else: break if r > self.coincidence: self.coincidence = r d = [l] if r == self.coincidence: d.append(l) if d: v = d v.sort(key = lambda x: x[1], reverse = False) v = [i for i in v if v[0][1] == i[1]] v = random.choice(v) self.visit[v[0]] += 1 return v[0]
2.4. Класс К – поиск маршрута и поиск самого короткого маршрута.
Конструктор в переменную end записывает координаты цели. Если параметр short равен False, то вызываем поиск маршрута. Иначе находим самый короткий маршрут.
Метод find_path в маршрут добавляет текущую координату. Если цель достигнута, тогда возвращает маршрут. Перебираем всех соседей. Если соседа нет в path, тогда рекурсивно вызываем себя. И то, что метод возвращает, записываем в newpath. Первым найденным маршрутом будет newpath. Отличие метода find_short_path в том, что, если длина newpath больше или равна длине shortest, тогда shortest перезаписывается.
def find_path(self, node, end, path=[]): path = path + [node] if node == end: return path for v in P.neighbor[node]: if v not in path: newpath = self.find_path(v, end, path) if newpath: return newpath
2.5. Класс J – подвижная цель.
Объект класса J передвигается как класс P. Если переменная класса J.disable равна верно, то объект останавливается.
2.6. Функция main.
Создаём главное окно. В переменную target записываем иерархическое имя цели. В список ban записаны координаты запретных клеток. В списке maps – сама карта. В цикле рисуем карту. Создаём объекты класса. Потом вызываем функцию update, которая обновляет объекты.
3. Выводы
Были проведены пять экспериментов как для подвижной цели, так и для неподвижной.
Из таблицы 1 видно, что самый лучший алгоритм – самый короткий маршрут. Второй по эффективности — это случайный поиск с памятью и иерархией. Худший – случайный поиск.
Таблица 1. Неподвижная цель
В случае подвижной цели результаты иные. Худшими стали те алгоритмы, которые изначально рассчитывали маршрут до цели. В таблице 2 прочерк означает, что цель не достигнута. Brown оказался хуже, чем Violet. Хуже, поскольку у Violet траектория длиньше (шанс встретить подвижную цель больше). Самый лучший алгоритм при поиске подвижной цели — случайный поиск с памятью и иерархией. На втором месте — случайный поиск с памятью.
Таблица 2. Подвижная цель
Если неизвестно подвижна ли цель или нет, то лучше использовать алгоритм случайный поиск с памятью и иерархией либо просто случайный поиск с памятью.
4. Заключение
Случайность важна, если поиск цели происходит в условиях неизвестности. Поиск сокращается, если данные представлены иерархично. Память, разумеется, тоже ценна.
ссылка на оригинал статьи https://habr.com/ru/post/479414/
Добавить комментарий