《算法设计与分析》第二章:分治与递归

分治与递归

分治:

(1)将大问题分为分解为若干个小问题(分-治-并)
(2)分治是一种问题求解(算法设计)方法,经常会用到递归技术,但有时候也不一定用到递归

递归:

(1)递归是一种程序设计技术(方法),自己调用自己,每层参数不同
(2)递归经常能将一个复杂的大问题化解为小问题,直至能容易求解(递归中止)
(3)一定要设置好递归中止条件,正确处理递归返回值
(4)递归算法分析:替换法、递归树、主方法

第一部分:作业

1.替换法求解递归方程

在这里插入图片描述

在这里插入图片描述

2.采用递归树和主方法,求解递归方程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.三分查找

问题描述:仿照折半(二分)查找,编写算法实现三分查找。并思考:二分查找,比较1次,查找范围缩小一半;三分查找,比较2次,查找范围缩小为1/3,是否分的越多越好,也就是四分、五分效果是否更佳?简要说明理由。

  • 在理想情况下,分得越多越好并不一定会导致查找效率的显著提高。虽然四分、五分查找可以将查找范围更快地缩小,但在实际中,这样做会带来额外的比较次数和更复杂的计算,可能会抵消掉因为查找范围缩小而带来的时间节省。

    • 主要原因:尽管每次分割后查找范围缩小了,但每一次比较的成本却可能会增加。比如,在二分查找中,只需要进行一次比较来确定目标值可能在左侧还是右侧,而在三分查找中,需要进行两次比较来确定目标值可能在哪个区域,从而带来了额外的成本。随着分割次数的增加,这种额外的成本也会随之增加。

    • 此外:四分、五分查找等相对复杂的算法可能需要更多的存储空间来存储中间分割点的索引,或者需要更多的计算来确定中间分割点的位置,这些也会增加算法的总体复杂度。

    • 因此,在实际应用中,二分查找通常是最为高效和实用的选择,因为它在大多数情况下能够提供良好的性能,并且实现相对简单。在特定情况下,如果数据结构的特性或者查找的模式能够保证更多分割的方式带来明显的优势,那么四分、五分查找等方式可能会被考虑,但这需要针对具体情况进行深入分析和测试。

  • 代码:

#include<bits/stdc++.h>

using namespace std;

int temp1 = 0;		//1表示找到

void three_find_x(int a[], int left, int right, int x) 
{	
	//两个节点
	int mid_left = left + (right - left) / 3;
	int mid_right = right - (right - left) / 3;
	
	//等于左节点
	if (x == a[mid_left]) 
	{
		temp1 = 1;
		cout << mid_left << endl;
		return;
	}
	
	//左节点大于等于右节点
	if (mid_left >= mid_right) 
	{
		return;
	}
	
	//小于左节点
	if (x < a[mid_left]) 
	{
		three_find_x(a, left, mid_left - 1, x);
	}
	
	//大于左节点
	if (x > a[mid_left]) 
	{
		//等于右节点
		if (x == a[mid_right]) 
		{
			temp1 = 1;
			cout << mid_right << endl;
			return;
		}
		//大于右节点
		if (x > a[mid_right]) 
		{
			three_find_x(a, mid_right + 1, right, x);
		}
		//小于右节点
		if (x < a[mid_right]) 
		{
			three_find_x(a, mid_left + 1, mid_right - 1, x);
		}
	}
}

int main() 
{
	int N;
	cin >> N;
	int a[10000] = {};
	int i;
	for (i = 1; i <= N; i++) 
	{
		cin >> a[i];
	}
	
	int x;
	cin >> x;
	three_find_x(a, 1, N, x);
	if (temp1 == 0) 
	{
		cout << -1 << endl;
	}
	return 0;
}
  • 运行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.递归实现直接插入

问题描述:直接插入算法是否可以用递归方式实现?如果可以,请描述大问题和小问题,给出递归出口,并实现该算法。

  • 大问题:实现整体的插入排序

  • 小问题:实现第i个数插入1~i-1中

  • 递归出口:i遍历到已经超过了总长度n

  • 代码:

#include<bits/stdc++.h>

using namespace std;

const int INF = 0x3f3f3f3f;
const int N = 1e3 + 20;
int v[N];

//大问题:实现整体的插入排序
//小问题:实现第i个数插入1~i-1中
void InsertSort(int v[], int i, int n)
{
	//递归出口
	if(i == n + 1) return;
	
	//正常递归
	int temp = v[i];
	int j;
	for(j = i - 1; temp <= v[j]; j -- )
	{
		v[j + 1] = v[j]; 
	}
	v[j + 1] = temp;
	InsertSort(v, i + 1, n);
}

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int n;
	cin >> n;
	v[0] = -INF;
	for(int i = 1; i <= n; i ++ )
	{
		int x;
		cin >> x;
		v[i] = x;
	}
	InsertSort(v,1,n);
	for(int i = 1; i <= n; i ++) cout << v[i] << ' ';
}
  • 运行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5.二叉树转中缀表达式

题目描述:给定一个二叉树,输出其中缀表达式,并加上必要的括号(不该加的括号不加)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 代码:
#include<iostream>

using namespace std;

typedef struct BiTNode
{
	char data;
	struct BiTNode* lchild, * rchild;
}BiTNode, * BiTree;

void CreatBitree(BiTree& T, char s[], int& i)
{
	if (s[i] == '#')
		T = NULL;
	else
	{
		T = new BiTNode;
		T->data = s[i];
		CreatBitree(T->lchild, s, ++i);
		CreatBitree(T->rchild, s, ++i);
	}
}

