二分查找与分治算法

二分查找

二分查找又称折半查找,假设表中元素是按升序排列,将表中间位置的关键字与查找关键字比较,
(1)如果两者相等,则查找成功;
(2)否则利用中间位置将表分成前、后两个子表;如果中间位置的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。

二分查找

已知一个排序数组A,如 A = [ -1, 2, 5, 20, 90, 100, 207, 800],另一个乱序数组B,如B = [ 50, 90, 3, -1, 207, 80],求B中任意某个元素,是否在A中出现,结果存储在数组C中,出现用1表示,未出现用0表示,如 C= [ 0, 1, 0, 1, 1, 0]。

class search_array
{
public:
	std::vector<int> search_array(std::vector<int>& sort_array, std::vector<int>& random_array)
	{
		std::vector<int> result(random_array.size(), 0);
		for(int i=0; i< random_array.size(); i++)
		{
			int res = binary_search(sort_array,random_array[i]);//循环方法
			//int res = binary_search(sort_array,0,sort_array.size()-1,random_array[i]);//递归方法
			result[i] = res;
		}
		return result;
	}
private:
	/* 二分查找 递归 */
	bool binary_search(std::vector<int>& sort_array, int begin, int end, int target)
	{
		if(begin > end) return;		// 终止条件
		int mid = (begin + end)/2;	// 取中间位置
		if( target == sort_array[mid])	// 中间位置满足,则返回成功
			return true;
		else if( target < sort_array[mid])	// 如果中间位置关键字 小于 目标 则查 前子表
			return binary_search(sort_array, begin, mid - 1, target);
		else	// 如果中间位置关键字 大于 目标,则查后子表
			return binary_search(sort_array, mid + 1, end, target);
	}

	/* 二分查找 循环 */
	bool binary_search(std::vector<int>& sort_array, int target)
	{
		int begin = 0;						// 取查找开始位置
		int end = sort_array.size() - 1;	// 取查找截止位置
		while(begin <= end)	// 满足查找条件(开始位置<=截止位置),则继续
		{
			int mid = (begin + end)/2;		// 取中间位置
			if( target == sort_array[mid])	// 中间位置满足,则返回成功
				return true;
			else if( target < sort_array[mid])// 中间位置关键字 大于 目标,则取前子表
				end = mid - 1;
			else		// 中间位置关键字 小于 目标,则取后子表
				begin = mid + 1;
		}
		return false;
	}
};

插入位置(二分查找)

给定一个排序数组nums(无重复元素)与目标target,如果target在nums里出现,则返回target所在下标,如果target在nums里未出现,则返回target应该插入位置的数组下标,使得将target插入数组nums后,数组仍有序。
如nums = [ 1, 3, 5, 6],插入元素为 0 时,返回 0;为 1 时,返回为 0;为 2时,返回为 1;为 3 时,返回为1。

int search_insert(std::vector<int>& nums, int target)
{
	if(nums.empty()) return 0;	// 异常判断返回
	int begin = 0;				// 查找开始位置
	int end = nums.size() - 1;	// 查找结束位置
	int index = -1;				// 定义查找位置
	while(index == -1)			// 退出循环条件为 找到查找位置
	{
		int mid = (begin + end)/2;	// 取 中间位置
		if(target == nums[mid])		// 中间关键字满足,则 index = mid
			index = mid;	
		else if(target < nums[mid])	// 中间关键字 大于 目标,则查找前子表
		{
			if(mid == 0 || target > nums[mid - 1])// 前子表的尾元素 小于 目标, 则 index = mid
				index = mid;
			end = mid - 1;
		}
		else	// 中间关键字 小于 目标,则 查找后子表
		{
			if(mid==nums.size()-1 || target<nums[mid+1])// 后子表的头元素 大于 目标, 则 index = mid + 1
				index = mid + 1;
			begin = mid + 1;
		}	
	}
	return index;
}

区间查找(二分查找)

给定一个排序数组nums(nums中有重复元素)与目标值target,如果target在nums里出现,则返回target所在区间的左右端点下标,[左端点,右端点],如果target在nums里未出现,则返回[-1,-1]。
如nums[]=[ 5,7,7,8,8, 8,8,10],target为8时,返回[ 3, 6];target为 6时,返回[-1, -1];target为 10时,返回[ 7,7]。

