Basic AI Algorithms. Search Algorithms for Traveling... | by Mengsay Loem | Towards Data Science

To solve a problem with a computer, it is necessary to represent the problem in numerical or symbolic form and offer a specific procedure using a programming language. However, working with problem-solving in the artificial intelligence (AI) field, it is difficult to specify a formulation of a problem from the beginning. Therefore, the flexibility of choosing a solution procedure during the observation of state changes is highly required. Some problems can be represented by a graph or tree structure where each node represents a state of the problem. This kind of problem formulation method is called State-space representation.
In a state-space representation method, each state of the problem is represented by a node of a graph or tree structure. In this case, the problem is translated as a search problem to determine the goal under specific operators and restrains. In this post, I will introduce Traveling Salesman Problem (TSP) as an example.
Representation a problem with the state-space representation needs:(1). A set of states of the problem(2). A set of operators to operate between states of the problem(3). Initial state and final state(goal)
Consider the following situation. You are given a list of n cities with the distance between any two cities. Now, you have to start with your office and to visit all the cities only once each and return to your office. What is the shortest path can you take? This problem is called the Traveling Salesman Problem (TSP).
To make the problem simple, we consider 3-city-problem. Let’s call the office ( A )and the 3 cities ( B ) ( C ) ( D ) respectively. We initialize the problem state by {A} means the salesman departed from his office. As an operator, when he visited city-B, the problem state is updated to {A, B}, where the order of elements in { } is considered. When the salesman visited all the cities, {A, B, C, D} in this case, the departed point A is automatically added to the state which means {A, B, C, D, A}. Therefore, the initial state of this TSP is {A} and the final state(goal) is {A, X1, X2, X3, A} where traveled distance is minimized.
Taking each state as a node of a tree structure, we can represent this TSP as the following tree search problem.
The depth-first search algorithm starts at the root node and explores as deep as possible along each branch before taking backtracking. In our TSP, when a state node with all city labels is visited, its total distance is memorized. This information will later be used to define the shortest path.
Let VISIT be a stack to save visited nodes, PATH be a set to save distances from the root node to the goal. The depth-first algorithm can be written as
The depth-first search algorithm starts at the root node and explores all of the nodes at the present depth level before moving on to the nodes at the next depth level. In our TSP, when a state node with all city labels is visited, its total distance is memorized. This information will later be used to define the shortest path.
Let VISIT be a queue to save visited nodes, PATH be a set to save distances from the root node to the goal. The breadth-first algorithm can be written as
In Brute-force search, all nodes are visited and the information from each node (distance from a node to a node) is not considered. This leads to a large amount of time and memory consumption. To solve this problem, a heuristic search is a solution. The information of each state node is used to consider visiting a node or not. This information is represented by a heuristic function which commonly set up by user’s experiences. For example, we can define the heuristic function by the distance from the root node to the present visit node, or the distance from the present visit node to the goal node.
In the Best-first search, we use the information of distance from the root node to decide which node to visit first. Let g(X) be the distance from the root node to node-X. Therefore, the distance from the root node to node-Y by visiting node-X is g(Y)=g(X)+d(X, Y), where d(X, Y) is the distance between X and Y.
Let VISIT be a list to save visited nodes. The best-first algorithm can be written as
In the A algorithm search, we use the information of distance from the present visit node to the goal as a heuristic function, h(X). Let g(X) be the distance from the root node to node-X. In this case, we consider the priority of node visit order by f(X)=g(X)+h(X).
In real-world problems, it is impossible to obtain the exact value of h(X). In that case, an estimation value of h(X), h’(X), is used. However, setting h’(X) takes risks in falling into a local optimal answer. To prevent this problem, choosing h’(X) which h’(X)≤h(X) for all X is recommended. In this case, it is known as A*-algorithm and it can be shown that the obtained answer is the global optimal answer.
In our experiment described in the following part, we are setting h’(X) as the sum of the minimum distance of all possible routes from each city which is not presented in the current visit node label, and the present city. For example, if the present node is {A, D}, then city-B and C is not present in the label. Therefore, h’(D)=min(all possible route distance from C)+min(all possible route distance from B)+min(all possible route distance from D).
Let VISIT be a list to save visited nodes. The A-algorithm can be written as
We simulate the TSP with each introduced algorithm and focus on effectiveness as a search problem. Here, the effectiveness of a search problem is evaluated by the number of nodes visited to reach the answer and theeffective branch number comparing to the tree’s depth level. The effective branch number b* of a problem where N nodes have been visited and the answer is a d depth-level of the tree, is calculated by N+1 = 1 + b* + (b*)2+(b*)3+...+(b*)^d . An algorithm with a smaller b* is a more effective algorithm. In this experiment, we use Newton’s method to solve this equation to obtain the approximate value of b*.
Discussion on the number of visited nodes
Discussion on the effective branch number
# Randomly generate problemimport randommax_city_number = 8Gs = []for NN in range(2,max_city_number):Gs.append([[random.randint(1,10) for e in range(NN+1)] for e in range(NN+1)])# Problem state representation: Tree structureclass node(object):def __init__(self,number=None):self.pre = Noneself.no = numberself.label = []self.child = []self.cost = Nonedef add_child(self,number):tmp_node = node(number=number)tmp_node.pre = selftmp_node.label=[i for i in self.label]tmp_node.label.append(number)tmp_node.cost= get_bound(tmp_node.label)self.child.append(tmp_node)# Evaluate Function for A Algorithmdef get_bound(label):f = 0for i in range(0,len(label)-1):f = f+graph[label[i]-1][label[i+1]-1]remain = city.difference(set(label))remain = list(remain)remain.append(label[-1])for i in remain:f = f+min_bound[i-1]if len(remain)==2:f=0label.append(remain[0])label.append(1)for i in range(0,len(label)-1):f = f+graph[label[i]-1][label[i+1]-1]return f# Evaluate Function for Best-first Algorithmdef get_bound(label):f = 0remain = city.difference(set(label))remain = list(remain)remain.append(label[-1])for i in remain:f = f+min_bound[i-1]if len(remain)==2:f=0label.append(remain[0])label.append(1)for i in range(0,len(label)-1):f = f+graph[label[i]-1][label[i+1]-1]return f# Evaluate Function for Depth-first/Breadth-first Algorithmdef get_bound(label,n_city):f = 0for i in range(0,len(label)-1):f = f+graph[label[i]-1][label[i+1]-1]if len(label)==len(graph):f = f+graph[label[-1]-1][0]return f# Effective Branch Number calculation (Newton's Method)def f(N,d,x):return (x**(d+1) - (N+1)*x + N)**2def df(N,d,x):return 2*f(N,d,x)*((d+1)*x**d-(N+1))def ddf(N,d,x):return 2*df(N,d,x)*((d+1)*x**d-(N+1))+2*f(N,d,x)*((d+1)*d*x**(d-1))def solve(N,d):x = 1.9delta = 1.0count = 0while abs(delta)>0.000001 and count<10000:delta = df(N,d,x)/(ddf(N,d,x)+0.00001)x = x - deltacount = count + 1return x# EXPERIMENT1/2: # Search for GOAL (for A algorithm/Best-first Algorithm)tree = node(number=1)tree.label.append(1)tree.cost=0for i in range(len(Gs)):print("----------i=%d------------"%i)graph = Gs[i]NN = len(graph)for idx in range(NN):graph[idx][idx] = float('inf')city = range(1,len(graph)+1)city = set(city)min_bound = [min(graph[i]) for i in range(len(graph))]tree = node(number=1)tree.label.append(1)tree.cost=0visit=[]visit.append(tree)count = 0fcnt = 0ans = 0while len(visit)>0:N = visit[0]if len(N.label)==(len(city)-2):fcnt = fcnt+1del(visit[0])count = count+1child_list = set(city).difference(set(N.label))if len(child_list)==0:ans = 1breakfor c in child_list:N.add_child(number=c)tmp = N.childfor i in tmp:visit.append(i)visit = sorted(visit,key= lambda x:x.cost)if ans==1:print("RESULT:",N.label,N.cost)b = solve(count,NN-2)print("d=%d ,N= %d ,  b*=%f"%(NN-2,count,b))print("ROUTEs: %d"%(fcnt))resultsA.append((NN-2,count,b))else:print("FAILED")# EXPERIMENT3: # Search for GOAL (for Depth-first algorithm)tree = node(number=1)tree.label.append(1)tree.cost=0for i in range(len(Gs)):print("----------i=%d------------"%i)graph = Gs[i]NN = len(graph)for idx in range(NN):graph[idx][idx] = float('inf')city = range(1,len(graph)+1)city = set(city)tree = node(number=1,n_city=len(city))tree.label.append(1)tree.cost=0visit=[]paths = []count = 0fcnt = 0visit.append(tree)while len(visit)>0:if len(visit)==0:breakN = visit.pop()if len(N.label)==(len(graph)+1):paths.append(N)fcnt = fcnt+1paths=sorted(paths,key= lambda x:x.cost)count = count+1child_list = set(city).difference(set(N.label))if len(child_list)==0:continuefor c in child_list:N.add_child(number=c)tmp = N.childfor i in tmp:visit.append(i)print("RESULT:",paths[0].label,paths[0].cost)print("d=%d ,N= %d ,  b*=%f"%(NN-2,count,solve(count,NN-2)))print("ROUTEs:%d"%(fcnt))# EXPERIMENT4: # Search for GOAL (for Breadth-first algorithm)tree = node(number=1)tree.label.append(1)tree.cost=0for i in range(len(Gs)):print("----------i=%d------------"%i)graph = Gs[i]NN = len(graph)for idx in range(NN):graph[idx][idx] = float('inf')city = range(1,len(graph)+1)city = set(city)tree = node(number=1,n_city=len(city))tree.label.append(1)tree.cost=0visit=[]paths = []count = 0fcnt = 0visit.append(tree)while len(visit)>0:if len(visit)==0:breakN = visit[0]del(visit[0])if len(N.label)==(len(graph)+1):paths.append(N)fcnt = fcnt+1paths=sorted(paths,key= lambda x:x.cost)count = count+1child_list = set(city).difference(set(N.label))if len(child_list)==0:continuefor c in child_list:N.add_child(number=c)tmp = N.childfor i in tmp:visit.append(i)print("RESULT:",paths[0].label,paths[0].cost)print("d=%d ,N= %d ,  b*=%f"%(NN-2,count,solve(count,NN-2)))print("ROUTEs:%d"%(fcnt))