void InOrder(BiTree& T, int deep)     //根结点和叶子结点不加括号
{
	if (T == NULL)
		return;
	else if (T->lchild == NULL && T->rchild == NULL)
		cout << T->data;
	else 
    {
		if (deep > 1)
			cout << "(";
		InOrder(T->lchild, deep + 1);
		cout << T->data;
		InOrder(T->rchild, deep + 1);
		if (deep > 1)
			cout << ")";
	}
	
}

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	BiTree T;
	char s[200];
	cin >> s;
	if(s[0] != '#')
	{
		int i = -1;
		int deep = 0;
		CreatBitree(T, s, ++i);
		InOrder(T, deep + 1);
		cout << endl;	
	}
	return 0;
}
  • 运行结果:(括号与否问题没有完全的优化好)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

6.最大连续子序列和

问题描述:给定一个有n个整数的序列,求出其中最大连续 子序列的和。例如序列{-2,11,-4,13,-5,-2}的最大连 续子序列和为20{11,-4,13};序列(-6,2,4,-7,5,3, 2,-1,6,-9,10,-2)的最大子序列和为16。 规定一个序列最大连续子序列和至少是 0如果小于0,其结果 为0。

  • 从中间节点分治
  • 每次算左max,右max,和含有中间节点的中max得到三者最大值即可
  • 代码:
#include<bits/stdc++.h>

using namespace std;

const int N = 1e3 + 20;
int a[N];

long maxSubSum(int a[],int left,int right)
{ 
	int i,j;
	long maxLeftSum,maxRightSum;
	long maxLeftBorderSum ,leftBorderSum;
	long maxRightBorderSum ,rightBorderSum;
	
	//子序列只有一个元素时,自顶向下
	if (left==right)
	{
		if(a[left]>0) return a[left];
		else return 0; 
	} 
	int mid=(left+right)/2; //求中间位置
	
	//自底向上合并
	maxLeftSum = maxSubSum(a ,left,mid); //求左边
	maxRightSum = maxSubSum(a ,mid+1,right); //求右边
	
	maxLeftBorderSum=0,leftBorderSum=0;
	for (i=mid;i>=left;i--)//求出以左边加上a[mid]元素
	{ 
		leftBorderSum+=a[i]; //构成的序列的最大和
		if (leftBorderSum>maxLeftBorderSum) maxLeftBorderSum=leftBorderSum;
	}
	
	maxRightBorderSum=0 ,rightBorderSum=0;
	for (j=mid+1;j<=right;j++) //求出a[mid]右边元素
	{ 
		rightBorderSum+=a[j]; //构成的序列的最大和
		if (rightBorderSum>maxRightBorderSum) maxRightBorderSum=rightBorderSum;
	}
	
	return max(maxLeftSum ,max(maxRightSum,maxLeftBorderSum+maxRightBorderSum)); 
} 


int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	
	int n;
	cin >> n;
	for(int i = 1; i <= n; i ++ ) cin >> a[i];
	cout << maxSubSum(a,1,n) << endl;
}
  • 运行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

7.寻找两个等长有序序列中位数

问题描述:对于一个长度为n的升序有序序列 a[1…n],处于中间位置的元素称为 a的中位数。设 计一个算法求给定的两个升序有序序列的中位数。

  • 拆分两组,每次算两者中位数,然后舍弃明显不存在的部分
  • 要注意两组元素数量是否相等
  • 代码:
#include <bits/stdc++.h>

using namespace std;

const int N = 1e3 + 20;
int a[N], b[N];

int midNum(int a[], int s1, int t1, int b[], int s2, int t2) 
{
	int m1, m2;
	
	//两序列只有一个元素时返回较小者
	if (s1 == t1 && s2 == t2) return a[s1] < b[s2] ? a[s1] : b[s2];
	
	else 
	{
		m1 = (s1 + t1) / 2; //求a的中位数位置
		m2 = (s2 + t2) / 2; //求b的中位数位置
		
		//两中位数相等时返回该中位数
		if (a[m1] == b[m2]) return a[m1];
		
		if (a[m1] < b[m2]) 
		{ 
			if ((s1 + t1) % 2 == 0) s1 = m1; //a 取后半部分,奇数个元素
			else s1 = m1 + 1; //偶数个元素
			t2 = m2; //b取前半部分
			return midNum(a, s1, t1, b, s2, t2);
		} 
		
		else 
		{ 
			t1 = m1; //a取前半部分
			if ((s2 + t2) % 2 == 0) s2 = m2; //b 取后半部分,奇数个元素
			else s2 = m2 + 1; //偶数个元素
			return midNum(a, s1, t1, b, s2, t2);
		}
	}
}

int main() {
	ios_base::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i ++ ) cin >> a[i];
	for (int i = 1; i <= m; i ++ ) cin >> b[i];
	cout << midNum(a, 1, n, b, 1, m) << endl;
}
  • 运行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

8.平面最近点对问题

问题描述:平面最近点对问题:平面上有点集P,包含n个点,在这n个点中找到两点之间最短的欧式距离,并分析算法时空复杂度。(提示:用分治法求解)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 每次把点分为左半边和右半边,处理左半边最短距离、右半边最短距离和交叉最短距离

  • 取中间mid - d~mid+d部分,依次遍历左边该部分的点,到右边该点对应“日”字形结构中的点,用“日”字形的结论即可

  • 时间复杂度:O(6n)(因为“日”字形内最多6个点)

  • 代码:

#include <bits/stdc++.h>

using namespace std;


const int maxn = 100000 + 6;
const long long inf = 0x7f7f7f7f7f7f7f7fLL;


