1. 程式人生 > >六、二分查詢與二叉查詢樹(小象)

六、二分查詢與二叉查詢樹(小象)

二分查詢演算法(遞迴,迴圈)

具有分治思想的多用迴圈,具有回溯思想的多用遞迴。

二分或者二叉排序樹都是在 分治的解決問題

二分查詢:    

二分查詢:待查數是跟中間的數對比,只有查詢的數恰好等於中間的數返回正確;

遞迴
若比中間的數大,則去搜索右區間 [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;
    }
};

最大的體會是今天不走,明天要跑。