Обилазак стабла

Из Википедије, слободне енциклопедије

У информатици, под обиласком стабала сматра се процес посећивања сваког чвора у стаблу(структура података), тачно једном, систематски. Овакви обиласци се класификују по реду у којем су чворови посећени. Следећи алгоритми су описани за бинарно стабло, али могу се генерализовати за остала стабла, такође.

Врсте[уреди]

У поређењу са линеарним структурама података као што су повезане листе и једнодимензионалним низовима, који користе канонски метод обиласка, структуре стабала могу се обићи на много различитих начина. Почевши од корена бинарног стабла, постоје три главна корака која се могу извести а ред по којем су изведени дефинише врсту обиласка. Ови кораци (без неког одређеног реда) су: извођење акције на тренутном чвору („посећивање“ чвора), обилазак на леви син чвора и обилазак на десни син чвора.

Обилазак стабла преставља повезивање свих чворова у петљу на неки начин. Зато што од једног датог чвора постоји више могућих следећих чворова (то није линеарна структура података), онда, код секвенцијалног рачунања (не паралелног), неки чворови морају бити одложен – складиштен на неки начин да би био посећен после. Ово се често ради преко стека (ЛИФО) или реда (ФИФО). Како је стабло самореференцијална (рекурзивно дефинисано) структура података, обилазак се може објаснити као рекурзија, или корекурзија, у том случају одложени чворови се складиште „прећутно“, а у случају рекурзије у стек позива.

Име дато одређеном стилу обиласка долази од реда којим се чворови посећују. Најједноставније, да ли један иде доле први или преко прво. Обилазак у дубину се даље квалификује по позицији коренског елемента са обзиром на леве и десне чворове. Замислите да су леви и десни чворови константа у простору, онда коренски чвор може бити постављен са леве стране левог чвора, између левог и десног чвора, или са десне стране десног чвора. Не постоји еквивалент варијација у обиласку у ширину – када се гледа ређање деце, обилазак у ширину је недвосмислен.

За сврхе илустрације, сматра се да леви чворови увек имају приоритет у односу на десне чворове. Ређање може бити и супротно под условом да се исто ређање примењује за све методе обиласка.

Обилазак у дубину је лако применљив преко стека, укључујући и рекузивно (преко кол стека), док је обилазак у дубину лако применљив преко реда, укључујући и корекурзивно.

Поред ових основних обиласаза, постоји много комплекснијих или хибридних шема, као то су лимитиране претраге у дубину и итеративне продубљујуће претраге у дубину.

Претрага у дубину[уреди]

Постоје три врсте обилазака у дубину: пре-ордер, ин-ордер и пост-ордер. За бинарно стабло, они су дефинисани као рекурзивне операције на сваком чвору, почевши са кореном чвора следе:

Преордер:[1]

  1. „Посета“ корену
  2. Обилазак левог подстабла
  3. Облазак десног подстабла


Инордер (симетрично):[1]

  1. Обилазак левог подстабла
  2. „Посета“ корену
  3. Обилазак десног подстабла


Постордер:[1]

  1. Обилазак левог подстабла
  2. Обилазак десног подстабла
  3. „Посета“ корену

Траг обиласка се назива секвенцијализација стабла. Ниједна секвенцијализација према пре-, ин- или пост-ордер не описује обилазак дрвета једниствено. Или пре-ордер или пост-ордер упарени са ин-ордером је довољна да се стабло опише уникатно, док пре-ордер са пост-ордером оставља неке двосмислености у структури стабла.[2]

Генеричко стабло

Да би обишао стабло у обиласку у дубину, изведите следеће операције рекурзивно на сваком чвору:

  1. Извести преодрер операцију
  2. for i=1 to n-1 do
    1. Посети дете[i], if постоји
    2. Извести инордер операцију
  3. Посетити дете[n], if постоји
  4. Извести постордер операцију