struct Point {
	int x, y;
} ps[maxn];           // 用结构体储存所有的点坐标


Point Ltmp[maxn], Rtmp[maxn];                // 临时数组
int lcnt, rcnt;


bool cmpx(const Point& A, const Point& B) {  // 按照 x 坐标进行排序
	return A.x != B.x ? A.x < B.x : A.y < B.y;
}


long long sqr(int x) {                       // 计算平方(注意 long long)
	return (long long)x * x;
}
long long dis(const Point& A, const Point& B) {
	return sqr(A.x - B.x) + sqr(A.y - B.y);  // 计算两点间距离的平方
}


void merge(int L, int mid, int R) {          // 按照 y 坐标进行归并排序的合并操作
	lcnt = 0;                                // 借用 Ltmp 临时数组进行归并排序
	int i = L, j = mid + 1;                  // i, j 分别为左侧数组和右侧数组的 "当前元素"
	while (i <= mid && j <= R) {
		if (ps[i].y != ps[j].y ? ps[i].y < ps[j].y : ps[i].x < ps[j].x) {
			// 按照 y 坐标从小到大排序
			Ltmp[++ lcnt] = ps[i];
			i ++;
		} else {
			Ltmp[++ lcnt] = ps[j];
			j ++;
		}
	}
	while (i <= mid) {                       // 左侧数组中仍有剩余元素
		Ltmp[++ lcnt] = ps[i];
		i ++;
	}
	while (j <= R) {                         // 右侧数组中仍有剩余元素
		Ltmp[++ lcnt] = ps[j];
		j ++;
	}
	for (int i = 1; i <= lcnt; i ++) {       // 将临时数组中的数据拷贝回 ps 数组
		ps[L + i - 1] = Ltmp[i];
	}
}


long long solve(int L, int R) {              // 递归计算区间 [L, R] 的最小距离,并将点按照 y 坐标排序
	if (L >= R) return inf;                  // 空区间 / 只有一个节点的区间不需要计算
	int mid  = (L + R) / 2;
	int midx = ps[mid].x;
	long long lans = solve(L,   mid);        // 递归计算左半区间和右半区间
	long long rans = solve(mid + 1, R);
	long long D = min(lans, rans);
	long long mindis = inf;
	lcnt = 0;
	for (int i = L; i <= mid; i ++) {        // 载入左半区间距中轴线距离 <= sqrt(mindis) 的点
		if (sqr(midx - ps[i].x) <= D) {
			Ltmp[++ lcnt] = ps[i];
		}
	}
	rcnt = 0;
	for (int i = mid + 1; i <= R; i ++) {    // 载入右半区间距中轴线距离 <= sqrt(mindis) 的点
		if (sqr(ps[i].x - midx) <= D) {
			Rtmp[++ rcnt] = ps[i];
		}
	}
	// 注:Ltmp 和 Rtmp 中的数据是按照 Y 坐标有序的
	int lpos = 1, rpos = 0;
	for (int i = 1; i <= lcnt; i ++) {       // 枚举 Ltmp 中的点的点
		while (rpos < rcnt && (Rtmp[rpos + 1].y < Ltmp[i].y || sqr(Rtmp[rpos + 1].y - Ltmp[i].y) <= D)) {
			rpos ++;
		}
		while (lpos < rcnt && Ltmp[i].y > Rtmp[lpos].y && sqr(Ltmp[i].y - Rtmp[lpos].y) > D) {
			lpos ++;
		}
		for (int j = lpos; j <= rpos; j ++) { // Rtmp[lpos .. rpos] 是 日字形中的点
			mindis = min(mindis, dis(Ltmp[i], Rtmp[j]));
		}
	}
	merge(L, mid, R);                        // 归并排序的合并操作
	return min(D, mindis);
}


int main() {
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i ++) {          // 输入所有点的坐标
		scanf("%d%d", &ps[i].x, &ps[i].y);
	}
	sort(ps + 1, ps + n + 1, cmpx);          // 调了一下午发现忘排序了 ......
	long long ans = solve(1, n);             // 递归计算
	printf("%.4lf\n", sqrt(ans));            // 输出最近点对距离
	return 0;
}
  • 运行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第二部分:算法积累

都已编译运行过,均可以run且测试正确

1.递归实现简单选择排序

  • 从前往后,每次从未排序部分选择最小值,加入排序部分
#include<bits/stdc++.h>

using namespace std;

//每次从i~n部分选择最小值
void SelectSort(int A[], int i, int n) //i的初值为1
{
	//递归出口
	if(i == n) return;
	
	//正常递归
	int k = i;
	for(int j = k + 1; j <= n; j ++ )
		if(A[j] < A[k]) k = j;
	if(k != i) swap(A[i], A[k]);
	
	SelectSort(A, i + 1, n);
}

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	
	int n;
	cin >> n;
	int a[n + 1];
	for(int i = 1; i <= n; i ++ )
		cin >> a[i];
	
	SelectSort(a, 1, n);
	
	for(int i = 1; i <= n; i ++ )
		cout <<  a[i] << ' ';
}

2.递归实现冒泡排序

  • 从前往后,每次冒泡将最大值放在最后
  • 可以用一个exchange布尔值判断是否还需要进行下一次冒泡
#include<bits/stdc++.h>

using namespace std;

