计算机算法设计与分析(第二章上机实践题)

7-1)二分查找

输入n值(1<=n<=1000)、n个非降序排列的整数以及要查找的数x,使用二分查找算法查找x,输出x所在的下标(0~n-1)及比较次数。若x不存在,输出-1和比较次数。

#include <iostream>
using namespace std;

int count = 0; 
//在数组a[left...right]中查找元素x
int biSearch(int x, int a[],  int left, int right) {
  if (left > right) //当数组中没有元素时
return -1;
count++;
int middle = (left + right) / 2;
  if (a[middle] == x) //如果找到,直接返回位置
return middle;
  else if (a[middle] > x) //如果中间的数字大于x,在数组左半部分递归查找
returnbiSearch(x, a, left, middle - 1);
  else //否则在数组右半部分递归查找
returnbiSearch(x, a, middle + 1, right);
}

int main() {
int n; 
cin>> n;
int a[n];
for (inti = 0; i< n; i++)
	cin>> a[i];
int x;
cin>> x;

cout<<biSearch(x, a, 0, n - 1) <<endl;
cout<< count <<endl;
}

7-2)改写二分搜索算法

设a[0:n-1]是已排好序的数组,请改写二分搜索算法,使得当x不在数组中时,返回小于x的最大元素位置i和大于x的最小元素位置j。当搜索元素在数组中时,i和j相同,均为x在数组中的位置。

#include <iostream>
using namespace std;

voidbiSearch(int x, int a[],  int left, int right) {
  if (left > right) { //当数组中没有元素时
cout<< right << " " << left <<endl;
	return;
  }
int middle = (left + right) / 2;
if (a[middle] == x) {
	cout<< middle << " " << middle <<endl;
	return;
  } else if (a[middle] > x)
returnbiSearch(x, a, left, middle - 1);
else
returnbiSearch(x, a, middle + 1, right);
}

int main() {
int n; 
int x;
cin>> n >> x;
int a[n];
for (inti = 0; i< n; i++)
	cin>> a[i];

biSearch(x, a, 0, n - 1);
}

7-3)两个有序序列的中位数

已知有两个等长的非降序序列S1, S2, 设计函数求S1与S2并集的中位数。有序序列A0,A1,⋯,AN−1的中位数指A(N−1)/2的值,即第⌊(N+1)/2⌋个数(A0为第1个数)。

方法一:
#include <iostream>
using namespace std;


//查找两个等长有序系列的中位数
/*
解题思路:中位数满足大于且仅大于n-1个元素,小于且仅小于n个元素
a_mid = (a_left + a_right) / 2
b_mid = (b_left + b_right) / 2
当a[a_mid]=b[b_mid]时,中位数为a[a_mid]
当a[mid]<b[mid]时:
若n为奇数,中位数存在范围为a[a_mid...a_right], b[b_left...b_mid]
若n为偶数,中位数存在范围为a[a_mid+1...a_right], b[b_left...b_mid]
当a[mid]>b[mid]时:
若n为奇数,中位数存在范围为a[a_left...a_mid], b[b_mid...b_right]
若n为偶数,中位数存在范围为a[a_left...a_mid], b[b_mid+1...b_right]
当问题规模为1时,只有两个数字,直接输出较小的值
*/ 
int find(int a[], int a_l, int a_r, int b[], int b_l, int b_r) {
int a_m, b_m;
int num;

  //如果查找数组包含两个数字,则直接找出中位数
if (a_r == a_l) {
	return a[a_l] < b[b_l] ? a[a_l] : b[b_l];
  }

  //第1个数组查找范围的中位数a_m。
a_m = (a_l + a_r) / 2;
  //第2个数组查找范围的中位数b_m。
b_m = (b_l + b_r) / 2;

  //如果两个中位数相等,则找到退出
if(a[a_m] == b[b_m])
  {
num = a[a_m];
  } else if(a[a_m] < b[b_m]) {
    //如果n为偶数,右半部分的开始位置为中位数位置加1
if ((a_r - a_l + 1) % 2 == 0) 
	a_m += 1; 
    //第1个数组查找范围为右半部分,第2个数组查找范围为左半部分
num = find(a, a_m, a_r, b, b_l, b_m);   		

  }
else {
    //如果n为偶数,右半部分的开始位置中位数位置加1
if ((b_r - b_l + 1) % 2 == 0) 
		b_m += 1;
    //第1个数组查找范围为左半部分,第2个数组查找范围为右半部分
num = find(a, a_l, a_m, b, b_m, b_r);	
  }
return num;

}

