முன்னோடியாக
இந்த கட்டுரை பைனரி தேடல் மரங்களைப் பற்றியது. பற்றி சமீபத்தில் ஒரு கட்டுரை எழுதினேன்
மரம் என்பது விளிம்புகளால் இணைக்கப்பட்ட முனைகளைக் கொண்ட தரவுக் கட்டமைப்பாகும். ஒரு மரம் ஒரு வரைபடத்தின் சிறப்பு வழக்கு என்று நாம் கூறலாம். இங்கே ஒரு உதாரணம் மரம்:
இது பைனரி தேடல் மரம் அல்ல! எல்லாம் வெட்டுக்குக் கீழே!
சொல்லியல்
ரூட்
மரத்தின் வேர் மிக உயர்ந்த முனை ஆகும். எடுத்துக்காட்டில், இது முனை A. மரத்தில், ஒரே ஒரு பாதை மட்டுமே வேரிலிருந்து வேறு எந்த முனைக்கும் செல்லும்! உண்மையில், எந்த முனையும் இந்த முனையுடன் தொடர்புடைய சப்ட்ரீயின் வேராகக் கருதப்படலாம்.
பெற்றோர்/சந்ததி
ரூட் தவிர அனைத்து முனைகளும் சரியாக ஒரு விளிம்பை மற்றொரு முனைக்கு கொண்டு செல்லும். தற்போதைய முனைக்கு மேலே உள்ள முனை அழைக்கப்படுகிறது பெற்றோர் இந்த முனை. தற்போதைய ஒன்றின் கீழே அமைந்துள்ள மற்றும் அதனுடன் இணைக்கப்பட்ட ஒரு முனை என்று அழைக்கப்படுகிறது வழித்தோன்றல் இந்த முனை. ஒரு உதாரணத்தை எடுத்துக் கொள்வோம். முனை B ஐ எடுத்துக் கொள்ளுங்கள், அதன் பெற்றோர் முனை A ஆகவும், அதன் குழந்தைகள் D, E மற்றும் F முனைகளாகவும் இருக்கும்.
தாள்
குழந்தைகள் இல்லாத ஒரு முனை மரத்தின் இலை என்று அழைக்கப்படுகிறது. எடுத்துக்காட்டில், முனைகள் D, E, F, G, I, J, K ஆகியவை இலைகளாக இருக்கும்.
இதுவே அடிப்படைக் கலைச்சொல். மற்ற கருத்துக்கள் பின்னர் விவாதிக்கப்படும். எனவே, பைனரி மரம் என்பது ஒரு மரமாகும், அதில் ஒவ்வொரு முனைக்கும் இரண்டு குழந்தைகளுக்கு மேல் இல்லை. நீங்கள் யூகித்தபடி, எடுத்துக்காட்டில் இருந்து மரம் பைனரியாக இருக்காது, ஏனென்றால் பி மற்றும் எச் முனைகளுக்கு இரண்டுக்கும் மேற்பட்ட குழந்தைகள் உள்ளனர். பைனரி மரத்தின் உதாரணம் இங்கே:
மரத்தின் முனைகள் எந்த தகவலையும் கொண்டிருக்கலாம். பைனரி தேடல் மரம் என்பது பைனரி மரமாகும், இது பின்வரும் பண்புகளைக் கொண்டுள்ளது:
- இடது மற்றும் வலது துணை மரங்கள் இரண்டும் பைனரி தேடல் மரங்கள்.
- தன்னிச்சையான முனை X இன் இடது துணை மரத்தின் அனைத்து முனைகளும் முனை X இன் தரவு விசை மதிப்பை விட குறைவான தரவு விசை மதிப்புகளைக் கொண்டுள்ளன.
- தன்னிச்சையான முனை X இன் வலது துணை மரத்தின் அனைத்து முனைகளும், முனை X இன் தரவு விசையின் மதிப்பை விட அதிகமாக அல்லது அதற்கு சமமான தரவு விசை மதிப்புகளைக் கொண்டுள்ளன.
முக்கிய - முனையின் சில பண்புகள் (உதாரணமாக, ஒரு எண்). இந்த விசையுடன் தொடர்புடைய மரத்தின் உறுப்பைக் கண்டுபிடிக்க விசை தேவைப்படுகிறது. பைனரி தேடல் மரம் உதாரணம்:
மரத்தின் பார்வை
நான் தொடர்ந்து செல்லும்போது, உங்கள் புரிதலை மேம்படுத்தும் வகையில் சில (ஒருவேளை முழுமையடையாத) குறியீடு துண்டுகளைச் சேர்ப்பேன். முழு குறியீடும் கட்டுரையின் முடிவில் இருக்கும்.
மரம் முனைகளால் ஆனது. முனை அமைப்பு:
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;
}
//...остальные методы узла
}
ஒவ்வொரு முனையிலும் இரண்டு குழந்தைகள் உள்ளனர் (இடது குழந்தை மற்றும்/அல்லது வலது குழந்தைகள் பூஜ்யமாக இருக்க வாய்ப்புள்ளது). இந்த வழக்கில் எண் தரவு என்பது முனையில் சேமிக்கப்பட்ட தரவு என்பதை நீங்கள் புரிந்துகொண்டிருக்கலாம்; விசை - முனை விசை.
நாங்கள் முடிச்சைக் கண்டுபிடித்தோம், இப்போது மரங்களைப் பற்றிய அழுத்தமான சிக்கல்களைப் பற்றி பேசலாம். இனிமேல், "மரம்" என்ற சொல் பைனரி தேடல் மரத்தின் கருத்தைக் குறிக்கும். பைனரி மர அமைப்பு:
public class BinaryTree<T> {
private Node<T> root;
//методы дерева
}
ஒரு வர்க்க புலமாக, எங்களுக்கு மரத்தின் வேர் மட்டுமே தேவை, ஏனெனில் வேரிலிருந்து, getLeftChild() மற்றும் getRightChild() முறைகளைப் பயன்படுத்தி, மரத்தின் எந்த முனையையும் நீங்கள் பெறலாம்.
மர வழிமுறைகள்
Поиск
உங்களிடம் ஒரு கட்டப்பட்ட மரம் இருப்பதாக வைத்துக்கொள்வோம். முக்கிய விசையுடன் உறுப்பை எவ்வாறு கண்டுபிடிப்பது? நீங்கள் மரத்தின் வேரிலிருந்து கீழே நகர்ந்து, விசையின் மதிப்பை அடுத்த முனையின் விசையுடன் ஒப்பிட வேண்டும்: விசை அடுத்த முனையின் விசையை விட குறைவாக இருந்தால், முனையின் இடது சந்ததிக்குச் செல்லவும், அதிகமாக இருந்தால் - வலதுபுறம், விசைகள் சமமாக இருந்தால் - விரும்பிய முனை காணப்படுகிறது! தொடர்புடைய குறியீடு:
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;
}
மின்னோட்டம் பூஜ்யமாக மாறினால், மறு செய்கை மரத்தின் முடிவை அடைந்துவிட்டது (ஒரு கருத்தியல் மட்டத்தில், நீங்கள் மரத்தில் இல்லாத இடத்தில் இருக்கிறீர்கள் - ஒரு இலையின் குழந்தை).
ஒரு சீரான மரத்தில் தேடல் அல்காரிதத்தின் செயல்திறனைக் கவனியுங்கள் (முனைகள் அதிகமாகவோ அல்லது குறைவாகவோ சமமாக விநியோகிக்கப்படும் ஒரு மரம்). பின்னர் தேடல் திறன் O(log(n)), மற்றும் அடிப்படை 2 மடக்கை. பார்க்கவும்: ஒரு சமநிலை மரத்தில் n உறுப்புகள் இருந்தால், இதன் பொருள் மரத்தின் log(n) அடிப்படை 2 நிலைகள் இருக்கும். தேடலில், சுழற்சியின் ஒரு படிக்கு, நீங்கள் ஒரு நிலைக்கு கீழே செல்கிறீர்கள்.
நுழைக்க
தேடலின் சாராம்சத்தை நீங்கள் புரிந்து கொண்டால், செருகலைப் புரிந்துகொள்வது உங்களுக்கு கடினமாக இருக்காது. நீங்கள் மரத்தின் இலைக்குச் செல்ல வேண்டும் (தேடலில் விவரிக்கப்பட்டுள்ள வம்சாவளியின் விதிகளின்படி) அதன் சந்ததியாக மாற வேண்டும் - இடது அல்லது வலது, விசையைப் பொறுத்து. செயல்படுத்தல்:
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;
}
}
}
}
}
இந்த வழக்கில், தற்போதைய முனைக்கு கூடுதலாக, தற்போதைய முனையின் பெற்றோர் பற்றிய தகவலை சேமிப்பது அவசியம். மின்னோட்டம் பூஜ்யமாக மாறும் போது, பெற்றோர் மாறி நமக்குத் தேவையான தாளைக் கொண்டிருக்கும்.
செருகும் திறன், தேடலின் செயல்திறனைப் போலவே இருக்கும் - O(log(n)).
அகற்றுதல்
நீக்குதல் என்பது மிகவும் சிக்கலான செயலாகும், இது ஒரு மரத்துடன் செய்யப்பட வேண்டும். முதலில் நாம் அகற்றப் போகும் உறுப்பைக் கண்டுபிடிப்பது அவசியம் என்பது தெளிவாகிறது. ஆனால் பிறகு என்ன? நாம் அதன் குறிப்பை பூஜ்யமாக அமைத்தால், இந்த முனையின் மூலமான சப்ட்ரீ பற்றிய தகவலை இழப்போம். மரங்களை அகற்றும் முறைகள் மூன்று வகைகளாக பிரிக்கப்பட்டுள்ளன.
முதல் வழக்கு. அகற்றப்பட வேண்டிய முனைக்கு குழந்தைகள் இல்லை.
நீக்கப்பட வேண்டிய முனைக்கு குழந்தைகள் இல்லை என்றால், அது ஒரு இலை என்று அர்த்தம். எனவே, நீங்கள் அதன் பெற்றோரின் இடது குழந்தை அல்லது வலது குழந்தை புலங்களை வெறுமையாக அமைக்கலாம்.
இரண்டாவது வழக்கு. அகற்றப்படும் முனையில் ஒரு குழந்தை உள்ளது
இந்த வழக்கும் மிகவும் கடினம் அல்ல. நமது உதாரணத்திற்கு திரும்புவோம். விசை 14 ஐக் கொண்ட ஒரு உறுப்பை நாம் நீக்க வேண்டும் என்று வைத்துக்கொள்வோம். அது முக்கிய 10 உடன் முனையின் சரியான குழந்தை என்பதால், அதன் வழித்தோன்றல்கள் (இந்த விஷயத்தில், சரியானது) 10 ஐ விட அதிகமான விசையைக் கொண்டிருக்கும் என்பதை ஒப்புக்கொள்க. மரத்திலிருந்து அதை எளிதாக "வெட்ட" முடியும், மேலும் பெற்றோரை நேரடியாக நீக்கப்படும் முனையின் குழந்தையுடன் இணைக்கலாம், அதாவது. விசை 10 உடன் முனை 13 உடன் இணைக்கவும். அதன் பெற்றோரின் இடது குழந்தையாக இருக்கும் ஒரு முனையை நாம் நீக்க வேண்டியிருந்தால் நிலைமை ஒத்ததாக இருக்கும். இதைப் பற்றி நீங்களே சிந்தியுங்கள் - ஒரு சரியான ஒப்புமை.
மூன்றாவது வழக்கு. கணுவுக்கு இரண்டு குழந்தைகள் உள்ளனர்
மிகவும் கடினமான வழக்கு. ஒரு புதிய உதாரணத்தைப் பார்ப்போம்.
ஒரு வாரிசைத் தேடுங்கள்
விசை 25ஐக் கொண்டு முனையை அகற்ற வேண்டும் என்று வைத்துக்கொள்வோம். அதன் இடத்தில் யாரை வைப்பது? அவரைப் பின்பற்றுபவர்களில் ஒருவராக (சந்ததியினர் அல்லது சந்ததியினர்) ஆக வேண்டும் வாரிசு(நீக்கிய முனையின் இடத்தைப் பிடிப்பவர்).
வாரிசு யார் என்று உங்களுக்கு எப்படித் தெரியும்? உள்ளுணர்வாக, இது மரத்தில் உள்ள முனையாகும், அதன் விசை அகற்றப்பட்ட முனையிலிருந்து அடுத்த பெரியது. அல்காரிதம் பின்வருமாறு. நீங்கள் அதன் வலது குழந்தையிடம் செல்ல வேண்டும் (எப்போதும் வலதுபுறம், ஏனெனில் வாரிசின் சாவி நீக்கப்பட்ட முனையின் சாவியை விட பெரியது என்று ஏற்கனவே கூறப்பட்டது), பின்னர் இந்த வலதுபுறத்தின் இடது குழந்தைகளின் சங்கிலி வழியாக செல்லுங்கள். குழந்தை. எடுத்துக்காட்டில், நாம் முக்கிய 35 உடன் முனைக்குச் செல்ல வேண்டும், பின்னர் அதன் இடது குழந்தைகளின் சங்கிலியை இலைக்கு கீழே செல்ல வேண்டும் - இந்த விஷயத்தில், இந்த சங்கிலியில் முக்கிய 30 உடன் முனை மட்டுமே உள்ளது. கண்டிப்பாகச் சொன்னால், நாங்கள் தேடுகிறோம் விரும்பிய முனையை விட பெரிய முனைகளின் தொகுப்பில் உள்ள சிறிய முனை.
வாரிசு தேடல் முறை குறியீடு:
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;//В зависимости от того, является ли удаляемый узел левым или правым потомком своего родителя, булевская переменная 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;
}
சிக்கலை O(log(n))க்கு தோராயமாக கணக்கிடலாம்.
ஒரு மரத்தில் அதிகபட்சம்/குறைந்தபட்சம் கண்டறிதல்
வெளிப்படையாக, மரத்தில் குறைந்தபட்ச / அதிகபட்ச மதிப்பை எவ்வாறு கண்டுபிடிப்பது - நீங்கள் முறையே மரத்தின் இடது / வலது உறுப்புகளின் சங்கிலி வழியாக செல்ல வேண்டும்; நீங்கள் இலைக்கு வரும்போது, அது குறைந்தபட்ச/அதிகபட்ச உறுப்பாக இருக்கும்.
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;
}
சிக்கலானது - O(log(n))
சமச்சீர் பைபாஸ்
டிராவர்சல் என்பது மரத்தின் ஒவ்வொரு முனையிலும் ஏதாவது ஒன்றைச் செய்ய ஒரு வருகை.
சுழல்நிலை சமச்சீர் டிராவர்சல் அல்காரிதம்:
- இடது குழந்தை மீது ஒரு செயலைச் செய்யுங்கள்
- நீங்களே ஒரு செயலைச் செய்யுங்கள்
- சரியான குழந்தை மீது நடவடிக்கை எடுக்கவும்
குறியீடு:
public void inOrder(Node<T> current) {
if (current != null) {
inOrder(current.getLeftChild());
System.out.println(current.getData() + " ");//Здесь может быть все, что угодно
inOrder(current.getRightChild());
}
}
முடிவுக்கு
இறுதியாக! நான் ஏதாவது விளக்கவில்லை அல்லது கருத்துகள் எதுவும் இல்லை என்றால், நான் கருத்துகளில் காத்திருக்கிறேன். வாக்குறுதியளித்தபடி, இங்கே முழுமையான குறியீடு உள்ளது.
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());
}
}
}
சோசலிஸ்ட் கட்சி
O(n) க்கு சிதைவு
உங்களில் பலர் கவனித்திருக்கலாம்: மரத்தை சமநிலையற்றதாக மாற்றினால் என்ன செய்வது? எடுத்துக்காட்டாக, அதிகரிக்கும் விசைகளுடன் மரத்தில் முனைகளை வைக்கவும்: 1,2,3,4,5,6... பின்னர் மரம் இணைக்கப்பட்ட பட்டியலை ஓரளவு நினைவூட்டும். ஆம், மரம் அதன் மர அமைப்பை இழக்கும், எனவே தரவு அணுகலின் திறன். தேடல், செருகல் மற்றும் நீக்குதல் செயல்பாடுகளின் சிக்கலானது இணைக்கப்பட்ட பட்டியலைப் போலவே மாறும்: O(n). இது மிக முக்கியமான ஒன்றாகும், என் கருத்துப்படி, பைனரி மரங்களின் தீமைகள்.
பதிவு செய்த பயனர்கள் மட்டுமே கணக்கெடுப்பில் பங்கேற்க முடியும்.
நான் நீண்ட காலமாக ஹப்ரேயில் இல்லை, மேலும் நீங்கள் எந்த தலைப்புகளில் எந்த கட்டுரைகளை அதிகம் பார்க்க விரும்புகிறீர்கள் என்பதை அறிய விரும்புகிறேன்?
-
தரவு கட்டமைப்புகள்
-
அல்காரிதம்கள் (டிபி, ரிகர்ஷன், டேட்டா கம்ப்ரஷன் போன்றவை)
-
நிஜ வாழ்க்கையில் தரவு கட்டமைப்புகள் மற்றும் அல்காரிதம்களின் பயன்பாடு
-
ஜாவாவில் ஆண்ட்ராய்டு பயன்பாடுகளை நிரலாக்கம்
-
ஜாவா வெப் அப்ளிகேஷன் புரோகிராமிங்
2 பயனர்கள் வாக்களித்தனர். 1 பயனர் வாக்களிக்கவில்லை.
ஆதாரம்: www.habr.com