//每次从1~n-i+1部分冒泡
void BubbleSort(int A[], int i, int n) //i初值为1
{
	//递归出口
	if(i == n) return;
	
	//正常递归
	bool exchange = false;
	for(int j = 1; j <= n - i; j ++)
		if(A[j] > A[j + 1])
		{
			swap(A[j], A[j + 1]);
			exchange = true;
		}
	if(exchange == false) return;
	else BubbleSort(A, i + 1, n);
}

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	
	int n;
	cin >> n;
	int a[n + 1];
	for(int i = 1; i <= n; i ++ )
		cin >> a[i];
	
	BubbleSort(a, 1, n);
	
	for(int i = 1; i <= n; i ++ )
		cout <<  a[i] << ' ';
}

3.递归实现全排列

问题描述:求n个元素的全排列(n < 100, 元素互不相同)

  • 每次交换k和k以后数的位置,从而遍历所有情况
  • 注意交换后要回溯,重新交换回来
#include<bits/stdc++.h>

using namespace std;

void perm(int list[], int k, int n)
{
	//递归出口
	if(k == n)
	{
		for(int i = 1; i <= n; i ++ )
			cout << list[i];
		
		cout << endl;
	}
	
	//正常递归
	else
	{
		for(int i = k; i <= n; i ++ )
		{
			swap(list[k], list[i]); //交换遍历
			perm(list, k + 1, n);
			swap(list[k], list[i]); //回溯
		}
	}
}

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	
	int n;
	cin >> n;
	int list[n + 1];
	for(int i = 1; i <= n; i ++ )
		list[i] = i;
	
	perm(list, 1, n);
}

4.递归实现先序+中序创建二叉树

问题描述:对于含n (n>0)个结点的二叉树,设计一个算法由其先序序列pre和中序序列in创建对应的二叉链存储结构

#include<bits/stdc++.h>

using namespace std;

// 定义二叉树结点结构
typedef struct BTNode 
{
	int data;
	struct BTNode *lchild;
	struct BTNode *rchild;
} BTNode;

// 由先序序列pre和中序序列in创建二叉链存储结构
BTNode *CreateBTree(int pre[], int in[], int n) 
{
	// 递归出口
	if (n < 1) return NULL;
	
	// 正常递归
	int root_data = pre[0]; // 根结点值
	BTNode *bt = (BTNode *)malloc(sizeof(BTNode));
	bt->data = root_data;
	
	int k;
	for (k = 0; k < n; k++) { // 在in中查找in[k] = root_data的根结点
		if (in[k] == root_data)
			break;
	}
	
	bt->lchild = CreateBTree(pre + 1, in, k); // 递归创建左子树
	bt->rchild = CreateBTree(pre + k + 1, in + k + 1, n - k - 1); // 递归创建右子树
	
	return bt;
}

// 中序遍历函数实现
void InOrderTraversal(BTNode *root) 
{
	if (root == NULL) return;
	InOrderTraversal(root->lchild);
	printf("%d ", root->data);
	InOrderTraversal(root->rchild);
}

int main() 
{
	int pre[] = {1, 2, 4, 7, 3, 5, 6, 8}; // 先序序列
	int in[] = {4, 7, 2, 1, 5, 3, 8, 6};  // 中序序列
	int n = sizeof(pre) / sizeof(pre[0]);
	
	BTNode *root = CreateBTree(pre, in, n);
	
	// 测试代码
	printf("Inorder traversal result: ");
	InOrderTraversal(root);
	printf("\n");
	
	return 0;
}

5.分治实现快速排序(考研真题)

问题描述:已知由n (n≥2)个正整数构成的集合A={ak) (1≤k≤n),将其划分为两个不相交的子集A1和A2,元素个数分别是n1和n2, A1和A2中元素之和分别为S1和S2。设计一个尽可能高效的划分算法,满足|n1-n2|最小且|S1-S2|最大

  • |n1-n2|最小尽可能从中间分开
  • |S1-S2|最大使得分开点位前面都小于后面的数,且如果无法等分数量,则S2多一个数
  • 使用不完整的快速排序,使得划分到该位置时立刻停止,让时间复杂度尽可能低
#include<bits/stdc++.h>

using namespace std;

//一趟快速排序
int partition(int a[], int low, int high) // 以a[low]为基准划分
{
	int i = low, j = high;
	int povit = a[low]; 
	while (i < j)
	{ 
		while (i < j && a[j] >= povit) j -- ;
		if(i < j) a[i] = a[j], i ++ ;
		while (i < j && a[i] <= povit) i ++ ;
		if(i < j) a[j] = a[i], j -- ;
	}
	a[i] = povit; 
	return i; 
}

//求解
int solution(int a[], int n)
{ 
	int low = 1, high = n; 
	while (1)
	{ 
		int i = partition(a, low, high);
		if (i == n/2) //a[i]为第n/2的元素
			break; 
		else if (i < n/2) //在右区间查找
			low = i + 1; 
		else 
			high = i - 1; //在左区间查找
	}
	int s1=0,s2=0;
	for (int i = 1; i <= n/2; i ++ ) s1 += a[i];
	for (int j = n/2 + 1; j <= n; j ++ ) s2 += a[j]; 
	return s2 - s1; 
}

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	
	int n;
	cin >> n;
	int a[n + 1];
	for(int i = 1; i <= n; i ++ )
		cin >> a[i];
	
	int ans = solution(a, n);
	
	cout << ans;
}

6.分治实现归并排序

  • 自顶向下分解问题,自底向上合并问题
#include<bits/stdc++.h>

using namespace std;