int main() {
int n;
cin>> n;
int a[n];//数组1
int b[n];//数组2

for (int i = 0; i< n; i++)
cin>> a[i];
for (int i = 0; i< n; i++)
cin>> b[i];
cout<< find(a, 0, n - 1, b, 0, n - 1) <<endl;

return 0;
}
方法二:
#include <stdio.h>
int main(){
intn,m;
	scanf("%d",&n);
	int a[n],b[n];
	for(inti=0; i<n;i++){
		scanf("%d",&a[i]);
	}
	for(inti=0; i<n;i++){
		scanf("%d",&b[i]);
	}
	inta_flag=0,b_flag=0;
	while(a_flag+b_flag<n-1){
		if(a[a_flag]>=b[b_flag])
			b_flag++;
		else
			a_flag++;
	}
	m=a[a_flag]>b[b_flag]?b[b_flag]:a[a_flag];
	printf("%d",m);
}

第二章作业题

7-2)找第k小的数

设计一个平均时间为O(n)的算法,在n(1<=n<=1000)个无序的整数中找出第k小的数。

提示:函数 int partition(int a[],int left,int right)的功能是根据a[left]~a[right]中的某个元素x(如a[left])对a[left]~a[right]进行划分,划分后的x所在位置的左段全小于等于x,右段全大于等于x,同时利用x所在的位置还可以计算出x是这批数据按升非降序排列的第几个数。因此可以编制int find(int a[],int left,int right,int k)函数,通过调用partition函数获得划分点,判断划分点是否第k小,若不是,递归调用find函数继续在左段或右段查找。
输入格式:
输入有两行:

第一行是n和k,0<k<=n<=10000

第二行是n个整数

输出格式:
输出第k小的数

输入样例:
在这里给出一组输入。例如:

10 4
2 8 9 0 1 3 6 7 8 2
输出样例:
在这里给出相应的输出。例如:

2

#include <iostream>
using namespace std;

//利用数组的第一个元素将数组划分为两部分,前面都比第一个元素小,
//后面都比第一个元素大,与快速排序划分完全相同
int partition(int a[], int left, int right) {
int i = left + 1;
int j = right;
int x = a[left];
while(true) {
while (a[i] < x &&i< right) i++;
while (a[j] > x) j--;
if (i>= j) break;
swap(a[i], a[j]);
  }
swap(a[left], a[j]);
return j;
}

//在下标从left到right的数组元素中输出第K小的数
int find(int num[], int left, int right, int k) {
int p = partition(num, left, right); //返回划分元素的下标
int n = p - left + 1; // 划分元素为第n小
int t;
if ( n == k)
        t = num[p];
    else if (n < k) //找右半部分的第k-n小
        t = find(num, p + 1, right, k - n);
    else //找左半部分的第k小
        t = find(num, left, p - 1, k);
return t;
}

int main() {
int n, k;
cin>> n >> k;
int num[n];
for (int i = 0; i< n; i++)
cin>>num[i];
cout<< find(num, 0, n - 1, k) <<endl;
return 0;
}

7-3)求逆序对数目

注意:本问题算法的时间复杂度要求为O(nlogn), 否则得分无效

题目来源:http://poj.org/problem?id=1804 Background Raymond Babbitt drives his brother Charlie mad. Recently Raymond counted 246 toothpicks spilled all over the floor in an instant just by glancing at them. And he can even count Poker cards. Charlie would love to be able to do cool things like that, too. He wants to beat his brother in a similar task.

