【算法设计zxd】第5章分治法

news2025/3/1 14:18:31

目录

分治算法策略的设计模式

分治思想:

分治算法求解问题的步骤:

设计模式

算法分析

二分查找算法

思考题

计算模型:

时间复杂度分析:

代码:

分治*大数乘法:

【例5-2】设X, Y是两个n位的十进制数,求X*Y

 问题分析:

1.1 计算方法:

 2.1 计算方法:

思考题:

 算法分析:

代码:

思考题:二分治法 和 VS算法 矩阵相乘

算法效率:

代码:

 棋盘覆盖问题:

【例5-4】残缺棋盘

问题分析:s=size/2 分治

计算模型​

算法分析

算法设计与描述:​

代码: 

 选择性问题:——非等分

一般性描述:

问题分析:

选择性问题:选第k小值

计算模型

算法分析

代码:

思考题:正元素 负元素排序

计算模型:

思考题:用分治法 求 数列的最大子段和


分治算法策略的设计模式

分治思想:

把一个较大的问题分解成几个与原问题相似的 子问题,
找到求出这几个子问题的解法后,
再以适当的方法组织,把它们合成求整个问题的解。

分治算法求解问题的步骤:

(1) 分解:将要解决的问题划分成若干规模较小的同类问题;
(2) 求解:当子问题划分得足够小时,用较简单的方法解决;
(3) 合并:按原问题的要求,将子问题的解逐层合并构成原问题的解。

设计模式

算法分析

用DC求解规模为n0 的问题耗费1个单位时间,用Merge合并 k个子问题的所需的时间为f(n)
,则分治算法的时间复杂度 为:

 根据主定理有

 

原问题分为多少个子问题比较合适?每个子问题是否规模要多大?

【子问题平衡原理】划分规模:等长>不等长规模 

二分查找算法

【例5-1】 设有n个元素的集合a[0]~a[n-1]是有序的,设计算
法从这n个元素中找出值为x的特定元素。
问题分析
思想:将n个元素分成个数大致相同的两半,取a[n/2]与x作
比较。如果x=a[n/2],则找到x,算法终止。如果x<a[n/2],则
在集合的前半部分继续查找,否则在后半部分查找。
计算模型
(1)中间下标为mid =(left+right)/2。
(2)找到待查数据,即有x==a[mid];
(3)暂未查到数,若x<a[mid],right=mid-1;若x>a[mid],则
left=mid+1。
算法描述
输入:x和n个元素的数组a[n]
输出:与x匹配的数组元素下标或未找到(下标为-1)

思考题

 

计算模型:

只剩一个数据时 max=x[low]

对元素进行分组,中间下标mid=(low+high)/2,递归使用findmax(x,low,mid,tempmax1) ,findmax(x,mid+1,high,tempmax2) 返回两区间最大值tempmax1 和tempmax2

最大值为tempmax1 和tempmax2 的较大值,

时间复杂度分析:

T(n) = 2*T(n/2) + 5; n>1

T(n)=O(1) ;n=1

设a=2 b=2 f(n)=5=n^(log_2 2-1)=O(1)   

根据主定理 T(n)=O(n)

代码:
 

#include<iostream>
#include<bits/stdc++.h>
using namespace std;

void findmax(int x[],int low,int high,int &max)
{
	int tempmax1=0,tempmax2=0;
	if(low==high)
	{
		max = x[low] ;
		return ;
	}
	int mid=(low+high)/2;
	findmax(x,low,mid,tempmax1);
	findmax(x,mid+1,high,tempmax2);
	if(tempmax2< tempmax1)max=tempmax1;
	else max=tempmax2;
}

int main()
{
	int n=10;
	int x[n];
	for(int i=0;i<n;i++)
	{
		x[i]=30+rand()%10;
		cout<<x[i]<<" ";
	}
	int max=0;
	findmax(x,0,n-1,max);
	cout<<endl<<max<<endl;
	
	return 0;
}

分治*大数乘法:

【例5-2】设X, Y是两个n位的十进制数,求X*Y

 

 问题分析:

1.1 计算方法

 1.2 时间复杂度:

 

 2.1 计算方法:

 

 2.2 时间复杂度:

 

 计算模型

(1) 计算公式

 (2)递归结束条件

当x=0或y=0时,结果为0。
当n=1时,返回x*y 。

思考题:

算法描述

输入:两个n位十进制数x,y 

核心操作:三个乘法

时间复杂度:T(n)=3*T(n/2)+c*n 

设a=3,b=2 ,f(n)=cn, n^{log_2 3-log_2 3/2} = n,

有f(n)=\Theta ( n^{log_2 3-log_2 3/2} )= \Theta (n),\varepsilon=log_2 3/2 >0 符合主定理(1),因此

 

 算法分析:

算法设计与描述算法分析
输入:两个n位十进制数
输出:乘积
Karatsuba(x,y)
{
    if(x==0||y==0)return 0;
    if(n==1)return x*y;
    n<- n/2;
    x1<- x / (int)pow(10,n) ,x0<- x % (int)pow(10,n);
    y1<- y/ (int)pow(10,n),y0<- y % (int)pow(10,n);
    //计算
    xy1<- Karatsuba(x1,y1);
    xy0<- Karatsuba(x0,y0);
    sum<- (x1-x0)*(y0-y1);
    return xy1*pow(10,(2*half) ) + (sum+xy1+xy0)*pow(10,half) +xy0;
}

(1)输入两个n位十进制数,

(2)核心语句:将两数分为前后两部分x1 x0 y1 y0,递归计算

(3)

 时间复杂度:T(n)=3*T(n/2)+c*n 

设a=3,b=2 ,f(n)=cn, n^{log_2 3-log_2 3/2} = n,

有f(n)=\Theta ( n^{log_2 3-log_2 3/2} )= \Theta (n),\varepsilon=log_2 3/2 >0 符合主定理(1),因此

 

代码:

#include<bits/stdc++.h>
using namespace std; 
/*
unsigned int 0~4294967295
int -2147483648~2147483647
unsigned long 0~4294967295
long -2147483648~2147483647
long long的最大值:9223372036854775807
long long的最小值:-9223372036854775808
unsigned long long的最大值:18446744073709551615

__int64的最大值:9223372036854775807
__int64的最小值:-9223372036854775808
unsigned __int64的最大值:18446744073709551615
*/


//Karatsuba方法 将大数拆分  两数位数相同 
//分治 
long long Karatsuba(long long num1,long long num2)
{
//	cout<<num1<<" "<<num2<<endl;
	if(num1<10 ||num2<10)return num1*num2;
	int size1=ceil(log(num1)/log(10));//位数 
	int size2=ceil(log(num2)/log(10));
	int half=max(size1,size2)/2;
//	cout<<size1<<" h "<<half<<endl;
	//拆分为 abcd
	long long x1=num1 / (int)pow(10,half) ,x0= num1 % (int)pow(10,half);
	long long y1=num2/ (int)pow(10,half),y0=num2 % (int)pow(10,half);
//	cout<<x1<<"  "<<x0<<endl;
	//计算
	long long xy1=Karatsuba(x1,y1);
	long long xy0=Karatsuba(x0,y0);
	long long sum= (x1-x0)*(y0-y1);
//	cout<<pow(10,5)<<endl;
	return xy1*pow(10,(2*half) ) + (sum+xy1+xy0)*pow(10,half) +xy0;
}

int main()
{
	cout<<endl<<"Karatsuba:"<<Karatsuba(155,999);//但是要注意 long long的数字限制 
	
	return 0;
}


思考题:二分治法 和 VS算法 矩阵相乘

算法效率:

普通的二分治算法:

T(n)=O(1) n=2

T(n)=8*T(n/2)+\Theta(n^2) n>2

设a=8,b=2,f(n)=\Theta(n^2),n^(log_2 8 -1)=n^2.  

有f(n)=\Theta(log_2 8 -1)=\Theta(n^2) 取 \varepsilon=1>0 

根据主定理 T(n)=\Theta(n^3) 

VS算法:【8次->7次乘法 m不唯一,但也增加了加法运算量】

T(n)=O(1) n=2

T(n)=7*T(n/2)+\Theta(n^2) 

设a=7,b=2,f(n)=\Theta(n^2),n^(log_2 7 -1)=n^2.  

有f(n)=\Theta(log_2 7 -0.8)=\Theta(n^2) 取\varepsilon =0.8>0  

根据主定理 T(n)=\Theta(n^2.8)

7个乘法,18个加法 

因此VS算法比普通二分治算法效率高

代码:

#include<iostream>
using namespace std;

class Mat{
	
public:
	int **m;
	int n;
	
	Mat(int nn){
//		cout<<"Mat:"<<nn<<endl;
		n=nn;
		m=new int*[n];
		for(int i=0;i<n;i++)
		{
			m[i]=new int[n];
		}
		for(int i=0;i<n;i++)
		{
			for(int j=0;j<n;j++)
			{
				m[i][j]=0;
			}
		}
//		cout<<"over"<<endl;
	}
	void input01(int** x)
	{
		int n=this->n;
		for(int i=0;i<n;i++)
		{
			for(int j=0;j<n;j++)
			{
				this->m[i][j]=x[i][j];
			}
		}
	}
	void show()
	{
		for(int i=0;i<n;i++)
		{
			for(int j=0;j<n;j++)
			{
				cout<<m[i][j]<<"\t";
			}
			cout<<endl;
		}cout<<endl<<endl;
	}
	Mat operator + (Mat &a)// 定义重载运算符"+"的友元函数
	{
		Mat c(a.n);
		for(int i=0;i<a.n;i++)
		{
			for(int j=0;j<n;j++)
			{
				c.m[i][j]=a.m[i][j]+this->m[i][j];
			}
		}
		return c;
	}
	Mat operator - (Mat &a)// 定义重载运算符"-"的友元函数
	{
		Mat c(a.n);
		for(int i=0;i<a.n;i++)
		{
			for(int j=0;j<n;j++)
			{
				c.m[i][j]=this->m[i][j] -a.m[i][j];
			}
		}
		return c;
	}
};
int ** change(int x[][4])
{
	int n=4;
//	int **xx=malloc(4*sizeof(int*));
	int **xx=new int*[4];
	for(int i=0;i<n;i++)
	{
		xx[i]=new int[4];
		for(int j=0;j<n;j++)
		{
			xx[i][j]=x[i][j];
			cout<<xx[i][j]<<"\t";
		}
		cout<<endl;
	}
		cout<<endl<<endl;
	return xx;
}

