六、二分查找与二叉查找树(小象)

目录

二分查找算法(递归,循环)

35.搜索插入的位置

34. 在排序数组中查找元素的第一个和最后一个位置

33、搜索旋转排序数组

二叉查找树:

1、二叉查找(排序)树的定义

2、二叉查找树插入结点(递归)

3、二叉查找树搜索

449.序列化与反序列化二叉搜索树(二叉查找树的编码与解码)

整型转字符串(字符串转整型)

1、整型转字符串

 2、字符串转整型

315.计算右侧小于当前元素的个数


二分查找算法(递归,循环)

具有分治思想的多用循环,具有回溯思想的多用递归。

二分或者二叉排序树都是在 分治的解决问题

二分查找:    

二分查找:待查数是跟中间的数对比,只有查找的数恰好等于中间的数返回正确;

递归
若比中间的数大,则去搜索右区间 [mid +1 ,end ]
若比中间的数小,则去搜索左区间 [begin ,mid - 1] 

函数参数传引用的原因是:

拷贝大的类类型对象或者容器对象比较低效,所以通过引用形参访问该类型的对象。

bool binary_search(vector<int> &vec,int begin ,int end ,int search_num) {
	if (begin > end) {
		return false;
	}
	int mid = ( end + begin  ) / 2;
	if (vec[mid] == search_num) {
		return true;
	}
	else if (search_num > vec[mid]) {
		binary_search(vec, mid+1 , end, search_num);
	}
	else if (search_num < vec[mid]) {
		binary_search(vec, begin, mid-1 , search_num);
	}
}

循环:

bool binary_search(vector<int> &vec, int search_num) {
	int begin = 0;
	int end = vec.size() - 1;
/*只有在搜查范围合法的时候才有查找的必要*/
	while (begin <= end) {
		int mid = (end + begin) / 2;
		if (vec[mid] == search_num) {
			return true;
		}
		if (search_num > vec[mid]) {
			begin = mid + 1;
		}
		else if (search_num < vec[mid]) {
			end = mid - 1;
		}
	}
	return false;
}

总结:

分清楚循环和递归的两个查找条件,一个是begin<= end 的时候才继续查找,一个是begin > end 的时候才停止递归搜索。

35.搜索插入的位置

边界条件mid==0时,此时还不等于search_value就说明要查找的那个值比最前面的元素都要小,说明只能插入到第一个元素的位置(0);同理,mid== nums.size()-1,只能插入到最后。

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
      int index = -1;
        int begin = 0;
        int end = nums.size()-1;
        while(index== -1){
            int mid = (begin+end)/2;
            if(nums[mid] ==target ){
                index = mid;
            }
            else if(target > nums[mid]){
                if( mid ==  nums.size()-1 || target < nums[mid+1]){
                    index = mid+1;
                }
                begin = mid +1 ;
            }
           else if(target < nums[mid]){
                if(mid == 0 || target > nums[mid-1]){
                    index = mid;
                }
               end = mid -1 ;
            }
        }
        return index;
    }
};

34. 在排序数组中查找元素的第一个和最后一个位置

思考:还是基础的二分查找,只是在判断左右边界的时候要添加约束条件。

左边界:若查找到的mid等于target,并且数组的前一个小于target或者mid已经是数组的最前面的元素,则这个元素的位置就是左边界;否则说明此时查找到的mid并不是左边第一个,再搜索区间[begin,mid-1],然后找到第一个出现的。

右边界:若查找到的mid等于target,并且数组的后一个大于target或者mid已经是数组的最后面的元素,则这个元素的位置就是右边界;否则说明此时查找到的mid并不是右边最后一个,再搜索区间[mid+1,end],然后找到最后一个出现的。