Problem Here’s what Charlie thinks of. Imagine you get a sequence of N numbers. The goal is to move the numbers around so that at the end the sequence is ordered. The only operation allowed is to swap two adjacent numbers. Let us try an example: Start with: 2 8 0 3 swap (2 8) 8 2 0 3 swap (2 0) 8 0 2 3 swap (2 3) 8 0 3 2 swap (8 0) 0 8 3 2 swap (8 3) 0 3 8 2 swap (8 2) 0 3 2 8 swap (3 2) 0 2 3 8 swap (3 8) 0 2 8 3 swap (8 3) 0 2 3 8

So the sequence (2 8 0 3) can be sorted with nine swaps of adjacent numbers. However, it is even possible to sort it with three such swaps: Start with: 2 8 0 3 swap (8 0) 2 0 8 3 swap (2 0) 0 2 8 3 swap (8 3) 0 2 3 8

The question is: What is the minimum number of swaps of adjacent numbers to sort a given sequence?Since Charlie does not have Raymond’s mental capabilities, he decides to cheat. Here is where you come into play. He asks you to write a computer program for him that answers the question in O(nlogn). Rest assured he will pay a very good prize for it.

输入格式:
The first line contains the length N (1 <= N <= 1000) of the sequence; The second line contains the N elements of the sequence (each element is an integer in [-1000000, 1000000]). All numbers in this line are separated by single blanks.

输出格式:
Print a single line containing the minimal number of swaps of adjacent numbers that are necessary to sort the given sequence.

输入样例:
在这里给出一组输入。例如:

6
-42 23 6 28 -100 65537
输出样例:
在这里给出相应的输出。例如:

5

#include <iostream>
using namespace std;

//分治法求排好序的a数组中l~m与m+1~r数组元素的交叉逆序数
//返回逆序数
int crossInv(int a[], int l, int m, int r) {
int i = l; 
int j = m + 1;
int k = 0;
int b[r - l + 1];
int count = 0; //保存交叉逆序数
while (i<= m && j <= r) {
if (a[i] <= a[j]) {
      //将a数组l~m子数组的数组元素复制到b数组时,m+1~r子数组已经复制了几个元素到b数组,就是该数组元素的逆序数
count += j - (m + 1);
b[k++] = a[i++];
    }
else
b[k++] = a[j++];
  }

  ///将a数组l~m子数组剩余数组元素复制到b数组,同时计算逆序数
while (i<= m) {
b[k++] = a[i++];
count += j - (m + 1);
  }

while (j <= r)
b[k++] = a[j++];

  //将b数组排好序的数字拷贝回a[l..r] 
for (int i = 0; i< k; i++)
a[l + i] = b[i];
return count;
}

int inverse(int a[], int l, int r) {
if (l == r)
return 0;
int m = (l + r) / 2;
int count = 0; //保存逆序数
  count += inverse(a, l, m); //左边数组的逆序数
  count += inverse(a, m + 1, r); //右边数组的逆序数
  count += crossInv(a, l, m, r); //左右数组的交叉逆序数
return count;
}

int main() {
int n;
cin>> n;
int* a = new int[n];
for (int i = 0; i< n; i++)
cin>> a[i];
int count = inverse(a, 0, n - 1);
cout<< count <<endl;
delete a;
return 0;
}

7-4) maximum number in a unimodal array

You are a given a unimodal array of n distinct elements, meaning that its entries are in increasing order up until its maximum element, after which its elements are in decreasing order. Give an algorithm to compute the maximum element that runs in O(log n) time.

输入格式:
An integer n in the first line, 1<= n <= 10000. N integers in the seconde line seperated by a space, which is a unimodal array.

输出格式:
A integer which is the maximum integer in the array

输入样例:
7
1 2 3 9 8 6 5
输出样例:
9

#include<iostream>
using namespace std;

const int MAXN = 10010;

int find(int* a, int l, int r) {
	//如果只有一个数直接返回
	if (l == r)
		return a[l];
		
	int mid = (l + r) / 2;
	//如果下降,找左半段;否则找右半段
	if (a[mid] > a[mid + 1])
		return find(a, l, mid);
	else
		return find(a, mid + 1, r);
}

