illustrer
Si vous avez besoin d'utiliser ces connaissances mais que vous ne les possédez pas, cela sera frustrant et pourrait conduire à un refus de l'entretien. Que vous passiez quelques jours à « faire du blitz » ou que vous utilisiez un temps fragmenté pour continuer à apprendre, cela vaut la peine de travailler sur la structure des données. Alors, quelles sont les structures de données en Python ? Des listes, des dictionnaires, des ensembles et... des piles ? Python a-t-il une pile ? Cette série d'articles donnera des pièces de puzzle détaillées.
Chapitre 13 : Arbre binaire
L'arbre binaire : Arbre binaire, chaque nœud n'a que deux nœuds enfants.
class _BinTreeNode : def __init__(self, data) : self.data = data self.left = None self.right = None # 三种third-first遍历 def preorderTrav(subtree): """ 先(根)序遍历"" " si le sous-arbre n'est pas Aucun : print(subtree.data) preorderTrav(subtree.left) preorderTrav(subtree.right) def inorderTrav(subtree) : """ 中(根)序遍历""" si le sous-arbre n'est pas Aucun : preorderTrav (sous-arbre.left) print(subtree.data) preorderTrav(sous-arbre.right) def postorderTrav(sous-arbre) : """ Traversée de l'ordre post (racine)""" si le sous-arbre n'est pas Aucun : preorderTrav(subtree.left) self.element = data preorderTrav(subtree.right) print(subtree.data) # 宽度优先遍历(bradth-First Traversal) : 一层一层遍历, 使用queue def widthFirstTrav(bintree) : from queue import Queue # py3 q = Queue() q. put(bintree) tant que ce n'est pas q.empty() : node = q.get() print(node.data) si node.left n'est pas None : q.put(node.left) si node.right n'est pas None : q .put(node.right) classe _ExpTreeNode : __slots__ = ('element', 'left', 'right') def __init__(self, data) : self.left = Aucun self.right = Aucun def __repr__(self) : return '<_ExpTreeNode: {} {} {}>'.format( self.element, self.left, self.right) à partir de la file d'attente d'importation Classe de file d'attente ExpressionTree : """ Arbre d'expression : un arbre binaire dans lequel les opérateurs sont stockés dans des nœuds internes et les opérandes sont stockés dans des nœuds feuilles. (L'arbre de symboles est vraiment difficile à saisir) * / \ + - / \ / \ 9 3 8 4 ( 9+3) * (8-4) Le type de données abstrait Expression Tree peut implémenter des opérateurs binaires ExpressionTree(expStr) : chaîne utilisateur comme paramètre de constructeur évaluer(varDict) : évalue l'expression et renvoie le résultat numérique toString() : construit et renvoie une représentation sous forme de chaîne de l'expression Utilisation : vars = {'a': 5, 'b': 12} 12} expTree = ExpressionTree("(a/(b-3))") print('Le résultat = ', expTree.evaluate(vars)) """ def __init__(self, expStr): self._expTree = None self._buildTree( expStr) def évaluer(self, varDict) : return self._evalTree(self._expTree, varDict) def __str__(self) : return self._buildString(self._expTree) def _buildString(self, treeNode) : """ chez un enfant Ajoutez des parenthèses avant que l'arborescence ne soit parcourue, ajoutez des parenthèses droites """ après la traversée du sous-arbre # print(treeNode) si treeNode.left est None et treeNode.right est None : return str(treeNode.élément) #Le nœud feuille est l'opérande et renvoie directement else : expStr = '(' expStr += self._buildString(treeNode.left) expStr += str(treeNode.element) expStr += self._buildString(treeNode.right) expStr += ')' return expStr def _evalTree(self, sous-arbre, varDict ): # Est-ce un nœud feuille ? Si oui, cela signifie que c'est un opérande et renvoie directement si subtree.left vaut None et subtree.right vaut None : # L'opérande est-il un numéro légal si subtree.element >= '0' et subtree.element <= '9' : return int(subtree.element) else : # L'opérande est une variable assert subtree.element dans varDict, 'variable invalide.' return varDict[subtree.element] else :L'opérateur # évalue ses sous-expressions lvalue = self._evalTree(subtree.left, varDict) rvalue = self._evalTree(subtree.right, varDict) print(subtree.element) return self._computeOp(lvalue, subtree.element,valeur) def _computeOp(self, left, op, right) : assert op op_func = { '+' : lambda gauche, droite : gauche + droite, # ou opérateur d'importation, Operator.add '-' : lambda gauche, droite : gauche - droite, '*' : lambda gauche, droite : gauche * droite, '/' : lambda gauche, droite : gauche/droite, '%' : lambda gauche, droite : gauche % right, } return op_func[op](left, right) def _buildTree(self, expStr) : expQ = Queue() pour le jeton dans expStr : # 遍历表达式字符串的每个字符 expQ.put(token) self._expTree = _ExpTreeNode(None) # Utiliser la racine self._recBuildTree(self._expTree, expQ) def _recBuildTree(self, curNode, expQ) : token = expQ.get() if token == '(': curNode.left = _ExpTreeNode(None) self._recBuildTree(curNode.left, expQ) # le prochain jeton sera un opérateur : + = * / % curNode.element = expQ.get() curNode.right = _ExpTreeNode(None) self._recBuildTree(curNode .right, expQ) # le prochain jeton sera ')', retirez-le expQ.get() sinon : # le jeton est un chiffre qui doit être converti en un entier. curNode.element = token vars = {'a' : 5, 'b' : 12} expTree = ExpressionTree("((2*7)+8)") print(expTree) print('Le résultat = ', expTree. évaluer(vars))
Tas : L’une des applications les plus directes des arbres binaires consiste à implémenter des tas. Le tas est un arbre binaire complet.Les valeurs des nœuds non-feuilles du plus grand tas sont plus grandes que celles des enfants et les valeurs des nœuds non-feuilles du plus petit tas sont plus petites que les enfants. Python dispose d'un module heapq intégré pour nous aider à implémenter des opérations sur le tas, comme l'utilisation du module heapq intégré pour implémenter le tri par tas :
# Utilisez le heapq intégré de python pour implémenter le tri par tas def heapsort(iterable) : depuis heapq import heappush, heappop h = [] pour la valeur dans itérable : heappush(h, value) return [heappop(h) for i in range(len (h) ))]
Cependant, généralement lors de l'implémentation d'un tas, celui-ci n'est pas réellement implémenté en comptant les nœuds, mais en utilisant des tableaux, ce qui est plus efficace. Pourquoi peut-il être implémenté avec un tableau ? En raison de la nature d'un arbre binaire complet, la relation entre les indices peut être utilisée pour représenter la relation entre les nœuds. Cela a été expliqué dans la docstring de MaxHeap.
classe MaxHeap : """ Heaps : arbre binaire complet. Les valeurs des nœuds non-feuilles du tas maximum sont plus grandes que les enfants, et les valeurs des nœuds non-feuilles du tas minimum sont inférieures à les enfants. Le tas contient deux propriétés, la propriété d'ordre et la propriété de forme (un arbre binaire complet), lors de l'insertion d'un nouveau nœud, conservez toujours ces deux attributs. Opération d'insertion : conserve les attributs du tas et complète les attributs de l'arbre binaire, l'opération de tri maintient les attributs du tas. Opération d'extraction : obtenir uniquement les données du nœud racine et convertir l'arborescence. Une fois le nœud inférieur droit copié sur le nœud racine, l'opération de tri conserve l'attribut du tas. Utilisez un tableau pour implémenter le tas. À partir de le nœud racine, numérotez chaque nœud de haut en bas de gauche à droite. Selon les propriétés d'un arbre binaire complet, définissez un nœud i, et les numéros de ses nœuds parent et enfant sont : parent = (i-1) / / 2 left = 2 * i + 1 rgiht = 2 * i + 2 L'utilisation d'un tableau pour implémenter un tas est plus efficace et permet d'économiser de l'argent. L'utilisation de la mémoire des nœuds d'arborescence peut également éviter les opérations de pointeur complexes et réduire la difficulté de débogage. "" " def __init__(self, maxSize): self._elements = Array(maxSize) # Array ADT implémenté au chapitre 2 self._count = 0 def __len__(self) : return self._count defcapacity(self) : return len(self._elements) def add(self, value) : assert self._count < self.capacity(), 'ne peut pas ajouter au tas complet' self._elements[self._count] = value self._count += 1 self._siftUp(self._count - 1) self.assert_keep_heap() # 确定每一步add操作都保持堆属性 def extract(self ): affirmer self._count > 0, 'impossible d'extraire d'un tas vide' value = self._elements[0] # enregistrer la valeur racine self._count -= 1 self._elements[0] = self._elements[self._count ] # 最右下的节点放到root et siftDown self._siftDown(0) self.assert_keep_heap() valeur de retour def _siftUp(self, ndx) : si 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) # et def _siftDown(self, ndx) : gauche = 2 * ndx + 1 droite = 2 * ndx + 2 # déterminer quel nœud contient la plus grande valeur la plus grande = ndx si (gauche < self._count et self._elements[left] >= self._elements[largest] et self._elements[left] >= self._elements[right]): # Ceci n'est pas écrit dans le livre original. En fait, celui que vous recherchez peut ne pas être le plus grand le plus grand = gauche elif droite < self._count et self._elements[right] >= self._elements[largest] : plus grand = droite si le plus grand != ndx : self._elements[ndx], self._elements[largest ] = self._elements[largest], self._elements[ndx] self._siftDown(largest) def __repr__(self): return ' '.join(map(str, self._elements)) def assert_keep_heap(self): "" " J'ai ajouté cette fonction pour vérifier qu'après chaque ajout ou extraction, la propriété du tas maximum est toujours conservée """ _len = len(self) for i in range(0, int((_len-1)/2)): # Nœud interne (nœud non-feuille) l = 2 * i + 1 r = 2 * i + 2 si l < _len et r < _len : assert self._elements[i] >= self._elements[l] et self._elements[i] >= self._elements[r] def test_MaxHeap(): """ Scénario de test unitaire pour une implémentation maximale du tas """ _len = 10 h = MaxHeap(_len) for i in range(_len): h.add(i) h.assert_keep_heap() for i in range(_len): # Assurez-vous que le plus grand nombre ressort à chaque fois, lors de l'ajout d'une assertion h.extract() == _len-i-1 test_MaxHeap() def simpleHeapSort(theSeq) : ajouté de petit à grand : """ Utilisez votre propre implémentation de MaxHeap pour implémenter le tri par tas et modifiez directement le tableau d'origine pour implémenter le tri sur place """ sinon theSeq : renvoie theSeq _len = len(theSeq) heap = MaxHeap(_len) pour i dans theSeq : heap.add(i) pour i in reverse(range(_len)) : theSeq[i] = heap.extract() return theSeq def test_simpleHeapSort() : """ Utilisez quelques cas de test pour prouver que le tri par tas implémenté peut fonctionner """ def _is_sorted(seq) : for i in range(len(seq)-1) : if seq[i] > seq[i+1] : return False return True à partir d'une importation aléatoire randint assert simpleHeapSort([]) == [] pour i in range(1000) : _len = randint(1, 100) to_sort = [] for i in range(_len): to_sort.append(randint(0, 100)) simpleHeapSort(to_sort) # Notez que le tri sur place est utilisé ici, modifiant directement le tableau assert _is_sorted(to_sort) test_simpleHeapSort()
Chapitre 14 : Arborescences de recherche
Propriétés de recherche d'arbre de différences binaires : pour chaque nœud interne V, 1. Toutes les clés plus petites que V.key sont stockées dans le sous-arbre gauche de V. 2. Toutes les clés supérieures à V.key sont stockées dans le sous-arbre droit de V. Effectuer un parcours dans l'ordre sur le BST entraînera une séquence de touches ascendante.
class _BSTMapNode : __slots__ = ('key', 'value', 'left', 'right') def __init__(self, key, value) : self.key = clé self.value = valeur self.left = Aucun self.right = Aucun def __repr__(self) : return '<{}:{}> left:{}, right:{}'.format( self.key, self.value, self.left, self.right) __str__ = __repr__ classe BSTMap : """ BST, les nœuds de l'arborescence contiennent des charges utiles clés. Utilisez BST pour implémenter le Map ADT précédemment implémenté avec un hachage. Propriétés : Pour chaque nœud interne V, 1. Pour le nœud V, toutes les clés inférieures à V.key sont stockées dans le sous-arbre gauche de V. 2. Toutes les clés supérieures à V.key sont stockées dans le sous-arbre droit de V. Effectuer un parcours dans l'ordre sur BST obtiendra la séquence de touches ascendante """ def __init__(self) : self._root = Aucun self._size = 0 self._rval = None # Comme valeur de retour de 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) n'est pas Aucun def valueOf(self, key): node = self._bstSearch(self._root, key) assert node n'est pas Aucun, 'Clé de mappage invalide.' return node .value def _bstSearch(self, subtree, target) : si le sous-arbre est Aucun : # Sortie récursive, traverse jusqu'au bas de l'arborescence si aucune clé n'est trouvée ou si l'arborescence est vide return None elif target < subtree.key : return self._bstSearch(subtree.left, target) elif target > subtree.key : return self._bstSearch(subtree.right, target) return subtree # Renvoie la référence def _bstMinumum(self, subtree) : """ Suivre l'arborescence Si is None : return subtree else : return subtree._bstMinumum(self, subtree.left) def add( self, key, value): """ Ajouter ou remplacer la valeur d'une clé, O (N) """ node = self._bstSearch(self._root, key) si le nœud n'est pas None :# si la clé existe déjà, mettre à jour la valeur node.value = value return False else : # insérer une nouvelle entrée self._root = self._bstInsert(self._root, key, value) self._size += 1 return True def _bstInsert(self, subtree, key, value) : """ Les nouveaux nœuds sont toujours insérés aux nœuds feuilles de l'arbre """ si le sous-arbre est Aucun : subtree = _BSTMapNode(key, value) elif key < subtree.key: subtree.left = self._bstInsert(subtree.left , key, value) elif key > subtree.key: subtree.right = self._bstInsert(subtree.right, key, value) # Notez qu'il n'y a pas d'instruction else ici. Il faut juger s'il y en a une dans la fonction add où il est appelé. Répéter le sous-arbre de retour de clé def remove(self, key) : """ O(N) Il existe trois types de nœuds supprimés : 1. Nœud feuille : définit directement le pointeur de son père vers le nœud sur Aucun 2. Le nœud a un enfant : delete Après le nœud , le père désigne un enfant approprié 3 du nœud. Le nœud a deux enfants : (1) Trouver le nœud N à supprimer et son successeur S (le nœud suivant après le parcours dans l'ordre) (2) Copier la clé de S à N (3) Supprimez le successeur S du sous-arbre droit de N (c'est-à-dire le plus petit du sous-arbre droit de N) "" " affirme la clé en soi, 'clé de mappage invalide' self._root = self._bstRemove (self. _root, key) self._size -= 1 return self._rval def _bstRemove(self, subtree, target) : # recherche l'élément dans l'arborescence si le sous-arbre est Aucun : renvoie le sous-arbre elif target < sous-arbre.clé: subtree.left = self._bstRemove(subtree.left, target) return subtree elif target > subtree.key : subtree.right = self._bstRemove(subtree.right, target) return subtree else : # trouvé le nœud contenant l'élément self. _rval = subtree.value si subtree.left est None et subtree.right est None : # 叶子node return None elif subtree.left est None ou subtree.right est None : # 有一个孩子节点 if subtree.left n'est pas None : return sous-arbre.left sinon : return subtree.right else : # Il y a deux nœuds enfants successeur = self._bstMinumum(subtree.right) subtree.key = successeur.key subtree.value = successeur.value subtree.right = self._bstRemove(subtree.right, successeur. key ) return subtree def __repr__(self): return '->'.join([str(i) for i in self]) def assert_keep_bst_property(self, subtree): """ Cette fonction est écrite pour vérifier que l'ajout et la suppression les opérations sont toujours maintenues Les propriétés de bst """ si le sous-arbre est Aucun : renvoie si subtree.left n'est pas Aucun et subtree.right n'est pas Aucun : 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 est None et subtree.right ne l'est pas Aucun : assert subtree.right.value >= subtree.value self.assert_keep_bst_property(subtree.right) elif subtree.left n'est pas None et subtree.right est None : assert subtree.left.value <= subtree.value self.assert_keep_bst_property( subtree.left) classe _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) : si self._curItem < len(self._theKeys) : key = self._theKeys[self. _curItem] self._curItem += 1 touche de retour sinon : augmentez StopIteration def _bstTraversal(self, subtree) : si le sous-arbre n'est pas Aucun : 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() for i in l: bst.add(i) def test_HashMap(): """ Précédemment utilisé pour tester la carte implémentée avec hachage, changement Test de carte implémenté dans 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.remove('b') assert b_v == 'b' assert len(h) == 1 h.remove('a') assert len(h) == 0 _len = 10 pour i dans range(_len) : h.add(str(i), i) assert len(h) == _len pour i dans range(_len) : assert str(i) ) in h for i in range(_len): print(len(h)) print('bef', h) _ = h.remove(str(i)) assert _ == i print('aft', h) print(len(h)) assert len(h) == 0 test_HashMap()