//一趟归并排序
void merge(int a[], int low, int mid, int high)
//a[low..mid],a[mid+1..high] 分别有序,将其归并为新的有序序列,并存入a[low,..high]
{ 
	int i=low, j=mid+1, k=1;
	int* tmp=(int *)malloc((high-low+2) *sizeof(int));
	
	while (i<=mid && j<=high)
		if (a[i]<=a[j])	tmp[k]=a[i], i++, k++;
	else tmp[k]=a[j], j++, k++;
	
	while (i<=mid) tmp[k]=a[i], i++, k++; 
	while (j<=high)	tmp[k]=a[j], j++, k++; 
	
	for (k=1,i=low;i<=high;k++, i++) a[i]=tmp[k];
	
	free(tmp);
}

//求解
void mergeSort(int a[], int low, int high)
{
	if (low<high) //子序列有两个或以上元素
	{
		int mid=(low+high)/2;
		mergeSort(a, low, mid); 
		mergeSort(a, mid+1, high);
		merge(a, low, mid, high);
	}
}

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	
	int n;
	cin >> n;
	int a[n + 1];
	for(int i = 1; i <= n; i ++ )
		cin >> a[i];
	
	mergeSort(a, 1, n);
	
	for(int i = 1; i <= n; i ++ )
		cout << a[i] << ' ';
}
  • 自底向上直接合并
#include<bits/stdc++.h>

using namespace std;

//一趟归并排序
void merge(int a[], int low, int mid, int high)
//a[low..mid],a[mid+1..high] 分别有序,将其归并为新的有序序列,并存入a[low,..high]
{ 
	int i=low, j=mid+1, k=1;
	int* tmp=(int *)malloc((high-low+2) *sizeof(int));
	
	while (i<=mid && j<=high)
		if (a[i]<=a[j])	tmp[k]=a[i], i++, k++;
	else tmp[k]=a[j], j++, k++;
	
	while (i<=mid) tmp[k]=a[i], i++, k++; 
	while (j<=high)	tmp[k]=a[j], j++, k++; 
	
	for (k=1,i=low;i<=high;k++, i++) a[i]=tmp[k];
	
	free(tmp);
}

//一次合并的具体步骤
void mergePass(int a[], int len, int n)
{
	int i;
	for (i=1;i+2*len-1<=n;i=i+2*len) 
		merge(a, i, i+len-1, i+2*len-1);
	if (i+len-1 < n) //余下两个子表,后者长度小于len
		merge(a, i, i+len-1, n); 
}

//遍历不同长度的合并方式
void mergeSort(int a[], int n)
{
	for (int len=1;len<n;len=2 *len) 
		mergePass(a, len, n); 
}

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	
	int n;
	cin >> n;
	int a[n + 1];
	for(int i = 1; i <= n; i ++ )
		cin >> a[i];
	
	mergeSort(a, n);
	
	for(int i = 1; i <= n; i ++ )
		cout << a[i] << ' ';
}

7.分治实现求最大最小值

问题描述;从给定的n个数中,找出最大和最小值。要求在最坏情况下最多进行(3n/2 - 2)上取整次比较

  • 分治法:每次平分为两组
#include<bits/stdc++.h>

using namespace std;

void maxMin(int a[],int i,int j,int *max,int *min) //i初值为1,j初值为n
{
	int mid;
	int lmax,lmin,rmax,rmin;
	if(j==i) 
	{ 
		*max=a[i]; 
		*min=a[i]; 
		return;
	}
	else if(j-i==1)
	{
		if(a[i]>a[j]) 
		{ 
			*max=a[i];
			*min=a[j];
			return;
		}
		else 
		{
			*max=a[j];
			*min=a[i];
			return;
		}
	}
	else
	{
		mid=(i+j)/2;
		maxMin(a,i,mid,&lmax,&lmin);
		maxMin(a,mid+1,j,&rmax,&rmin);
		if(lmax>rmax) *max=lmax;
		else *max=rmax;
		if(lmin<rmin) *min=lmin;
		else *min=rmin;
	}
}

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	
	int n;
	cin >> n;
	int a[n + 1];
	for(int i = 1; i <= n; i ++ )
		cin >> a[i];
	
	int max, min;
	maxMin(a, 1, n, &max, &min);
	
	cout << max << ' ' << min << endl;
}
  • 分治+非递归:全部拆分为两个一组
#include<bits/stdc++.h>

using namespace std;

void maxMin(int a[],int n,int *max,int *min)
{ 
	int i,tmpMin,tmpMax; 
	
	if(a[1]>a[2]) tmpMax=a[1],tmpMin=a[2];
	else tmpMin=a[1],tmpMax=a[2];
	
	for(i=3;i<=n-1;i=i+2)
	{
		int bigger, smaller;
		
		if(a[i]>a[i+1]) 
		{
			bigger=a[i]; 
			smaller=a[i+1];
		}
		
		else 
		{
			bigger=a[i+1]; 
			smaller=a[i];
		}
		
		if(bigger>tmpMax) tmpMax=bigger;
		if(smaller<tmpMin) tmpMin=smaller;
		
	}
	
	if(n % 2 == 1)
	{
		if(a[n]>tmpMax) tmpMax=a[n];
		if(a[n]<tmpMin) tmpMin=a[n];
	}
	
	*max=tmpMax; *min=tmpMin; 
}

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	
	int n;
	cin >> n;
	int a[n + 1];
	for(int i = 1; i <= n; i ++ )
		cin >> a[i];
	
	int max, min;
	maxMin(a, n, &max, &min);
	
	cout << max << ' ' << min << endl;
}

8.判断数组是否存在出现次数超过一半的元素

  • 用一个map来记录每个数的出现次数,使得遍历只有一次
#include <bits/stdc++.h>

using namespace std;

const int N = 1e3 + 20;
const int INF = 0x3f3f3f3f;
int n;
int a[N];
int ans = INF;
map<int,int> mp;