int main() {
	int n;
	cin>> n;
	int a[n];
	for (int i = 0; i< n; i++) {
		cin>> a[i];
	}
	cout<<find(a, 0, n - 1);
	return 0;
}

7-5)二分法求函数的零点

有函数:f(x)=x
​5
​​ −15x
​4
​​ +85x
​3
​​ −225x
​2
​​ +274x−121 已知f(1.5)>0,f(2.4)<0 且方程f(x)=0 在区间[1.5,2.4] 有且只有一个根,请用二分法求出该根。 提示:判断函数是否为0,使用表达式 fabs(f(x)) < 1e-7
输入格式:
无。

输出格式:x
该方程在区间[1.5,2.4]中的根。要求四舍五入到小数点后6位。。

输入样例:

输出样例:

#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;

//计算函数的值
double f(double x) {
	return pow(x, 5) - 15 * pow(x, 4) + 85 * pow(x, 3) - 225 * pow(x, 2) + 274 * x - 121;
}

//分治法。利用递归求解
double solve(double l, double r) {
	if (r - l < 1e-7) return l; //当区间近似为一个点时,返回
	double mid = (l + r) / 2;
	double value = f(mid);
	//中点的值如果近似为0,返回中点;如果大于零,查找右半区间;
	//如果小于零,查找左半区间
	if (fabs(value)  < 1e-7) 
		return mid;
	else if (value > 0) 
		return solve(mid, r);
	else
		return solve(l, mid);
}

int main() {
	double l = 1.5;
	double r = 2.4;
	cout<<fixed <<setprecision(6) << solve(1.5, 2.4) <<endl;
	return 0;
}

7-6)(选做题)派

我的生日要到了!根据习俗,我需要将一些派分给大家。我有N个不同口味、不同大小的派。有F个朋友会来参加我的派对,每个人会拿到一块派(必须一个派的一块,不能由几个派的小块拼成;可以是一整个派)。

我的朋友们都特别小气,如果有人拿到更大的一块,就会开始抱怨。因此所有人拿到的派是同样大小的(但不需要是同样形状的),虽然这样有些派会被浪费,但总比搞砸整个派对好。当然,我也要给自己留一块,而这一块也要和其他人的同样大小。

请问我们每个人拿到的派最大是多少?每个派都是一个高为1,半径不等的圆柱体。

输入格式:
第一行包含两个正整数N和F,1 ≤ N, F ≤ 10 000,表示派的数量和朋友的数量。 第二行包含N个1到10000之间的整数,表示每个派的半径。

输出格式:
输出每个人能得到的最大的派的体积,精确到小数点后三位。

输入样例:
3 3
4 3 3
输出样例:
在这里给出相应的输出。例如:

25.133

#include <iostream>
#include <iomanip>
using namespace std;
const double PI = 3.1415926;
const int MAX_NUM = 100002;

double pie[MAX_NUM];//每个派的体积
int n;//派的数量
int f;//人数

double maxPie(double l, double r) { 
	if (r - l < 1e-7) 
		return r;
	double mid = (l + r) / 2;
	
	//判断每份大小为mid的派最多可以分多少份
	int max = 0;
	for (int i = 0; i< n; i++)
		max += int(pie[i] / mid);
		
	//如果够分,利用二分找更大的派;否则找更小的派
	if (max >= f + 1)
		return maxPie(mid, r);
	else
		return maxPie(l, mid);
}

int main() {
	cin>> n >> f;
	double sum = 0;
	double max = 0;
	int r;
	for (int i = 0; i< n; i++) {
		cin>> r;
		pie[i] = PI * r * r;
		sum += pie[i];
		if (pie[i] > max)
			max = pie[i];
	}
	
	//每份派的最大值为所有派的体积平分
	double avg = sum / f;

	cout<< fixed <<setprecision(3) <<maxPie(0, max) <<endl;
}

  • 2
    点赞
  • 15
    收藏
  • 打赏
    打赏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:技术工厂 设计师:CSDN官方博客 返回首页
评论

打赏作者

小番茄夫斯基

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值