Пример #1
0
def heap_sort(unsorted_arr):
    # Converting unsorted array to a Max Heap
    heap_arr = heap.Heap(unsorted_arr, len(unsorted_arr) - 1)
    heap_arr.build_max_heap()

    # Needed to store elements in correct order
    sorted_arr = [None] * heap_arr.n
    sorted_arr_index = heap_arr.n - 1

    while not heap_arr.is_empty():

        # Storing highest element in new array, ascending order maintained
        max_element = heap_arr.heap[1]
        sorted_arr[sorted_arr_index] = max_element
        sorted_arr_index -= 1

        # Removing root from heap by exchanging with last leaf
        heap_arr.heap[1], heap_arr.heap[heap_arr.n] = heap_arr.heap[
            heap_arr.n], heap_arr.heap[1]
        heap_arr.n -= 1

        # Above exchange may violate Max Heap property at root, correcting
        heap_arr.max_heapify(1)

    return sorted_arr
Пример #2
0
    def dijkstra(self, start):
        '''
        Dijkstra's algorithm builds off of Prim's algorithm. The objective of Dijkstra's algorithm is
        to find the shortest path between a starting node and a destination node. However, at
        the end of running Dijkstra's algorithm, we find that we have the shortest path to 
        every other single vertex in our graph. When I am throwing around the word shortest path, I am
        referencing the smallest total edge weight path. Dijkstra's algorithm only changes one thing
        from Prim's algorithm: instead of updating the weight to be edge weight, we total up 
        the total distance or edge weight we have seen so far and add it to the vertex's weight. 
        Dijkstra's algorithm is very useful because it handles both directed and undirected graphs. In
        addition, Dijkstra's algorithm accounts will make the correct call when deciding between
        many small weighted edges and one large weighted edge. One downside of Dijkstra's algorithm
        is that it cannot handle neight weight cycles. Finally, if we are trying to find the path
        from start to finish, we start with the destination node and work backwards using each 
        node's predecessor.

        INPUT:
            start: Starting node we are finding
        OUTPUT:
            Shortest path to every single vertex in our graph from the starting point
        
        Runtime - O(n + mlg(n)) - We know that 'initializing' each of the vertices takes O(n) time.
        Next, building a minHeap takes O(n) time. Once we enter the for loop and traverse all of the 
        vertices, we have an outer runtime of O(n). On the inside of the for loop, we remove from
        the minheap which takes O(lg(n)) time since we might have to heapify down.
        So that small portion gives us a runtime of nlg(n). The other portion inside
        of the for loop runs in time proportional to mlg(n) since we visit m edges, and 
        building the heap again takes lg(n) time. Once we add these up, we get a total
        runtime of O(mlg(n) + nlg(n)). Other implementations will get you better runtimes depending on
        what you want from the graph and the type of graph you are expecting. Using a adjacency list and
        minheap, we get this runtime. Using a adjacency matrix and a heap we also get the same runtime.
        However, if we swapped the heap for an unsorted array, we get a runtime of O(n^2) for both
        implementations of a graph. The best way to truely improve the runtime is to use a fibonacci heap,
        this would reduce total runtime to O(nlg(n) + m).
        '''

        for v in self.vertices.keys():
            v.weight = math.inf
            v.predecessor = None
        v.weight = 0
        priorityQueue = heap.Heap() # heap to store our vertices
        priorityQueue.buildHeap(self.vertices.keys())
        sssp = Graph() # Single source shortest path (Dijkstras)
        for __ in range(len(self.vertices.keys())):
            vert = priorityQueue.remove()
            sssp.__createdVertexInsertion(vert)
            if vert.predecessor:
                e = self.areAdjacent(vert.predecessor, vert)
                key = e.key
                weight = e.weight
                sssp.insertEdge(vert.predecessor, vert, key, weight)
            # Can add additional code to account for directed graphs.
            for adjvert in self.adjacentVertices(vert):
                if adjvert not in sssp.vertices.keys():
                    existing_weight = self.areAdjacent(adjvert, vert).weight + vert.weight
                    if existing_weight < adjvert.weight:
                        adjvert.weight = existing_weight
                        adjvert.predecessor = vert
                        priorityQueue.buildHeap()
        return sssp
