-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtrees.py
More file actions
407 lines (336 loc) · 13.9 KB
/
Copy pathtrees.py
File metadata and controls
407 lines (336 loc) · 13.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
from collections import deque
import random
def getName():
return "Asad, Usman"
## This class is used to create a binary tree, where there is a parent node that can have a max of 2 children.
# The tree inserts new data in a way that it enforces that it is a full binary tree (aka 'complete' binary tree),
# filling the tree up from left to right
class Tree():
#In this implementation tree's are initiated with data, we can't have an empty tree.
def __init__(self, data, parent=None):
# Initialize this node, and store data in it
self.data = data
self.left = None
self.right = None
self.height = 0
self.descendents = 0
self.parent = parent
def getLeft(self):
# Return the left child of this node, or None
return self.left
def getRight(self):
# Return the right child of this node, or None
return self.right
def getData(self):
# Return the data contained in this node
return self.data
## This method is used to find the number of children that a specific node has
def findDecendants(self):
if (self == None):
return 0
queue = deque()
queue.append(self)
# Number of kids, set to -1 since self is going to be counted, and it shouldn't be
count = -1
while(len(queue) != 0):
popped = queue.popleft()
count += 1
if (popped.getLeft() != None):
queue.append(popped.getLeft())
if (popped.getRight() != None):
queue.append(popped.getRight())
return count
#This method is used to update the height of node in a tree
def updateHeight(self):
leftHeight = -1
rightHeight = -1
if(self.left != None):
leftHeight = self.left.getHeight()
if(self.right != None):
rightHeight = self.right.getHeight()
if (self.isLeaf()):
self.height = 0
else:
self.height = (1 + max(leftHeight, rightHeight))
#This updates the height of every node in the tree
def updateAll(self, tree):
if (tree != None):
tree.updateHeight()
if(tree.parent == None):
return tree
return tree.updateAll(tree.parent)
#Checks if both left and right branches of a tree have data
def isFull(self):
if self.left != None and self.right != None:
return True
#Checks if the specific tree is complete or not.
def isComplete(self, tree):
if (tree == None or tree.isLeaf()):
return True
if (tree.isFull()):
return tree.isComplete(tree.left) and tree.isComplete(tree.right)
return False
def isLeaf(self):
if self.left == None and self.right == None:
return True
def insert(self, data, tree=None):
# Insert data into the tree, descending from this node
# Ensure the tree remains complete - every level is filled save for the last, and each node is as far left as possible
# Return this node after data has been inserted
if (tree == None):
tree = self
totalKids = tree.findDecendants()
leftSize = 0
rightSize = 0
leftHeight = -1
rightHeight = -1
if (tree.left != None):
leftSize = tree.left.findDecendants()
leftHeight = tree.left.getHeight()
if(tree.right != None):
rightSize = tree.right.findDecendants()
rightHeight = tree.right.getHeight()
# for adding first child
if (totalKids == 0):
tree.left = Tree(data, tree)
tree.updateAll(tree)
return tree
elif (totalKids == 1):
tree.right = Tree(data, tree)
tree.updateAll(tree)
# print("put-right", data)
return tree
# goal is to fill up left first
if (leftSize > rightSize):
# check if left is full, if not insert somewhere
if(tree.left.isFull()):
tree.insert(data, tree.right)
else:
tree.insert(data, tree.left)
else:
if(leftSize == rightSize):
tree.insert(data, tree.left)
#returning the node that has been added
return(tree)
def __str__(self):
l = ""
m = ""
r = ""
if (self.left != None):
l = self.left.data
if(self.data != None):
m = self.data
if(self.right != None):
r = self.right.data
return (str(l) + " " + str(m) + " " + str(r))
def getHeight(self):
# Return the height of this node
return self.height
## Creating a Binary Search Tree, this tree extends the initial tree we made above
class BST(Tree):
#In this implementation tree's are initiated with data, we can't have an empty tree.
def __init__(self, data, parent=None):
# Initialize this node, and store data in it
super().__init__(data, parent)
#Inserts nodes into the tree maintaing a propper order where leftNode < parentNode < rightNode
# returns the created node
def insert(self, data, tree=None):
if (tree == None):
tree = self
#If data being inserted is smaller than parent, insert on left
if(data < tree.data):
#if left tree is empty, add current data and update tree's height
if (tree.left == None):
tree.left = BST(data, tree)
tree.updateAll(tree)
return tree
#other wise keep going left
else:
tree.insert(data, tree.left)
else:
#If the right tree is empty, add the current data and update the tree's height
if(tree.right == None):
tree.right = BST(data, tree)
tree.updateAll(tree)
return tree
#otherwise keep going down
else:
tree.insert(data, tree.right)
return tree
#This method checks if the given element is present in the tree
def __contains__(self, data, tree=None):
# Returns true if data is in this node or a node descending from it
if (tree == None):
tree = self
if (data < tree.data):
if (tree.left != None):
# print("recursing here,",tree.left)
return tree.__contains__(data, tree.left)
return False
elif (tree.data == data):
return True
else:
if (tree.right != None):
return tree.__contains__(data, tree.right)
return False
return False
def __str__(self):
l = ""
m = ""
r = ""
if (self.left != None):
l = self.left.data
if(self.data != None):
m = self.data
if(self.right != None):
r = self.right.data
return (str(l) + " " + str(m) + " " + str(r))
#This AVL tree extends the previous binary search tree
# An AVL tree is a version of a binary search tree,
# what it does is it try's to maintain a balance as elements are inserted.
# Try's to keep the tree as complete as possible, so it doesn't become too heavy
# on the left or right side.
class AVL(BST):
def __init__(self, data, parent=None):
# Initialize this node, and store data in it
super().__init__(data, parent)
# This method is used to figure out if the left branch or right branch is balanced.
# Balance is determined by comparing the heights of the left and right branches of the tree.
# the maximum difference in height between the two branches is 1. Otherwise the tree needs to be balanced
# Positive means leanig towards left.
# Negative means leaning towards right
def getBalanceFactor(self):
# Return the balance factor of this node
left = -1
right = -1
if (self.left != None):
left = self.left.getHeight()
if (self.right != None):
right = self.right.getHeight()
return (left - right)
# Using the balance factors of the tree's we can figure out what type of rotation needs to be done
# inorder to balance the tree's.
def checkBalance(self, tree):
if (tree != None):
#Get's the tree's balance factor
bf = tree.getBalanceFactor()
if (abs(bf) >= 2):
# narrowing down cases, check if left or right heavy
# positive balance factor means leaning towards left
if(bf > 0):
bal = 0
if (tree.left != None):
bal = tree.left.getBalanceFactor()
if(bal > 0):
# ROTATE RIGHT
tree.rightRotate()
if(bal < 0):
#DOUBLE ROTATION
tree.left.leftRotate()
tree.rightRotate()
# negative balance factor means leaning towards right
elif (bf < 0):
bal = 0
if (tree.right != None):
bal = tree.right.getBalanceFactor()
if(bal < 0):
tree.leftRotate()
if(bal > 0):
tree.right.rightRotate()
tree.leftRotate()
# Insert data into the tree, descending from this node
# Ensure that the tree remains a valid AVL tree
# Return the node in this node's position after data has been inserted
# Using the parameters 'tree'. 'inserted' and 'final' to help with the recursion later on
def insert(self, data, tree=None, inserted=False, final=None):
#Check's if the tree is balanced after the insertion,
# if so, it returns the root node of the tree
if(data == "balance" and tree == None):
return final
# If a tree hasn't been passed, uses self as the tree to work on.
if (tree == None):
tree = self
#Check's if the data element has been inserted, if it isn't it try's to insert it
if(not inserted):
# If data being inserted is smaller than parent, insert on left
if(data < tree.data):
if (tree.left == None):
tree.left = AVL(data, tree)
#Insert the data into the tree, set data to "balance", and inserted to true
#so we can work on balancing the tree after the insertion
return tree.insert("balance", tree.left, True)
else:
#Keep going left since right spot for data hasn't been found
return tree.insert(data, tree.left, False)
# If data being inserted is greater than parent, insert on right
else:
if(tree.right == None):
tree.right = AVL(data, tree)
return tree.insert("balance", tree.right, True)
else:
return tree.insert(data, tree.right, False)
# Data has been inserted, now we have to check if the AVL balance is being maintained
else:
#Update the height after insertion of new data
tree.updateHeight()
#Checks if current node is the root of the tree
if(tree.parent == None):
#check if the tree is balanced, if it isn't balance it, then return root node
if abs(tree.getBalanceFactor()) > 1:
tree.checkBalance(tree)
#Note this return statement is being given data for the 'final' parameter
# up above we have a statement that terminates the rescursion if a final is not null
return tree.insert("balance", tree.parent, True, tree)
else:
#check if the tree is balanced, if it isn't balance it, then call function on parent
if abs(tree.getBalanceFactor()) > 1:
tree.checkBalance(tree)
return tree.insert("balance", tree.parent, True)
# Rotating a tree to the left
def leftRotate(self):
parent = self.parent
current = self
newTop = current.right
# setting old Top's right node to as the old left node
if (newTop != None):
temp = newTop.left
if(temp != None):
temp.parent = current
current.right = temp
# linked the nodes
current.parent = newTop
newTop.left = current
newTop.parent = parent
if (parent != None):
if (parent.right == current):
parent.right = newTop
elif (parent.left == current):
parent.left = newTop
current.updateAll(current)
# Rotating a tree to the Right
def rightRotate(self):
parent = self.parent
current = self
newTop = current.left
# setting old Top's left node to as the old right node
if (newTop != None):
temp = newTop.right
if (temp != None):
temp.parent = current
current.left = temp
current.parent = newTop
newTop.right = current
newTop.parent = parent
if (parent != None):
if (parent.left == current):
parent.left = newTop
elif (parent.right == current):
parent.right = newTop
current.updateAll(current)
#Returns the depth of each node.
def getDepth(self, counter = 0):
if self.parent == None:
return counter
else:
counter +=1
return self.parent.getDepth(counter)