int left_bound(std::vector<int>& nums, int target) {
	int begin = 0;
	int end = nums.size()-1;
	while (begin <= end ) {
		int mid = (begin + end) / 2;
		if (nums[mid] == target) {
			if (nums[mid - 1] < target || mid == 0) {
				return mid;
			}
			//若nums[mid - 1] == target 则说明此时查找到的mid并不是左边第一个
			//再搜索区间[begin,mid-1],然后找到第一个出现的
			end = mid - 1;
		}
		else if (target >nums[mid]) {
			begin = mid + 1;
		}
		else if (target <nums[mid]) {
			end = mid - 1;
		}
	}
		return -1;
}

int right_bound(std::vector<int>& nums, int target) {
	int begin = 0;
	int end = nums.size() - 1; 
	while (begin <= end) {
		int mid = (begin + end) / 2;
		if (nums[mid] == target) {
			if (nums[mid + 1] > target || mid == nums.size() - 1) {
				return mid;
			}
			//若nums[mid + 1] == target 则说明此时查找到的mid并不是右边最后一个
			//再搜索区间[mid+ 1 ,end],然后找到第一个出现的
			begin = mid + 1;
		}
	else if (target >nums[mid]) {
			begin = mid + 1;
		}
		else if (target <nums[mid]) {
			end = mid - 1;
		}
	}
	return -1;
}
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int> range;
       range.push_back( left_bound(  nums,   target));
         range.push_back( right_bound(  nums,   target));
        return range;
    }
};

33、搜索旋转排序数组

思考:

        旋转了之后,整个数组不再满足升序的关系,如果此时查找一个数,单纯的通过和nums[mid]进行对比,可能会判断错误查找的区间,从而得到错误的解。比如,要查找图中的“0”元素,nums[mid] = 7,则会到左边查找,可是此时数组的两部分,左边部分[4,5,6],并没有0,所以会返回查找失败。若查找图中“5”元素,则可以通过二分查找查到。就是说在二分查找的时候,我们要添加一些约束条件,才能到正确的查找区间去寻找target。

      特征:给定的升序数组,无论怎么旋转,它的nums[begin]一定满足大于nums[end]。因为它是绕着某个点旋转,前面的部分叫做part1,后面叫part2,则part2的第一个结点一定大于part1的最后一个结点,所以nums[begin]一定满足大于nums[end]。

算法思路:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int begin = 0;
        int end = nums.size()-1;
        while(begin <= end){
            int mid= (begin + end)/2;
//整体还是满足二分查找,只是在查找的时候添加了一些约束条件。
        if(nums[mid]==target){
            return mid;
        }
        //因为nums[begin] > nums[end]
        if(target > nums[mid]){//找旋转区间和递增区间
            if(nums[begin] < nums[mid]){ 
                //则说明是递增的区间
                //但是此时target > nums[mid],就肯定不会在这个区间了,所以去另外一个区间查找
                begin = mid + 1;
            }
            if(nums[begin] > nums[mid]){ 
                //则说明是旋转区间
                //但是,并不能一定判断它就在旋转区间,也有可能在递增区间
                if(target >= nums[begin]){
                    //若taget比nums[begin]还大,又因为nums[begin] > nums[end],此时肯定不会在递增区间了,所以在旋转区间搜索。
                    end = mid -1;
                }
                else{
                    //否则就只用在递增区间搜索
                       begin = mid +1; 
            }
                if(nums[mid]== nums[begin]){
                    //此时就两个数了,肯定只用查找剩下的那个了
                    begin = mid +1 ;
                }
            }
        }
           if(target < nums[mid]){
                        //递增区间
                        if(nums[begin] < nums[mid]){
                            if(target >= nums[begin]){
                                end = mid -1 ;
                            }
                          else{
                                begin = mid +1 ;
                          }
                        }
                        //旋转区间
                          else if(nums[begin] > nums[mid]){
                            end  = mid -1;
                        }
                          else if(nums[begin] == nums[mid]){
                            begin = mid +1 ;
                        }
                    }
                }
                      return -1;
              }
};

二叉查找树:

1、二叉查找(排序)树的定义

2、二叉查找树插入结点(递归)

代码:

void BST_insert(TreeNode* root, TreeNode* node) {
	if (node->value < root->value) {
		if (!root->left) {
			root->left = node;
		}
		else {
			BST_insert(root->left, node);
		}
	}
	else {
		if (!root->right) {
			root->right = node;
		}
		else {
			BST_insert(root->right, node);
		}
	}
}

3、二叉查找树搜索

bool BST_search(TreeNode* root, TreeNode* node) {
	if (node->value == root->value) {
		return true;
	}
	if (node->value < root->value) {
		if (root->left) {
			BST_search(root->left, node);
		}
		else {
			return false;
		}
	}
	else {
		if (root->right) {
			BST_search(root->right, node);
		}
		else {
			return false;
		}
	}
}

代码:

bool BST_search(TreeNode* root, TreeNode* node) {
	if (node->value == root->value) {
		return true;
	}
	if (node->value < root->value) {
		if (root->left) {
			BST_search(root->left, node);
		}
		else {
			return false;
		}
	}
	else {
		if (root->right) {
			BST_search(root->right, node);
		}
		else {
			return false;
		}
	}
}

449.序列化与反序列化二叉搜索树(二叉查找树的编码与解码)

为什么要实现序列化和反序列化?

在计算机网络传输的过程中,如果有一个数据结构要传递给另一个主机,不能直接拷贝这个数据结构的代码,所以得通过把它存储成字符串的形式,然后到另一个主机通过解码把它还原成原来的结构。

 

先序遍历:8 3 1 6 10 15   //按照这种重新插入成一个新的二叉搜索树和原先的是一样的,所以按照先序遍历得到的结果,再按照二叉查找树插入,可以得到原始的二叉查找树。

中序遍历:1 3 6 8 10 15   //首先是二叉树查找树的根节点都变了,并且,这样插入成了一个右斜的树,成了个链表了。

后序遍历:1 6 3 10 15 8   //二叉树查找树的根节点都变了

 

整型转字符串(字符串转整型)

1、整型转字符串

把某一个数转换成字符串的形式。

比如89,要从int --->> string ,首先要定义个字符串str,再利用对10取余,获得最低位的9,此时9是int型,加一个字符'0',然后就变成了字符型的'9',此时把数字89除以10,获得次低位8,迭代停止的条件就是while(num),最后再对字符串取反,这里用的是<algorithm>中的reverse;也可以利用for(size_t i = str.size();i >= 0;i++),逆序一下。最后添加的“#”,代表一个终止符,标记一下的意思,因为如果你传入一串数字,再解码的时候,不知道从哪里断开是不行的。

代码:

string IntegerTostring(int num, string &temp) {
	while (num) {
		temp += num % 10 + '0';
		num /= 10;
	}
	reverse(temp.begin(), temp.end());
	temp += "#";
	return temp;
}

 2、字符串转整型

比如字符串“89#23#”,从string--->> int,首先要定义一个整数integer,对字符串每一位检索,当遇到"#"的时候,则输出一个数字,同时把临时变量integer清零,以便接下来还要用它来输出第二个数字;如果是检索到字符的时候,integer = integer * 10 + str[i] - '0';因为是从高到低的遍历,所以一开始读的是高位,要乘以10。

代码:

int stringToInteger(string str, int &integer) {
	for (auto _str : str) {
		if (_str == '#') {
			return integer;
		}
		else {
			//integer += _str * 10 - '0';
			integer += integer * 10 + _str - '0';
		}
	}
	return integer;
}

整体的代码:


/**
* Definition for a binary tree node.
* struct TreeNode {
*     int val;
*     TreeNode *left;
*     TreeNode *right;
*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
void  integerToString(int num, string &str) {
	while (num) {
		str += num % 10 + '0';
		num /= 10;
	}

	reverse(str.begin(), str.end());
    	str += "#";

}
void stringToInteger(string &str, vector<TreeNode*>& node_num) {
	int integer = 0;
	for (auto item : str) {
		if (item == '#') {
			node_num.push_back(new TreeNode(integer));
			integer = 0;/*每一个数字存储了之后要把integer清空*/
		}
		else {
			integer = integer * 10 + item - '0';
		}
	}
}