std::vector<int> search_range(std::vector<int>& nums, int target)
{
	int left_bound = search_left_bound(nums,target);
	int right_bound = search_right_bound(nums,0,nums.size()-1,target,-1);
	std::vector<int> result;
	result.push_back(left_bound);
	result.push_back(right_bound);
	return result;
}
/* 查找目标左边界 (使用循环方法)*/
int search_left_bound(std::vector<int>& nums, int target)
{
	int begin = 0;	//查找的起始位置
	int end = nums.size() - 1; //查找的终止位置
	int index = -1;  //索引的返回值
	while(begin <= end)	//终止条件
	{
		int mid = (begin + end)/2;		//二分位置
		//(循环)二分查找的经典判断(三分支)
		if( target == nums[mid])	 	//目标值等于索引值
		{
			index = mid;	//更新index
			end = mid - 1;	//(重点操作)左移终止位置,以便下个循环更新index
		}
		else if( target < nums[mid]) 	//目标值小于索引值
			end = mid - 1;	//左移终止位置
		else						 	//目标值大于索引值
			begin = mid + 1;//右移起始位置
	}
	return index;
}
/* 查找目标右边界 (使用递归方法)*/
int  search_right_bound(std::vector<int>& nums, int begin, int end, int target, int index)
{
	//终止条件
	if(begin > end) 
		return index;
	int mid = (begin + end)/2;
	//(递归)二分查找的经典判断三分支
	if( target == nums[mid])		//(重点)目标值等于索引值,则更新起始位置和index值
		return search_right_bound(nums, mid + 1, end, target, mid);
	else if( target < nums[mid])	//目标值小于索引值,则更新终止位置
		return search_right_bound(nums, begin, mid - 1, target, index);
	else							//目标值大于索引值,则更新起始位置
		return search_right_bound(nums, mid + 1, end, target, index);
}

旋转数组查找(二分查找)

给定一个排序数组nums(nums中无重复元素),且nums可能以某个未知下标旋转,给定目标值target,求target是否在nums中出现,若出现返回所在下标,未出现则返回-1。
如 nums原来为[1,3,6,7,9,12,15,20],旋转后可能为[6,7,9,12,15,20,1,3],[3,6,7,9,12,15,20,1]。

int search(std::vector<int>& nums, int target)
{
	int begin = 0;				//起始位置
	int end = nums.size() - 1;	//终止位置
	while(begin <= end)			//循环终止条件
	{
		int mid = (begin + end)/2;
		/* (循环)二分查找的 经典判断 (三分支)*/
		if(target == nums[mid])		//目标值等于中间值
			return mid;		//返回 mid 索引位置
		else 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;
			}
		}
		else if(target > nums[mid])
		{
			/* 根据起始位置值与中间位置值进行讨论 */
			if(nums[begin] < nums[mid])
				begin = mid + 1;
			else if(nums[begin] > nums[mid])
			{
				if(target >= nums[begin])
					end = mid - 1;
				else
					begin = mid + 1;
			}
			else if(nums[begin] == nums[mid])
			{
				begin = mid + 1;
			}
		}
	}
	return -1;
}

分治算法

分治算法:
将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解后进行合并,就可到原问题的解。
一般步骤:
1.分解,将要解决的问题划分成若干规模较小的同类问题;
2.求解,当子问题划分得足够小时,用较简单的方法解决;
2.合并,按原问题的要求,将子问题的解逐层合并构成原问题的解。

合并数组

void merge_two_vec(	std::vector<int>& sub_vec1,	//数组1
					std::vector<int>& sub_vec2,	//数组2
					std::vector<int>& vec)		//合并后的数组
{
	int i = 0;
	int j = 0;
	while(i < sub_vec1.size() && j < sub_vec2.size())
	{
		if( sub_vec1[i] < sub_vec2[j])
			vec.push_back(sub_vec1[i++]);
		else
			vec.push_back(sub_vec2[j++]);
	}
	for(;i < sub_vec1.size();i++)
		vec.push_back(sub_vec1[i]);
	for(;j < sub_vec2.size();j++)
		vec.push_back(sub_vec2[j]);
}

void merge_sort(std::vector<int>& vec)
{
	if(vec.size() < 2) return;	// 求解:子问题足够小时,直接求解

	//对原问题进行拆解,即对原数组拆分为两个规模相同的数组,再对它们分别求解(排序)
	int mid = vec.size() / 2;
	std::vector<int> sub_vec1,sub_vec2;
	for(int i=0; i<mid;i++)
		sub_vec1.push_back(vec[i]);
	for(int i= mid; i<size();i++)
		sub_vec2.push_back(vec[i]);

	//对拆解后的两个子问题进行求解
	merge_sort(sub_vec1);
	merge_sort(sub_vec2);

	//合并,将子问题的解进行合并
	merge_two_vec(sub_vec1,sub_vec2,vecll);
}

归并排序与分治算法

逆序数(归并排序)

已知数组nums,求新数组count,count[i]代表了在nums[i]右侧且比nums[i]小的元素个数。
例如:
nums=[5,2,6,1],count=[2,1,1,0];
nums=[6,6,6,1,1,1],count=[3,3,3,0,0,0];
nums=[5,-7,9,1,3,5,-2,1],count=[5,0,5,1,2,2,0,0];