void solve()
{
	for (int i = 1; i <= n; i ++ )
	{
		cin >> a[i];
		mp[a[i]] ++;
		if(mp[a[i]] >= (n+1)/2)
		{
			ans = a[i];
		}
	}
}

int main() 
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	cin >> n;
	solve();
	if(ans != INF) cout << ans << endl;
	else cout << "NO ANSWER" << endl;
}

9.判断数组是否存在两元素和恰好等于k

  • 用一个map来记录每个数是否已经存在
#include <bits/stdc++.h>

using namespace std;

const int N = 1e3 + 20;
const int INF = 0x3f3f3f3f;
int n,k;
int a[N];
int ans = INF;
map<int,int> mp;

void solve()
{
	for(int i = 1; i <= n; i ++ )
	{
		cin >> a[i];
		mp[a[i]] ++;
	}
	for(int i = 1; i <= n; i ++ )
	{
		if(mp[a[i]] != 0 && mp[k-a[i]] != 0)
		{
			ans = a[i];
			break;
		}
	}
}

int main() 
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	cin >> n >> k;
	solve();
	if(ans != INF) cout << ans << ' ' << k-ans << endl;
	else cout << "NO ANSWER" << endl;
}

10.寻假币(二分or三分)

  • 二分实现
#include <bits/stdc++.h>

using namespace std;

const int N = 1e3 + 20;
int n;
int weight[N];

int SUM(int weight[], int m, int n)//求重量和
{
	int sum = 0;
	for (int i = m; i <= n; i++)
	{
		sum += weight[i];
	}
	return sum;
}

int find(int weight[], int left, int right)
{
	//只有两个或者一个的时候
	if (right - left == 1) return weight[right] > weight[left] ? left : right;
	if (right == left) return right;
	
	int mid = (right + left) / 2;
	
	if ((right - left) % 2 == 0)//硬币数量为奇数个数时
	{
		int lw = SUM(weight, left, mid - 1);
		int rw = SUM(weight, mid + 1, right);
		if (lw == rw) return mid;
		lw > rw ? find(weight, mid + 1, right) : find(weight, left, mid - 1);
	}
	
	else if ((right - left) % 2 == 1)//硬币为偶数个数时
	{
		int lw = SUM(weight, left, mid);
		int rw = SUM(weight, mid + 1, right);
		lw > rw ? find(weight, mid + 1, right) : find(weight, left, mid);
	}
}

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> weight[i];
	}
	int ff = find(weight, 1, n);
	cout << "第" << ff << "个是假币" << endl;
	return 0;
}
  • 三分实现
#include <bits/stdc++.h>

using namespace std;

int weightSum(int *coins, int begin, int end) 
{
	int sum = 0;
	for (int i = begin; i <= end; i++) 
	{
		sum += coins[i];
	}

	return sum;
}

int findBadCoinRecursion(int *coins, int begin, int end, int &model) 
{
	int n = end - begin + 1;
	if (n == 1) 
	{
		return begin;//因为只剩一个硬币了
	} 
	else if (n == 2) 
	{
		return coins[begin] != model ? begin : end;//两个之中只有一个是假币,注意model肯定是真币,因为在n >= 3的时候求过了
	}

	int midl = n / 3 - 1;//A堆的最后一个
	int midr = 2 * (n / 3) + n % 3;//B堆的第一个
	int sumA = weightSum(coins, begin, midl + begin);
	int sumB = weightSum(coins, midr + begin, end);
	
	//假币在中间
	if (sumA == sumB) 
	{
		model = coins[begin];
		return findBadCoinRecursion(coins, midl + begin + 1, midr + begin - 1, model);
	} 
	
	//假币在两边
	else 
	{
		model = coins[midl + begin + 1];
		int num = midl + 1;//A,B堆的硬币个数,A,B的硬币个数相等,A堆的数量是num = midl + begin - begin + 1 = midl + 1
		if (sumA == num * model) 
		{
			return findBadCoinRecursion(coins, midr + begin, end, model);//假币在B堆
		} 
		else 
		{
			return findBadCoinRecursion(coins, begin, midl + begin, model);//假币在A堆
		}
	}
}

void findBadCoinTest() 
{

	int n = 0;
	cin >> n;

	int coins[n];
	for (int i = 0; i < n; i++) 
	{
		cin >> coins[i];
	}

	int model = -1;
	int index = -1;
	model = -1;
	
	index = findBadCoinRecursion(coins, 0, n - 1, model);
	
	if (coins[index] != model) 
	{
		cout << "第" << index + 1 << "个硬币是假币" << endl;
	} 
	else
	{
		cout << "没有假币" << endl;
	}
}


int main() 
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	findBadCoinTest();
	return 0;
}

11.查找数组第k小的元素

  • 和上一道考研真题十分类似,都是实现不完整的快速排序
#include <bits/stdc++.h>

using namespace std;

const int N = 1e3 + 20;
int n, k;
int a[N];

int quickSort(int a[],int s,int t,int k) // 在a[s..t]序列中找第k小的元素
{ 
	int i,j,tmp;
	if(s==t && s==k) return a[s];
	if(s>t) return 0; 
	
	//一趟快排
	tmp=a[s]; 
	i=s; 
	j=t; 
	while(i<j)
	{ 
		while (i<j && a[j]>=tmp) j--;
		if(i<j) a[i]=a[j], i++;
		while (i<j && a[i]<=tmp) i++;
		if(i<j) a[j]=a[i], j--;
	}
	a[i]=tmp;
	if (i==k) return a[i];
	
	//递归
	else if (i>k) return quickSort(a, s, i-1, k); // 在左区间中递归查找
	else return quickSort(a, i+1, t, k); // 在右区间中递归查找 
}

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	cin >> n >> k;
	for(int i = 1; i <= n; i ++ ) cin >> a[i];
	cout << quickSort(a, 1, n, k) << endl;
}

