How does JavaScript implement a binary search tree? The following article will introduce you the method of implementing binary search tree using JavaScript.
How does JavaScript implement a binary search tree? The following article will introduce you the method of implementing binary search tree using JavaScript. There is a certain reference value, friends in need can refer to it, I hope to help everyone.
One of the most commonly used and discussed data structures in computer science is the binary search tree . This is usually the first data structure introduced with a non-linear interpolation algorithm. A binary search tree is similar to a double-linked list, with each node containing some data and two pointers to other nodes; they differ in the way these nodes are related to each other. The pointers of the binary search tree nodes are commonly called "left" and "right" to indicate the subtree related to the current value. A simple JavaScript implementation of such a node is as follows:
var node = {
value: 125,
left: null,
right: null
};
As can be seen from the name, the binary search tree is organized into a tree structure of layers. The first item becomes the root node, and each additional value is added to the tree as an ancestor of that root. However, the values on the nodes of the binary search tree are unique, and they are sorted according to the values they contain: the value of the left subtree as the node is always smaller than the value of the node, and the value in the right subtree is greater than the value of the node. In this way, finding the value in the binary search tree becomes very simple, as long as the value you are looking for is smaller than the node being processed, it will go to the left, and if the value is larger, it will move to the right. There must be no duplicates in a binary search tree, because duplicates break this relationship. The following figure represents a simple binary search tree.
The figure above shows a binary search tree with a root value of 8. When the value 3 is added, it becomes the left child of the root, because 3 is less than 8. When the value 1 is added, it becomes the left child of 3 because 1 is less than 8 (so left) and then 1 is less than 3 (and then left). When the value 10 is added, it becomes the right child node because 10 is greater than 8. Use this process to continue processing values 6, 4, 7, 14, and 13. The depth of this binary search tree is 3, which means that the node furthest from the root is three nodes.
Binary search trees end in a naturally sorted order, so they can be used to quickly find data because you can eliminate the possibility of each step immediately. You can search faster by limiting the number of nodes you need to find. Suppose you want to find the value 6 in the tree above. Starting at the root, determine that 6 is less than 8, so go to the left child of the root. Since 6 is greater than 3, you will go to the right node. You will find the correct value. So you only need to visit three rather than nine nodes to find this value.
To implement a binary search tree in JavaScript, the first step is to define the basic interface:
function BinarySearchTree () {
this._root = null;
}
BinarySearchTree.prototype = {
// restore constructor
constructor: BinarySearchTree,
add: function (value) {
},
contains: function (value) {
},
remove: function (value) {
},
size: function () {
},
toArray: function () {
},
toString: function () {
}
};
Basic connection is similar to other data structures, there are methods to add and delete values. I also added some convenience methods size(), toArray()and toString(), they are useful for JavaScript.
To master the method uses a binary search tree, the best from the contains()start method. contains()The method accepts a value as a parameter and returns if the value exists in the tree true, otherwise it returns false. This method follows a basic binary search algorithm to determine if the value exists:
BinarySearchTree.prototype = {
// more code
contains: function (value) {
var found = false,
current = this._root
// make sure there's a node to search
while (! found && current) {
// if the value is less than the current node's, go left
if (value <current.value) {
current = current.left;
// if the value is greater than the current node's, go right
} else if (value> current.value) {
current = current.right;
// values are equal, found it!
} else {
found = true;
}
}
// only proceed if the node was found
return found;
},
// more code
};
The search starts at the root of the tree. If no data is added, there may be no roots, so you must check. Traversing the tree follows the simple algorithm discussed earlier: if the value to be found is less than the current node, it moves to the left, and if the value is greater, it moves to the right. Cover every time currentthe pointer, until I find the value (in this case foundset true) or there are no more nodes in that direction (in this case, the value is not in the tree).
In the contains()method of use it can also be used to insert new values in the tree. The main difference is that you are looking for where to put the new value, rather than looking for the value in the tree:
BinarySearchTree.prototype = {
// more code
add: function (value) {
// create a new item object, place data in
var node = {
value: value,
left: null,
right: null
},
// used to traverse the structure
current;
// special case: no items in the tree yet
if (this._root === null) {
this._root = node;
} else {
current = this._root;
while (true) {
// if the new value is less than this node's value, go left
if (value <current.value) {
// if there's no left, then the new node belongs there
if (current.left === null) {
current.left = node;
break;
} else {
current = current.left;
}
// if the new value is greater than this node's value, go right
} else if (value> current.value) {
// if there's no right, then the new node belongs there
if (current.right === null) {
current.right = node;
break;
} else {
current = current.right;
}
// if the new value is equal to the current one, just ignore
} else {
break;
}
}
}
},
// more code
};
When adding values to a binary search tree, the special case is when there is no root. In this case, simply setting the root to the new value makes the job easy. In other cases, the basic algorithm and contains()substantially the same as used in the algorithm: new value less than the current node to the left, to the right if the value is greater. The main difference is that when you can't move on, this is where the new value is. So if you need to move to the left without a left node, the new value will become the left node (same as the right node). Because there are no duplicates, if a node with the same value is found, the operation stops.
In continuing the discussion size()before the method, I would like to discuss in depth the tree traversal. In order to calculate the size of the binary search tree, each node in the tree must be visited. Binary search trees usually have different types of traversal methods, the most commonly used is an ordered traversal. Ordered traversal is performed on each node by processing the left subtree, then the node itself, and then the right subtree. Since the binary search tree is sorted this way, from left to right, the result is that the nodes are processed in the correct sort order. For the size()method, the order of node traversal does not really matter, but its toArray()method is very important. Since both methods require the implementation of traversal, I decided to add a can of generic traverse()method:
BinarySearchTree.prototype = {
// more code
traverse: function (process) {
// helper function
function inOrder (node) {
if (node) {
// traverse the left subtree
if (node.left! == null) {
inOrder (node.left);
}
// call the process method on this node
process.call (this, node);
// traverse the right subtree
if (node.right! == null) {
inOrder (node.right);
}
}
}
// start with the root
inOrder (this._root);
},
// more code
};
This method accepts one parameter process, which is a function that should be run on each node in the tree. This defines a method named inOrder()auxiliary function recursively traverse the tree. Note that if the current node exists, the recursion moves only left and right (to avoid multiple processing null). Then traverse()method begins sequentially traversed from the root, process()the function of each processing node. You can then use this method to achieve size(), toArray(), toString():
BinarySearchTree.prototype = {
// more code
size: function () {
var length = 0;
this.traverse (function (node) {
length ++;
});
return length;
},
toArray: function () {
var result = [];
this.traverse (function (node) {
result.push (node.value);
});
return result;
},
toString: function () {
return this.toArray (). toString ();
},
// more code
};
size()And toArray()calls the traverse()method and pass in a function to run on each node. In the use of size()the case, the function simply increments the variable length, and toArray()using the function values of the nodes to add to the array. toString()In the method invocation toArray()before the return of the array to a string and returns.
When deleting a node, you need to determine if it is the root node. The root node is handled similarly to other nodes, but the obvious exception is that the root node needs to be set to a different value at the end. For simplicity, this will be considered a special case in JavaScript code.
The first step in deleting a node is to determine if the node exists:
BinarySearchTree.prototype = {
// more code here
remove: function (value) {
var found = false,
parent = null,
current = this._root,
childCount,
replacement,
replacementParent;
// make sure there's a node to search
while (! found && current) {
// if the value is less than the current node's, go left
if (value <current.value) {
parent = current;
current = current.left;
// if the value is greater than the current node's, go right
} else if (value> current.value) {
parent = current;
current = current.right;
// values are equal, found it!
} else {
found = true;
}
}
// only proceed if the node was found
if (found) {
// continue
}
},
// more code here
};
remove()The first part of the method uses binary search to locate the node to be deleted. If the value is less than the current node, it moves to the left, and if the value is greater than the current node, it moves to the right. When traversing also keeps track of parentthe node, because ultimately you need to remove the node from its parent. When foundequal true, the currentvalue of the node to be removed.
There are three conditions to be aware of when deleting nodes:
Removing content other than leaf nodes from a binary search tree means that values must be moved to properly sort the tree. The first two are relatively simple to implement, removing only one leaf node, deleting a node with a child node and replacing it with its child nodes. The last case is a bit complicated for later access.
Before you learn how to delete a node, you need to know how many child nodes exist on the node. Once you know it, you must determine if the node is the root node, leaving a fairly simple decision tree:
BinarySearchTree.prototype = {
// more code here
remove: function (value) {
var found = false,
parent = null,
current = this._root,
childCount,
replacement,
replacementParent;
// find the node (removed for space)
// only proceed if the node was found
if (found) {
// figure out how many children
childCount = (current.left! == null? 1: 0) +
(current.right! == null? 1: 0);
// special case: the value is at the root
if (current === this._root) {
switch (childCount) {
// no children, just erase the root
case 0:
this._root = null;
break;
// one child, use one as the root
case 1:
this._root = (current.right === null?
current.left: current.right);
break;
// two children, little work to do
case 2:
// TODO
// no default
}
// non-root values
} else {
switch (childCount) {
// no children, just remove it from the parent
case 0:
// if the current value is less than its
// parent's, null out the left pointer
if (current.value <parent.value) {
parent.left = null;
// if the current value is greater than its
// parent's, null out the right pointer
} else {
parent.right = null;
}
break;
// one child, just reassign to parent
case 1:
// if the current value is less than its
// parent's, reset the left pointer
if (current.value <parent.value) {
parent.left = (current.left === null?
current.right: current.left);
// if the current value is greater than its
// parent's, reset the right pointer
} else {
parent.right = (current.left === null?
current.right: current.left);
}
break;
// two children, a bit more complicated
case 2:
// TODO
// no default
}
}
}
},
// more code here
};
When dealing with the root node, this is a simple process that covers it. For non-root nodes, must be removed in accordance with the set value of the node parentcorresponding pointer: if the value is less than the deleted parent node, the leftpointer must be reset null (for node has no children), or the delete node leftpointer; if removed value is greater than the parent must be righta pointer reset nullor deleted node rightpointer.
As mentioned earlier, deleting a node with two children is the most complicated operation. Consider the following representation of a binary search tree.
The root is 8 and the left child is 3. What happens if 3 is deleted? There are two possibilities: 1 (the child on the left of 3, called the ordered predecessor) or 4 (the leftmost child on the right subtree, called the ordered successor) can replace 3.
Either of these two options is appropriate. To find the ordered predecessor, that is, the value before the value is deleted, check the left subtree of the node to be deleted and select the rightmost child node; find the ordered successor, the value that appears immediately after the value is deleted, and reverse the process And check the leftmost right subtree. Each of them requires another traversal of the tree to complete the operation:
BinarySearchTree.prototype = {
// more code here
remove: function (value) {
var found = false,
parent = null,
current = this._root,
childCount,
replacement,
replacementParent;
// find the node (removed for space)
// only proceed if the node was found
if (found) {
// figure out how many children
childCount = (current.left! == null? 1: 0) +
(current.right! == null? 1: 0);
// special case: the value is at the root
if (current === this._root) {
switch (childCount) {
// other cases removed to save space
// two children, little work to do
case 2:
// new root will be the old root's left child
//...maybe
replacement = this._root.left;
// find the right-most leaf node to be
// the real new root
while (replacement.right! == null) {
replacementParent = replacement;
replacement = replacement.right;
}
// it's not the first node on the left
if (replacementParent! == null) {
// remove the new root from it's
// previous position
replacementParent.right = replacement.left;
// give the new root all of the old
// root's children
replacement.right = this._root.right;
replacement.left = this._root.left;
} else {
// just assign the children
replacement.right = this._root.right;
}
// officially assign new root
this._root = replacement;
// no default
}
// non-root values
} else {
switch (childCount) {
// other cases removed to save space
// two children, a bit more complicated
case 2:
// reset pointers for new traversal
replacement = current.left;
replacementParent = current;
// find the right-most node
while (replacement.right! == null) {
replacementParent = replacement;
replacement = replacement.right;
}
replacementParent.right = replacement.left;
// assign children to the replacement
replacement.right = current.right;
replacement.left = current.left;
// place the replacement in the right spot
if (current.value <parent.value) {
parent.left = replacement;
} else {
parent.right = replacement;
}
// no default
}
}
}
},
// more code here
};
The code for root and non-root nodes with two children is almost the same. This implementation always looks for an ordered precursor by looking at the left subtree and looking for the rightmost child node. Traversal using whilecycle replacementand replacementParentvariable done. replacementAlternatively eventually become a node in currentthe node, thus by its parent rightto replace a pointer is provided leftto remove it from the current pointer position. For the root node, when the replacementimmediate child of the root node, the replacementParentwill null, and therefore replacementthe rightpointer is set to only the root rightpointer. The final step is to assign the replacement node to the correct location. For the root node, the new root replacement setting; for non-root nodes, are assigned to replace the original parentproper position.
A note on this implementation: Always replacing nodes with ordered precursors can lead to an unbalanced tree, most of which will be on one side of the tree. Imbalanced trees mean that search efficiency is low, so they should be of interest in practical scenarios. In a binary search tree implementation, it is necessary to determine whether to use an ordered predecessor or an ordered successor to keep the tree properly balanced (commonly known as a self-balancing binary search tree).
Source CodeGithub: https://github.com/humanwhocodes/computer-science-in-javascript
we will explain how Binary Search in javascript work and what is the bigO of what and how to calculates the result need in js. Understanding data structures, algorithms, and basic programming concepts are essential for one to become a better...
we will explain how Binary Search in javascript work and what is the bigO of what and how to calculates the result need in js.
Understanding data structures, algorithms, and basic programming concepts are essential for one to become a better developer.
as we know, we are used multi ALgrathmic to search items in the array or an object, but what is distance one algorithmic about others is the complexity and bigO.
What is Binary Search?
Binary search is algorithmic used to find them to find elements or items found in array sorting before we start.
what is meaning it is sorting
example
[2,5,6,9,13,15,28,30],
as we see this array is sorting
let discussion the code
var start = 0;
var end = arr.length - 1;
var middle = Math.floor((start + end) / 2);
while(arr[middle] !== elem && start <= end) {
if(elem < arr[middle]) end = middle - 1;
else start = middle + 1;
middle = Math.floor((start + end) / 2);
}
return arr[middle] === elem ? middle : -1;
}
binarySearch([2,5,6,9,13,15,28,30], 103)
how it work
at first we spilt arry into 2 sub array and detarment the center element found in array middle by this code var middle = Math.floor((start + end) / 2);
then we need check element if equales the elemnt parctical if true we found the elemnt .
at first we will contiououse untaill the
while(arr[middle] !== elem && start <= end)
//this for elemnt not found from the first time and start <=end to reach the last elenment in the array.
the image explains the simple example of how it works
the best case O(1) but the worst case it is the O(logN)
When we learn computer programming languages, we always encounter the calculation of binary numbers. This article introduces binary numbers in JavaScript to everyone. I hope it will be helpful to everyone.
Computers use binary numbers to store calculations. When we learn computer programming languages, we always encounter the calculation of binary numbers. This article introduces binary numbers in JavaScript to everyone. I hope it will be helpful to everyone.
In this article you will learn the following:
Binary number representation
Binary integer in js
Bit operations in js
This article assumes that you know that binary numbers are used to store and calculate numbers in computers, and that you are familiar with the representation of binary numbers.
In order to achieve different purposes, it is all to simplify the problem. Binary numbers are represented in the computer in different ways, such as original code, reverse code, complement code, and shift code.
Note: This article asks for simplified operations. Binary numbers use one byte-8 binary bits to simplify the description.
Let's talk about the truth value first. We indicate that natural numbers include positive numbers, negative numbers, and 0. The following is the binary representation of 1 and -1. We call them true values.
+ 00000001 # +1
-00000001 # -1
The range of truth values that an 8-bit binary number can represent is [-2 ^ 8, + 2 ^ 8].
Since the computer can only store 0 and 1, it cannot store positive and negative, so the highest bit of the 8 binary bits is used to represent the sign, 0 is positive, 1 is negative, and the last seven bits are used to represent the absolute value of the true value. The method is called the original code notation, referred to as the original code. The original codes of 1 and -1 above are as follows:
0 0000001 # +1
1 0000001 # -1
Since 10000000 means -0, this has no meaning. All this number is used to represent -128. All negative numbers are one more than integers.
Since the most significant bit is used to represent the symbol, the range that can be represented now is [-2 ^ 7, + 2 ^ 7-1], which is [-128, +127]
Inverse code is another method of representing numbers. The rule is that the inverse code of an integer is the same as the original code. The negative code of the negative number does not change the sign bit of the original code. The other bits are negated.
0 0000001 # +1
1 1111110 # -1
The range of the reverse code is [-2 ^ 7, + 2 ^ 7-1], which is [-128, +127]
Two's complement is another method of representation. It is a digital representation invented to simplify the operation and change subtraction into addition. The rule is that the complement of an integer is the same as the original code. The complement of a negative number is the addition of 1 to the end of its complement.
0 0000001 # +1
1 1111111 # -1
The rule for quickly calculating the complement of a negative number is to find the first 1, 1 from its original code to the high order, and its low order is unchanged. The high order before 1 can be reversed bitwise. I don't know if you can think of the clever principle.
The range represented by 8's complement is [-2 ^ 7, + 2 ^ 7-1], which is [-128, +127]
2. Binary integer in jsLet's talk about binary integers in js. A qualified jser should support only one number type in js, which is floating point type. The floating point number of js follows the IEEE 754 specification.
However, there is another type of data in js, which is an integer represented by 32 bits. As long as any number in js is bitwise operated, the operating system will convert it to integers. Try to enter the following in the console The code
2.1 | 0 # OR operation
>>> 2
This shaping in js distinguishes between positive and negative numbers. According to the above knowledge, we infer that the range of integer representation in js is [-2 ^ 31, + 2 ^ 31-1], which is [-2147483648, +2147483647]. Output the following code in the console to verify our inference.
-2147483648 | 0
>>> -2147483648
-2147483649 | 0
>>> 2147483647
2147483647 | 0
>>> 2147483647
2147483648 | 0
>>> -2147483648
From the above results, we can see that the sign is changed when the values greater than and less than the lowest and highest values are converted.
3. Bit operations in jsThe bit operators in js have the following. When performing these operations on numbers, the system will say that 64 floating-point numbers are converted to 32-bit integers.
& versus
| Or
~ Non
^ XOR
<< shift left
Arithmetic right shift (signed right shift)
Logical right shift (unsigned right shift)
The following examples illustrate the role of each operator. Before starting, let's introduce some knowledge points that will be used.
Native binary literals were introduced in es6. The syntax of binary numbers starts with 0b. We will use this new feature. Currently the latest version of Chrome already supports it.
0b111 // 7
0b001 // 1
First let's introduce a method that will be used below-the Number.prototype.toString method can convert numbers into strings. There is an optional parameter that determines the number to be displayed in the specified base. You can view 3 below. Binary representation of
3..toString (2)
>> 11
& Bitwise AND performs the AND operation on the same operand and operand. If both are 1, it is 1, and if one is 0, it is 0.
101
011
---
001
The result of 101 and 011 is 001, and the verification is performed in js.
(0b101 & 0b011) .toString (2)
>>> "1"
| Bitwise or the same position as long as one is 1 is 1, and both are 0 is 0
101
001
---
101
101 and 001 or the end result is 101, the following verification in js
(0b101 | 0b001) .toString (2)
>>> "101"
The ~ operator inverts each bit of the operand. If it is 1, it becomes 0. If it is 0, the edge is 1.
101
---
010
The result of 101 bitwise NOT is 010, which is verified in js below
(~ 0b101) .toString (2)
>>> "-110"
Ah, why is it wrong! !! !! As mentioned above, the numbers in js are signed. We have forgotten the sign of the most significant bit. In order to simplify, we reduced the 32 bits to 8 bits. Note that the most significant bit is the sign bit.
0 0000101
1 1111010 // non-post result
1 0000101 // negation
1 0000110 // ask for complement
1 1111010 is obviously a negative number, and it is a complement of a negative number. We find its original code, that is, complement it again. 1 0000110 is the true value of this number, which means that the result shows -110. Oh, O (∩_∩) O haha ~
In fact, the above AND and OR also operate on the sign bit. I don't believe you try the following two, you can see that the sign bit is involved in the operation
(0b1 & -0b1)
>>> 1
(0b1-0b1)
>>> -1
Let's talk about XOR. This is more interesting. XOR, as the name implies, sees if the two bits are different-different, if the two bits are different, it is 1, and if the two bits are the same, it is 0
101
001
---
100
101 and 001 XOR result is 100, verified in js
(0b101 ^ 0b001) .toString (2)
>>> "100"
The rule of left shift is that each bit is shifted to the left by one, and the end is filled with 0. The effect is equivalent to × 2. In fact, the computer uses the shift operation to calculate the multiplication.
010
---
0100
010 left one bit will become 100, verify in js below
(0b010 << 1) .toString (2)
>>> "100"
Arithmetic right shift is also called signed right shift, that is, when shifting, the high order is complemented by its sign bit, the integer is supplemented by 0, and the negative number is supplemented by 1.
(0b111 >> 1) .toString (2)
>>> "11"
(-0b111 >> 1) .toString (2)
>>> "-100"
Negative numbers do n’t seem right, let ’s see what ’s going on
-111 // true
1 0000111 // original code
1 1111001 // complement
1 1111100 // shift right
1 0000100 // original code after shifting
-100 // shifted truth value
Logical right shift is also referred to as unsigned right shift, that is, when shifting right, the high order bits are always filled with 0. There is no difference between integer and arithmetic right shift.
(0b111 >>> 1) .toString (2)
>>> "11"
It's different for negative numbers, it will become positive after shifting right
(-0b111 >>> 1) .toString (2)
>>> "1111111111111111111111111111100"
Questions about the beginning
So much for binary numbers, let's talk about the problem at the beginning. The problem at the beginning can be broken down into the following problem. Because search will return -1 and find the index of the position, it will become the following problem.
! ~ -1
>>> ture
! ~ 0
>>> false
! ~ 1
>>> false
The result of the NOT operation on numbers is equivalent to changing the sign, and the absolute value of its value is -1
~ -1
>>> 0
~ 0
>>> -1
~ 1
>>> -2
In fact, it can be seen! The logic of x is to determine whether x is -1. My god ’s logic is really against the sky. I still advise everyone to write x === -1.
JavaScript SEO - How Google Search Indexes JavaScript Sites. Learn how Googlebot handles JavaScript when crawling, rendering and indexing. Learn how JavaScript influences SEO and how to optimize your JavaScript-powered website to be search-friendly.
Martin Splitt, Webmaster Trends Analyst, explains how JavaScript influences SEO and how to optimize your JavaScript-powered website to be search-friendly.