Где је n број чворова деце. У зависности од датог проблема, пре-ордер, ин-ордер и пост-ордер операције могу бити празне, или желите само да посетите одређен чвор, па су ове операције опционалне. Такође, у пракси више од једне пре, ин и пост-ордер операције могу бити потребне. Нпр, приликом уношења у тернарно стабло, пре-ордер операција се изводи поређењем елемената. Пост-ордер операције могу бити потребне касније да врате стабло у равнотежу.

Претрага у ширину[уреди]

Стабла могу такође бити обилажена по нивоима, где се, пре преласка на нижи ниво, посећује сваки чвор.

Употреба[уреди]

Пре-ордер обиласци приликом дуплирања чворова и вредности могу направити комплетну копију бинарног стабла. Такође се може користити за прављење префиксних израза(Пољска нотација) из стабала израза: обилазак стабла израза пре-ордерски.

Ин-ордер обилаци се веома често користе за бинарну претрагу стабала, јер враћа вредности из подвученог сета по реду према који компаратор поставља у стаблу за бинарну претрагу.

Пост-ордер обиласци приликом брисања или ослобађања чворова и вредности могу избрисати или ослободити цело бинарно стабло. Такође, могу генериати постфиксну репрезентацију бинарног стабла.


Пример[уреди]

Бинарно стабло: A sorted binary tree

Претрага у дубину
  • Преордер секвенца обиласка: F, B, A, D, C, E, G, I, H (корен, лево, десно) preorder traversal of binary tree

  • Инордер секвенца обиласка: A, B, C, D, E, F, G, H, I (лево, корен, десно) inorder traversal of binary tree

  • Постордер секвенца обиласка: A, C, E, D, B, H, I, G, F (лево, десно, корен) postorder traversal of binary tree

Претрага у ширину
  • Секвенца обиласка по нивоима: F, B, G, A, D, I, C, E, H breadth-first traversal of binary tree

Имплементације[уреди]

Претрага у дубину[уреди]

Преордер[уреди]

preorder(node)
  if node == null then return
  visit(node)
  preorder(node.left) 
  preorder(node.right)
iterativePreorder(node)
  parentStack = empty stack
  while not parentStack.isEmpty() or node != null
    if node != null then
      visit(node)
      parentStack.push(node.right)
      node = node.left
    else
      node = parentStack.pop()

Инордер[уреди]

inorder(node)
  if node == null then return
  inorder(node.left)
  visit(node)
  inorder(node.right)
iterativeInorder(node)
  parentStack = empty stack
  while not parentStack.isEmpty() or node != null
    if node != null then
      parentStack.push(node)
      node = node.left
    else
      node = parentStack.pop()
      visit(node)
      node = node.right

Постордер[уреди]

postorder(node)
  if node == null then return
  postorder(node.left)
  postorder(node.right)
  visit(node)
iterativePostorder(node)
  if node == null then return
  nodeStack.push(node)
  prevNode = null
  while not nodeStack.isEmpty()
    currNode = nodeStack.peek()
    if prevNode == null or prevNode.left == currNode or prevNode.right == currNode
      if currNode.left != null
        nodeStack.push(currNode.left)
      else if currNode.right != null
        nodeStack.push(currNode.right)
    else if currNode.left == prevNode
      if currNode.right != null
        nodeStack.push(currNode.right)
    else
      visit(currNode)
      nodeStack.pop()
    prevNode = currNode

Све имплементације изнад захтевају простор за позив стека пропорцијалан висини дрвета. У лоше балансираном стаблу, ово може бити битно. Можемо уклонити потребност стека одржавајући показиваче у сваком чвору, или дељењем стабла на нити.(следећи део)

Морисов инордер обилазак користећи нити[уреди]

Бинарно стабло је се дели на нити постављањем сваког показивача детета да показује на ин-ордер претходник чвора и сваки десни показивач детета да показује на ин-ордер следбеника чвора. Предности:

  1. Избегава рекурзије, које користе кол стек и узимају меморију и време
  2. Чвор чува податке родитеља

Недостаци

  1. Стабло је комплексније
  2. Више је склоно грешкама када оба детета нису приказана и обе вредности чворова показују на своје претке.