int a[4][4]={
1,0,2,1,
4,1,1,0,
0,1,3,0,
5,0,2,1
};
int **aa=change(a);

int b[4][4]={
0,1,0,1,
2,1,0,4,
2,0,1,1,
1,3,5,0
};
int **bb=change(b);

int n=4;
Mat ma(n),mb(n),mc01(n),mc02(n);

//二分治法

void bs(int n,Mat a,Mat b,Mat c)
{
//	cout<<endl<<endl<<"in vs:"<<n<<endl;
	if(n==1)
	{
		c.m[0][0]=ma.m[0][0] * mb.m[0][0];
//		cout<<"n==1"<<c.m[0][0];
		return ;
	}
	else if(n==2)
	{
//		cout<<"n==2"<<endl;
		c.m[0][0]=a.m[0][0]*b.m[0][0]+a.m[0][1]*b.m[1][0];
		c.m[0][1]=a.m[0][0]*b.m[0][1]+a.m[0][1]*b.m[1][1];
		c.m[1][0]=a.m[1][0]*b.m[0][0]+a.m[1][1]*b.m[1][0];
		c.m[1][1]=a.m[1][0]*b.m[0][1]+a.m[1][1]*b.m[1][1];
		
//		cout<<"n==2"<<c.m[0][0];
		return ;
	}
	//划分为4个矩阵
	
	Mat a00(n/2),a01(n/2),a10(n/2),a11(n/2);
	Mat b00(n/2),b01(n/2),b10(n/2),b11(n/2);
//	cout<<"划分"<<endl; 
	for(int i=0;i<n/2;++i)
	{
		for(int j=0;j<n/2;++j)
		{
			//左上 
//			cout<<"左上"<<i<<" "<<j<<endl; 
			a00.m[i][j]=a.m[i][j];
			b00.m[i][j]=b.m[i][j];
//			cout<<"右上"<<endl;
			//右上 
			a01.m[i][j]=a.m[i][j+n/2];
			b01.m[i][j]=b.m[i][j+n/2];
			//左下 
			a10.m[i][j]=a.m[i+n/2][j];
			b10.m[i][j]=b.m[i+n/2][j];
			//右下 
			a11.m[i][j]=a.m[i+n/2][j+n/2];
			b11.m[i][j]=b.m[i+n/2][j+n/2];
			
		}
	}
//	cout<<"递归"<<endl;
	//递归求四个方阵
	Mat ab0000(n/2);
	bs(n/2,a00,b00,ab0000);
	Mat ab0110(n/2);
	bs(n/2,a01,b10,ab0110);
	Mat ab0001(n/2);
	bs(n/2,a00,b01,ab0001); 
	Mat ab0111(n/2);
	bs(n/2,a01,b11,ab0111);
	Mat ab1000(n/2);
	bs(n/2,a10,b00,ab1000);
	Mat ab1110(n/2);
	bs(n/2,a11,b10,ab1110);
	Mat ab1001(n/2);
	bs(n/2,a10,b01,ab1001);
	Mat ab1111(n/2);
	bs(n/2,a11,b11,ab1111);
	
	Mat c00(n/2),c01(n/2),c10(n/2),c11(n/2);
	c00=ab0000+ab0110;
	c01=ab0001+ab0111;
	c10=ab1000+ab1110;
	c11=ab1001+ab1111;
//	c00 =m1+m4-m5+m7;
//	c01 =m3+m5;
//	c10=m2+m4;
//	c11=m1+m3-m2+m6;

	//合并
	for(int i=0;i<n/2;++i)
	{
		for(int j=0;j<n/2;++j)
		{
			c.m[i][j]=c00.m[i][j];
			c.m[i][j+n/2]=c01.m[i][j];
			c.m[i+n/2][j]=c10.m[i][j];
			c.m[i+n/2][j+n/2]=c11.m[i][j];
		}
	 } 
}

//VS算法