12.最大连续子序列和

问题描述:给定一个有n个整数的序列,求出其中最大连续 子序列的和。例如序列{-2,11,-4,13,-5,-2}的最大连 续子序列和为20{11,-4,13};序列(-6,2,4,-7,5,3, 2,-1,6,-9,10,-2)的最大子序列和为16。 规定一个序列最大连续子序列和至少是 0如果小于0,其结果 为0。

  • 从中间节点分治
  • 每次算左max,右max,和含有中间节点的中max得到三者最大值即可
#include<bits/stdc++.h>

using namespace std;

const int N = 1e3 + 20;
int a[N];

long maxSubSum(int a[],int left,int right)
{ 
	int i,j;
	long maxLeftSum,maxRightSum;
	long maxLeftBorderSum ,leftBorderSum;
	long maxRightBorderSum ,rightBorderSum;
	
	//子序列只有一个元素时,自顶向下
	if (left==right)
	{
		if(a[left]>0) return a[left];
		else return 0; 
	} 
	int mid=(left+right)/2; //求中间位置
	
	//自底向上合并
	maxLeftSum = maxSubSum(a ,left,mid); //求左边
	maxRightSum = maxSubSum(a ,mid+1,right); //求右边
	
	maxLeftBorderSum=0,leftBorderSum=0;
	for (i=mid;i>=left;i--)//求出以左边加上a[mid]元素
	{ 
		leftBorderSum+=a[i]; //构成的序列的最大和
		if (leftBorderSum>maxLeftBorderSum) maxLeftBorderSum=leftBorderSum;
	}
	
	maxRightBorderSum=0 ,rightBorderSum=0;
	for (j=mid+1;j<=right;j++) //求出a[mid]右边元素
	{ 
		rightBorderSum+=a[j]; //构成的序列的最大和
		if (rightBorderSum>maxRightBorderSum) maxRightBorderSum=rightBorderSum;
	}
	
	return max(maxLeftSum ,max(maxRightSum,maxLeftBorderSum+maxRightBorderSum)); 
} 


int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	
	int n;
	cin >> n;
	for(int i = 1; i <= n; i ++ ) cin >> a[i];
	cout << maxSubSum(a,1,n) << endl;
}

13.寻找两个等长有序序列中位数

问题描述:对于一个长度为n的升序有序序列 a[1…n],处于中间位置的元素称为 a的中位数。设 计一个算法求给定的两个升序有序序列的中位数。

  • 拆分两组,每次算两者中位数,然后舍弃明显不存在的部分
  • 要注意两组元素数量是否相等
#include <bits/stdc++.h>

using namespace std;

const int N = 1e3 + 20;
int a[N], b[N];

int midNum(int a[], int s1, int t1, int b[], int s2, int t2) 
{
	int m1, m2;
	
	//两序列只有一个元素时返回较小者
	if (s1 == t1 && s2 == t2) return a[s1] < b[s2] ? a[s1] : b[s2];
	
	else 
	{
		m1 = (s1 + t1) / 2; //求a的中位数位置
		m2 = (s2 + t2) / 2; //求b的中位数位置
		
		//两中位数相等时返回该中位数
		if (a[m1] == b[m2]) return a[m1];
		
		if (a[m1] < b[m2]) 
		{ 
			if ((s1 + t1) % 2 == 0) s1 = m1; //a 取后半部分,奇数个元素
			else s1 = m1 + 1; //偶数个元素
			t2 = m2; //b取前半部分
			return midNum(a, s1, t1, b, s2, t2);
		} 
		
		else 
		{ 
			t1 = m1; //a取前半部分
			if ((s2 + t2) % 2 == 0) s2 = m2; //b 取后半部分,奇数个元素
			else s2 = m2 + 1; //偶数个元素
			return midNum(a, s1, t1, b, s2, t2);
		}
	}
}

int main() {
	ios_base::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i ++ ) cin >> a[i];
	for (int i = 1; i <= m; i ++ ) cin >> b[i];
	cout << midNum(a, 1, n, b, 1, m) << endl;
}

14.递归实现大整数乘法

  • 大整数*大整数
  • 将两个大整数都拆分为两部分,实现分治
  • 将计算化为为子问题的合并,并实现递归
    • string z0 = multiply(num1Low, num2Low);
      string z1 = multiply(num1High, num2High);
      string z2 = multiply(add(num1Low, num1High), add(num2Low, num2High));
      string z3 = subtract(subtract(z2, z1), z0);
    • string result = add(add(z0 + string(mid * 2, ‘0’), z1), z3 + string(mid, ‘0’));
#include <bits/stdc++.h>

using namespace std;

// 大整数相加函数
string add(string num1, string num2) {
	int carry = 0;
	string result;
	
	// 从最低位开始逐位相加
	int i = num1.length() - 1;
	int j = num2.length() - 1;
	
	while (i >= 0 || j >= 0 || carry > 0) {
		int digit1 = (i >= 0) ? num1[i] - '0' : 0;
		int digit2 = (j >= 0) ? num2[j] - '0' : 0;
		
		int sum = digit1 + digit2 + carry;
		carry = sum / 10; // 进位
		result = to_string(sum % 10) + result; // 把当前位的结果加到最终结果的前面
		i--;
		j--;
	}
	
	return result;
}

