主要介紹:介紹一下二分搜索樹相關的知識,有二分查找法、二分搜索樹等。php
是一種在有序數組中查找某一特定元素的搜索算法。搜索過程從數組的中間元素開始,若是中間元素正好是要查找的元素,則搜索過程結束;若是某一特定元素大於或者小於中間元素,則在數組大於或小於中間元素的那一半中查找,並且跟開始同樣從中間元素開始比較。若是在某一步驟數組爲空,則表明找不到。這種搜索算法每一次比較都使搜索範圍縮小一半。node
注意ios
對於有序數列,才能使用二分查找法。c++
<?php
/** * 二分查找 */
// 二分查找法,在有序數組arr中,查找target
// 若是找到target,返回相應的索引index
// 若是沒有找到target,返回-1
function binarySearch($arr, $n, $target){
//在數組arr[l...r]中查找target
$l = 0;
$r = $n -1;
while ($l <= $r) {
//$mid = (int)(($l + $r) / 2);//容易越界
$mid = $l + (int)(($r - $l) / 2);
if ($arr[$mid] == $target) {
return $mid;
}elseif ($arr[$mid] < $target) {
//在arr[mid + 1, r]中尋找
$l = $mid + 1;
}else{
$r = $mid - 1;
}
}
return -1;
}
echo "程序運行\n";
//測試
$arr = array();
$n = 100000;
for ($i=0; $i < $n; $i++) {
$arr[$i] = $i;
}
$t1 = microtime(true);
for ($i=0; $i < 2 * $n; $i++) {
$v = binarySearch($arr, $n, $i);
if ($i < $n) {
assert($v == $i);
}elseif ($i >= $n) {
assert($v == -1);
}
}
$t2 = microtime(true);
echo "{$sortName}運行的時間爲:". (($t2-$t1)).'s'."\n";
複製代碼
//$mid = (int)(($l + $r) / 2);//容易越界
$mid = $l + (int)(($r - $l) / 2);
複製代碼
求mid值不採用1是由於可能形成越界算法
<?php
/** * 二分查找 */
//遞歸法,邊界函數
function _binarySearch2($arr, $l, $r, $target){
if ($l > $r) {
return -1;
}
$mid = $l + (int)(($r - $l) / 2);
if ($arr[$mid] == $target) {
return $mid;
}elseif($arr[$mid] < $target){
return _binarySearch2($arr, $mid + 1, $r, $target);
}else{
return _binarySearch2($arr, $l, $mid - 1, $target);
}
}
function binarySearch2($arr, $n, $target){
return _binarySearch2($arr, 0, $n-1, $target);
}
echo "程序運行\n";
//測試
$arr = array();
$n = 100000;
for ($i=0; $i < $n; $i++) {
$arr[$i] = $i;
}
$t1 = microtime(true);
for ($i=0; $i < 2 * $n; $i++) {
$v = binarySearch2($arr, $n, $i);
if ($i < $n) {
assert($v == $i);
}elseif ($i >= $n) {
assert($v == -1);
}
}
$t2 = microtime(true);
echo "{$sortName}運行的時間爲:". (($t2-$t1)).'s'."\n";
複製代碼
二分查找法, 在有序數組arr中, 查找target; 若是找到target, 返回第一個target相應的索引index; 若是沒有找到target, 返回比target小的最大值相應的索引, 若是這個最大值有多個, 返回最大索引; 若是這個target比整個數組的最小元素值還要小, 則不存在這個target的floor值, 返回-1;數組
int floor(T arr[], int n, T target){
assert( n >= 0 );
// 尋找比target小的最大索引
int l = -1, r = n-1;
while( l < r ){
// 使用向上取整避免死循環
int mid = l + (r-l+1)/2;
if( arr[mid] >= target )
r = mid - 1;
else
l = mid;
}
assert( l == r );
// 若是該索引+1就是target自己, 該索引+1即爲返回值
if( l + 1 < n && arr[l+1] == target )
return l + 1;
// 不然, 該索引即爲返回值
return l;
}
複製代碼
二分查找法, 在有序數組arr中, 查找target 若是找到target, 返回最後一個target相應的索引index; 若是沒有找到target, 返回比target大的最小值相應的索引, 若是這個最小值有多個, 返回最小的索引; 若是這個target比整個數組的最大元素值還要大, 則不存在這個target的ceil值, 返回整個數組元素個數n;bash
int ceil(T arr[], int n, T target){
assert( n >= 0 );
// 尋找比target大的最小索引值
int l = 0, r = n;
while( l < r ){
// 使用普通的向下取整便可避免死循環
int mid = l + (r-l)/2;
if( arr[mid] <= target )
l = mid + 1;
else // arr[mid] > target
r = mid;
}
assert( l == r );
// 若是該索引-1就是target自己, 該索引+1即爲返回值
if( r - 1 >= 0 && arr[r-1] == target )
return r-1;
// 不然, 該索引即爲返回值
return r;
}
複製代碼
也可叫作二分查找樹。它不只能夠查找數據,還能夠高效地插入、刪除數據。 特色:每一個節點的key值大於左子節點,小於右子節點。注意它不必定是徹底的二叉樹。 因此節點的key是惟一的,咱們就是經過它來索引key對應的value,注意圖中標註的都是key哦。微信
因此二叉搜索樹也不適合用數組來表示,通常都是用node節點來表示。 相比數組的數據結構的優點:數據結構
還有一個優點是,它的key能夠本身定義好比用String來做爲key來實現一個查找表,而數組只能用索引dom
先實現每個節點:節點的要素有key、value、左子節點、右子節點。
struct Node{
Key key;
Value value;
Node *left;
Node *right;
Node(Key key, Value value){
this->key = key;
this->value = value;
this->left = this->right = NULL;
}
};
複製代碼
爲了方便操做在這裏構建的一個BST的類。
#include <iostream>
using namespace std;
template <typename Key, typename Value>
class BST{
private:
struct Node{
Key key;
Value value;
Node *left;
Node *right;
Node(Key key, Value value){
this->key = key;
this->value = value;
this->left = this->right = NULL;
}
};
Node *root;
int count;
public:
BST(){
root = NULL;
count = 0;
}
~BST(){
// TODO: ~BST()
}
int size(){
return count;
}
bool isEmpty(){
return count == 0;
}
};
int main() {
return 0;
}
複製代碼
其實樹的創建就是一個插入過程,每次插入都不改變其樹的性質和結構。若是該節點存在,直接更新
思想 核心思想:從根節點開始找插入的位置,知足二叉搜索樹的特性,比左子節點大,比右子節點小.
** 注意**
此時就用到了遞歸,那麼遞歸是對某一個問題,它的子問題須要是一樣的模型。 此處的一個小的問題就是:對某個node,而後進行操做,因此參數應該有個node才能實現循環起來。 此處向以node爲根的二叉搜索樹中,插入節點(key, value).此處就都用int類型了,外部的用戶是 不須要了解node的概念.它們只須要知道傳入的的key和value就行。 暫時的設計便於理解傳入用戶本身的key和value,到時候也方便用於本身根據key進行因此.
** 實現**
public:
void insert(Key key, Value value){
root = insert(root, key, value);
}
private:
// 向以node爲根的二叉搜索樹中,插入節點(key, value)
// 返回插入新節點後的二叉搜索樹的根
Node* insert(Node *node, Key key, Value value){
if( node == NULL ){
count ++;
return new Node(key, value);
}
if( key == node->key )
node->value = value;
else if( key < node->key )
node->left = insert( node->left , key, value);
else // key > node->key
node->right = insert( node->right, key, value);
return node;
}
};
複製代碼
public:
bool contain(Key key){
return contain(root, key);
}
Value* search(Key key){
return search( root , key );
}
private:
// 查看以node爲根的二叉搜索樹中是否包含鍵值爲key的節點
bool contain(Node* node, Key key){
if( node == NULL )
return false;
if( key == node->key )
return true;
else if( key < node->key )
return contain( node->left , key );
else // key > node->key
return contain( node->right , key );
}
// 在以node爲根的二叉搜索樹中查找key所對應的value
Value* search(Node* node, Key key){
if( node == NULL )
return NULL;
if( key == node->key )
return &(node->value);
else if( key < node->key )
return search( node->left , key );
else // key > node->key
return search( node->right, key );
}
複製代碼
遍歷(Traversal)是指沿着某條搜索路線,依次對樹中每一個結點均作一次且僅作一次訪問。二叉樹的遍歷有三種:
上面第一種的遞歸訪問怎麼理解那?好比中序遍歷,任何節點的左子節點未訪問完,繼續訪問它的左子節點,直到左子節點完 全遍歷完畢,接下來纔會從最早訪問完的那個節點,進行中序遍歷,而後依次往上。
前序遍歷
中序遍歷
後續遍歷
注: 每個節點都會被訪問三次,這三種的前序中序後序,是說的當前節點的順序(也就是中間那個)
#include <iostream>
#include <queue>
using namespace std;
template <typename Key, typename Value>
class BST{
private:
struct Node{
Key key;
Value value;
Node *left;
Node *right;
Node(Key key, Value value){
this->key = key;
this->value = value;
this->left = this->right = NULL;
}
};
Node *root;
int count;
public:
BST(){
root = NULL;
count = 0;
}
~BST(){
destroy( root );
}
int size(){
return count;
}
bool isEmpty(){
return count == 0;
}
void insert(Key key, Value value){
root = insert(root, key, value);
}
bool contain(Key key){
return contain(root, key);
}
Value* search(Key key){
return search( root , key );
}
// 前序遍歷
void preOrder(){
preOrder(root);
}
// 中序遍歷
void inOrder(){
inOrder(root);
}
// 後序遍歷
void postOrder(){
postOrder(root);
}
// 層序遍歷
void levelOrder(){
queue<Node*> q;
q.push(root);
while( !q.empty() ){
Node *node = q.front();
q.pop();
cout<<node->key<<endl;
if( node->left )
q.push( node->left );
if( node->right )
q.push( node->right );
}
}
private:
// 向以node爲根的二叉搜索樹中,插入節點(key, value)
// 返回插入新節點後的二叉搜索樹的根
Node* insert(Node *node, Key key, Value value){
if( node == NULL ){
count ++;
return new Node(key, value);
}
if( key == node->key )
node->value = value;
else if( key < node->key )
node->left = insert( node->left , key, value);
else // key > node->key
node->right = insert( node->right, key, value);
return node;
}
// 查看以node爲根的二叉搜索樹中是否包含鍵值爲key的節點
bool contain(Node* node, Key key){
if( node == NULL )
return false;
if( key == node->key )
return true;
else if( key < node->key )
return contain( node->left , key );
else // key > node->key
return contain( node->right , key );
}
// 在以node爲根的二叉搜索樹中查找key所對應的value
Value* search(Node* node, Key key){
if( node == NULL )
return NULL;
if( key == node->key )
return &(node->value);
else if( key < node->key )
return search( node->left , key );
else // key > node->key
return search( node->right, key );
}
// 對以node爲根的二叉搜索樹進行前序遍歷
void preOrder(Node* node){
if( node != NULL ){
cout<<node->key<<endl;
preOrder(node->left);
preOrder(node->right);
}
}
// 對以node爲根的二叉搜索樹進行中序遍歷
void inOrder(Node* node){
if( node != NULL ){
inOrder(node->left);
cout<<node->key<<endl;
inOrder(node->right);
}
}
// 對以node爲根的二叉搜索樹進行後序遍歷
void postOrder(Node* node){
if( node != NULL ){
postOrder(node->left);
postOrder(node->right);
cout<<node->key<<endl;
}
}
void destroy(Node* node){
if( node != NULL ){
destroy( node->left );
destroy( node->right );
delete node;
count --;
}
}
};
int main() {
srand(time(NULL));
BST<int,int> bst = BST<int,int>();
int n = 10;
for( int i = 0 ; i < n ; i ++ ){
int key = rand()%n;
// 爲了後續測試方便,這裏value值取和key值同樣
int value = key;
cout<<key<<" ";
bst.insert(key,value);
}
cout<<endl;
// test size
cout<<"size: "<<bst.size()<<endl<<endl;
// test preOrder
cout<<"preOrder: "<<endl;
bst.preOrder();
cout<<endl<<endl;
// test inOrder
cout<<"inOrder: "<<endl;
bst.inOrder();
cout<<endl<<endl;
// test postOrder
cout<<"postOrder: "<<endl;
bst.postOrder();
cout<<endl<<endl;
// test levelOrder
cout<<"levelOrder: "<<endl;
bst.levelOrder();
cout<<endl<<endl;
return 0;
}
複製代碼
2 0 8 3 6 8 3 0 0 1
size: 6
preOrder:
2
0
1
8
3
6
inOrder:
0
1
2
3
6
8
postOrder:
1
0
6
3
8
2
levelOrder:
2
0
8
1
3
6
複製代碼
咱們前面提到的都是經過遞歸實現的深度優先遍歷,只要往下的節點還有符合要求的條件,那麼就會繼續西先往下執行。
而層序遍歷是一種廣度優先的遍歷方式,先遍歷根節點這一層,再遍歷第二層,依次這樣從上到下,從左到右。此處實現的思想:利用隊列的先入先出的特性.(因爲對隊列的具體實現不清楚,暫時只理解此處的思想). 在隊列不爲空的時候,開始進行操做,隊列不爲空那麼root節點是確定存在的,先把root入隊,而後開始循環判斷:判斷條件隊列爲kong,先遍歷處理當前節點,而後出隊,(此時隊列爲空了)而後看看這個節點有沒有左右子節點,若是有入隊(這樣就又不爲空了,而且往下走了一層),左右子節點處理完畢的時候.再繼續循環作一樣的操做。
public:
// 層序遍歷
void levelOrder(){
queue<Node*> q;
q.push(root);
while( !q.empty() ){
Node *node = q.front();
q.pop();
cout<<node->key<<endl;
if( node->left )
q.push( node->left );
if( node->right )
q.push( node->right );
}
}
private:
void destroy(Node* node){
if( node != NULL ){
destroy( node->left );
destroy( node->right );
delete node;
count --;
}
}
};
}
複製代碼
注意
對二叉樹的刪除就是使用後續遍歷
// 尋找最小的鍵值
Key minimum(){
assert( count != 0 );
Node* minNode = minimum( root );
return minNode->key;
}
// 尋找最大的鍵值
Key maximum(){
assert( count != 0 );
Node* maxNode = maximum(root);
return maxNode->key;
}
// 在以node爲根的二叉搜索樹中,返回最小鍵值的節點
Node* minimum(Node* node){
if( node->left == NULL )
return node;
return minimum(node->left);
}
// 在以node爲根的二叉搜索樹中,返回最大鍵值的節點
Node* maximum(Node* node){
if( node->right == NULL )
return node;
return maximum(node->right);
}
複製代碼
// 從二叉樹中刪除最小值所在節點
void removeMin(){
if( root )
root = removeMin( root );
}
// 從二叉樹中刪除最大值所在節點
void removeMax(){
if( root )
root = removeMax( root );
}
// 刪除掉以node爲根的二分搜索樹中的最小節點
// 返回刪除節點後新的二分搜索樹的根
Node* removeMin(Node* node){
if( node->left == NULL ){
Node* rightNode = node->right;
delete node;
count --;
return rightNode;
}
node->left = removeMin(node->left);
return node;
}
// 刪除掉以node爲根的二分搜索樹中的最大節點
// 返回刪除節點後新的二分搜索樹的根
Node* removeMax(Node* node){
if( node->right == NULL ){
Node* leftNode = node->left;
delete node;
count --;
return leftNode;
}
node->right = removeMax(node->right);
return node;
}
複製代碼
刪除任意節點:由Hibbard提出的一種方法,稱爲Hubbard Deletion 刪除任意節點和刪除最小、最大節點的區別就是,刪除任意節點的時候有可能左右兩個都有子節點。 首先咱們不能夠簡單的把左子節點或者右子節點,直接放到當前刪除的節點的位置,由於這樣 很容易致使不知足二叉搜索樹的特性,咱們應該找到當前述的前驅或者後繼放入當前位置,前驅:前面 一個比它小的元素;後繼:後面一個比它打的元素。好比一種方法,咱們找到它的右子節點中全部的節點 中的最小的節點,而後把這個最小的節點放入到刪除的節點中,此時仍然知足二叉搜索樹的特性,左子節點 都小於它,右子節點都大於它。還一種中相似的就是找左子樹中全部節點的最大的節點.
** 步驟**
// 從二叉樹中刪除鍵值爲key的節點
void remove(Key key){
root = remove(root, key);
}
// 刪除掉以node爲根的二分搜索樹中鍵值爲key的節點
// 返回刪除節點後新的二分搜索樹的根
Node* remove(Node* node, Key key){
if( node == NULL )
return NULL;
if( key < node->key ){
node->left = remove( node->left , key );
return node;
}
else if( key > node->key ){
node->right = remove( node->right, key );
return node;
}
else{ // key == node->key
if( node->left == NULL ){
Node *rightNode = node->right;
delete node;
count --;
return rightNode;
}
if( node->right == NULL ){
Node *leftNode = node->left;
delete node;
count--;
return leftNode;
}
// node->left != NULL && node->right != NULL
Node *successor = new Node(minimum(node->right));
count ++;
successor->right = removeMin(node->right);
successor->left = node->left;
delete node;
count --;
return successor;
}
}
複製代碼
想要知道二叉搜索樹中的某個key在書中排名第幾? 爲每一個樹的節點,添加一個域,這個域來標記當前節點有多少個子節點,而後就能夠經過簡單的邏輯和計算得 出排名第幾了。注意相似這種添加一個域記錄東西的,最難的在insert和remove的時候對相應的域進行維護,必定不要忘記了。
支持重複元素的二叉搜索樹
一樣的數據,能夠對應不一樣的二分搜索樹。 當數據的插入順序接近有序的時候,二叉搜索樹就有可能退化成鏈表此時的時間複雜度從logn又變成了n 可是咱們也不能一次性打亂元素,由於有可能數據時一點點你輸入的,你沒法拿到所有的元素。此時就改進的二叉樹了:
平衡二叉樹的實現有: 1)、紅黑樹 2)、2-3 tree 3)、AVL tree 4)、Splay tree 5)、Treap 平衡二叉樹和堆的結合. Balanced Binary Tree 具備如下性質:它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,而且左右兩個子樹都是一棵平衡二叉樹.
#include <iostream>
#include <queue>
#include <cassert>
using namespace std;
template <typename Key, typename Value>
class BST{
private:
struct Node{
Key key;
Value value;
Node *left;
Node *right;
Node(Key key, Value value){
this->key = key;
this->value = value;
this->left = this->right = NULL;
}
Node(Node *node){
this->key = node->key;
this->value = node->value;
this->left = node->left;
this->right = node->right;
}
};
Node *root;
int count;
public:
BST(){
root = NULL;
count = 0;
}
~BST(){
destroy( root );
}
int size(){
return count;
}
bool isEmpty(){
return count == 0;
}
void insert(Key key, Value value){
root = insert(root, key, value);
}
bool contain(Key key){
return contain(root, key);
}
Value* search(Key key){
return search( root , key );
}
// 前序遍歷
void preOrder(){
preOrder(root);
}
// 中序遍歷
void inOrder(){
inOrder(root);
}
// 後序遍歷
void postOrder(){
postOrder(root);
}
// 層序遍歷
void levelOrder(){
queue<Node*> q;
q.push(root);
while( !q.empty() ){
Node *node = q.front();
q.pop();
cout<<node->key<<endl;
if( node->left )
q.push( node->left );
if( node->right )
q.push( node->right );
}
}
// 尋找最小的鍵值
Key minimum(){
assert( count != 0 );
Node* minNode = minimum( root );
return minNode->key;
}
// 尋找最大的鍵值
Key maximum(){
assert( count != 0 );
Node* maxNode = maximum(root);
return maxNode->key;
}
// 從二叉樹中刪除最小值所在節點
void removeMin(){
if( root )
root = removeMin( root );
}
// 從二叉樹中刪除最大值所在節點
void removeMax(){
if( root )
root = removeMax( root );
}
// 從二叉樹中刪除鍵值爲key的節點
void remove(Key key){
root = remove(root, key);
}
private:
// 向以node爲根的二叉搜索樹中,插入節點(key, value)
// 返回插入新節點後的二叉搜索樹的根
Node* insert(Node *node, Key key, Value value){
if( node == NULL ){
count ++;
return new Node(key, value);
}
if( key == node->key )
node->value = value;
else if( key < node->key )
node->left = insert( node->left , key, value);
else // key > node->key
node->right = insert( node->right, key, value);
return node;
}
// 查看以node爲根的二叉搜索樹中是否包含鍵值爲key的節點
bool contain(Node* node, Key key){
if( node == NULL )
return false;
if( key == node->key )
return true;
else if( key < node->key )
return contain( node->left , key );
else // key > node->key
return contain( node->right , key );
}
// 在以node爲根的二叉搜索樹中查找key所對應的value
Value* search(Node* node, Key key){
if( node == NULL )
return NULL;
if( key == node->key )
return &(node->value);
else if( key < node->key )
return search( node->left , key );
else // key > node->key
return search( node->right, key );
}
// 對以node爲根的二叉搜索樹進行前序遍歷
void preOrder(Node* node){
if( node != NULL ){
cout<<node->key<<endl;
preOrder(node->left);
preOrder(node->right);
}
}
// 對以node爲根的二叉搜索樹進行中序遍歷
void inOrder(Node* node){
if( node != NULL ){
inOrder(node->left);
cout<<node->key<<endl;
inOrder(node->right);
}
}
// 對以node爲根的二叉搜索樹進行後序遍歷
void postOrder(Node* node){
if( node != NULL ){
postOrder(node->left);
postOrder(node->right);
cout<<node->key<<endl;
}
}
void destroy(Node* node){
if( node != NULL ){
destroy( node->left );
destroy( node->right );
delete node;
count --;
}
}
// 在以node爲根的二叉搜索樹中,返回最小鍵值的節點
Node* minimum(Node* node){
if( node->left == NULL )
return node;
return minimum(node->left);
}
// 在以node爲根的二叉搜索樹中,返回最大鍵值的節點
Node* maximum(Node* node){
if( node->right == NULL )
return node;
return maximum(node->right);
}
// 刪除掉以node爲根的二分搜索樹中的最小節點
// 返回刪除節點後新的二分搜索樹的根
Node* removeMin(Node* node){
if( node->left == NULL ){
Node* rightNode = node->right;
delete node;
count --;
return rightNode;
}
node->left = removeMin(node->left);
return node;
}
// 刪除掉以node爲根的二分搜索樹中的最大節點
// 返回刪除節點後新的二分搜索樹的根
Node* removeMax(Node* node){
if( node->right == NULL ){
Node* leftNode = node->left;
delete node;
count --;
return leftNode;
}
node->right = removeMax(node->right);
return node;
}
// 刪除掉以node爲根的二分搜索樹中鍵值爲key的節點
// 返回刪除節點後新的二分搜索樹的根
Node* remove(Node* node, Key key){
if( node == NULL )
return NULL;
if( key < node->key ){
node->left = remove( node->left , key );
return node;
}
else if( key > node->key ){
node->right = remove( node->right, key );
return node;
}
else{ // key == node->key
if( node->left == NULL ){
Node *rightNode = node->right;
delete node;
count --;
return rightNode;
}
if( node->right == NULL ){
Node *leftNode = node->left;
delete node;
count--;
return leftNode;
}
// node->left != NULL && node->right != NULL
Node *successor = new Node(minimum(node->right));
count ++;
successor->right = removeMin(node->right);
successor->left = node->left;
delete node;
count --;
return successor;
}
}
};
void shuffle( int arr[], int n ){
srand( time(NULL) );
for( int i = n-1 ; i >= 0 ; i -- ){
int x = rand()%(i+1);
swap( arr[i] , arr[x] );
}
}
int main() {
srand(time(NULL));
BST<int,int> bst = BST<int,int>();
int n = 10000;
for( int i = 0 ; i < n ; i ++ ){
int key = rand()%n;
// 爲了後續測試方便,這裏value值取和key值同樣
int value = key;
//cout<<key<<" ";
bst.insert(key,value);
}
// test remove
// remove elements in random order
int order[n];
for( int i = 0 ; i < n ; i ++ )
order[i] = i;
shuffle( order , n );
for( int i = 0 ; i < n ; i ++ )
if( bst.contain( order[i] )){
bst.remove( order[i] );
cout<<"After remove "<<order[i]<<" size = "<<bst.size()<<endl;
}
return 0;
}
複製代碼
-------------------------華麗的分割線--------------------
看完的朋友能夠點個喜歡/關注,您的支持是對我最大的鼓勵。
想了解更多,歡迎關注個人微信公衆號:番茄技術小棧