void vs(int n,Mat a,Mat b,Mat c)
{
//	cout<<endl<<endl<<"in vs:"<<n<<endl;
	if(n==1)
	{
		c.m[0][0]=ma.m[0][0] * mb.m[0][0];
//		cout<<"n==1"<<c.m[0][0];
		return ;
	}
	else if(n==2)
	{
//		cout<<"n==2"<<endl;
		c.m[0][0]=a.m[0][0]*b.m[0][0]+a.m[0][1]*b.m[1][0];
		c.m[0][1]=a.m[0][0]*b.m[0][1]+a.m[0][1]*b.m[1][1];
		c.m[1][0]=a.m[1][0]*b.m[0][0]+a.m[1][1]*b.m[1][0];
		c.m[1][1]=a.m[1][0]*b.m[0][1]+a.m[1][1]*b.m[1][1];
		
//		cout<<"n==2"<<c.m[0][0];
		return ;
	}
	//划分为4个矩阵
	
	Mat a00(n/2),a01(n/2),a10(n/2),a11(n/2);
	Mat b00(n/2),b01(n/2),b10(n/2),b11(n/2);
//	cout<<"划分"<<endl; 
	for(int i=0;i<n/2;++i)
	{
		for(int j=0;j<n/2;++j)
		{
			//左上 
//			cout<<"左上"<<i<<" "<<j<<endl; 
			a00.m[i][j]=a.m[i][j];
			b00.m[i][j]=b.m[i][j];
//			cout<<"右上"<<endl;
			//右上 
			a01.m[i][j]=a.m[i][j+n/2];
			b01.m[i][j]=b.m[i][j+n/2];
			//左下 
			a10.m[i][j]=a.m[i+n/2][j];
			b10.m[i][j]=b.m[i+n/2][j];
			//右下 
			a11.m[i][j]=a.m[i+n/2][j+n/2];
			b11.m[i][j]=b.m[i+n/2][j+n/2];
			
		}
	}
//	cout<<"递归"<<endl;
	//递归求四个方阵
	int half=n/2;
	//m1=(a00+a11)*(b00+b11)
	Mat m1(n/2);
	vs(n/2,a00+a11,b00+b11,m1);
//	cout<<"m1"<<endl;
	//m2=(a10+a11)*(b00)
	Mat m2(n/2);
	vs(n/2,a10+a11,b00,m2);
	//m3=(a00)*(b01-b11)
	Mat m3(n/2);
	vs(n/2,a00,b01-b11,m3);
	//m4=(a11)*(b10-b00)
	Mat m4(n/2);
	vs(n/2,a11,b10-b00,m4);
	//m5=(a00+a01)*(b11)
	Mat m5(n/2);
	vs(n/2,a00+a01,b11,m5);
	//m6=(a10-a00)*(b00+b01)
	Mat m6(n/2);
	vs(n/2,a10-a00,b00+b01,m6);
	//m7=(a01-a11)*(b10+b11)
	Mat m7(n/2);
	vs(n/2,a01-a11,b10+b11,m7);
//	cout<<"finish m1-7"<<endl;
	Mat c00(n/2),c01(n/2),c10(n/2),c11(n/2);
	c00 =m1+m4-m5+m7;
	c01 =m3+m5;
	c10=m2+m4;
	c11=m1+m3-m2+m6;
	//合并
	for(int i=0;i<n/2;++i)
	{
		for(int j=0;j<n/2;++j)
		{
			c.m[i][j]=c00.m[i][j];
			c.m[i][j+n/2]=c01.m[i][j];
			c.m[i+n/2][j]=c10.m[i][j];
			c.m[i+n/2][j+n/2]=c11.m[i][j];
		}
	 } 
}

int main()
{
	ma.input01(aa);
	mb.input01(bb);
	
	vs(n,ma,mb,mc01);
	mc01.show();
	
	bs(n,ma,mb,mc02);
	mc02.show();
	
	
	return 0;
 } 
 /*
 
 1       0       2       1
4       1       1       0
0       1       3       0
5       0       2       1


0       1       0       1
2       1       0       4
2       0       1       1
1       3       5       0


5       4       7       3
4       5       1       9
8       1       3       7
5       8       7       7


5       4       7       3
4       5       1       9
8       1       3       7
5       8       7       7
 */

 棋盘覆盖问题

【例5-4】残缺棋盘

残缺棋盘是一个有2^ k ×2^ k (k≥1)个方格的棋盘,其中恰
有一个方格残缺。如图给出k=1时各种可能的残缺棋盘,其
中残缺的方格用阴影表示。

这样的棋盘称作“三格板”,残缺棋盘问题就是要用这四
种三格板覆盖更大的残缺棋盘。在此覆盖中要求:
1)两个三格板不能重叠
2)三格板不能覆盖残缺方格,但必须覆盖其他所有的方格
在这种限制条件下,所需要的三格板总数为(2^ k ×2^ k -1 )/3。【(方格数-1)/3】

问题分析:s=size/2 分治

分4种情况:
1. 若坏格子在左上角,则把图5-2中①号放置在二分后的中间
位置,如图(2)
2. 若坏格子在右上角,则把图5-2中②号放置在二分后的中间
位置,如图(3)
3. 若坏格子在左下角,则把图5-2中③号放置在二分后的中间
位置,如图 (4)
4. 若坏格子在右下角,则把图5-2中④号置在二分后的中间位
置,如图(5)

如何确定棋盘,如何区分棋盘?

——用棋盘左上角进行定位,用棋盘边长划定范围。

计算模型