Морисов обилазак је имплементација ин-ордер обиласка који користи нити:

  1. Креира веза са ин-ордер следбеником
  2. Штампа податке користећи ове везе
  3. Враћа промене на оригинално дрво

Претрага у ширину[уреди]

Такође, испод је наведен псеудокод за једноставни обилазак нивоа заснован на реду и захтеваће простор пропорцијалан максималном броју чворова на датој ширини. Може бити једнак укупном броју чворова / 2. Ефикаснији по питању простора приступ за ову врсту обиласка може бити имплементиран користећи итеративну продубљујућу претрагу у дубину(iterative deepening depth-first search).

levelorder(root)
  q = empty queue
  q.enqueue(root)
  while not q.empty do
    node := q.dequeue()
    visit(node)
    if node.left ≠ null
      q.enqueue(node.left)
    if node.right ≠ null
      q.enqueue(node.right)

Бесконачна стабла[уреди]

Иако се обиласци обично раде за стабла са ограниченим бројем чланова, могу бити урађени и са бесконачним стаблима. Ово је од посебног значаја за функционално програмирање, како се бесконачне структуре података могу често лако дефинисати и радити, иако нису оцењене, ово би узело бесконачно много времена. Нека стабла ограниченим бројем чланова су превелика за репрезентацију, као што су стабло игре за шах, тако да је корисно анализирати их као да су бесконачна.

Основна потреба обиласка је посећивање сваког чвора. За бесконачна стабла, једноставни алгоритми често не успеју ово да ураде. Нпр, када је дато бинарно стабло бесконачне дубине, обилазак у дубину ће ићи надоле по једној страни стабла, никад не посећивајући остатак, и ин и пост-ордер неће никада посетити ниједан члан, јер није достигао лист. Обилазак у ширину, по нивоима, ће обићи бинарно стабло бесконачне дубине без проблема, и стварно ће обићи свако везано стабло.

Са друге стране, ако је дато стабло дубине 2, где коренски члан има бесконачно много деце, и свако од те деце има двоје деце, обилазак у дубину ће обићи све чворове, када исцрпи унуке, он ће прећи на следеће. Док код обилазака у ширину, никада неће доћи до унука, зато што ће покушавати да исцрпи сву децу прво.

Софитициранија анализа може се остварити преко бесконачних редних бројева: нпр, обилазак у ширниу дубине 2 стабла изнад ће узети ?· два корака ? за први ниво, а други ? за други ниво.

Једноставне претраге обилазака у дубину и ширину не обиђу свако бесконачно стабло, и нису тако ефикасни на веома великим стаблима. Ипак, хибридне методе обићи свако бесконачно стабло, есенцијално преко диагоналног аргумента (диагонално – комбинација вертикалног и хоризонталног – одговара комбинацији дубине и ширине).

Конкретно, узевши гранајуће бесконачно стабло бесконачне дубине, означавамо корен (),, децу чвора(1), (2), \dots, а унуке чвора (1,1), (1,2), \ldots, (2,1), (2,2), \ldots, и тако даље. Чланови су 1-1(бијекција) са одређеним секвенцама позитивних бројева, који су бројиви и могу бити постављени у ред прво по броју улаза, а затим и лексикографским редом узевши у обзир дати број улаза, који дају обилазак. Експлицитно:

0: ()
1: (1)
2: (1,1) (2)
3: (1,1,1) (1,2) (2,1) (3)
4: (1,1,1,1) (1,1,2) (1,2,1) (1,3) (2,1,1) (2,2) (3,1) (4)

итд.

Ово може бити тумачено као уношења на мапу бинарног стабла бесконачне дубине на ово стабло и онда примењивање обиласка у ширину. Заменити доње ивице повезивањем родитељског члана на његов следечи а затим дете са десним ивицама првогд детета са другим, другог са трећим и тако дања. Иако сваким корако може ићи или доле или лево, који показује односе између бесконачног бинарног стабла и нумерисања одозго; сума свих улаза одговара дистанци од корена, који одговара 2н-1 на дубини н-1 у бесконачном бинарном стаблу.


Референце[уреди]

Литература[уреди]

Спољашње везе[уреди]