// 大整数相减函数
string subtract(string num1, string num2) {
	string result;
	int borrow = 0;
	
	// 从最低位开始逐位相减
	int i = num1.length() - 1;
	int j = num2.length() - 1;
	
	while (i >= 0) {
		int digit1 = num1[i] - '0';
		int digit2 = (j >= 0) ? num2[j] - '0' : 0;
		
		// 减去借位
		int diff = digit1 - digit2 - borrow;
		
		if (diff < 0) {
			diff += 10; // 借位
			borrow = 1;
		}
		else {
			borrow = 0;
		}
		
		result = to_string(diff) + result; // 把当前位的结果加到最终结果的前面
		i--;
		j--;
	}
	
	// 去除结果中的前导零
	size_t pos = result.find_first_not_of('0');
	if (pos != string::npos) {
		result = result.substr(pos);
	}
	
	return (result.empty()) ? "0" : result;
}

// 大整数乘法函数
string multiply(string num1, string num2) {
	int n = num1.length();
	int m = num2.length();
	
	// 基本情况:如果有一个操作数为0,则结果为0
	if (n == 0 || m == 0 || num1 == "0" || num2 == "0") {
		return "0";
	}
	
	// 基本情况:如果有一个操作数为1,则结果为另一个操作数
	if (num1 == "1") {
		return num2;
	}
	
	if (num2 == "1") {
		return num1;
	}
	
	// 如果操作数很小,直接相乘
	if (n <= 2 || m <= 2) {
		long long int result = stoll(num1) * stoll(num2);
		return to_string(result);
	}
	
	// 将操作数分成两部分
	int mid = min(n, m) / 2;
	string num1Low = num1.substr(0, n - mid);
	string num1High = num1.substr(n - mid);
	string num2Low = num2.substr(0, m - mid);
	string num2High = num2.substr(m - mid);
	
	// 递归计算子问题的乘积
	string z0 = multiply(num1Low, num2Low);
	string z1 = multiply(num1High, num2High);
	string z2 = multiply(add(num1Low, num1High), add(num2Low, num2High));
	string z3 = subtract(subtract(z2, z1), z0);
	
	// 计算最终的乘积
	string result = add(add(z0 + string(mid * 2, '0'), z1), z3 + string(mid, '0'));
	
	return result;
}

int main() {
	ios_base::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	//相乘的两个整数
	string num1;
	string num2;
	cin >> num1 >> num2;
	
	string product = multiply(num1, num2);
	
	cout << "两个整数乘积为: " << product << endl;
	
	return 0;
}

15.棋盘覆盖

问题描述:有一个2k×2k(k>0)的棋盘,恰好有一个方格与其他方格不同,称之为特殊方格。现在要用如下的L型骨牌覆 盖除了特殊方格外的其他全部方格,骨牌可以任意旋转,并且 任何两个骨牌不能重叠。请给出一种覆盖方法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 每次在中心位置防止一个L型方块,使得满足刚好覆盖中心位置四方格的三格,且空闲一格所处于的象限已经包含了一个已经被覆盖的方格
#include <bits/stdc++.h>

using namespace std;

const int MAX = 1025;

//问题表示
int k; //棋盘大小, 行列为2
int x, y; //特殊方格的位置

//求解问题表示
int board[MAX][MAX];
int title = 1;

void ChessBoard(int tr, int tc, int dr, int dc, int size) 
{
	if (size == 1) return; //递归出口
	int t = title; //取一个L型骨,其牌号为tile
	int s = size / 2; //分割棋盘
	title++;
	
	//考虑左上角
	if (dr < tr + s && dc < tc + s) //特殊方格在此象限中
		ChessBoard(tr, tc, dr, dc, s);
	else 
	{ 
		//此象限中无特殊方格
		board[tr + s - 1][tc + s - 1] = t; //用t号L型骨牌覆盖右下角
		ChessBoard(tr, tc, tr + s - 1, tc + s - 1, s);
	}
	
	//考虑右上角
	if (dr < tr + s && dc >= tc + s)
		ChessBoard(tr, tc + s, dr, dc, s); //特殊方格在此象限中
	else 
	{ 
		//此象限中无特殊方格
		board[tr + s - 1][tc + s] = t; //用t号L型骨牌覆盖左下角
		ChessBoard(tr, tc + s, tr + s - 1, tc + s, s); //将左下角作为特殊方格继续处理该象限
	}
	
	//考虑左下角
	if (dr >= tr + s && dc < tc + s) //特殊方格在此象限中
		ChessBoard(tr + s, tc, dr, dc, s);
	else 
	{ 
		//此象限中无特殊方格
		board[tr + s][tc + s - 1] = t; // 用t号L型骨牌覆盖右上角
		ChessBoard(tr + s, tc, tr + s, tc + s - 1, s);//将右上角作为特殊方格继续处理该象限
	}
	
	//考虑右下角
	if (dr >= tr + s && dc >= tc + s) //特殊方格在此象限中
		ChessBoard(tr + s, tc + s, dr, dc, s);
	else 
	{ 
		//此象限中无特殊方格
		board[tr + s][tc + s] = t; //用t号L型骨牌覆盖左上角
		ChessBoard(tr + s, tc + s, tr + s, tc + s, s); //将左上角作为特殊方格继续处理该象限
	}
}

int main() 
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	cin >> k >> x >> y;
	ChessBoard(1, 1, x, y, k);
	
	for(int i = 1; i <= k; i ++ )
	{
		for(int j = 1; j <= k; j ++ )
			cout << board[i][j] << ' ';
		cout << endl;
	}
}
  • 16
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值