(1)棋盘:用二维数组CBoard[n][n]存储
(2)棋盘的识别
• tr 棋盘中左上角方格所在行。
• tc 棋盘中左上角方格所在列。
• dr 残缺方块所在行。
• dc 残缺方块所在列。
• size 棋盘的行数或列数。
覆盖残缺棋盘所需要的三格板数目为:( size^ 2 -1 ) / 3
将这些三格板编号为1到( size^ 2 -1 ) / 3。则将残缺棋
盘三格板编号的存储在数组 CBoard[n][n] 的对应位置中

(3)计算过程
设s=size/2,当s或size<2时,覆盖结束。
• 当 行坐标dr<tr+s and 列坐标 dc<tc+s时,坏格在左上子棋盘
中,用①(tr+s-1, tc+s);(tr+s, tc+s-1) ; (tr+s, tc+s)。//红色部分

• 当dr<tr+s and dc≥tc+s时,坏格在右上子棋盘

中,用② (tr+s-1, tc+s-1); (tr+s, tc+s-1) ; (tr+s, tc+s)。
• 当dr≥tr+s and dc<tc+s时,坏格在左下子棋盘
中,用③(tr+s-1, tc+s-1); (tr+s-1, tc+s) ; (tr+s, tc+s)。

• 当dr≥tr+s and dc≥tc+s时,坏格在右下子棋盘 中,

用④ (tr+s-1, tc+s-1); (tr+s-1, tc+s) ; (tr+s, tc+s-1)。

算法分析

 

 

算法设计与描述:

代码: 

#include<iostream>
using namespace std;

int n=8;//需要是2^x 
int amount=1;
//棋盘
int CBoard[100][100];
//坏格子坐标

//覆盖之后棋盘
int chessboard[100][100]; 

//左上角方格所在行int tr,左上角 列int tc, (tr.tc)
//残缺 行int dr,残缺 列int dc   (dr,dc )
//棋盘的行数or列数int size)
void CBCover(int CBoard[][100],int tr,int tc,int dr,int dc,int size)
{
	if(size<2)return;
	int t=amount;
	amount++;//所使用的三格板的数目
	//二分 
	int s=size/2;//子问题的棋盘
	//残缺方格位于左上棋盘  1号隔板 
	if(dr<tr+s && dc<tc+s )
	{
		//递归 
		CBCover(CBoard,tr,tc,dr,dc,s);
		//到最接近的 结束 
		 
		//覆盖这三块方格 1号隔板 
		CBoard[tr+s-1][tc+s]=t;//注意是相对于隔板的标记坐标 
		CBoard[tr+s][tc+s-1]=t;//不是相对于残格的坐标 
		CBoard[tr+s][tc+s]=t; //
		
		//覆盖其余部分
		//下侧棋盘 覆盖的那块隔板 会在新的棋盘上占用一个方格 也就是一个坏块 
		CBCover(CBoard,tr,tc+s,tr+s-1,tc+s,s);
		//右侧 
		CBCover(CBoard,tr+s,tc,tr+s,tc+s-1,s);
		//右下侧 
		CBCover(CBoard,tr+s,tc+s,tr+s,tc+s,s);
		
	}
	//残缺方格位于右上棋盘 2号隔板 
	else if(dr<tr+s && dc>=tc+s )
	{
		//递归 
		CBCover(CBoard,tr,tc+s,dr,dc,s);
		
		CBoard[tr+s-1][tc+s-1]=t;//覆盖2号三格板 
		CBoard[tr+s][tc+s-1]=t;
		CBoard[tr+s][tc+s]=t;
		//覆盖其余部分
		CBCover(CBoard,tr,tc,tr+s-1,tc+s-1,s);
		CBCover(CBoard,tr+s,tc,tr+s,tc+s-1,s);
		CBCover(CBoard,tr+s,tc+s,tr+s,tc+s,s);
		
	}
	//残缺方格位于左下棋盘 3号隔板 
	else if(dr>= tr+s && dc < tc+s )
	{
		//递归 
		CBCover(CBoard,tr+s,tc,dr,dc,s);
		CBoard[tr+s-1][tc+s-1]=t;//上
		CBoard[tr+s-1][tc+s]=t;//右上
		CBoard[tr+s][tc+s]=t;//右 
		//覆盖其余部分
		CBCover(CBoard,tr,tc,tr+s-1,tc+s-1,s);
		CBCover(CBoard,tr,tc+s,tr+s-1,tc+s,s);
		CBCover(CBoard,tr+s,tc+s,tr+s,tc+s,s);
		
	}
	//残缺方格位于右下棋盘 4号隔板 
	else if(dr>=tr+s && dc>=tc+s )
	{
		//递归 
		CBCover(CBoard,tr+s,tc+s,dr,dc,s);
		
		CBoard[tr+s-1][tc+s-1]=t;//覆盖2号三格板 
		CBoard[tr+s-1][tc+s]=t;
		CBoard[tr+s][tc+s-1]=t;
		//覆盖其余部分
		CBCover(CBoard,tr,tc,tr+s-1,tc+s-1,s);
		CBCover(CBoard,tr,tc+s,tr+s-1,tc+s,s);
		CBCover(CBoard,tr+s,tc,tr+s,tc+s-1,s);
		
	}
	 
}
void output(int a[][100])
{
	cout<<"a:"<<endl;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			cout<<a[i][j]<<"\t";
		}
		cout<<endl;
	}
	
}
int main()
{
	output(CBoard);
	CBCover(CBoard,0,0,0,0,n);
	output(CBoard);
	return 0;
} 

 选择性问题:——非等分