在这里插入代码片
std::vector<int> count_smaller(std::vector<int>& nums)
{
	//将元素nums[i]与元素位置i绑定为Pair,如<nums[i],i>,排序时,按照nums[i]的大小对pair进行排序,这样无论nums[i]如何排序,都知道nums[i]在原数组中的哪个位置。
	//利用pair对<nums[i],i>中的i对count[i]进行n更新,任何一次子数组的归并,都可以认为是前半段与后半段有序数组逆序数的计算,只需根据绑定的位置i将逆序数累加至count数组中。
	std::vector<std::pair<int,int>> vec;
	std::vector<int> count;
	for(int i=0; i<nums.size(); i++)
	{
		vec.push_back(std::make_pair(nums[i],i));
		count.push_back(0);
	}
	merge_sort(vec, count);
	return count;
}

void merge_sort_two_vec(std::vector<std::pair<int,int>>& sub_vec1,
						std::vector<std::pair<int,int>>& sub_vec2,
						std::vector<std::pair<int,int>>& vec,
						std::vector<int>& count)
{
	int i=0;
	int j=0;
	while(i<sub_vec1.size() && j<sub_vec2.size())
	{
		if(	sub_vec1[i].first <= sub_vec2[j].first)
		{
			count[sub_vec1[i].second] += j;
			vec.push_back(sub_vec1[i]);
			i++;
		}
		else
		{
			vec.push_back(sub_vec2[j]);
			j++;
		}
	}
	for(;i<sub_vec1.size();i++)
	{
		count[sub_vec1[i].second] += j;
		vec.push_back(sub_vec1[i]);
	}
	for(;j<sub_vec2.size();j++)
		vec.push_back(sub_vec2[j]);
}

void merge_sort(std::vector<std::pair<int,int>>& vec,
				std::vector<int>& count)
{
	if( vec.size() < 2) return;
	
	int mid = vec.size() / 2;
	std::vector<std::pair<int,int>> sub_vec1;
	std::vector<std::pair<int,int>> sub_vec2;
	for(int i=0; i< mid; i++)
		sub_vec1.push_back(nums[i]);
	for(int i=mid; i< nums.size(); i++)
		sub_vec2.push_back(nums[i]);
	
	merge_sort(sub_vec1, count);
	merge_sort(sub_vec2, count);
	vec.clear();
	merge_sort_two_vec(sub_vec1,sub_vec2,vec,count);
}

K个排序链表的合并(分割算法)

已知k个已排序链表头节点指针,将这k个链表合并,合并后仍为有序的,返回合并后的头节点。

 struct ListNode {
     int val;
     ListNode *next;
     ListNode() : val(0), next(nullptr) {}
     ListNode(int x) : val(x), next(nullptr) {}
     ListNode(int x, ListNode *_next) : val(x), next(_next) {}
 };

ListNode* mergeLists(ListNode* l1, ListNode* l2)
{
	// 定义哑结点
	ListNode dummy;
	ListNode* tail = &dummy;	// 定义 尾指针
	while(l1 && l2)				// 结点指针都不为空时
	{
		if(l1->val < l2->val)
		{
			tail->next = l1;
			l1 = l1->next;	
		}
		else
		{
			tail->next = l2;
			l2 = l2->next;
		}
		tail = tail->next;
		tail->next = nullptr;
	}
	if(l1) tail->next = l1;
	if(l2) tail->next = l2;
	return dummy.next;
}

// 递归方式 合并K个列表
ListNode* mergeKLists(std::vector<ListNode*>& lists)
{
	if(lists.size() == 0) return nullptr;
	if(lists.size() == 1) return lists[0];
	if(lists.size() == 2) return mergeTwoLists(lists[0],lists[1]);
	
	int mid = lists.size()/2;
	std::vector<ListNode*> sub_lists1, sub_lists2;
	for(int i = 0; i < mid; i++)
		sub_lists1.push_back(lists[i]);
	for(int i = mid; i < lists.size();i++)
		sub_lists2.push_back(lists[i]);
		
	ListNode* l1 = mergeKLists(sub_lists1);
	ListNode* l2 = mergeKLists(sub_lists2);
	return mergeLists(l1,l2);
}

// 循环方式 合并K个列表
ListNode* mergeKLists(std::vector<ListNode*> lists)
{
	if(lists.empty()) return nullptr;
	while(lists.size() > 1)
	{
		std::vector<ListNode*> temp;
		for(int i = 0; i < lists.size()-1; i = i + 2)
			temp.push_back(mergeLists(lists[i],lists[i+1]));
		if(lists.size() % 2)
			temp.push_back(lists[lists.size()-1]));
		lists = temp;
	}
	return lists[0];
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值