Foreplay
Ang artikulong ito ay tungkol sa binary search tree. Nagsulat ako kamakailan ng isang artikulo tungkol sa
Ang puno ay isang istraktura ng data na binubuo ng mga node na konektado sa pamamagitan ng mga gilid. Masasabi nating ang puno ay isang espesyal na kaso ng isang graph. Narito ang isang halimbawa ng puno:
Ito ay hindi isang binary search tree! Lahat ay nasa ilalim ng hiwa!
terminolohiya
Root
ugat ng puno ay ang pinakamataas na node. Sa halimbawa, ito ay node A. Sa puno, isang landas lamang ang maaaring humantong mula sa ugat patungo sa anumang iba pang node! Sa katunayan, anumang node ay maaaring ituring na ugat ng subtree na tumutugma sa node na ito.
Mga magulang/anak
Ang lahat ng mga node maliban sa ugat ay may eksaktong isang gilid na humahantong sa isa pang node. Ang node sa itaas ng kasalukuyang node ay tinatawag magulang node na ito. Ang isang node na matatagpuan sa ibaba ng kasalukuyang isa at konektado dito ay tinatawag inapo node na ito. Kumuha tayo ng isang halimbawa. Kunin ang node B, kung gayon ang magulang nito ay magiging node A, at ang mga anak nito ay magiging mga node D, E, at F.
Dahon
Ang isang node na walang mga anak ay tinatawag na dahon ng puno. Sa halimbawa, ang mga node D, E, F, G, I, J, K ay magiging mga dahon.
Ito ang pangunahing terminolohiya. Ang iba pang mga konsepto ay tatalakayin sa ibang pagkakataon. Kaya, ang isang binary tree ay isang puno kung saan ang bawat node ay magkakaroon ng hindi hihigit sa dalawang anak. Tulad ng iyong nahulaan, ang puno mula sa halimbawa ay hindi magiging binary, dahil ang mga node B at H ay may higit sa dalawang anak. Narito ang isang halimbawa ng isang binary tree:
Ang mga node ng puno ay maaaring maglaman ng anumang impormasyon. Ang binary search tree ay isang binary tree na may mga sumusunod na katangian:
- Parehong kaliwa at kanang subtree ay binary search tree.
- Ang lahat ng mga node ng kaliwang subtree ng isang arbitrary na node X ay may mga data key values ββna mas mababa kaysa sa data key value ng node X mismo.
- Ang lahat ng mga node ng kanang subtree ng isang arbitrary na node X ay may mga halaga ng data key na mas malaki kaysa sa o katumbas ng halaga ng data key ng node X mismo.
Key - ilang katangian ng node (halimbawa, isang numero). Kailangan ang susi upang mahanap ang elemento ng puno kung saan tumutugma ang susi na ito. Halimbawa ng binary search tree:
view ng puno
Habang nagpapatuloy ako, isasama ko ang ilang (marahil hindi kumpleto) mga piraso ng code upang mapabuti ang iyong pang-unawa. Ang buong code ay nasa dulo ng artikulo.
Ang puno ay binubuo ng mga node. Istraktura ng node:
public class Node<T> {
private T data;
private int key;
private Node<T> leftChild;
private Node<T> rightChild;
public Node(T data, int key) {
this.data = data;
this.key = key;
}
public Node<T> getLeftChild() {
return leftChild;
}
public Node<T> getRightChild() {
return rightChild;
}
//...ΠΎΡΡΠ°Π»ΡΠ½ΡΠ΅ ΠΌΠ΅ΡΠΎΠ΄Ρ ΡΠ·Π»Π°
}
Ang bawat node ay may dalawang anak (posible na ang leftChild at/o rightChild na mga bata ay magiging null). Marahil ay naunawaan mo na sa kasong ito ang data ng numero ay ang data na nakaimbak sa node; susi - node key.
Nalaman namin ang buhol, ngayon ay pag-usapan natin ang mga problema sa pagpindot tungkol sa mga puno. Pagkatapos nito, ang salitang "puno" ay mangangahulugan ng konsepto ng isang binary search tree. Binary tree structure:
public class BinaryTree<T> {
private Node<T> root;
//ΠΌΠ΅ΡΠΎΠ΄Ρ Π΄Π΅ΡΠ΅Π²Π°
}
Bilang isang field ng klase, kailangan lang natin ang ugat ng puno, dahil mula sa ugat, gamit ang getLeftChild() at getRightChild() na mga pamamaraan, maaari kang makarating sa anumang node ng puno.
Mga Algorithm ng Puno
paghahanap
Sabihin nating mayroon kang built tree. Paano makahanap ng elemento na may key key? Kailangan mong sunud-sunod na lumipat mula sa ugat pababa sa puno at ihambing ang halaga ng susi sa susi ng susunod na node: kung ang susi ay mas mababa sa susi ng susunod na node, pagkatapos ay pumunta sa kaliwang inapo ng node, kung higit pa - sa kanan, kung ang mga susi ay pantay - ang nais na node ay matatagpuan! Kaugnay na code:
public Node<T> find(int key) {
Node<T> current = root;
while (current.getKey() != key) {
if (key < current.getKey())
current = current.getLeftChild();
else
current = current.getRightChild();
if (current == null)
return null;
}
return current;
}
Kung ang kasalukuyang ay nagiging null, pagkatapos ay ang pag-ulit ay umabot sa dulo ng puno (sa isang konseptwal na antas, ikaw ay nasa isang hindi umiiral na lugar sa puno - isang anak ng isang dahon).
Isaalang-alang ang kahusayan ng algorithm ng paghahanap sa isang balanseng puno (isang puno kung saan ang mga node ay ipinamamahagi nang higit pa o hindi gaanong pantay-pantay). Pagkatapos ang kahusayan sa paghahanap ay magiging O(log(n)), at ang base 2 logarithm. Tingnan: kung mayroong n elemento sa isang balanseng puno, nangangahulugan ito na magkakaroon ng log(n) base 2 na antas ng puno. At sa paghahanap, para sa isang hakbang ng ikot, bumaba ka ng isang antas.
magpasok
Kung naunawaan mo ang kakanyahan ng paghahanap, hindi magiging mahirap para sa iyo na maunawaan ang pagpasok. Kailangan mo lamang bumaba sa dahon ng puno (ayon sa mga patakaran ng pagbaba na inilarawan sa paghahanap) at maging inapo nito - kaliwa o kanan, depende sa susi. Pagpapatupad:
public void insert(T insertData, int key) {
Node<T> current = root;
Node<T> parent;
Node<T> newNode = new Node<>(insertData, key);
if (root == null)
root = newNode;
else {
while (true) {
parent = current;
if (key < current.getKey()) {
current = current.getLeftChild();
if (current == null) {
parent.setLeftChild(newNode);
return;
}
}
else {
current = current.getRightChild();
if (current == null) {
parent.setRightChild(newNode);
return;
}
}
}
}
}
Sa kasong ito, bilang karagdagan sa kasalukuyang node, kinakailangan na mag-imbak ng impormasyon tungkol sa magulang ng kasalukuyang node. Kapag naging null ang kasalukuyang, maglalaman ang parent variable ng sheet na kailangan namin.
Ang kahusayan sa pagpapasok ay malinaw na kapareho ng sa paghahanap - O(log(n)).
Pag-aalis
Ang pagtanggal ay ang pinaka-kumplikadong operasyon na kailangang gawin sa isang puno. Malinaw na kailangan munang hanapin ang elementong aalisin natin. Pero ano? Kung itatakda lang natin ang reference nito sa null, mawawalan tayo ng impormasyon tungkol sa subtree na ang ugat ay ang node na ito. Ang mga paraan ng pagtanggal ng puno ay nahahati sa tatlong kaso.
Unang kaso. Ang node na aalisin ay walang mga anak.
Kung ang node na tatanggalin ay walang mga anak, nangangahulugan ito na ito ay isang dahon. Samakatuwid, maaari mo lamang itakda ang leftChild o rightChild na mga field ng magulang nito sa null.
Pangalawang kaso. Ang node na aalisin ay may isang anak
Ang kasong ito ay hindi rin napakahirap. Bumalik tayo sa ating halimbawa. Ipagpalagay na kailangan nating tanggalin ang isang elemento na may key 14. Sumang-ayon na dahil ito ang tamang anak ng isang node na may key 10, kung gayon ang alinman sa mga inapo nito (sa kasong ito, ang tama) ay magkakaroon ng key na mas malaki sa 10, kaya ikaw madaling "puputol" ito mula sa puno, at direktang ikonekta ang magulang sa anak ng node na tinatanggal, i.e. ikonekta ang node na may key 10 sa node 13. Magiging katulad ang sitwasyon kung kailangan nating tanggalin ang isang node na kaliwang anak ng magulang nito. Isipin ito para sa iyong sarili - isang eksaktong pagkakatulad.
Pangatlong kaso. Si Node ay may dalawang anak
Ang pinakamahirap na kaso. Tingnan natin ang isang bagong halimbawa.
Maghanap ng kahalili
Sabihin nating kailangan nating tanggalin ang node na may susi 25. Sino ang ilalagay natin sa lugar nito? Ang isa sa kanyang mga tagasunod (kaapu-apuhan o inapo ng mga inapo) ay dapat maging kahalili(ang papalit sa tinanggal na node).
Paano mo malalaman kung sino ang dapat na maging kahalili? Sa madaling salita, ito ang node sa puno na ang susi ay ang susunod na pinakamalaki mula sa node na inaalis. Ang algorithm ay ang mga sumusunod. Kailangan mong pumunta sa kanang anak nito (laging sa kanan, dahil sinabi na ang susi ng kahalili ay mas malaki kaysa sa susi ng node na tinatanggal), at pagkatapos ay dumaan sa kadena ng kaliwang mga anak ng kanang ito bata. Sa halimbawa, dapat tayong pumunta sa node na may susi 35, at pagkatapos ay bumaba sa kadena ng mga kaliwang anak nito sa dahon - sa kasong ito, ang kadena na ito ay binubuo lamang ng node na may susi 30. Sa mahigpit na pagsasalita, hinahanap natin ang ang pinakamaliit na node sa hanay ng mga node na mas malaki kaysa sa nais na node.
Code ng paraan ng paghahanap ng kahalili:
public Node<T> getSuccessor(Node<T> deleteNode) {
Node<T> parentSuccessor = deleteNode;//ΡΠΎΠ΄ΠΈΡΠ΅Π»Ρ ΠΏΡΠ΅Π΅ΠΌΠ½ΠΈΠΊΠ°
Node<T> successor = deleteNode;//ΠΏΡΠ΅Π΅ΠΌΠ½ΠΈΠΊ
Node<T> current = successor.getRightChild();//ΠΏΡΠΎΡΡΠΎ "ΠΏΡΠΎΠ±Π΅Π³Π°ΡΡΠΈΠΉ" ΡΠ·Π΅Π»
while (current != null) {
parentSuccessor = successor;
successor = current;
current = current.getLeftChild();
}
//Π½Π° Π²ΡΡ
ΠΎΠ΄Π΅ ΠΈΠ· ΡΠΈΠΊΠ»Π° ΠΈΠΌΠ΅Π΅ΠΌ ΠΏΡΠ΅Π΅ΠΌΠ½ΠΈΠΊΠ° ΠΈ ΡΠΎΠ΄ΠΈΡΠ΅Π»Ρ ΠΏΡΠ΅Π΅ΠΌΠ½ΠΈΠΊΠ°
if (successor != deleteNode.getRightChild()) {//Π΅ΡΠ»ΠΈ ΠΏΡΠ΅Π΅ΠΌΠ½ΠΈΠΊ Π½Π΅ ΡΠΎΠ²ΠΏΠ°Π΄Π°Π΅Ρ Ρ ΠΏΡΠ°Π²ΡΠΌ ΠΏΠΎΡΠΎΠΌΠΊΠΎΠΌ ΡΠ΄Π°Π»ΡΠ΅ΠΌΠΎΠ³ΠΎ ΡΠ·Π»Π°
parentSuccessor.setLeftChild(successor.getRightChild());//ΡΠΎ Π΅Π³ΠΎ ΡΠΎΠ΄ΠΈΡΠ΅Π»Ρ Π·Π°Π±ΠΈΡΠ°Π΅Ρ ΡΠ΅Π±Π΅ ΠΏΠΎΡΠΎΠΌΠΊΠ° ΠΏΡΠ΅Π΅ΠΌΠ½ΠΈΠΊΠ°, ΡΡΠΎΠ±Ρ Π½Π΅ ΠΏΠΎΡΠ΅ΡΡΡΡ Π΅Π³ΠΎ
successor.setRightChild(deleteNode.getRightChild());//ΡΠ²ΡΠ·ΡΠ²Π°Π΅ΠΌ ΠΏΡΠ΅Π΅ΠΌΠ½ΠΈΠΊΠ° Ρ ΠΏΡΠ°Π²ΡΠΌ ΠΏΠΎΡΠΎΠΌΠΊΠΎΠΌ ΡΠ΄Π°Π»ΡΠ΅ΠΌΠΎΠ³ΠΎ ΡΠ·Π»Π°
}
return successor;
}
Ang kumpletong code ng paraan ng pagtanggal:
public boolean delete(int deleteKey) {
Node<T> current = root;
Node<T> parent = current;
boolean isLeftChild = false;//Π Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ ΠΎΡ ΡΠΎΠ³ΠΎ, ΡΠ²Π»ΡΠ΅ΡΡΡ Π»ΠΈ ΡΠ΄Π°Π»ΡΠ΅ΠΌΡΠΉ ΡΠ·Π΅Π» Π»Π΅Π²ΡΠΌ ΠΈΠ»ΠΈ ΠΏΡΠ°Π²ΡΠΌ ΠΏΠΎΡΠΎΠΌΠΊΠΎΠΌ ΡΠ²ΠΎΠ΅Π³ΠΎ ΡΠΎΠ΄ΠΈΡΠ΅Π»Ρ, Π±ΡΠ»Π΅Π²ΡΠΊΠ°Ρ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½Π°Ρ isLeftChild Π±ΡΠ΄Π΅Ρ ΠΏΡΠΈΠ½ΠΈΠΌΠ°ΡΡ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ true ΠΈΠ»ΠΈ false ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²Π΅Π½Π½ΠΎ.
while (current.getKey() != deleteKey) {
parent = current;
if (deleteKey < current.getKey()) {
current = current.getLeftChild();
isLeftChild = true;
} else {
isLeftChild = false;
current = current.getRightChild();
}
if (current == null)
return false;
}
if (current.getLeftChild() == null && current.getRightChild() == null) {//ΠΏΠ΅ΡΠ²ΡΠΉ ΡΠ»ΡΡΠ°ΠΉ
if (current == root)
current = null;
else if (isLeftChild)
parent.setLeftChild(null);
else
parent.setRightChild(null);
}
else if (current.getRightChild() == null) {//Π²ΡΠΎΡΠΎΠΉ ΡΠ»ΡΡΠ°ΠΉ
if (current == root)
root = current.getLeftChild();
else if (isLeftChild)
parent.setLeftChild(current.getLeftChild());
else
current.setRightChild(current.getLeftChild());
} else if (current.getLeftChild() == null) {
if (current == root)
root = current.getRightChild();
else if (isLeftChild)
parent.setLeftChild(current.getRightChild());
else
parent.setRightChild(current.getRightChild());
}
else {//ΡΡΠ΅ΡΠΈΠΉ ΡΠ»ΡΡΠ°ΠΉ
Node<T> successor = getSuccessor(current);
if (current == root)
root = successor;
else if (isLeftChild)
parent.setLeftChild(successor);
else
parent.setRightChild(successor);
}
return true;
}
Ang pagiging kumplikado ay maaaring tinantya sa O(log(n)).
Paghahanap ng maximum/minimum sa isang puno
Malinaw, kung paano hanapin ang minimum / maximum na halaga sa puno - kailangan mong sunud-sunod na dumaan sa kadena ng kaliwa / kanang elemento ng puno, ayon sa pagkakabanggit; kapag nakarating ka sa dahon, ito ang magiging minimum/maximum na elemento.
public Node<T> getMinimum(Node<T> startPoint) {
Node<T> current = startPoint;
Node<T> parent = current;
while (current != null) {
parent = current;
current = current.getLeftChild();
}
return parent;
}
public Node<T> getMaximum(Node<T> startPoint) {
Node<T> current = startPoint;
Node<T> parent = current;
while (current != null) {
parent = current;
current = current.getRightChild();
}
return parent;
}
Pagiging kumplikado - O(log(n))
Symmetric Bypass
Ang Traversal ay isang pagbisita sa bawat node ng puno upang magawa ang isang bagay dito.
Recursive symmetric traversal algorithm:
- Gumawa ng aksyon sa kaliwang bata
- Gumawa ng isang aksyon sa iyong sarili
- Gumawa ng aksyon sa tamang bata
Code:
public void inOrder(Node<T> current) {
if (current != null) {
inOrder(current.getLeftChild());
System.out.println(current.getData() + " ");//ΠΠ΄Π΅ΡΡ ΠΌΠΎΠΆΠ΅Ρ Π±ΡΡΡ Π²ΡΠ΅, ΡΡΠΎ ΡΠ³ΠΎΠ΄Π½ΠΎ
inOrder(current.getRightChild());
}
}
Konklusyon
Sa wakas! Kung wala akong ipinaliwanag o may anumang komento, naghihintay ako sa mga komento. Gaya ng ipinangako, narito ang kumpletong code.
Node.java:
public class Node<T> {
private T data;
private int key;
private Node<T> leftChild;
private Node<T> rightChild;
public Node(T data, int key) {
this.data = data;
this.key = key;
}
public void setLeftChild(Node<T> newNode) {
leftChild = newNode;
}
public void setRightChild(Node<T> newNode) {
rightChild = newNode;
}
public Node<T> getLeftChild() {
return leftChild;
}
public Node<T> getRightChild() {
return rightChild;
}
public T getData() {
return data;
}
public int getKey() {
return key;
}
}
BinaryTree.java:
public class BinaryTree<T> {
private Node<T> root;
public Node<T> find(int key) {
Node<T> current = root;
while (current.getKey() != key) {
if (key < current.getKey())
current = current.getLeftChild();
else
current = current.getRightChild();
if (current == null)
return null;
}
return current;
}
public void insert(T insertData, int key) {
Node<T> current = root;
Node<T> parent;
Node<T> newNode = new Node<>(insertData, key);
if (root == null)
root = newNode;
else {
while (true) {
parent = current;
if (key < current.getKey()) {
current = current.getLeftChild();
if (current == null) {
parent.setLeftChild(newNode);
return;
}
}
else {
current = current.getRightChild();
if (current == null) {
parent.setRightChild(newNode);
return;
}
}
}
}
}
public Node<T> getMinimum(Node<T> startPoint) {
Node<T> current = startPoint;
Node<T> parent = current;
while (current != null) {
parent = current;
current = current.getLeftChild();
}
return parent;
}
public Node<T> getMaximum(Node<T> startPoint) {
Node<T> current = startPoint;
Node<T> parent = current;
while (current != null) {
parent = current;
current = current.getRightChild();
}
return parent;
}
public Node<T> getSuccessor(Node<T> deleteNode) {
Node<T> parentSuccessor = deleteNode;
Node<T> successor = deleteNode;
Node<T> current = successor.getRightChild();
while (current != null) {
parentSuccessor = successor;
successor = current;
current = current.getLeftChild();
}
if (successor != deleteNode.getRightChild()) {
parentSuccessor.setLeftChild(successor.getRightChild());
successor.setRightChild(deleteNode.getRightChild());
}
return successor;
}
public boolean delete(int deleteKey) {
Node<T> current = root;
Node<T> parent = current;
boolean isLeftChild = false;
while (current.getKey() != deleteKey) {
parent = current;
if (deleteKey < current.getKey()) {
current = current.getLeftChild();
isLeftChild = true;
} else {
isLeftChild = false;
current = current.getRightChild();
}
if (current == null)
return false;
}
if (current.getLeftChild() == null && current.getRightChild() == null) {
if (current == root)
current = null;
else if (isLeftChild)
parent.setLeftChild(null);
else
parent.setRightChild(null);
}
else if (current.getRightChild() == null) {
if (current == root)
root = current.getLeftChild();
else if (isLeftChild)
parent.setLeftChild(current.getLeftChild());
else
current.setRightChild(current.getLeftChild());
} else if (current.getLeftChild() == null) {
if (current == root)
root = current.getRightChild();
else if (isLeftChild)
parent.setLeftChild(current.getRightChild());
else
parent.setRightChild(current.getRightChild());
}
else {
Node<T> successor = getSuccessor(current);
if (current == root)
root = successor;
else if (isLeftChild)
parent.setLeftChild(successor);
else
parent.setRightChild(successor);
}
return true;
}
public void inOrder(Node<T> current) {
if (current != null) {
inOrder(current.getLeftChild());
System.out.println(current.getData() + " ");
inOrder(current.getRightChild());
}
}
}
PS
Pagkabulok sa O(n)
Maaaring napansin ng marami sa inyo: paano kung gagawin mong hindi balanse ang puno? Halimbawa, ilagay ang mga node na may pagtaas ng mga susi sa isang puno: 1,2,3,4,5,6... Pagkatapos ang puno ay magiging kamukha ng isang naka-link na listahan. At oo, mawawalan ng istraktura ng puno ang puno, at samakatuwid ang kahusayan ng pag-access ng data. Ang pagiging kumplikado ng mga operasyon sa paghahanap, pagpasok, at pagtanggal ay magiging kapareho ng sa isang naka-link na listahan: O(n). Ito ay isa sa pinakamahalaga, sa palagay ko, ang mga disadvantages ng mga binary tree.
Ang mga rehistradong user lamang ang maaaring lumahok sa survey.
Medyo matagal na akong wala sa HabrΓ©, at gusto kong malaman kung anong mga artikulo sa anong mga paksa ang gusto mong makita pa?
-
Mga Istraktura ng Data
-
Algorithm (DP, recursion, data compression, atbp.)
-
Application ng mga istruktura ng data at algorithm sa totoong buhay
-
Pagprograma ng mga android application sa Java
-
Java Web Application Programming
2 mga gumagamit ang bumoto. 1 user ang umiwas.
Pinagmulan: www.habr.com