计算机算法设计与分析 第二章 递归与分治策略 作业

各种排序的复杂度与稳定性回顾

各种排序的复杂度与稳定性回顾各种排序的复杂度与稳定性回顾

判断题

1-1
算法分析的两个主要方面是时间复杂度和空间复杂度的分析。
(2分)
T F

1-2
在具有N个结点的单链表中,访问结点和增加结点的时间复杂度分别对应为O(1)和O(N)。
T F

访问节点的时间复杂度为O(N)

1-3
仅基于比较的算法能得到的最好的“最坏时间复杂度”是O(NlogN)。
T F

1-4
对N个记录进行快速排序,在最坏的情况下,其时间复杂度是O(NlogN)。
T F

1-5
(neuDS)直接插入排序算法在最好情况下的时间复杂度为O(n)。
T F

单选题

2-1
下列排序算法中,哪种算法可能出现:在最后一趟开始之前,所有的元素都不在其最终的位置上?(设待排元素个数N>2)
A.冒泡排序
B.插入排序
C.堆排序
D.快速排序

2-2
对N个记录进行归并排序,归并趟数的数量级是:
A.O(logN)
B.O(N)
C.O(NlogN)
D.O(N2 )

2-3
对N个记录进行归并排序,空间复杂度为:
A.O(logN)
B.O(N)
C.O(NlogN)
D.O(N2)

2-4
采用递归方式对顺序表进行快速排序,下列关于递归次数的叙述中,正确的是:

A.每次划分后,先处理较长的分区可以减少递归次数
B.每次划分后,先处理较短的分区可以减少递归次数
C.递归次数与每次划分后得到的分区处理顺序无关
D.递归次数与初始数据的排列次序无关

2-5
用二分查找从100个有序整数中查找某数,最坏情况下需要比较的次数是:

A.7
B.10
C.50
D.99

二分查找的最大比较次数是
⌊ l o g ( N ) ⌋ + 1 \lfloor log(N) \rfloor+1 log(N)+1
二分搜索方法充分利用了元素间的次序关系,采用分治策略,可在最坏情况下用 O(logn) 时间完成搜索任务;

该题中有log100+1=6+1=7

2-6
用分治法解决一个规模为N的问题。下列哪种方法是最慢的?

A.每步将问题分成规模均为N/3的2个子问题,且治的步骤耗时O(N)
B.每步将问题分成规模均为N/3的2个子问题,且治的步骤耗时O(NlogN)
C.每步将问题分成规模均为N/2的3个子问题,且治的步骤耗时O(N)
D.每步将问题分成规模均为N/3的3个子问题,且治的步骤耗时O(NlogN)

C不仅子问题规模大,数目还多,合并子问题时间还长;

2-7
给定 100,000,000 个待排记录,每个记录 256 字节,内存为128MB。若采用简单2路归并,需要做多少轮?

A.10
B.9
C.8
D.7

1MB有4个记录,内存总共128×4 = 512个记录,512=29,则需要做 log 29 = 9(log以2为底)轮;

2-8
在外排序中,设我们有5个长度分别为2、8、9、5、3的有序段。则下列哪种归并顺序可以得到最短归并时间?

A.归并长度为2和3的有序段,得到段Run#1;将Run#1与长度为5的有序段归并,得到段Run#2;将Run#2与长度为8的有序段归并,得到段Run#3;将Run#3与长度为9的有序段归并
B.归并长度为2和3的有序段,得到段Run#1;将Run#1与长度为5的有序段归并,得到段Run#2;归并长度为8和9的有序段,得到段Run#3;归并Run#2和Run#3
C.归并长度为2和3的有序段,得到段Run#1;归并长度为5和8的有序段,得到段Run#2;归并Run#1和Run#2,得到段Run#3;将Run#3与长度为9的有序段归并
D.归并长度为2和3的有序段,得到段Run#1;归并长度为5和8的有序段,得到段Run#2;将Run#2与长度为9的有序段归并,得到段Run#3;归并Run#1和Run#3

2-9
(NeuDS_C++)对线性表进行二分查找时,要求线性表必须( )。

A.以顺序方式存储
B.以链接方式存储
C.以顺序方式存储,且结点按关键字有序排序
D.以链接方式存储,且结点按关键字有序排序

