分治策略
思想
将一个难以直接解决的大问题,分割成一系列规模小的问题,分而治之,最后将这些小的问题合并就是原问题的解
核心:分而治之
在分治策略这个思想中我们常用到递归的方法,原因就是递归的特点:就是解决的是同一个问题,但是每次问题的规模都会发生变化,所以我们常用递归的方法。
注意
由大问题分解出的各个子规模是相互独立的。
步骤
1.分解:将问题分解成一些子问题,子问题和原问题是相同的,只是规模变小
2.解决:递归求解子问题,如果子问题的规模足够小停止递归直接求解
3.合并:将小规模的解组合成原规模问题的解
例1.阶乘
n!=1* 2* 3* 4* ......*n;
int funa(int n){
if(n==1){
return 1;
}
else{
return n*funa(n-1);
}
}
注意每次调用递归函数 对应的n会发生相应的变化
原因:
递归函数涉及到栈帧的开辟和调用完后的回归,上图黄色为栈帧的开辟,后面的蓝色是回调后与原值的乘积,因此每递归一次开辟一次栈帧,空间复杂度就是S(n),时间复杂度是O(n);
例2.斐波那契数列
1 1 2 3 5 8 13 21.。。。。
非递归实现
int funa(int n){
intc=1;
for(int i=3;i<=n;i++){
c=a+b;
a=b;
b=c;
}
return c;
}
非递归的方法中,当输入规模是1或者2都不满足for循环所以直接返回c的值,当规模大于3的时候开始进入循环,计算一次,a和b的值就要发生相应的移动。
递归实现
int funa(int n){
if(n==1||n==2){
return 1;
}else{
return funa(n-1)+funa(n-2);
}
}
我们来分析这个递归函数的时间复杂度和空间复杂度
首先先来看时间复杂度:这个的调用类似于一颗二叉树,所以是2^n的规模去扩大,所以时间复杂度是o(n)
我们再来看空间复杂度:大家可以猜猜是什么,会不会也是S(2^n)
答案是O(n),我们来分析一下,每次调用这个函数等于开辟一次栈帧,当我们的值为1或者2时递归结束,函数回退到前面的,栈帧就释放了,所以空间复杂度只与数的高度有关,即S(n)
例3:打印54321--->1 2 3 4 5
非递归的方法
递归的方法
void funa(int a){
if(a<=0){
return;
}else{
printf("%d",a%10);
funa(a/10);}
}
二分查找
思想:首先注意不管是数组还是线性表只要是能存储数据的容器都必须是升序或者降序,要满足一定的顺序,将数组中间位置的数据与查找数据比较,如果两者相等,则查找成功;否则利用中间位置记录数组分成left,right两个指针,如果中间位置数据大于查找的数据,则查找right表,left的指针指向中间位置的右边第一个位置;否则查找left表,right指针指向中间位置的左边第一个位置。重复上述过程,知道找到满足条件的数据,返回位置信息,或者字表中不存在数据,此时查找不成功,返回-1;
非递归的方式 (easy模式)
#include<stdio.h>
int funa(const int *arr, int left, int right,int num) {
while (left<=right) {
int mid= (left+right) /2;
if (arr[mid] <num) {
left=mid+1;
}
elseif (arr[mid] >num) {
right=mid-1;
}
else {
return mid;
}
}
}
int main() {
int ar[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i=funa(ar, 0, 9, 8);
printf("%d在第%d位置",i+1,i);
}
原理分析图:
第一步:所要找的数据:8比mid的值大,所以向mid右边的数表进行查询
第二步:mid的值与所查询的数据值相等返回
注意:
1.left<=right:left,right代表区域(问题规模),当left==right有一个数据没有进行查询
当left > right表示区域没有元素,就是结束的条件
2.mid=(left+right)/2:表示减小问题规模----》可以等价的写成 mid=(right-left)/2+left
right-left是相对的区域大小,定义数据时需要加上left这个偏移量
递归遍历
int funa(const int*ar, int left, int right, int num) {
int pos=-1;
if (left<=right) {
int mid= (left+right) /2;
if (ar[mid] <num) {
pos=funa(ar, mid+1, right, num);
}
elseif (ar[mid] >num) {
pos=funa(ar, left, mid-1, num);
}
else {
pos=mid;
}
}
return pos;//防止需要查询的数据不在该数组当中
}
int main() {
int ar[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i=funa(ar, 0, 9, 8);
printf("%d在第%d位置",i+1,i);
}
const int*arr:只能读取数据,不能改变数据值
变化1:找到最左边的与所查询数据一致的值
第一种思路
int funa(int*ar, int left, int right, int num) {
int pos=-1;
if (left<=right) {
int mid= (left+right) /2;
if (ar[mid] <num) {
pos=funa(ar, mid+1, right, num);
}
elseif (ar[mid] >num) {
pos=funa(ar, left, mid-1, num);
}
else {
while (mid>left&&ar[mid-1] ==num) {
--mid;
}
pos=mid;
}
}
return pos;
}
int main() {
int ar[10] = { 1,2,3,3,3,3,3,3,3,3};
int j=3;
int i=funa(ar, 0, 9, 3);
printf("%d在第%d位置",3,i+1);
}
原理:首先找到中间位置第一次出现与所找数据值相同的位置,然后向前移动并判断是否与所找数据相等
第二种方法:首先找到中间位置第一次出现与所找数据值相同的位置,然后将righe指针指向mid-1位置,然后进行二分查找
int BinaryFind(const int *nums,int n,int key){
int left=0, right=n-1, mid=0, pos=-1;
while (left<=right) {
mid= ((right-left) >>1) +left;
if (nums[mid] >key) {
right=mid-1;
}
elseif (nums[mid] <key) {
left=mid+1;
}
else {
right=mid-1;
if (nums[mid-1] !=key||mid==left) {
pos=mid;
break;
}
}
}
return pos;
}
int main() {
int arr[10] = { 2,23,23,23,23,23,23,23,23,23 };
int search=0;
scanf_s("%d", &search);
int i=BinaryFind(arr, 10, search);
printf("%d", i);
变化2:求最右边与所查找数据相同的
使用二分查找:首先找到中间位置与所查找数据相同的,其次将左指针指向mid+1位置,二分查找下去
判断条件:1.不能数组越界,即mid!=right
2.判断mid位置的下一个元素是否也与所查找元素相同,相同继续查找,不同就返回
int funa(int* ar, int left, int right, int num) {
int pos = -1;
while(left <= right) {
int mid = (left + right) / 2;
if (ar[mid] < num) {
left = mid + 1;
}
else if (ar[mid] > num) {
right = mid - 1;
}
else {
if (ar[mid + 1] != num || mid == right) {
pos= mid;
break;
}
else {
left= mid + 1;
}
}
}
return pos;
}
int main() {
int ar[10] = { 1,2,3,3,3,3,3,3,3,3};
int j = 3;
int i= funa(ar, 0, 9, 3);
printf("%d在第%d位置",3,i+1);
}
变化3:查找第一个大于等于给定值的元素
思路:首先我们先找到最中间元素的数据值,与给定值进行比较,判断mid+1位置的数值是否比给定值大,如果大则返回mid+1,否则继续循环,
int funa(int* ar, int left, int right, int num) {
int pos = -1;
while(left <= right) {
int mid = (left + right) / 2;
if (ar[mid] < num) {
left = mid + 1;
if (ar[left] > num) {
pos = left;
break;
}
}
else if (ar[mid] > num) {
right = mid - 1;
}
else {
pos = mid;
}
}
return pos;
}
int main() {
int ar[10] = { 12,23,34,45,56,67,78,89,90,100};
int j = 60;
int i= funa(ar, 0, 9, 60);
printf("第%d位置", i);
}
变化四:找到第一个小于等于给定值的元素
思路:首先我们先找到最中间元素的数据值,与给定值进行比较,判断mid-1位置的数值是否比给定值小,如果小则返回mid-1,否则继续循环,
int funa(int* ar, int left, int right, int num) {
int pos = -1;
while(left <= right) {
int mid = (left + right) / 2;
if (ar[mid] < num) {
left = mid + 1;
/*if (ar[left] > num) {
pos = left;
break;
}*/
}
else if (ar[mid] > num) {
right = mid - 1;
if (ar[right]<num) {
pos = right;
break;
}
}
else {
pos = mid;
}
}
return pos;
}
int main() {
int ar[10] = { 12,23,34,45,56,67,78,89,90,100};
int j = 60;
int i= funa(ar, 0, 9, 60);
printf("第%d位置:%d", i,ar[i]);
}
变化五:有序循环数组的二分查找
大家可以先思考思考,我们下期再见