选最大值、最小值、选中位数、选第二大值

一般性描述:

设A是含有n个元素的集合,从A中选出第k小的元素,其中
1≤k≤n。这里的第k小是指当A中元素从小到大排序之后,
第k个位置的元素,当k=1时,选出的是A中的最小值,当
k=n时,选出的就是最大值。

问题分析:

(1)排序--找第k小的元素,时间渐近复杂度为O(nlogn)——高级排序算法 极限
(2)蛮力算法--当k=1或k=n时【最大或最小】,一趟即可找到解,时间渐近复杂度只有O(n)。
(3)二分治策略--当k=2时,将原数列分为两个子集,每个子 集各选出一个最小值和第二小值,从这个4个数字里可找到当前的最小值

选择性问题:选第k小值

计算模型

选择pivot=a[left]值,j=right对集合进行二分
(1) j-left=k-1,则分界数据就是选择问题的答案
(2) j-left>k-1,则选择问题的答案继续在左子集中找,问题规模变小了。
(3) j-left<k-1,则选择问题的答案继续在右子集中找,问题变为选择第k-left-1小的数,问题的规模也变小。

算法分析

(1)最坏情况下的复杂性是O(n 2 ),left 总是为空,第k个元素总是位于 right
子集中。
(2)设n=2 k ,算法的平均复杂性是O (n+logn)。若仔细地选择分界元素,则
最坏情况下的时间开销也可以变成O(n)。
(3)它与本章第一个例子非常相似,只对一半子集进行搜索,所不同的时,
由于pivot点选择的不同,多数情况下它是非等分的。
算法设计与分析
输入
输出