2-10
若数据元素序列{ 12, 13, 8, 11, 5, 16, 2, 9 }是采用下列排序方法之一得到的第一趟排序后的结果,则该排序算法只能是:

A.快速排序
B.选择排序
C.堆排序
D.归并排序

编程题

7-1 找第k小的数 (30 分)

题目描述

设计一个平均时间为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
//结尾无空行

参考代码1(不按题目提示):

#include <iostream>
#include <algorithm>
#include <math.h>

using namespace std;
int main()
{
	int n,k;
	cin>>n>>k;
	int a[n];
	for(int i=0;i<n;i++)
	{
		cin>>a[i];
	}
	sort(a,a+n);
	cout<<a[k-1]<<endl;
}

参考代码2(按题目提示):

# include <iostream>
using namespace std;

int partition(int a[], int left, int right) {
	int i = left, j = right+1;
	int x = a[left];
	//将小于x的元素交换到左边区域,将大于x的元素交换到右边区域
	while(true) {
		while(a[++i] < x && i < right);
		while(a[--j] > x);
		if(i >= j)
			break;
		swap(a[i], a[j]);
	} 
	a[left] = a[j];
	a[j] = x;
	return j;
}

int find(int a[], int left, int right, int k) {
	int pos = partition(a, left, right);
	if(k-1 == pos){
		cout << a[k-1];
	}else if(k-1 < pos){
		find(a, left, pos-1, k);
	}else if(k-1 > pos){
		find(a, pos+1, right, k);
	}
	return 0;
}

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

习题答案

#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;
}

作者:陈晓梅
单位:广东外语外贸大学
代码长度限制:16 KB
时间限制:400 ms
内存限制:64 MB

7-2 求逆序对数目 (30 分)

注意:本问题算法的时间复杂度要求为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.

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

题目翻译后:
7-2 求逆序对数目 (30 分)

题目描述

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

题目来源:http://poj.org/problem?id=1804 背景 Raymond Babbitt 把他的兄弟 Charlie 逼疯了。最近,雷蒙德瞥了一眼,就数出了 246 根牙签瞬间洒在地板上。他甚至可以数扑克牌。Charlie 也希望能够做这样很酷的事情。他想在类似的任务中击败他的兄弟。

问题 这是查理的想法。想象一下,你得到了 N 个数字的序列。目标是移动数字,以便在最后对序列进行排序。唯一允许的操作是交换两个相邻的数字。让我们尝试一个例子: 开始: 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 交换(8 2) 0 3 2 8 交换(3 2) 0 2 3 8 交换(3 8) 0 2 8 3 交换(8 3) 0 2 3 8

所以序列 (2 8 0 3) 可以用相邻数字的九次交换进行排序。但是,甚至可以使用三个这样的交换对其进行排序: 开始于: 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

问题是:对给定序列进行排序的相邻数字的最小交换次数是多少?由于查理没有雷蒙德的心智能力,他决定作弊。这就是你发挥作用的地方。他要求你为他编写一个计算机程序,用 O(nlogn)来回答这个问题。请放心,他会为此付出非常丰厚的奖金。

输入格式:
第一行包含序列的长度N (1 <= N <= 1000); 第二行包含序列的N个元素(每个元素为[-1000000, 1000000]中的整数)。此行中的所有数字均由单个空格分隔。

输出格式:
打印一行,其中包含对给定序列进行排序所需的相邻数字的最小交换次数。

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

6
-42 23 6 28 -100 65537
//结尾无空行

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

5
//结尾无空行

参考代码

#include <iostream>
using namespace std;
int count=0;
void swap(int *a,int *b){//注意swap函数的写法 
	int t;
	t=*a;
	*a=*b;
	*b=t;
	
}
int minn(int a[],int n,int flag){
	int min=a[flag];
	for(int i=flag;i<n;i++)
	{
		if(a[i]<min)
		{
			min=a[i];
		}
		
	}
	return min;
}
void left_m(int a[],int n,int flag,int k)
{
	while(k-flag>=1&&k-1>=0)
	{
		int t;
		t=a[k];
		a[k]=a[k-1];
		a[k-1]=t;
		k--; 
		count++;
	}
	
	
}
int main()
{
	int n;
cin>>n;
	int a[n];
//	a[0]=1;a[1]=10;
//	swap(a[0],a[1]);
//	cout<<a[0];
	for(int i=0;i<n;i++)
	{
		cin>>a[i];
	 } 
	 int flag=0;
	while(true)
	{
		int min=minn(a,n,flag);
//		cout<<min;
		int k;
		for(int i=0;i<n;i++)
		{
			if(a[i]==min)
			{
				k=i;
				break;
			}
		 } 
		 left_m(a,n,flag,k);
		 flag++;
		 
		 if(flag==n)
		 {
		 	break;
		 }
	}
	cout<<count;

 } 

