二分搜索算法

用于解决查找问题

1 二分查找法

只能在有序数组中查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#include <iostream>
#include <cassert>
#include <ctime>

using namespace std;

// 二分查找法,在有序数组arr中,查找target
// 如果找到target,返回相应的索引index
// 如果没有找到target,返回-1
template<typename T>
int binarySearch(T arr[], int n, T target){

// 在arr[l...r]之中查找target
int l = 0, r = n-1;
while( l <= r ){

//int mid = (l + r)/2;
// 防止极端情况下的整形溢出,使用下面的逻辑求出mid
int mid = l + (r-l)/2;

if( arr[mid] == target )
return mid;

if( arr[mid] > target )
r = mid - 1;
else
l = mid + 1;
}

return -1;
}


// 用递归的方式写二分查找法
template<typename T>
int __binarySearch2(T arr[], int l, int r, T target){

if( l > r )
return -1;

//int mid = (l+r)/2;
// 防止极端情况下的整形溢出,使用下面的逻辑求出mid
int mid = l + (r-l)/2;

if( arr[mid] == target )
return mid;
else if( arr[mid] > target )
return __binarySearch2(arr, l, mid-1, target);
else
return __binarySearch2(arr, mid+1, r, target);
}

template<typename T>
int binarySearch2(T arr[], int n, T target){

return __binarySearch2( arr , 0 , n-1, target);
}


// 比较非递归和递归写法的二分查找的效率
// 非递归算法在性能上有微弱优势
int main() {

int n = 1000000;
int* a = new int[n];
for( int i = 0 ; i < n ; i ++ )
a[i] = i;

// 测试非递归二分查找法
clock_t startTime = clock();

// 对于我们的待查找数组[0...N)
// 对[0...N)区间的数值使用二分查找,最终结果应该就是数字本身
// 对[N...2*N)区间的数值使用二分查找,因为这些数字不在arr中,结果为-1
for( int i = 0 ; i < 2*n ; i ++ ){
int v = binarySearch(a, n, i);
if( i < n )
assert( v == i );
else
assert( v == -1 );
}
clock_t endTime = clock();
cout << "Binary Search (Without Recursion): " << double(endTime - startTime) / CLOCKS_PER_SEC << " s"<<endl;


// 测试递归的二分查找法
startTime = clock();

// 对于我们的待查找数组[0...N)
// 对[0...N)区间的数值使用二分查找,最终结果应该就是数字本身
// 对[N...2*N)区间的数值使用二分查找,因为这些数字不在arr中,结果为-1
for( int i = 0 ; i < 2*n ; i ++ ){
int v = binarySearch2(a, n, i);
if( i < n )
assert( v == i );
else
assert( v == -1 );
}
endTime = clock();
cout << "Binary Search (Recursion): " << double(endTime - startTime) / CLOCKS_PER_SEC << " s"<<endl;

delete[] a;

return 0;
}

1 O(n)2

1
2
3
4
5
6
7
8
9
for(int i = 0 ; i < n ; i ++){
// 寻找[i, n)区间里的最小值
int minIndex = i;
for( int j = i + 1 ; j < n ; j ++ )
if( arr[j] < arr[minIndex] )
minIndex = j;

swap( arr[i] , arr[minIndex] );
}

2 二分搜索树

树适用于解决递归问题。
二分搜索树1

二分搜索树2

二分搜索树2

二分搜索树不一定是完全二叉树。
包括插入、查找、遍历、删除
前序遍历、中序遍历、后序遍历、层序遍历(使用队列)。

删除节点

删除节点

删除节点的两边都有节点时,使用右节点二叉树的最小值代替该节点。

二分搜索树的局限性:同样的数据可能对应不同的树,树可能层数很高,退化为链表,但不如链表效率高。可以通过平衡二叉树解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
#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;
}

// 向二分搜索树中插入一个新的(key, value)数据对
void insert(Key key, Value value){
root = insert(root, key, value);
}

// 查看二分搜索树中是否存在键key
bool contain(Key key){
return contain(root, key);
}

// 在二分搜索树中搜索键key所对应的值。如果这个值不存在, 则返回NULL
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不存在, 则返回NULL
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;
}
}

// 释放以node为根的二分搜索树的所有节点
// 采用后续遍历的递归算法
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 *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] );
}
}


// 测试 remove
int main() {

srand(time(NULL));
BST<int,int> bst = BST<int,int>();

// 取n个取值范围在[0...n)的随机整数放进二分搜索树中
int n = 10000;
for( int i = 0 ; i < n ; i ++ ){
int key = rand()%n;
// 为了后续测试方便,这里value值取和key值一样
int value = key;
bst.insert(key,value);
}
// 注意, 由于随机生成的数据有重复, 所以bst中的数据数量大概率是小于n的

// order数组中存放[0...n)的所有元素
int order[n];
for( int i = 0 ; i < n ; i ++ )
order[i] = i;
// 打乱order数组的顺序
shuffle( order , n );

// 乱序删除[0...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;
}

// 最终整个二分搜索树应该为空
cout << bst.size() << endl;

return 0;
}