veranschaulichen
Wenn Sie dieses Wissen nutzen müssen, es aber nicht haben, ist das frustrierend und kann zur Ablehnung des Vorstellungsgesprächs führen. Unabhängig davon, ob Sie ein paar Tage mit „Blitzen“ verbringen oder fragmentierte Zeit zum Weiterlernen nutzen, lohnt es sich, an der Datenstruktur zu arbeiten. Welche Datenstrukturen gibt es also in Python? Listen, Wörterbücher, Mengen und ... Stapel? Hat Python einen Stack? Diese Artikelserie enthält detaillierte Puzzleteile.
Kapitel 13: Binärbaum
Der Binärbaum: Binärbaum, jeder Knoten hat nur zwei untergeordnete Knoten.
class _BinTreeNode: def __init__(self, data): self.data = data self.left = None self.right = None # 三种 Depth-First遍历 def preorderTrav(subtree): """ 先(根)序遍历"" " wenn der Teilbaum nicht None ist: print(subtree.data) preorderTrav(subtree.left) preorderTrav(subtree.right) def inorderTrav(subtree): """ 中(根)序遍历""" wenn der Teilbaum nicht None ist: preorderTrav (subtree.left) print(subtree.data) preorderTrav(subtree.right) def postorderTrav(subtree): „““ Post-(Root-)Order-Traversal“ wenn der Teilbaum nicht „None“ ist: preorderTrav(subtree.left) preorderTrav(subtree.right) print(subtree.data) # 宽度优先遍历(bradth-First Traversal): 一层一层遍历, 使用queue def widththFirstTrav(bintree): from queue import Queue # py3 q = Queue() q.put(bintree) while not q.empty(): node = q.get() print(node.data) if node.left is not None: q.put(node.left) if node. right ist nicht None: q.put(node.right) class _ExpTreeNode: __slots__ = ('element', 'left', 'right') def __init__(self, data): self.element = data self.left = None self.right = None def __repr__(self): return '<_ExpTreeNode: {} {} {}>'.format( self.element, self.left, self.right) from queue import Queue class ExpressionTree : """ Ausdrucksbaum: Ein binärer Baum, in dem Operatoren in inneren Knoten und Operanden in Blattknoten gespeichert werden. (Der Symbolbaum ist wirklich schwer zu tippen) * / \ + - / \ / \ 9 3 8 4 ( 9+3) * (8-4) Der abstrakte Datentyp „Expression Tree“ kann binäre Operatoren implementieren. ExpressionTree(expStr): Benutzerzeichenfolge als Konstruktorparam „ evaluieren(varDict):“ wertet den Ausdruck aus und gibt das numerische Ergebnis zurück erstellt eine Zeichenfolgendarstellung des Ausdrucks und gibt sie zurück. Verwendung: vars = {'a': 5, 'b':12} expTree = ExpressionTree("(a/(b-3))") print('The result = ', expTree.evaluate(vars)) """ def __init__(self, expStr): self._expTree = Keine self._buildTree(expStr) defvalue(self, varDict): return self._evalTree(self._expTree, varDict) def __str__(self): return self. _buildString(self._expTree) def _buildString(self, treeNode): „““ Fügen Sie Klammern hinzu, bevor ein Teilbaum durchlaufen wird, und fügen Sie schließende Klammern hinzu, nachdem der Teilbaum durchlaufen wird „““ # print(treeNode) return expStr wenn treeNode.left None und treeNode.right None ist: return str(treeNode.Element) #Der Blattknoten ist der Operand und gibt direkt zurück : expStr = '(' expStr += self._buildString(treeNode.left) expStr += str(treeNode.element) expStr += self._buildString(treeNode.right) expStr += ')' def _evalTree(self, subtree, varDict ): # Ist es ein Blattknoten? Wenn ja, bedeutet es, dass es ein Operand ist und direkt zurückgegeben wird, wenn subtree.left None und subtree.right None ist: # Ist der Operand eine zulässige Zahl, wenn subtree.element >= '0' und subtree.element <= '9': return int(subtree.element) else: # Der Operand ist eine Variable. Assert subtree.element in varDict, 'ungültige Variable'. return varDict[subtree.element] else:Der #-Operator wertet seine Unterausdrücke aus lvalue = self._evalTree(subtree.left, varDict) rvalue = self._evalTree(subtree.right, varDict) print(subtree.element) return self._computeOp(lvalue, subtree.element,rWert) def _computeOp(self, left, op, right): Assert op op_func = { '+': Lambda left, right: left + right, # oder Importoperator, Operator.add '-': Lambda links, rechts: links - rechts, '*': Lambda links, rechts: links * rechts, '/': Lambda links, rechts: links / rechts, '%': Lambda links, rechts: links % right, } return op_func[op](left, right) def _buildTree(self, expStr): expQ = Queue() for token in expStr: # 遍历表达式字符串的每个字符 expQ.put(token) self._expTree = _ExpTreeNode(None) # 创建root节点 self._recBuildTree(self._expTree, expQ) def _recBuildTree(self, curNode, expQ): token = expQ.get() if token == '(': curNode.left = _ExpTreeNode(None) self._recBuildTree(curNode.left, expQ) # nächstes Token wird ein Operator sein: + = * / % curNode.element = expQ.get() curNode.right = _ExpTreeNode(None) self._recBuildTree(curNode .right, expQ) # das nächste Token wird ')' sein, entferne es expQ.get() else: # das Token ist eine Ziffer, die in einen int umgewandelt werden muss. curNode.element = token vars = {'a': 5, 'b': 12} expTree = ExpressionTree("((2*7)+8)") print(expTree) print('The result = ', expTree. bewerten(vars))
Heap: Eine der direktesten Anwendungen von Binärbäumen ist die Implementierung von Heaps. Der Heap ist ein vollständiger Binärbaum. Die Werte der Nicht-Blattknoten im größten Heap sind größer als die der untergeordneten Knoten, und die Werte der Nicht-Blattknoten im kleinsten Heap sind kleiner als die der untergeordneten Knoten. Python verfügt über ein integriertes Heapq-Modul, das uns bei der Implementierung von Heap-Operationen hilft, z. B. die Verwendung des integrierten Heapq-Moduls zur Implementierung der Heap-Sortierung:
# Verwenden Sie Pythons integriertes Heapq, um die Heap-Sortierung zu implementieren def heapsort(iterable): from heapq import heappush, heappop h = [] for value in iterable: heappush(h, value) return [heappop(h) for i in range(len (h) ))]
Im Allgemeinen erfolgt die Implementierung eines Heaps jedoch nicht durch das Zählen von Knoten, sondern durch die Verwendung von Arrays, was effizienter ist. Warum kann es mit einem Array implementiert werden? Aufgrund der Natur eines vollständigen Binärbaums kann die Beziehung zwischen Indizes verwendet werden, um die Beziehung zwischen Knoten darzustellen. Dies wurde im Dokumentstring von MaxHeap erläutert
Klasse MaxHeap: „““ Heaps: Vollständiger Binärbaum. Die Werte der Nicht-Blattknoten des maximalen Heaps sind größer als die untergeordneten Werte und die Werte der Nicht-Blattknoten des minimalen Heaps sind kleiner als Die Kinder. Der Heap enthält zwei Eigenschaften, die Eigenschaft „Reihenfolge“ und die Eigenschaft „Form“ (ein vollständiger Binärbaum). Wenn Sie einen neuen Knoten einfügen, behalten Sie immer diese beiden Attribute bei. Einfügungsvorgang: Behalten Sie die Heap-Attribute und vollständigen Binärbaumattribute bei, die Sift-up-Operation behält bei Die Heap-Attribute. Extraktionsvorgang: Nur die Wurzelknotendaten abrufen und den Baum konvertieren. Nachdem der Knoten ganz rechts unten auf den Wurzelknoten kopiert wurde, behält der Sift-Down-Vorgang das Heap-Attribut bei. Verwenden Sie ein Array, um den Heap zu implementieren. Beginnend mit Nummerieren Sie jeden Knoten vom Wurzelknoten aus von oben nach unten und von links nach rechts. Definieren Sie gemäß den Eigenschaften eines vollständigen Binärbaums einen Knoten i, und die Nummern seiner übergeordneten und untergeordneten Knoten sind: parent = (i-1) / / 2 left = 2 * i + 1 rgiht = 2 * i + 2 Die Verwendung eines Arrays zur Implementierung eines Heaps ist effizienter und spart Geld. Durch die Speichernutzung von Baumknoten können auch komplexe Zeigeroperationen vermieden und die Schwierigkeit des Debuggens verringert werden. "" " def __init__(self, maxSize): self._elements = Array(maxSize) # Array ADT implementiert in Kapitel 2 self._count = 0 def __len__(self): return self._count def Capacity(self): return len(self._elements) def add(self, value): Assert self._count < self.capacity(), 'kann nicht Zum vollständigen Heap hinzufügen' self._elements[self._count] = value self._count += 1 self._siftUp(self._count - 1) self.assert_keep_heap() # 确定每一步add操作都保持堆属性 def extract(self ): behaupten self._count > 0, 'kann nicht aus einem leeren Heap extrahieren' value = self._elements[0] # Wurzelwert speichern self._count -= 1 self._elements[0] = self._elements[self._count ] # Klicken Sie auf die Schaltfläche „root“ und „siftDown“. self._siftDown(0) self.assert_keep_heap() Rückgabewert def _siftUp(self, ndx): if ndx > 0: parent = (ndx - 1) // 2 # print(ndx, parent) if self._elements[ndx] > self._elements[parent]: # swap self._elements[ndx], self._elements[parent] = self._elements[parent], self._elements[ndx] self._siftUp(parent) # 递归 def _siftDown(self, ndx): left = 2 * ndx + 1 right = 2 * ndx + 2 # Bestimmen Sie, welcher Knoten den größeren Wert enthält. most = ndx if (left < self._count and self._elements[left] >= self._elements[largest] and self._elements[left] >= self._elements[right]): # Dies steht nicht im Originalbuch. Tatsächlich das, nach dem Sie suchen ist möglicherweise nicht der größte größte = links elif rechts < self._count und self._elements[rechts] >= self._elements[größter]: größter = rechts , wenn größter != ndx: self._elements[ndx], self._elements[größter ] = self._elements[largest], self._elements[ndx] self._siftDown(largest) def __repr__(self): return ' '.join(map(str, self._elements)) def Assert_keep_heap(self): "" „Ich habe diese Funktion hinzugefügt, um zu überprüfen, ob nach jedem Hinzufügen oder Extrahieren die Eigenschaft des maximalen Heaps weiterhin erhalten bleibt. „““ _len = len(self) for i in range(0, int((_len-1)/2)): # Interner Knoten (Nicht-Blattknoten) l = 2 * i + 1 r = 2 * i + 2, wenn l < _len und r < _len : affirm self._elements[i] >= self._elements[l] and self._elements[i] >= self._elements[r] def test_MaxHeap(): """ Unit-Testfall für maximale Heap-Implementierung""" _len = 10 h = MaxHeap(_len) for i in range(_len): h.add(i) h.assert_keep_heap() for i in range(_len): # Stellen Sie sicher, dass beim Hinzufügen von Assert jedes Mal die größte Zahl herauskommt h.extract() == _len-i-1 test_MaxHeap() def simpleHeapSort(theSeq): von klein nach groß hinzugefügt : „““ Verwenden Sie Ihre eigene Implementierung von MaxHeap, um die Heap-Sortierung zu implementieren, und ändern Sie das ursprüngliche Array direkt, um die Inplace-Sortierung zu implementieren.“ wenn nicht theSeq: return theSeq _len = len(theSeq) heap = MaxHeap(_len) for i in theSeq: heap.add(i) for i in reversed(range(_len)): theSeq[i] = heap.extract() return theSeq def test_simpleHeapSort(): """ Verwenden Sie einige Testfälle, um zu beweisen, dass die implementierte Heap-Sortierung funktionieren kann""" def _is_sorted(seq): for i in range(len(seq)-1): if seq[i] > seq[i+1]: return False return True from random import randint Assert simpleHeapSort([]) == [] for i in range(1000): _len = randint(1, 100) to_sort = [] for i in range(_len): to_sort.append(randint(0, 100)) simpleHeapSort(to_sort) # Beachten Sie, dass hier In-Place-Sortierung verwendet wird, wodurch das Array direkt geändert wird. Assert _is_sorted(to_sort) test_simpleHeapSort()
Kapitel 14: Suchbäume
Eigenschaften für die Suche nach binären Differenzbäumen: für jeden internen Knoten V, 1. Alle Schlüssel, die kleiner als V.key sind, werden im linken Teilbaum von V gespeichert. 2. Alle Schlüssel, die größer als V.key sind, werden im rechten Teilbaum von V gespeichert. Die Durchführung einer In-Order-Traversierung auf dem BST führt zu einer aufsteigenden Schlüsselsequenz.
Klasse _BSTMapNode: __slots__ = ('key', 'value', 'left', 'right') def __init__(self, key, value): self.key = key self.value = value self.left = Keine self.right = Keine def __repr__(self): return '<{}:{}> left:{}, right:{}'.format( self.key, self.value, self.left, self.right) __str__ = __repr__ Klasse BSTMap: „““ BST, Baumknoten enthalten Schlüsselnutzlasten. Verwenden Sie BST, um das zuvor mit Hash implementierte Map ADT zu implementieren. Eigenschaften: Für jeden internen Knoten V, 1. Für Knoten V werden alle Schlüssel kleiner als V.key gespeichert Linker Teilbaum von V. 2. Alle Schlüssel, die größer als V.key sind, werden im rechten Teilbaum von V gespeichert. Wenn Sie BST in der richtigen Reihenfolge durchqueren, erhalten Sie die aufsteigende Schlüsselfolge „““ def __init__(self): self._root = Keine self._size = 0 self._rval = None # Als Rückgabewert von Remove def __len__(self): return self._size def __iter__(self): return _BSTMapIterator(self._root, self._size) def __contains__(self, key ) : return self._bstSearch(self._root, key) is not None def valueOf(self, key): node = self._bstSearch(self._root, key) Assert node is not None, 'Ungültiger Kartenschlüssel.' return node . value def _bstSearch(self, subtree, target): if subtree is None: # Rekursiver Exit, zum Ende des Baums durchlaufen, wenn kein Schlüssel gefunden wird oder der Baum leer ist, return None elif target < subtree.key: return self._bstSearch(subtree.left, target) elif target > subtree.key: return self._bstSearch(subtree.right, target) return subtree # Return reference def _bstMinumum(self, subtree): „““ Dem Baum folgen, wenn is None: return subtree else : return subtree._bstMinumum(self, subtree.left) def add( self, key, value): """ Den Wert eines Schlüssels hinzufügen oder ersetzen, O (N) „““ node = self._bstSearch(self._root, key) wenn node nicht None ist:# Wenn der Schlüssel bereits vorhanden ist, aktualisieren Sie den Wert node.value = value return False else: # einen neuen Eintrag einfügen self._root = self._bstInsert(self._root, key, value) self._size += 1 return True def _bstInsert(self, subtree, key, value): „ “ “ Neue Knoten werden immer an den Blattknoten des Baums eingefügt . key, value) elif key > subtree.key: subtree.right = self._bstInsert(subtree.right, key, value) # Beachten Sie, dass es hier keine else-Anweisung gibt. Es sollte beurteilt werden, ob es in der Add-Funktion wo eine gibt Es heißt. Wiederholen Sie den Schlüssel und geben Sie den Teilbaum zurück def remove(self, key): """ O(N) Es gibt drei Arten gelöschter Knoten: 1. Blattknoten: Setzen Sie den Zeiger seines Vaters auf den Knoten direkt auf None 2. Der Knoten hat ein untergeordnetes Element: delete Nach dem Knoten , der Vater zeigt auf ein geeignetes Kind 3 des Knotens. Der Knoten hat zwei Kinder: (1) Suchen Sie den zu löschenden Knoten N und seinen Nachfolger S (den nächsten Knoten nach der Durchquerung in der Reihenfolge) (2) Kopieren Sie den Schlüssel von S bis N (3) Löschen Sie den Nachfolger S aus dem rechten Teilbaum von N (d. h . den kleinsten im rechten Teilbaum von N) (self. _root, key) self._size -= 1 return self._rval def _bstRemove(self, subtree, target): # Suche nach dem Element im Baum , wenn subtree None ist: return subtree elif target < subtree.Schlüssel: subtree.left = self._bstRemove(subtree.left, target) return subtree elif target > subtree.key: subtree.right = self._bstRemove(subtree.right, target) return subtree else: # hat den Knoten gefunden, der das Element self enthält. _rval = subtree.value, wenn subtree.left None und subtree.right None ist: # 叶子node return None elif subtree.left is None or subtree.right is None: # 有一个孩子节点 if subtree.left is not None: return subtree.left sonst: return subtree.right else: # Es gibt zwei untergeordnete Knoten successor = self._bstMinumum(subtree.right) subtree.key = successor.key subtree.value = successor.value subtree.right = self._bstRemove(subtree.right, successor. key ) return subtree def __repr__(self): return '->'.join([str(i) for i in self]) def Assert_keep_bst_property(self, subtree): """ Diese Funktion wurde geschrieben, um das Hinzufügen und Löschen zu überprüfen Operationen werden immer beibehalten. Die Eigenschaften von bst „““, wenn subtree None ist: Rückgabe , wenn subtree.left nicht None und subtree.right nicht None ist: Assert subtree.left.value <= subtree.value Assert subtree.right.value >= subtree.value self.assert_keep_bst_property(subtree.left) self.assert_keep_bst_property(subtree.right) elif subtree.left ist None und subtree.right ist nicht Keine: behaupten subtree.right.value >= subtree.value self.assert_keep_bst_property(subtree.right) elif subtree.left ist nicht None und subtree.right ist None: Assert subtree.left.value <= subtree.value self.assert_keep_bst_property( subtree.left) Klasse _BSTMapIterator: def __init__(self, root, size): self._theKeys = Array(size) self._curItem = 0 self._bstTraversal(root) self._curItem = 0 def __iter__(self): return self def __next__(self): if self._curItem < len(self._theKeys): key = self._theKeys[self. _curItem] self._curItem += 1 Rückgabeschlüssel sonst: StopIteration erhöhen def _bstTraversal(self, subtree): wenn subtree nicht None ist: self._bstTraversal(subtree.left) self._theKeys[self._curItem] = subtree.key self. _curItem += 1 self._bstTraversal(subtree.right) def test_BSTMap(): l = [60, 25, 100, 35, 17, 80] bst = BSTMap() für i in l: bst.add(i) def test_HashMap(): „““ Zuvor verwendet, um die mit Hash implementierte Karte zu testen, ändern Test für Map implementiert in BST „““ # h = HashMap() h = BSTMap() Assert len(h) == 0 h.add('a', 'a') Assert h.valueOf('a') = = 'a' Assert len(h) == 1 a_v = h.remove('a') Assert a_v == 'a' Assert len(h) == 0 h.add('a', 'a') h .add('b', 'b') Assert len(h) == 2 Assert h.valueOf('b') == 'b' b_v = h.entferne('b') behaupten b_v == 'b' behaupten len(h) == 1 h.remove('a') behaupten len(h) == 0 _len = 10 für i im Bereich(_len): h.add(str(i), i) behaupten len(h) == _len für i im Bereich(_len): behaupten str(i ) in h für i in range(_len): print(len(h)) print('bef', h) _ = h.remove(str(i)) Assert _ == i print('aft', h) print(len(h)) affirm len(h) == 0 test_HashMap()