int select(int a[],int left,int right,int k)
{
    int i,j,pivot,t;
    if(left>=right)return a[left];
    i=left;
    j=right+1;
    pivot=a[left];
    while(true)
    {
        do{
            i++;
        }
        while(a[i]<pivot);
        if(i>=j)break;
        t=a[i];
        a[i]=a[j];
        a[j]=t;
        
    }
    if(j-left+1==k) return pivot;
    a[left]=a[j];
    a[j]=pivot;
    if(k<(j-left+1))select(a,left,j-1,k);
    else select(a,j+1,right,k-j-j+left);

代码:

#include<iostream>
using namespace std;

void show(int a[])
{
	cout<<"a[]:";
	for(int i=0;i<8;i++)
		cout<<a[i]<<"\t";
	cout<<endl;
}

int select(int a[],int left,int right,int k)
{
	cout<<endl<<"select:left:"<<left<<" right:"<<right<<" k: "<<k<<endl;
	cout<<"left不变 right不变"<<endl;
	show(a);
	
	int i,j,pivot,t;
	if(left>=right) {
		cout<<"left>=right a[left]"<<a[left]<<endl;
		return a[left];
	}
	i=left;
	j=right+1;
	pivot=a[left];
	cout<<"left:"<<left<<" a[left] = pivot: "<<pivot<<endl;
	while(true)
	{
		cout<<endl<<"in while:"<<endl;
		do{
			i++;
		}
		while(a[i]<pivot);
		
		do{
			j--;
		}while(a[j]>pivot);
		cout<<"now pivot is "<<pivot<<" 从left到i都小于pivot "<<i<<":"<<a[i]<<" 从j到right都大于pivot "<<j<<":"<<a[j]<<endl;
		if(i>=j){
			cout<<"i>=j break;"<<endl;
			break;
		}
		
		t=a[i];
		a[i]=a[j];
		a[j]=t;
		cout<<"swap "<<i<<" "<<j<<endl;
		show(a);
	}
	cout<<endl<<"while结束 此时 j left k 是"<<j<<" "<<left<<" "<<k<<endl;
	
	
	cout<<endl<<"最开始right-left+1等于总的元素个数,现在j往前移动 也就是比pivot大的数有(right-j)个"<<endl
	<<"选第k小的元素 应该是有sum1=k-1个数比x小,有sum2=right-left+1-k个数比x大"<<endl
	<<"此时 sum2=(right-j) 那么k=right-left+1-(right-j) =j-left+1 也就是此时是第(j-left+1)小 "<<endl
	<<"需要让此时的 (j-left+1)==k 就求出了答案 "<<endl<<endl; 
	
	if(j-left+1==k) return pivot;
	cout<<"然而 (j-left+1)!=k 需要继续寻找 "<<endl;
	cout<<"a[left]是基准元素,j小于等于基准,因此交换 "<<endl ;
	cout<<"交换 left j 的元素"<<endl;
	a[left]=a[j];
	a[j]=pivot;
	show(a);
	
	if(k<(j-left+1))
	{
		cout<<endl<<"左侧";
		select(a,left,j-1,k);	
	} 
	else 
	{
		cout<<endl<<"右侧";
		select(a,j+1,right,k-j-1+left);
	}
} 

int a[]={1,4,5,3,2,6,8,7};
int k=2;
int main()
{
	
	int left=0;
	int right=7;
	int res= select(a,left,right,k);
	cout<<res<<endl;
	return 0;
}

思考题:正元素 负元素排序

问题分析:
使数组中所有负数位于正数之前,空间效率:原数组的空间不可改变,临时变量尽可能少,在原数组上改变 不增加结果数组。时间效率:运算次数尽可能少。

一定会遍历一遍数组,且每个元素与0进行比较,因此从两侧开始,若左侧小于0 不变,大于0需要后移,右侧大于0不变,小于0需要后移。因此找到两位置left right ,进行交换。再以此为范围进行下一轮递归,直到left>=right 结束递归。

计算模型:

从[left,right]范围内进行二分。

(1)left>=right 结束循环

(2)从left开始找到第一个大于0的元素下标,更新left

(3)从right开始找到第一个小于0的元素下标,更新right

(4)若left>=right 结束循环

(5)否则从新的【left,right】范围进行递归排序

算法设计与分析

算法设计与描述

算法分析
输入:数组a[]
输出:排序后数组

void fun(int a[],int left,int right)
{
    if(left>=right) {
        return ;
    }
    while(a[left]<0) ++left;
    
    while(a[right]>0) --right;

    if(left<right)
    {
        t=a[left];
        a[left]=a[right];
        a[right]=t;
        fun(a,left,right);
    }
    else return ;
}

(1)输入规模n
(2)核心语句:比较和交换语句

(3)

时间复杂度 按最坏情况 每交换一次 都只能纠正2个元素的位置,则需要递归n/2次 :

T(n)= T(n-2) + c

=T(n-4) + 2*c

=T(n-6)+3*c

共n/2次 T(n)=O(n)

空间复杂度:则S(n)= n/2 +2;

 

思考题:用分治法 求 数列的最大子段和

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1107334.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

掌动智能分析云性能监控的重要性

云计算已成为现代企业的核心基础设施&#xff0c;它为企业提供了灵活性、可扩展性和成本效益。然而&#xff0c;将业务迁移到云上并不意味着问题就此消失。企业必须仍然保持对其云基础设施和应用程序的严格监控&#xff0c;以确保其性能和可靠性。本文将深入探讨企业云性能监控…

共享模型之管程

1、共享带来的问题 线程出现问题的根本原因是因为线程上下文切换&#xff0c;导致线程里的指令没有执行完就切换执行其它线程了&#xff0c;下面举一个例子 Test13.java static int count 0;public static void main(String[] args) throws InterruptedException {Thread t1 …

学习最优化课程中的一些疑惑

感谢gpt I: 你是一个数学专业教授&#xff0c;请给我讲解一下卡氏积的含义 GPT: 卡氏积&#xff08;Cartesian product&#xff09;是集合论中的一个概念&#xff0c;用来描述两个集合之间的关系。假设有两个集合A和B&#xff0c;卡氏积A B定义为所有有序对 (a, b)&#xf…

【Mysql】InnoDB数据页结构(五)

概述 页是InnoDB存储引擎管理存储空间的基本单位&#xff0c;一个页的大小默认是16KB 。InnoDB 为了不同的目的而设计了许多种不同类型的页 &#xff0c;比如存放记录的索引页&#xff0c;存放表空间头部信息的页&#xff0c;存放 Insert Buffer信息的页&#xff0c;存放 INOD…

23 种设计模式详解(C#案例)

&#x1f680;设计模式简介 设计模式&#xff08;Design pattern&#xff09;代表了最佳的实践&#xff0c;通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时…

力扣-消失的数字(两种方法)

题目内容&#xff1a; 方法1&#xff1a; 根据题目我们可以知道这个数组的内容是0~N的数字&#xff0c;而那个消失的数字就是也是这个0~n其中一个&#xff0c;那么我们可以得到0~N所有的数字之和后&#xff0c;再将数组中的数字全部减去&#xff0c;那么我们就得到了那个~消失…

项目播报 | 璞华科技助力苏州巨迈科构建数字化管理体系

项目播报 近日&#xff0c;苏州巨迈科智能科技有限公司&#xff08;以下简称&#xff1a;苏州巨迈科&#xff09;签约璞华科技实施PLM项目&#xff0c;建立苏州巨迈科统一的研发管理平台&#xff0c;实现产品数据在部门间的共享&#xff0c;提升企业技术管理水平和综合竞争力&…

【Java 进阶篇】JavaScript 动态表格案例

在这篇博客中&#xff0c;我们将深入了解JavaScript如何创建和操作动态表格。我们将从头开始构建一个动态表格&#xff0c;并逐步添加各种功能&#xff0c;使其能够实现数据的添加、删除和编辑。这个示例将有助于理解如何在前端开发中使用JavaScript创建交互性强大的表格。 准…

mysql—表单二

一、查询环境 1.创建student和score表 CREATE TABLE student ( id INT(10) NOT NULL UNIQUE PRIMARY KEY , name VARCHAR(20) NOT NULL , sex VARCHAR(4) , birth YEAR, department VARCHAR(20) , address VARCHAR(50) ); 创建score表。SQL代码如下&#xff1a; CR…

Linux高性能服务器编程——ch3笔记

第3章 TCP 协议详解 3.1 TCP服务的特点 面向连接&#xff1a;在数据读写前必须先建立连接&#xff0c;并分配内核资源。全双工&#xff08;读写可以通过一个连接进行&#xff09;。数据交换后必须断开连接&#xff0c;释放系统资源。 一对一的&#xff0c;基于广播和多播&…

软件考试学习笔记(希赛)

软考学习笔记-软件设计师 1. 软考基本介绍1.1 软考分数制1.2软考考试分类介绍1.3软件考试报名网站1.4考试内容1.4.1上午考试内容-综合知识1.4.2下午考试内容-软件设计 2.数据的表示2.1进制转换2.1.1R进制------》十进制转换2.1.2十进制-----》R进制转换2.1.3二进制与八进制与16…

SVM支持向量机

定义 支持向量机&#xff08;SVM&#xff09;&#xff0c;Supported Vector Machine,基于线性划分&#xff0c;输出一个最优化的分隔超平面&#xff0c;该超平面不但能将两类正确分开,且使分类间隔(margin)最大 **所有训练数据点距离最优分类超平面的距离都要大于支持向量距离…

我的创作一周年纪念日

目录 机缘收获憧憬 机缘 还真是,像极了<<匆匆>>里的那一段话. “聪明的,你告诉我,我们的日子为什么一去不复返呢?——是有人偷了他们罢:那是谁?又藏在何处呢&#xff1f;是他们自己逃走了罢&#xff1a;现在又到了哪里呢&#xff1f;” 一年的光景就这么过去了…

企业级API网关,金蝶是如何架构的?

说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;很多小伙伴拿到一线互联网企业如阿里、网易、有赞、希音、百度、滴滴的面试资格。 最近&#xff0c;尼恩指导一个小伙伴简历&#xff0c;写了一个《高并发网关项目》&#xff0c;此项目帮这个小伙拿到 字节/阿里/…

论文阅读之《Kindling the Darkness: A Practical Low-light Image Enhancer》

目录 摘要 介绍 已有方法回顾 普通方法 基于亮度的方法 基于深度学习的方法 基于图像去噪的方法 提出的方法 2.1 Layer Decomposition Net 2.2 Reflectance Restoration Net 2.3 Illumination Adjustment Net 实验结果 总结 Kindling the Darkness: A Practical L…

安科瑞Acrel-7000工业能耗在线监测系统,企业能源管控平台

安科瑞虞佳豪 能源管理系统采用分层分布式系统体系结构&#xff0c;对建筑的电力、燃气、水等各分类能耗数据进行采集、处理&#xff0c;并分析建筑能耗状况&#xff0c;实现建筑节能应用等。通过能源计划&#xff0c;能源监控&#xff0c;能源统计&#xff0c;能源消费分析&a…

脏数据绕过waf,Yakit爆破base64编码密码,ssh无密码登录受害主机

文章目录 waf、Yakit、ssh 技巧waf 脏数据绕过Yakit 工具对明文密码通过base64编码的格式进行爆破 SSH实操 waf、Yakit、ssh 技巧 waf 脏数据绕过 以pikachu靶场的文件上传功能为例 上传一个木马图片 显示已拦截。 找到form-data;字段加一个分号&#xff0c;中间写一些脏数…

电子器件系列49:CD4050B缓冲器

同相和反向缓冲器 还搞不懂缓冲电路&#xff1f;看这一文&#xff0c;工作原理作用电路设计使用方法 - 知乎 (zhihu.com) 缓冲器_百度百科 (baidu.com) 1、缓冲器的定义 缓冲器是数字元件的其中一种&#xff0c;它对输入值不执行任何运算&#xff0c;其输出值和输入值一样&…

Linux真的很难吗?文末送5本《Linux运维之道(第3版)》

目录 一、百度百科二、VMWare中安装centos1、下载地址2、网络适配器简介 三、克隆和快照1、克隆一般用于项目部署2、快照相当于SVN&#xff0c;是Linux系统的版本管理手段 四、XShell和Xftp1、XShell百度百科2、Xftp百度百科3、xshell7和xftp7下载地址 五、vi和vim1、vim基本编…

无管道后门(Backdoor)编写

1. 代码编写 这是基于异步套接字的后门, 利用windows提供的WSASocket API函数, 这个API和socket的区别是, 其无需等待收发完成就可以继续执行, 也就是异步的。这可实现不用管道进行收发数据。 代码: #include <WinSock2.h> #include <windows.h> #include <tc…