void preOrder(TreeNode* root,vector<int>& node_num ) {
	if (!root) {
		return;
	}
	node_num.push_back(root->val);
	preOrder(root->left, node_num);
	preOrder(root->right, node_num);
}

void BST_insert(TreeNode* root, TreeNode * node) {
	if (root->val < node->val) {
		if (root->right) {
			BST_insert(root->right, node);
		}
		else {
			root->right = node;
		}
	}
	else {
		if (root->left) {
			BST_insert(root->left, node);
		}
		else {
			root->left = node;
		}
	}
}
class Codec {
public:
	
	// Encodes a tree to a single string.
	string serialize(TreeNode* root) {
		//先序遍历
        vector<int> node_val;
		string str;
        if(!root){
            return str;
        }
		preOrder(root, node_val);
		for (auto item : node_val) {
			string temp;
			integerToString(item, temp);
			str +=  temp;
		}
        return str;
	}

	// Decodes your encoded data to tree.
	TreeNode* deserialize(string data) {
        if(data.empty()){
            return NULL;
        }
		vector<TreeNode*> node_ptr;
		stringToInteger(data, node_ptr);
		for (size_t i = 1; i < node_ptr.size(); i++) {
			BST_insert(node_ptr[0], node_ptr[i]); /*直接把第一个节点当做根节点。*/
		}
		return node_ptr[0];
	}
};

// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));

315.计算右侧小于当前元素的个数

思考:

这个题上次用的归并排序和pair<>对来解决的,这次利用二叉搜索树来解决。

利用的数据结构:多出一个count;

struct BST_node {
	int val;
	int count; /*用来保存左子树数量*/
	BST_node* left;
	BST_node* right;
	BST_node(int x) :val(x), count(0), left(NULL), right(NULL) {}
};

 

!!!关键!!!

为什么count_small += node->count + 1 ?

答:以数组[1,-2,5,3,1]为例,如果这个时候要插入的是9。首先9大于root->val(1),所以直接肯定大于root左侧的2个,此时它的count_small += root->count + 1 /*加1 在于比这个root也更大了啊!*/  ,然后插入到右子树,这时遇到了root->val(5),它的左子树个数是1个,然后又插入到5的右侧,此时count_small = 3 + 2= 5;正确!如果只是单纯的count_small = root->count + 1,那么多次更新之后9 的count_small 反而只等于2,错误!

遇到的问题:这里用size_t 定义的i,可以看出来!i是无符号数!!!所以i = 0 的时候,再减1 ,反而成了个无穷大的数!哎!

代码:

struct Treenode{
    int val;
    int count;
    Treenode* left;
    Treenode* right;
    Treenode(int x):val(x),count(0),left(NULL),right(NULL){}
};

void BST_insert(Treenode* root,Treenode* node,int &smaller_count){
    if(node->val > root->val){
       smaller_count += root->count + 1 ;
        if(root->right){
            BST_insert(root->right,node,smaller_count);
        }
        else{
            root->right = node;
        }
    }
    else{
        root->count++;
         if(root->left){
            BST_insert(root->left,node,smaller_count);
        }
        else{
            root->left = node;
        }
    }
}

class Solution {
public:
    vector<int> countSmaller(vector<int>& nums) {
        vector<Treenode*> node_vec;
        vector<int> count;
     if(nums.empty()){
         return count ;
     }
        for(int i = nums.size() - 1 ;i >= 0 ;i--){
            node_vec.push_back(new Treenode(nums[i]));
        }
        count.push_back(0);
        for(size_t i = 1; i<node_vec.size();i++ ){
               int smaller_count = 0;
            BST_insert(node_vec[0],node_vec[i],smaller_count);
            count.push_back(smaller_count);
        }
        reverse(count.begin(),count.end());
        return count;
    }
};

最大的体会是今天不走,明天要跑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值