Бинарно стабло претраге
Овај чланак је започет или проширен кроз пројекат семинарских радова. Потребно је проверити превод, правопис и вики-синтаксу. Када завршите са провером, допишете да након |проверено=. |
У информатици, бинарно стабло претраге (БСП), познато и као сортирано бинарно стабло, је бинарно стабло засновано на чворовима, где сваки чвор има упоредљиви кључ (са додељеном вредношћу) и задовољава услов да је вредност сваког чвора већа од вредности сваког чвора у његовом левом подстаблу и мања од вредности сваког чвора у његовом десном подстаблу. Сваки чвор има највише два детета. Свако дете мора да буде или лист (нема ниједно дете) или корен још једног бинарног стабла претраге. БСП је динамичка структура података, и величина БСП-а је ограничена само количином слободне меморије у оперативном систему. Највећа предност бинарног стабла претраге је да остаје уређено, што омогућава брже време претраге него већина других структура. Основна својства бинарног стабла претраге:[1]
- Лево подстабло чвора садржи само чворове које имају мању вредност од њега.
- Десно подстабло чвора садржи само чворове које имају већу вредност од њега.
- Лево и десно подстабло морају такође бити бинарна стабла претраге.
- Сваки чвор може имати највише 2 детета.
- Не смеју постојати дупликати чворова.
- Постоји јединствен пут од корена до сваког чвора.
Највећа предност бинарног стабла претраге у односу на остале структуре података је да алгоритми сортирања и алгоритми претраге као нпр. претрага у дубину (ин-ордер) могу бити веома ефикасни. Остале предности су:
- Бинарно стабло претраге има брзо убацивање и брисање елемената када је балансирано.
- Код је једноставнији него код осталих структура података.
- Чува кључеве у чворовима на такав начин да се претрага, убацивање и брисање елемената може урадити ефикасно.
- Веома једноставна имплементација.
- Чворови у стаблу су динамички по природи.
Бинарно стабло претраге је фундаментална структура података помоћу које се конструишу апстрактније структуре података као нпр. скупови, мултискупови и асоцијативни низови. Неки од недостатака су:
- Облик бинарног стабла претраге зависи само од реда убацивања елемената, и може бити дегенерисано.
- Када убацујемо или тражимо елемент у бинарном стаблу претраге, кључ сваког посећеног чвора мора да се упореди са кључем елемента ког убацујемо или тражимо.
- Кључеви у бинарном стаблу претраге могу бити дугачки, што може утицати на временску сложеност.
Провера да ли је стабло БСП или није
уредиПонекад имамо бинарно стабло, и потребно је одредити да ли је оно БСП. Ово је занимљив проблем који има једноставно рекурзивно решење.
БСП својство--сваки чвор у десном подстаблу мора да буде већи од тренутног чвора и сваки чвор у левом подстаблу мора да буде мањи (или једнак) од тренутног чвора--је кључна ствар помоћу које одређујемо да ли је стабло БСП или није. На први поглед, изгледа као да је довољно само да обиђемо стабло, проверимо да ли сваки чвор садржи вредност која је већа од вредности његовог левог детета и мања од вредности његовог десног детета, и ако ово тврђење важи за сваки чвор у стаблу, онда је то БСП. Ово је такозвани "Похлепни приступ". Али, очигледно је да овај приступ неће радити за следеће стабло:
20 / \ 10 30 / \ 5 40
I наведеном стаблу, сваки чвор задовољава услов да садржи вредност већу од његовог левог детета а мању од десног, али ипак, то није БСП: вредност 5 је у десном подстаблу чвора који садржи 20, што није у складу са својством БСП.
Како решити ово? Испоставља се да уместо да донесемо одлуку засновану искључиво на вредности чвора и његове деце, потребне су нам информације које потичу и од родитеља. У претходном примеру, када бисмо запамтили чвор са вредношћу 20, видели бисмо да чвор са вредношћу 5 не задовољава услове БСП.
Тако да, услови које треба да проверимо за сваки чвор су: а) ако је чвор лево дете свог родитеља, онда мора бити мањи (или једнак) од родитеља и мора да пренесе вредност од свог родитеља свом десном подстаблу да би били сигурни да ниједан од чворова у том подстаблу није већа од родитеља, и слично б) ако је чвор десно дете свог родитеља, онда мора бити веће од свог родитеља и мора да пренесе вредност од свог родитеља свом левом подстаблу да би били сигурно да ниједан од чворова у том подстаблу није мањи од родитеља.
Једноставно, али елегантно рекурзивно решење у Јави, може да објасни ово мало боље:
public static boolean isBST(TreeNode node, int leftData, int rightData) { if (node == null) return true; if (node.getData() > leftData || node.getData() <= rightData) return false; return (isBST(node.left, node.getData(), rightData) && isBST(node.right, leftData, node.getData())); }
Позив ове функције може изгледати овако:
if(isBST(root, Integer.MAX_VALUE, Integer.MIN_VALUE)) System.out.println("This is a BST."); else System.out.println("This is NOT a BST!");
Разлике између бинарног стабла и бинарног стабла претраге
уредиБинарно стабло: Укратко, бинарно стабло је стабло код кога сваки чвор има два највише два детета. У бинарном стаблу, лево и десно дете могу имати вредности независне од вредности родитеља.
3 / \ 4 5
Бинарно стабло претраге: Код бинарног стабла претраге, лево дете садржи вредност мању од родитеља, а десно дете садржи вредност која је већа од вредности родитеља.
4 / \ 3 5
Операције
уредиОперације, као што су претрага, код бинарног стабла захтевају поређење између чворова. Ова поређења се обављају са позивима функцији компаратора. Компаратор може бити дефинисам имплицитно или експлицитно, у зависности од језика у коме је имплементирано бинарно стабло претраге. Чест компаратор је мање од функције, на пример, а < б, где су а и б кључеви два чвора а и б у бинарном стаблу претраге.
Претрага
уредиПретрага бинарног стабла претраге за одређеним кључем може се урадити рекурзивно или итеративно.
Почињемо са кореним чворем. Ако је стабло нулл, онда кључ који тражимо не постоји у стаблу. Ако је кључ једнак корену, онда је претрага готова и враћамо чвор. Ако је кључ мањи од вредности корена, онда претражујемо лево подстабло. Слично томе, ако је кључ већи од корена, претражујемо десно подстабло. Овај процес се понавља све док не пронађемо кључ или преостало подстабло не постане нулл. Ако кључ није пронађен пре него што дођемо до нулл подстабла, онда тај кључ не постоји у стаблу. Ово се лако ради са рекурзивним алгоритмом:
function Find-recursive(key, node): // call initially with node = root if node = Null or node.key = key then return node else if key < node.key then return Find-recursive(key, node.left) else return Find-recursive(key, node.right)
Исти алгоритам се може имплементирати итеративно:
function Find(key, root): current-node := root while current-node is not Null do if current-node.key = key then return current-node else if key < current-node.key then current-node := current-node.left else current-node := current-node.right return Null
У најгорем случају, овај алгоритам мора да тражи од корена стабла до листа најдаљег од корена, тако да операција претраге траје пропорцијално висини стабла. У просеку, бинарно стабло претраге са н чворова има висину О (лог н). Али, у најгорем случају, висина може бити и О(н).
Убацивање елемента
уредиУбацивање елемента почиње исто као и претрага; ако кључ није једнак корену, претражујемо лево или десно подстабло ка малопре. На крају долазимо до екстерног чвора и додајемо нову кључ-вредност (овде као 'неwНоде') као његово лево или десно дете, у зависности од вредности чвора.
Ево како изгледа типично додавање елемента у бинарно стабло претраге у језику C++:
void insert(Node*& root, int data) {
if (!root)
root = new Node(data);
else if (data < root->data)
insert(root->left, data);
else if (data > root->data)
insert(root->right, data);
return;
}
Наведена деструктивна функција модификује стабло у месту. Након њеног извршавања, изгубиће се претходна верзија стабла. Друга могућност је да, као у следећем примеру у језику Пyтхон, реконструишемо све претке убаченог чвора; свака референца на оригинално стабло остаје валидна, и тако је стабло стална структура података:
def binary_tree_insert(node, key, value):
if node is None:
return TreeNode(None, key, value, None)
if key == node.key:
return TreeNode(node.left, key, value, node.right)
if key < node.key:
return TreeNode(binary_tree_insert(node.left, key, value), node.key, node.value, node.right)
else:
return TreeNode(node.left, node.key, node.value, binary_tree_insert(node.right, key, value))
Убацивање елемента у БСП, захтева време пропорционално висини стабла у најгорем случају, а то је О(н). У просечном случају, сложеност је О(лог н).
Брисање елемента
уредиПостоје три случаја које треба размотрити:
- Брисање листа (чвора без деце): Брисање листа је лако, само га избацујемо из стабла.
- Брисање чвора са једним дететом: Брише се чвор, и замењује са својим дететом.
- Брисање чвора са оба детета: Нека се чвор који бришемо зове Н. Не бришемо Н. Уместо тога, бирамо његовог ин-ордер следбеника, или његовог ин-ордер претходнка, Р. Копирамо вредност Р у Н, и рекурзивно позовемо брисање на Р док не дођемо до неког од прва два случаја.
Чворови са децом су тежи за брисање. Као са свим бинарним стаблима, ин-ордер следбеник чвора је најлевљи чвор у његовом десном подстаблу, а његов ин-ордер претходник је најдешњи елемент у његовом левом подстаблу. Бришемо га у слкаду са једним од два једноставина случаја изнад.
Уколико често користимо ин-ордер следбеника или ин-ордер претходика за сваки случај са два детета, може доћи до небалансираног стабла, тако да неке имплементације бирају један од та два случаја у различитим тренуцима.
def find_min(self): # Gets minimum node (leftmost leaf) in a subtree
current_node = self
while current_node.left_child:
current_node = current_node.left_child
return current_node
def replace_node_in_parent(self, new_value=None):
if self.parent:
if self == self.parent.left_child:
self.parent.left_child = new_value
else:
self.parent.right_child = new_value
if new_value:
new_value.parent = self.parent
def binary_tree_delete(self, key):
if key < self.key:
self.left_child.binary_tree_delete(key)
elif key > self.key:
self.right_child.binary_tree_delete(key)
else: # delete the key here
if self.left_child and self.right_child: # if both children are present
successor = self.right_child.find_min()
self.key = successor.key
successor.binary_tree_delete(successor.key)
elif self.left_child: # if the node has only a *left* child
self.replace_node_in_parent(self.left_child)
elif self.right_child: # if the node has only a *right* child
self.replace_node_in_parent(self.right_child)
else: # this node has no children
self.replace_node_in_parent(None)
Обилазак стабла
уредиКада је бинарно стаблно креирано, можемо вратити његове елементе ин-ордер, рекурзивним обиласком левог подстабла чвора, приступом самом чвору, и онда рекурзивним обиласком десног подстабла чвора, затим настављајући овај шаблон за сваки чвор након што му рекурзивно приступимо. Као са свим бинарним стаблима, имамо и пре-ордер и пост-ордер обилазак, али ниједан није користан за бинарно стабло претраге. Ин-ордер обилазак ће увек вратити сортиран низ елемената чвора.
Код за ин-ордер обилазак у језику Пyтхон:
def traverse_binary_tree(node, callback):
if node is None:
return
traverse_binary_tree(node.leftChild, callback)
callback(node.value)
traverse_binary_tree(node.rightChild, callback)
Обилазак је сложености О(н) , зато што се мора обићи сваки чвор.
Сортирање
уредиБинарно стабло претраге се може искористити за имплементацију једноставног, али ефикасног алгоритма за сортирање. Слично као код хеап сорт-а, убацујемо све вредности које желимо да сортирамо у нову структуру података, у овом случају бинарно стабло претраге, и онда га обиђемо ин-ордер обиласком, и добијамо резултат:
def build_binary_tree(values):
tree = None
for v in values:
tree = binary_tree_insert(tree, v)
return tree
def get_inorder_traversal(root):
'''
Returns a list containing all the values in the tree, starting at *root*.
Traverses the tree in-order(leftChild, root, rightChild).
'''
result = []
traverse_binary_tree(root, lambda element: result.append(element))
return result
Најгори случај build_binary_tree
је —ако му дамо сортирани низ вредности, он их уланча у повезану листу без левог подстабла. На пример, build_binary_tree([1, 2, 3, 4, 5])
ствара стабло трее (1 (2 (3 (4 (5)))))
.
Постоји неколико начина за избегавање ове мане са једноставним бинарним стаблима; најчешћа је самобалансирајуће бинарно стабло претраге. Ако урадимо исту процедуру користећо такво стабло, најгора сложеност је О(нлог н).
Врсте
уредиПостоји много врста бинарних стабла претраге. АВЛ-стабла и Црвено-црна стабла су облици самобалансирајућег бинарног стабла претраге. Раширено стабло је бинарно стабло претраге које аутоматски помера елементе којима се често приступа ближе корену. У Треап-у (хеап стабло), сваки чвор чува и (насумично изабран) приоритет и родитељски чвор има виши приоритет од своје деце. Танго стабло су стабла оптимизована за брзу претрагу.
Друга два придева која описују бинарно стабло претраге су комплетно и дегенерисано стабло.
Комплетно бинарно стабло је бинарно стабло које је скроз пуно, са могућношћу изузетка доњег нивоа, које је напуњено са лева на десно. У комплетном бинарном стаблу, сви чворови су најлевље могуће. То је стабло са н нивоа, где за сваки ниво д <= н - 1, број постојећих чворова на нивоу д је једнак 2д. Ово значи да сви могући чворови постоје на овим нивоима. Додатан услов за комплетно бинарно стабло је да за н-ти ниво, сви постојећи чворови морају да буду попуњени с лева на десно.
Дегенерисано стабло је стабло где за сваки родитељски чвор, постоји само једно дете чвор. Оно је небалансирано и, у најгорем случају, перформансе се спуштају на ниво повезане листе. Ако функција за додавање чвора не подржава ребалансирање, онда се лако може створити дегенерисано стабло ако му додајемо податке који су већ сортирани. Ово значи да у мерењу перформанси, стабло ће се на крају понашати као повезана листа.
Поређење перформанси
уредиD. А. Хегер (2004)[2] је објавио поређење перформанси бинарних стабла претраге. Треап има најбоље перформансе у просеку, док Црвено-црна стабла има најмање вариајција у перформансама.
Оптимално бинарно стабло претраге
уредиАко не желимо да модификујемо стабло претраге, и знамо тачно колико често ће се приступати сваком елементу, можемо да конструишемо[3] оптимално бинарно стабло претраге, стабло претраге код кога је просечна цена потраживања елемента минимализована.
Ако не знамо унапред којим редом ће се приступати елементима стабла, можемо да користимо Раширено дрво.
Пример у Пyтхон-у:
импорт сyс импорт рандом
- рандом лист генератор
деф рандом_лист (мин, маx, елементс):
list = random.sample(range(min, max), elements) return list
- цласс фор нодес
цласс Ноде :
def __init__(self, data) : self.p = None self.r = None self.l = None self.data1 = data self.data2 = str(data)
- трее цласс
цласс Трее :
#root is only field #constructor def __init__(self) : self.root = None
#method for adding nodes to tree def add(self, val) : #same as insert method from PDF if(self.root == None) : self.root = Node(val) else : self._add(val, self.root)
#side method for recursive adding def _add(self, val, node) : #if less add it to left side if(val < node.data1) : if(node.l != None) : self._add(val, node.l) else : node.l = Node(val) node.l.p = node #if val is greater add it to right side else : if(node.r != None) : self._add(val, node.r) else : node.r = Node(val) node.r.p = node
#In-Order-Walk def in_order_walk(self, node) : if(node != None) : self.in_order_walk(node.l) print("print node data : ", node.data1, node.data2) self.in_order_walk(node.r)
#Print-root def print_root(self) : print("Data in root node", self.root.data1, self.root.data2)
#Search the tree for wanted key def tree_search(self, current, key) : if (current == None or key == current.data1) : return current if (key < current.data1) : return self.tree_search(current.l, key) else : return self.tree_search(current.r, key)
#Search the tree for wanted key iterative way def iterative_tree_search(self, current, key) : while (current != None and key != current.data1) : if (key < current.data1) : current = current.l else : current = current.r return current
#Find the tree minimum def tree_minimum(self, node) : while (node.l != None) : node = node.l return node
#Find tree maximum def tree_maximum(self, node) : while(node.r != None) : node = node.r return node
#Print node's parent def print_parrent(self, node) : if (node.p != None) : print("Parent node of node ", node.data1, "is", node.p.data1) else : print("The node ", node.data1, "is root")
#Transplant def transplant(self, u, v) : if (u.p == None) : self.root = v elif (u == u.p.l) : u.p.l = v else : u.p.r = v if (v != None) : v.p = u.p
#Deleting node def delete(self, node) : if (node.l == None) : self.transplant(node, node.r) elif (node.r == None) : self.transplant(node, node.l) else : y = self.tree_minimum(node.r) if (y.p != z) : self.transplant(y, y.r) y.r = node.r y.r.p = y self.transplant(node, y) y.l = node.l y.l.p = y
#In-Order-Walk-With-RetVal def in_order_walk_with_retval(self, node) : global x if(node != None) : self.in_order_walk_with_retval(node.l) x.append(node) self.in_order_walk_with_retval(node.r)
- Тестирање
x = лист()
- Тестинг хере
трее = Трее(); А = рандом_лист(0, 20, 10) принт(А) кеy = А[9] кеy_и = А[6] кеy_инвалид = 100
фор и ин ранге(0, лен(А)) :
tree.add(A[i])
трее.принт_роот()
- тестинг ин ордер wалк
трее.ин_ордер_wалк(трее.роот)
- тестинг трее сеарцх
ф_к = трее.трее_сеарцх(трее.роот, кеy) иф (ф_к != Ноне) :
print("Found the key", f_k.data1, "and we searched for ", key)
елсе :
print("No key", key, "found")
ф_к = трее.трее_сеарцх(трее.роот, кеy_инвалид) иф (ф_к != Ноне) :
print("Found the key", f_k.data1, "and we searched for ", key_invalid)
елсе :
print("No key", key_invalid, "found")
- тестинг итеративе сеарцх
ф_к_и = трее.итеративе_трее_сеарцх(трее.роот, кеy_и) иф (ф_к_и != Ноне) :
print("Found the key", f_k_i.data1, "and we searched for ", key_i)
елсе :
print("No key", key_i, "found")
- тестинг трее минимум
темп = трее.трее_минимум(трее.роот) принт("Трее минимум", темп.дата1)
- тестинг трее маxимум
темп = трее.трее_маxимум(трее.роот) принт("Трее минимум", темп.дата1)
- тестинг принт парент
трее.принт_паррент(темп) трее.принт_паррент(трее.роот)
- Тестинг инсертинг
принт("Инсертинг 110 анд -5 то трее") трее.адд(110) трее.адд(-5) трее.ин_ордер_wалк(трее.роот)
- тестинг трее минимум
темп = трее.трее_минимум(трее.роот) принт("Трее минимум", темп.дата1)
- тестинг трее маxимум
темп = трее.трее_маxимум(трее.роот) принт("Трее минимум", темп.дата1)
- тестинг принт парент
трее.принт_паррент(темп)
- Трyинг то делете маxимум
темп = трее.трее_маxимум(трее.роот) трее.делете(темп) принт("Трее афтер делетинг маx") трее.ин_ордер_wалк(трее.роот)
- Трyинг то делете минимум
темп = трее.трее_минимум(трее.роот) трее.делете(темп) принт("Трее афтер делетинг мин") трее.ин_ордер_wалк(трее.роот)
принт("########")
трее.ин_ордер_wалк_wитх_ретвал(трее.роот)
фор и ин x :
print(i.data1)
Види још
уредиРеференце
уреди- ^ Гилберг, Р.; Фороузан, Б. (2001), „8”, Дата Струцтурес: А Псеудоцоде Аппроацх Wитх C++, Пацифиц Грове, ЦА: Броокс/Цоле, стр. 339, ИСБН 978-0-534-95216-7
- ^ Хегер, Доминиqуе А. (2004), „А Дисqуиситион он Тхе Перформанце Бехавиор оф Бинарy Сеарцх Трее Дата Струцтурес” (ПДФ), Еуропеан Јоурнал фор тхе Информатицс Профессионал, 5 (5): 67—75, Архивирано из оригинала (ПДФ) 27. 03. 2014. г., Приступљено 20. 04. 2014
- ^ Гоннет, Гастон. „Оптимал Бинарy Сеарцх Треес”. Сциентифиц Цомпутатион. ЕТХ Зüрицх. Архивирано из оригинала 30. 08. 2002. г. Приступљено 1. 12. 2013.
Литература
уреди- Paul E. Black, Binary Search Tree at the National Institute of Standards and Technology, Dictionary of Algorithms and Data Structures.
- Цормен, Тхомас Х.; Леисерсон, Цхарлес Е.; Ривест, Роналд L.; Стеин, Цлиффорд (2001). „12: Бинарy сеарцх треес, 15.5: Оптимал бинарy сеарцх треес”. Интродуцтион то Алгоритхмс (2нд изд.). МИТ Пресс & МцГраw-Хилл. стр. 253–272,356–363. ИСБН 978-0-262-03293-3.
- Јарц, Дуане Ј. (3. 12. 2005). „Бинарy Трее Траверсалс”. Интерацтиве Дата Струцтуре Висуализатионс. Университy оф Марyланд. Архивирано из оригинала 27. 02. 2014. г. Приступљено 20. 04. 2014.
- Кнутх, Доналд (1997). „6.2.2: Бинарy Трее Сеарцхинг”. Тхе Арт оф Цомпутер Программинг. 3: "Сортинг анд Сеарцхинг" (3рд изд.). Аддисон-Wеслеy. стр. 426—458. ИСБН 978-0-201-89685-5.
- Лонг, Сеан. „Бинарy Сеарцх Трее” (ППТ). Дата Струцтурес анд Алгоритхмс Висуализатион-А ПоwерПоинт Слидес Басед Аппроацх. СУНY Онеонта.
- Парланте, Ницк (2001). „Бинарy Треес”. ЦС Едуцатион Либрарy. Станфорд Университy.
Спољашње везе
уреди- Литерате имплементатионс оф бинарy сеарцх треес ин вариоус лангуагес Архивирано на сајту Wayback Machine (21. април 2017) он ЛитератеПрограмс
- Голета, Максим (27. 11. 2007). „Голетас.Цоллецтионс”. голетас.цом. Архивирано из оригинала 21. 04. 2014. г. Приступљено 20. 04. 2014. Инцлудес ан итеративе C# имплементатион оф АВЛ треес.
- Јансенс, Дана. „Персистент Бинарy Сеарцх Треес”. Цомпутатионал Геометрy Лаб, Сцхоол оф Цомпутер Сциенце, Царлетон Университy. C имплементатион усинг ГЛиб.
- Бинарy Трее Висуализер (ЈаваСцрипт аниматион оф вариоус БТ-басед дата струцтурес)
- Ковац, Кубо. „Бинарy Сеарцх Треес”. Корешпонденчнý семинáр з програмованиа. Архивирано из оригинала (Јава апплет) 30. 04. 2018. г. Приступљено 20. 04. 2014.
- Мадру, Јустин (18. 8. 2009). „Бинарy Сеарцх Трее”. ЈДСервер. Архивирано из оригинала 28. 03. 2010. г. Приступљено 20. 04. 2014. C++ имплементатион.
- Тарреау, Wиллy (2011). „Еластиц Бинарy Треес (ебтрее)”. 1wт.еу.
- Бинарy Сеарцх Трее Еxампле ин Пyтхон Архивирано на сајту Wayback Machine (11. март 2010)
- „Референцес то Поинтерс (C++)”. МСДН. Мицрософт. 2005. Гивес ан еxампле бинарy трее имплементатион.
- Игусхев, Едуард. „Бинарy Сеарцх Трее C++ имплементатион”. Архивирано из оригинала 17. 06. 2013. г. Приступљено 20. 04. 2014.
- Стромберг, Даниел. „Пyтхон Сеарцх Трее Емпирицал Перформанце Цомпарисон”.