This is the final code of my individual project from Algorithm class CSCI 3101 Hawaii Pacific University.
알고리듬 수업에서 수행한 개인 프로젝트의 결과물입니다.
Freckles – Skiena and Revilla Programming Challenges book.
알고리즘 트레이닝 북에 실린 주근깨 문제입니다.
I implemented Prim and Kruskal algorithms for this project with Python. See attached files on the bottom of this post if you need.
You are allowed to edit attached source codes, but please do not remove my name as an author on the top.
참고로, 한국에서 판매하는 책에 해답(C로 작성된 소스코드)이 실려있지만, 실제로 작성해서 컴파일/실행해본 결과 제대로 작동이 되질 않았습니다. 따라서, 이것을 필자가 파이썬으로 재작성했고, 프로젝트의 조건은 Prim과 Kruskal 두 개의 알고리즘으로 구현해야하는 것이었습니다. 소스코드는 첨부된 하단에 파일을 참고해주세요. 내용은 수정해도 되지만, 제 이름은 삭제하지 말아주세요.
The below is the problem from the book.
아래는 알고리즘 트레이닝 북에 실린 문제의 원문입니다.
In an episode of the Dick Van Dyke television show, Dick’s son Richie connects the freckles on his father’s back to form a picture of the Liberty Bell. Consider Dick’s back to be a plane with freckles at various (x, y) locations. Your job is to connect the dots so as to minimize the amount of ink used. Richie connects the dots by drawing straight lines between pairs, possibly lifting the pen between lines. When Richie is done there must be a sequence of connected lines from any freckle to any other freckle.
대강 번역하자면, 아버지의 등에 나있는 주근깨들을 가장 가까운 주근깨로 한 번에 하나씩 연결하여 모든 주근깨를 연결하는 것입니다.
Input
The input begins with a single positive integer on a line by itself indicating the number of test cases, followed by a blank line.
The first line of each test case contains 0 < n <= 100, giving the number of freckles on Dick’s back. For each freckle, a line follows; each following line contains two real numbers indicating the (x, y) coordinates of the freckle.
Put a blank line between each two consecutive test cases.
Output
For each test case, your program must print a single real number to two decimal places: the minimum total length of ink lines that can connect all the freckles. The output of each two consecutive cases must be separated by a blank line.
Sample Input
3 1.0 1.0 2.0 2.0 2.0 4.0
Sample Output
3.41
———————————————————————————————————
My source codes
Prim:
#!/usr/bin/python # -*- coding: UTF-8 -*- """ Class: CSCI 3101 Name: Seowon Jung Professor: Yi Zhu Filename: SeowonJung_Prim.py Programming Project from Skienna and Revilla Programming Challenge book. This script has been written with Python v2.7.1. """ import math, sys # Declare a global variable result = 0 searchedNode = 1 # Creat a class to store node and visited info into each object # for each node class Freckle: def __init__(self, curX, curY, visited, distances, freckles): self.x = curX # Current X-Coordinate self.y = curY # Current Y-Coordinate self.visitedList = [] # Store visited list self.distances = [0]*(freckles+1) # Store all distances between current node and other nodes. # Calculate distance between two nodes def CalcDistance(currentNode, closestNode): if closestNode == 0: return 0 else: dist = round(math.sqrt(pow(abs(float(node[currentNode].x) - float(node[closestNode].x)), 2) + pow(abs(float(node[currentNode].y) - float(node[closestNode].y)), 2)), 2) return dist # Create a visited list for a "starting node", which tries to connect. # Search all nodes in a same group def VisitedList(nodeInCurrentGroup): global visitList visitList = [] def visitedListFromAllSubNode(subNode): global visitList for nodeFS in node[subNode].visitedList: if nodeFS in visitList: visitList = list(set(visitList)) # Removing duplicated elements elif nodeFS == nodeInCurrentGroup: visitList = list(set(visitList)) else: visitList.append(nodeFS) visitedListFromAllSubNode(nodeFS) for nodeF in node[nodeInCurrentGroup].visitedList: visitList.append(nodeF) visitedListFromAllSubNode(nodeF) visitList = list(set(visitList)) return visitList # Updated visited nodes list for each node to avoid being re-searched by the same closest node def UpdateVisitedList(srcNode, destNode): node[srcNode].visitedList.append(destNode) node[destNode].visitedList.append(srcNode) # Remove duplicated nodes node[srcNode].visitedList = list(set(node[srcNode].visitedList)) node[destNode].visitedList = list(set(node[destNode].visitedList)) # Cycle test, True/False def CycleTest(srcNode, destNode): srcVisitedList = VisitedList(srcNode) destVisitedList = VisitedList(destNode) cycleMagicVar = False for magicNode in srcVisitedList: if magicNode in destVisitedList: cycleMagicVar = True break return cycleMagicVar def nodeLocationShortestDistance(distanceList): # To avoid an error that searches a minimum value except zero if the distanceList has all zero value, # which means, there is a no more closest node. zeroCheck = 0 for i in distanceList: if i > 0: zeroCheck += 1 if zeroCheck > 0: dist = min([x for x in distanceList if not x ==0]) location = distanceList.index(dist) return dist, location else: return 0, 0 def prim(currentNodesList): #print '\n', currentNodesList, '------------------------------------' global result # To avoid an infinite loop if len(currentNodesList) == freckles: print 'No more node' print 'Prim: ', result sys.exit(0) # Create a dictionary for storing the shortest path and both node # numbers on an edge for each node. tempDistanceList = {} # Calculate the shortest path from current nodes list for eachNode in currentNodesList: for i in range(1, freckles+1, 1): # If a node which needs to be investigated is in a visited list, # a distance will be zero to avoid re-searching if i in node[eachNode].visitedList: node[eachNode].distances[i] = 0 else: node[eachNode].distances[i] = CalcDistance(eachNode, i) #print eachNode, ': ', node[eachNode].distances dist, location = nodeLocationShortestDistance(node[eachNode].distances) #print '\tdist: ', dist, ', location: ', location tempDistanceList[dist] = [eachNode,location] #print "List: ", tempDistanceList # To figure out whether no more shortest path exists or not zeroCheck = 0 for i in tempDistanceList.keys(): if i > 0: zeroCheck += 1 if zeroCheck > 0: # If the minimum value except zero exists minValue = min([x for x in tempDistanceList.keys() if not x == 0]) minEdge = tempDistanceList.get(minValue) else: print 'No more node' print 'Prim: ', result sys.exit(0) del zeroCheck # Cycle test if CycleTest(minEdge[0], minEdge[1]) == True: UpdateVisitedList(minEdge[0], minEdge[1]) prim(currentNodesList) else: # Update visited list for both nodes on the edge UpdateVisitedList(minEdge[0], minEdge[1]) result += minValue print 'Min value: ', minValue, ', Min edge: ', minEdge, ', Result: ', result # Add both nodes on the edge to the investigating list currentNodesList.append(minEdge[0]) currentNodesList.append(minEdge[1]) # Remove duplicated nodes currentNodesList = list(set(currentNodesList)) prim(currentNodesList) def main(): # Sample cases for testing A = ['407.00 805.00','514.00 603.00','974.00 666.00','100.00 129.00','733.00 704.00','266.00 607.00','973.00 602.00','242.00 94.00','538.00 296.00','284.00 466.00','584.00 447.00','580.00 81.00','380.00 57.00','344.00 906.00','207.00 989.00','45.00 778.00','794.00 723.00','545.00 769.00','553.00 646.00','62.00 286.00','515.00 328.00','57.00 488.00','95.00 300.00','582.00 633.00','597.00 867.00','263.00 181.00','477.00 7.00','426.00 22.00','228.00 934.00','929.00 435.00','923.00 974.00','377.00 882.00','698.00 923.00','651.00 415.00','570.00 877.00','701.00 249.00','369.00 758.00','737.00 464.00','222.00 483.00','98.00 819.00','514.00 525.00','164.00 992.00','532.00 754.00','14.00 761.00','688.00 943.00','360.00 612.00','82.00 737.00','658.00 944.00','661.00 310.00','359.00 395.00','187.00 224.00','644.00 557.00','146.00 545.00','186.00 368.00','29.00 448.00','351.00 706.00','973.00 679.00','699.00 669.00','433.00 877.00','594.00 121.00','984.00 954.00','897.00 67.00','856.00 555.00','175.00 681.00','29.00 697.00','76.00 380.00','85.00 884.00','101.00 395.00','593.00 288.00','763.00 786.00','736.00 278.00','492.00 873.00','957.00 355.00','543.00 554.00','233.00 138.00','676.00 218.00','256.00 737.00','448.00 113.00','293.00 787.00','794.00 486.00','485.00 34.00','867.00 734.00','82.00 132.00','129.00 676.00','420.00 56.00','626.00 320.00','334.00 118.00','193.00 455.00','474.00 900.00','9.00 871.00','38.00 849.00','89.00 458.00','586.00 701.00','571.00 879.00','488.00 529.00','529.00 137.00','728.00 396.00','871.00 810.00','529.00 164.00','650.00 113.00'] # A: 6658.39 B = ['1.4 0.4', '1.0 0.3'] # B: 0.41 C = ['3.4 5.0'] # C: 0 D = ['4.2 1.6', '0.3 4.6', '6.2 0.0', '8.9 6.8', '3.9 6.7', '6.0 3.3', '9.7 7.4', '1.5 0.7', '6.5 8.9', '2.1 3.0'] # D: 23.94 E = ['6.6 7.3'] # E: 0 F = ['8.0 3.2', '0.9 9.7', '6.8 7.6', '8.9 2.1', '9.6 6.0', '8.6 6.2', '6.4 9.1', '5.0 8.8', '3.1 1.8'] # F: 20.06 G = ['4.0 7.7', '4.4 1.7', '8.3 2.0', '1.8 8.2', '2.8 7.0', '2.4 2.2', '4.4 0.1', '7.9 2.4', '7.3 6.2'] # G: 18.22 H = ['0.6 4.0', '9.1 8.5'] # H: 9.62 I = ['8.0 3.4', '7.7 5.4', '7.7 4.6', '9.9 0.3', '8.7 6.9', '3.4 1.6'] # I: 12.42 J = ['6.7 7.5'] # J: 0 K = ['9.1 0.8', '8.0 9.0', '9.0 9.1', '7.5 5.1'] # K: 9.52 L = ['1.0 1.0', '2.0 2.0', '2.0 4.0'] # L: # 3.41 sampleInput = A # Declare variables and matrix maxNum = 101 # Create a list for nodes filled up with 0 global node, freckles node = [0]*maxNum # Get an input for the number of test case # testCase = input('How many test cases: ') testCase = 1 # Just enter # raw_input('') for x in range(1, testCase+1, 1): print "Case #"+str(x)+"----------------" # freckles = input('How many freckles: ') freckles = len(sampleInput) for i in range(1, freckles+1, 1): # dots_raw = raw_input('Two real numbers for X and Y coodinates #'+str(i)+': ') dots_raw = sampleInput[i-1] # Split input to x and y node by a blank token dots = dots_raw.strip().split(" ") node[i] = Freckle(dots[0], dots[1], None, None, freckles) prim([1]) print "\n\nPrim: "+str(result) #print visitedNodeList if(x > 0): print "\n" print '\n' if __name__ == "__main__": main()
Kruskal:
#!/usr/bin/python # -*- coding: UTF-8 -*- """ Class: CSCI 3101 Name: Seowon Jung Professor: Yi Zhu Filename: SeowonJung_Kruskal.py Programming Project from Skienna and Revilla Programming Challenge book. This script has been written and worked with Python v2.7.1. """ import math, operator, sys # Declare a global variable result = 0 # Creat a class to store node and visited info into each object # for each node class Freckle: def __init__(self, curX, curY, closest, dist, visited): self.x = curX # Current X-Coordinate self.y = curY # Current Y-Coordinate self.visitedList = [] # Store visited list # Calculate distance between two nodes def calcDistance(currentNode, closestNode): if closestNode == 0: return 0 else: dist = round(math.sqrt(pow(abs(float(node[currentNode].x) - float(node[closestNode].x)), 2) + pow(abs(float(node[currentNode].y) - float(node[closestNode].y)), 2)), 2) # print "calcDistance--------- currentNode: "+str(currentNode)+"\tClosestNode: "+str(closestNode)+"\tDistance: "+str(dist) return dist # This function returns two values - a closest distance and its a node def nodeLocationShortestDistance(distanceList): # To avoid an error that searches a minimum value except zero if the distanceList has all zero value, # which means, there is a no more closest node. zeroCheck = 0 for i in distanceList: if i > 0: zeroCheck += 1 if zeroCheck > 0: dist = min([x for x in distanceList if not x ==0]) location = distanceList.index(dist) return dist, location else: return 0, 0 # This function removes two-way closest path in the list. def removeTwoWayNode(shortest): for j in range(1, freckles+1, 1): if j in shortest: if shortest[j][1] in shortest: if j == shortest[shortest[j][1]][1]: #print j, ': ', shortest[j], ', ', shortest[j][1], 'deleted' del shortest[shortest[j][1]] return shortest # Create a visited list for a "starting node", which tries to connect # Search all nodes in a same group def visitedListFrom(nodeFrom): global fromVisitList def visitedListFromAllSubNode(subNode): global fromVisitList for nodeFS in node[subNode].visitedList: if nodeFS in fromVisitList: fromVisitList = list(set(fromVisitList)) elif nodeFS == nodeFrom: fromVisitList = list(set(fromVisitList)) else: fromVisitList.append(nodeFS) visitedListFromAllSubNode(nodeFS) for nodeF in node[nodeFrom].visitedList: fromVisitList.append(nodeF) visitedListFromAllSubNode(nodeF) fromVisitList = list(set(fromVisitList)) return fromVisitList # Create a visited list for a "ending node", which is a destination # Search all nodes in a same group def visitedListTo(nodeTo): global destVisitList def visitedListToAllSubNode(subNode): global destVisitList for nodeDS in node[subNode].visitedList: if nodeDS in destVisitList: destVisitList = list(set(destVisitList)) elif nodeDS == nodeTo: destVisitList = list(set(destVisitList)) else: destVisitList.append(nodeDS) visitedListToAllSubNode(nodeDS) for nodeD in node[nodeTo].visitedList: destVisitList.append(nodeD) visitedListToAllSubNode(nodeD) destVisitList = list(set(destVisitList)) return destVisitList # Updated visited node list for each node to avoid re-search its the same closest node def updateVisitedList(srcNode, destNode): node[srcNode].visitedList.append(destNode) node[destNode].visitedList.append(srcNode) def kruskal(): global result shortest = {} # Calculate all distances for each node for i in range(1, freckles+1, 1): # From tempDistanceList = [0]*(freckles+1) for j in range(1, freckles+1, 1): # To tempDistanceList[j] = calcDistance(i, j) # Closest node number and its distance dist, location = nodeLocationShortestDistance(tempDistanceList) if dist == 0 and location == 0: # print "No more closest node" print "Kruskal: ", result sys.exit(0) else: shortest[i] = [dist, location] # print i, ": ", " Distance: ", shortest[i][0], ", Closest node: ", shortest[i][1] del tempDistanceList # Remove two-way nodes shortest = removeTwoWayNode(shortest) # print '\nBefore sort: ', shortest # print shortest # Sort the shortest list by distances shortest = sorted(shortest.iteritems(), key=operator.itemgetter(1)) # print '\nAfter sort: ', shortest, '\n' # Link nodes!!! - Initial for each in shortest: # each[0] = From node[each[0]].visitedList.append(each[1][1]) # Closest node # Add the current node into the Closest Node's visit list. # Because, the shortest distance and closest node are same each other. # Therefore, all nodes are connected node[each[1][1]].visitedList.append(each[0]) # Calculate the total length. result += each[1][0] print 'Link ', each[0], ' -> ', each[1][1], ",\t", each[1][0], node[each[0]].visitedList # print '' # Delete shortest list to be used for second closest node del shortest # This function is for searching next connections after the initial connection. # This function finds the next closest node and then do cycle-test. If it is OK, then it connects to the node. # But, if it is a cycle, it updates each node's visited list, and then re-create a distance list for each node. # And then do cycle-test def findAnotherClosestNode(): global result shortest = {} # Next closest node for all single node in order to figure out whether another closest node exists or not. # Create a distance list except the linked shortest node for i in range(1, freckles+1, 1): tempDistanceList = [0]*(freckles+1) for j in range(1, freckles+1, 1): if j in node[i].visitedList: tempDistanceList[j] = 0 else: tempDistanceList[j] = calcDistance(i, j) # Closest node number and its distance dist, location = nodeLocationShortestDistance(tempDistanceList) # If closest distance and node number are empty, it's done. if dist == 0 and location == 0: # print "No more closest node" print "Kruskal: ", result sys.exit(0) else: dist, location = nodeLocationShortestDistance(tempDistanceList) shortest[i] = [dist, location] # print i, ": ", " Distance: ", shortest[i][0], ", Closest node: ", shortest[i][1] # To avoid duplicated distances and locations del tempDistanceList # print shortest # Remove two-way nodes shortest = removeTwoWayNode(shortest) # Sort in ascending order shortest = sorted(shortest.iteritems(), key=operator.itemgetter(1)) # print '\nAfter sort: ', shortest, '\n' cycleMagicNum = False # For figure out a destination node is a cycle or not # Cycle test for each in shortest: # each[0] = Source node # each[1][1] = Destination node # If the destination node's visited list has at least one same element in the From node's visited list, it's cyclic # Create a list for storing visited nodes global fromVisitList, destVisitList fromVisitList = [] destVisitList = [] # print each[0],' ---------------------------------------------------' # A visit list for a source node fromVisitList = visitedListFrom(each[0]) # A visit list for a destination destVisitList = visitedListTo(each[1][1]) # print 'From visit list: '#,fromVisitList # print 'Dest visit list: '#,destVisitList # Test that the source and destination nodes have at least one common node for nodeCycle in fromVisitList: if nodeCycle in destVisitList: # print '\t\tIt is a cycle, ', each[0], ' -> ', each[1][1] # print '' # print '\t\t', each[0], ': ', node[each[0]].visitedList, ', ', each[1][1], ': ', node[each[1][1]].visitedList # Update visited list for both source and destination nodes to avoid including the distance list updateVisitedList(each[0], each[1][1]) # print '\t\t', each[0], ': ', node[each[0]].visitedList, ', ', each[1][1], ': ', node[each[1][1]].visitedList cycleMagicNum = True break # If no cycle found, link this node! if not cycleMagicNum == True: updateVisitedList(each[0], each[1][1]) print 'Added: ', each[1][1],' -> ', each[0], '\tResult: ', result result += each[1][0] # print '' # To create new lists every loop del fromVisitList, destVisitList return cycleMagicNum for i in range(1, freckles+1, 1): # print i, ' ---------------------------------------------' findAnotherClosestNode() def main(): A = ['407.00 805.00','514.00 603.00','974.00 666.00','100.00 129.00','733.00 704.00','266.00 607.00','973.00 602.00','242.00 94.00','538.00 296.00','284.00 466.00','584.00 447.00','580.00 81.00','380.00 57.00','344.00 906.00','207.00 989.00','45.00 778.00','794.00 723.00','545.00 769.00','553.00 646.00','62.00 286.00','515.00 328.00','57.00 488.00','95.00 300.00','582.00 633.00','597.00 867.00','263.00 181.00','477.00 7.00','426.00 22.00','228.00 934.00','929.00 435.00','923.00 974.00','377.00 882.00','698.00 923.00','651.00 415.00','570.00 877.00','701.00 249.00','369.00 758.00','737.00 464.00','222.00 483.00','98.00 819.00','514.00 525.00','164.00 992.00','532.00 754.00','14.00 761.00','688.00 943.00','360.00 612.00','82.00 737.00','658.00 944.00','661.00 310.00','359.00 395.00','187.00 224.00','644.00 557.00','146.00 545.00','186.00 368.00','29.00 448.00','351.00 706.00','973.00 679.00','699.00 669.00','433.00 877.00','594.00 121.00','984.00 954.00','897.00 67.00','856.00 555.00','175.00 681.00','29.00 697.00','76.00 380.00','85.00 884.00','101.00 395.00','593.00 288.00','763.00 786.00','736.00 278.00','492.00 873.00','957.00 355.00','543.00 554.00','233.00 138.00','676.00 218.00','256.00 737.00','448.00 113.00','293.00 787.00','794.00 486.00','485.00 34.00','867.00 734.00','82.00 132.00','129.00 676.00','420.00 56.00','626.00 320.00','334.00 118.00','193.00 455.00','474.00 900.00','9.00 871.00','38.00 849.00','89.00 458.00','586.00 701.00','571.00 879.00','488.00 529.00','529.00 137.00','728.00 396.00','871.00 810.00','529.00 164.00','650.00 113.00'] # A: 6658.39 B = ['1.4 0.4', '1.0 0.3'] # B: 0.41 C = ['3.4 5.0'] # C: 0 D = ['4.2 1.6', '0.3 4.6', '6.2 0.0', '8.9 6.8', '3.9 6.7', '6.0 3.3', '9.7 7.4', '1.5 0.7', '6.5 8.9', '2.1 3.0'] # D: 23.94 E = ['6.6 7.3'] # E: 0 F = ['8.0 3.2', '0.9 9.7', '6.8 7.6', '8.9 2.1', '9.6 6.0', '8.6 6.2', '6.4 9.1', '5.0 8.8', '3.1 1.8'] # F: 20.06 G = ['4.0 7.7', '4.4 1.7', '8.3 2.0', '1.8 8.2', '2.8 7.0', '2.4 2.2', '4.4 0.1', '7.9 2.4', '7.3 6.2'] # G: 18.22 H = ['0.6 4.0', '9.1 8.5'] # H: 9.62 I = ['8.0 3.4', '7.7 5.4', '7.7 4.6', '9.9 0.3', '8.7 6.9', '3.4 1.6'] # I: 12.42 J = ['6.7 7.5'] # J: 0 K = ['9.1 0.8', '8.0 9.0', '9.0 9.1', '7.5 5.1'] # K: 9.52 L = ['1.0 1.0', '2.0 2.0', '2.0 4.0'] # L: 3.41 sampleInput = D # Declare a variable for total number. First location in the list [0] won't be used. maxNum = 101 # All node and a number of freckles need to be global variables. global node, freckles node = [0]*maxNum # Get an input for the number of test case # testCase = input('How many test cases: ') testCase = 1 # Just enter # raw_input('') for x in range(1, testCase+1, 1): print "Case #"+str(x)+"----------------" # freckles = input('How many freckles: ') freckles = len(sampleInput) for i in range(1, freckles+1, 1): # dots_raw = raw_input('Two real numbers for X and Y coodinates #'+str(i)+': ') dots_raw = sampleInput[i-1] # Split input to x and y node by a blank token dots = dots_raw.strip().split(" ") node[i] = Freckle(dots[0], dots[1], None, None, None) kruskal() print "\n\nKruskal: "+str(result) if(x > 0): print "\n" print '\n' if __name__ == "__main__": main()
Leave a Reply