习题答案

#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;
}

求逆序对方法详解
求逆序对数目,分治、递归、归并排序的思想
《剑指 Offer》 51. 数组中的逆序对【LeetCode 力扣官方题解】

7-3 maximum number in a unimodal array (25 分)

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.

翻成中文后:

7-3 单峰数组中的最大数(25 分)

题目描述

你是一个给定的 n 个不同元素的单峰数组,这意味着它的条目按递增顺序排列直到它的最大元素,然后它的元素按降序排列。给出一个算法来计算在 O(log n) 时间内运行的最大元素。

输入格式:
第一行一个整数n,1<= n <= 10000。第二行用空格隔开的N个整数,是一个单峰数组。

输出格式:
一个整数,它是数组中的最大整数

输入样例:

7
1 2 3 9 8 6 5
//结尾无空行

输出样例:

9
//结尾无空行

参考代码:

#include <iostream>
using namespace std;
int main()
{
	int n;
	cin>>n;
	int a[n];
	for(int i=0;i<n;i++)
	{
		cin>>a[i];
	}
	bool flag=false;
	for(int i=0;i<n;i++)
	{
		if(a[i]<=a[i-1]&&i!=0)
		{
			cout<<a[i-1];
			flag=true;
			break;
		}
	}
	if(!flag)
	{
		cout<<a[n-1];
	}
	
//	int max=a[0];
//	for(int i=0;i<n;i++)
//	{
//		if(a[i]>max)
//		{
//			max=a[i];
//		}
//	}
//	cout<<max;
	return 0;
}

习题答案

#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-4 二分法求函数的零点 (25 分)

题目描述

有函数:f(x)=x5 −15x4+85x3−225x2+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 <algorithm>
#include <iomanip>
#include <math.h>
using namespace std;
double f(double x)
{
	double res=pow(x,5)-15*pow(x,4)+85*pow(x,3)-225*pow(x,2)+274*x-121;
	return res;
	
}
int main()
{
//	cout<<f(2.4);
double x;
double left=1.5,right=2.4;
double middle=(left+right)/2;

while(fabs(f(middle))>=1e-7)
{
	if(f(middle)>0)
	{
		left=middle;
		middle=(left+right)/2;
	}
	else if(f(middle)<0)
	{
		right=middle;
		middle=(left+right)/2;
	}
	
}
//cout<<f(middle)<<endl;
cout<<setiosflags(ios::fixed)<<setprecision(6)<<middle;
//printf("%.6lf",middle);


}

习题答案

#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-5 派 (15 分)

题目描述

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

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

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

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

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

输入样例:

3 3
4 3 3
//结尾无空行

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

25.133
//结尾无空行

参考代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
double pi=acos(-1.0);
int n,f;
double r[10010];
bool check(double d){
    int cnt=0;
    for(int i=0;i<n;i++){
        int tmp=floor(r[i]/d);
        cnt+=tmp;
    }
    if(cnt>=f+1) return true;
    else return false;
}
int main(){
    cin>>n>>f;
    for(int i=0;i<n;i++){
        int tmp;
        cin>>tmp;
        r[i]=tmp*tmp*pi;
    }
    sort(r,r+n);
    double l=1,h=r[n-1];
    double s=-1;
    while(h-l>1e-5){
        double mid=l+(h-l)/2;
        if(check(mid)) 
            l=mid;
        else
            h=mid;
    }
    printf("%.3lf",l);
    return 0;
}

习题答案

#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;
}
  • 各大排序的回顾理解
  • 本章书上算法的自学理解
  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GCTTTTTT

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

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值