Пример #3
0
    def mstPrim(self, v):
        '''
        Before we talk about the algorithm, lets touch base on what a MST exactly is once again. A
        minimum spanning tree (MST) is a graph that is minimally connected. This means that we 
        have created a path between any two nodes in our graph, have no cycles, and have a minimum
        total weight. In order to build a MST using Prim's algorithm, we first give each vertex a weight
        and a predecessor. Once we do this, we set the predecessor and weight in each vertex to be
        none and +inf respectively. In Prim's algorithm, we need to have a starting vertex. We set the
        weight of the starting vertex to 0. We then build a minheap to hold all of our vertices. We
        also create a new graph object that will end up being our MST. Finally, here we start our 
        algorithm. 
        for each vertex in vertices:
            min = queue.remove()
            if predecessor:
                Mst.addedge(vertex,predecessor)
            for neighbor of vertex not in new graph:
                if current_weight > weight_of_edge_connecting_vertex_neighbor:
                    neighbor.weight = weight_of_edge_connecting_vertex_neighbor
                    neighbor.predecessor = vertex

        Runtime - O(n + mlg(n)) - We know that 'initializing' each of the vertices takes O(n) time.
        Next, building a minHeap takes O(n) time. Once we enter the for loop and traverse all of the 
        vertices, we have an outer runtime of O(n). On the inside of the for loop, we remove from
        the minheap which takes O(lg(n)) time since we might have to heapify down.
        So that small portion gives us a runtime of nlg(n). The other portion inside
        of the for loop runs in time proportional to mlg(n) since we visit m edges, and 
        building the heap again takes lg(n) time. Once we add these up, we get a total
        runtime of O(mlg(n) + nlg(n)). Other implementations will get you better runtimes depending on
        what you want from the graph and the type of graph you are expecting. Using a adjacency list and
        minheap, we get this runtime. Using a adjacency matrix and a heap we also get the same runtime.
        However, if we swapped the heap for an unsorted array, we get a runtime of O(n^2) for both
        implementations of a graph. The best way to truely improve the runtime is to use a fibonacci heap,
        this would reduce total runtime to O(nlg(n) + m).
        '''

        for v in self.vertices.keys():
            v.predecessor = None
            v.weight = math.inf

        v.weight = 0
        priorityQueue = heap.Heap()
        priorityQueue.buildHeap(self.vertices.keys())
        minimumSpanningTree = Graph()

        for __ in range(len(self.vertices)):
            e = priorityQueue.remove()
            minimumSpanningTree.__createdVertexInsertion(e)
            if e.predecessor:
                key = self.areAdjacent(e, e.predecessor).key
                minimumSpanningTree.insertEdge(e, e.predecessor, key, e.weight)
            for neighbor in self.adjacentVertices(e):
                if not neighbor in minimumSpanningTree.vertices.keys():
                    weight = self.areAdjacent(neighbor, e).weight
                    if weight < neighbor.weight:
                        neighbor.weight = weight
                        neighbor.predecessor = e
                        priorityQueue.buildHeap()
        return minimumSpanningTree
Пример #4
0
    def mstKruskal(self):
        '''
        Before we talk about the algorithm, lets touch base on what a MST exactly is once again. A
        minimum spanning tree (MST) is a graph that is minimally connected. This means that we 
        have created a path between any two nodes in our graph, have no cycles, and have a minimum
        total weight. In order to build a MST using Kruskal's algorithm, we first put each vertex in 
        a seperate set in a disjoint set. The purpose we do this is to check whether two vertices have
        the same representative element. If they do, that means they are part of some minimum spanning 
        tree for that labeled set. Next, we place all of the edges in a heap and build a minheap based
        on the edge weights. This will allow for us to properly remove each edge and to have the
        edges in correct order in linear time (build heap runs in O(n) time). Finally, we create a new
        graph which will be the minimum spanning tree.
        Once we do all of this we follow this algorithm:
        # while edges < n - 1:
            # for each edge:
                # if vertices in edge are in seperate sets, union and make the edge a discovery edge
                # else make the edge not discovery but visited
        
        Runtime - O(n + mlg(n)) - Since it takes O(n) time to build the disjoint set, O(m) time to 
        build a minheap(partially sorted), O(m) time to loop through the while loop since we might have
        to visit every edge, and finally O(lg(m)) = o(lg(n)) time to remove from a heap, we get a total runtime
        of O(n + mlg(n)). The runtime of implementing this algorithm using a sorted array is the same runtime.
        However, a minheap allows you to update edge weights with less of a cost and works better with a dense 
        graph.
        '''

        minimumSpanningTree = Graph()
        forest = disjointset.DisjointSet(self.vertices.keys())
        edgeWeights = heap.Heap() # priority queue for our impl. of sorting edges.
        edgeWeights.buildHeap(self.edges.toList())

        while len(minimumSpanningTree.edges) < (len(self.vertices.keys()) - 1):
            edge = edgeWeights.remove()
            index1 = index2 = 0
            # Room for optimization

            for index, value in enumerate(forest.array):
                if value.data == edge.origin:
                    index1 = index
                elif value.data == edge.destination:
                    index2 = index

            if forest.find(index1) != forest.find(index2):
                forest.union(index1, index2)
                if not edge.origin in minimumSpanningTree.vertices.keys():
                    minimumSpanningTree.__createdVertexInsertion(edge.origin)
                if not edge.destination in minimumSpanningTree.vertices.keys():
                    minimumSpanningTree.__createdVertexInsertion(edge.destination)

                minimumSpanningTree.insertEdge(edge.origin, edge.destination, edge.key, edge.weight)

        return minimumSpanningTree
Пример #5
0
    def sumWeights(self):
        '''
        This is a helper function that sums the weights of all the edges in our 
        graph.
        INPUT:
            none
        OUTPUT:
            Sum of all the weights of all the edges
        Runtime - O(m) - Since we have to sum up all of the edges, our algorithm runs 
        in time proportional to O(m).
        '''

        minHeap = heap.Heap()
        minHeap.buildHeap(self.edges.toList())
        minSum = 0
        while not minHeap.isEmpty():
            minSum += minHeap.remove().weight
        return minSum