算法第二章上机实验报告
软工1802 20181003080 吴子熙
实验题目一:
输入n值(1<=n<=1000)、n个非降序排列的整数以及要查找的数x,使用二分查找算法查找x,输出x所在的下标(0~n-1)及比较次数。若x不存在,输出-1和比较次数。
输入格式:
输入共三行: 第一行是n值; 第二行是n个整数; 第三行是x值。
输出格式:
输出x所在的下标(0~n-1)及比较次数。若x不存在,输出-1和比较次数。
输入样例:
4
1 2 3 4
1
输出样例:
0
2
代码如下:
#include <iostream> using namespace std; int binarysearch(int *a, int n, int key, int &count){ int left = 0; int right = n-1; while(left <= right){ int mid = (left + right)/2; count++; if(key == a[mid]){ return mid; } else if(key < a[mid]){ right = mid - 1; } else { left = mid + 1; } } return -1; } int main(){ int n , key; int count = 0; int *a = new int [1000]; cin>>n; for(int i = 0; i < n; i++){ cin>>a[i];} cin>>key; int median = binarysearch(a, n, key, count); cout<<median<<endl; cout<<count; }
算法描述:
第一题比较简单,首先说算法思路。
题目要求二分搜索,而且输入数组为非降序,于是可以采用取数组的中值下标的方法,判断a[mid]和目标元素key的大小关系。并将其放入while循环当中,不断地缩小
搜索范围。
如果相等则直接返回count计数器的值即可。
若a[mid]>key,则使得数组的右极限缩减到mid元素之前,即right下标等于mid-1,否则左极限增加到mid元素之后,即left下标等于mid+1。同时count++进行一次查找计数。
选择执行while的条件为
left <= right
则最后一次循环结束之后仍然a[mid]不等于key的,即可结束循环返回-1,元素不存在。
该算法的时间复杂度为O(log(n))
通过T(n) = T(n/2)+O(1)算得。
空间复杂度为O(1)。
实验题目二:
题目来源:《计算机算法设计与分析》,王晓东
设a[0:n-1]是已排好序的数组,请改写二分搜索算法,使得当x不在数组中时,返回小于x的最大元素位置i和大于x的最小元素位置j。当搜索元素在数组中时,i和j相同,均为x在数组中的位置。
输入格式:
输入有两行:
第一行是n值和x值; 第二行是n个不相同的整数组成的非降序序列,每个整数之间以空格分隔。
输出格式:
输出小于x的最大元素的最大下标i和大于x的最小元素的最小下标j。当搜索元素在数组中时,i和j相同。 提示:若x小于全部数值,则输出:-1 0 若x大于全部数值,则输出:n-1的值 n的值
输入样例:
在这里给出一组输入。例如:
6 5
2 4 6 8 10 12
输出样例:
在这里给出相应的输出。例如:
1 2
代码如下:
#include <iostream> using namespace std; int binarysearch1(int *a, int n, int key){ int left = 0; int right = n-1; int i = 0, j =0; while(left <= right){ int mid = (left + right)/2; if(key == a[mid]){ i = j = mid; cout<<i<<" "<<j; return mid; } else if(key < a[mid]){ right = mid - 1; } else { left = mid + 1; } i = right; j = left; } cout<<i<<" "<<j; } int main(){ int n ,key; int *a = new int [10000]; cin>>n>>key; for(int i = 0; i < n; i++){ cin>>a[i]; } binarysearch1(a, n ,key); }
算法思路:
本体基于第一题的二分搜索进行修改,核心内容不变,只需添加输出的内容即可。
在最后一次执行while循环的时,条件为left等于right,证明现在数组只剩下一个元素了,所以left和right下标所指向的一定是同一个元素。这个元素和key不相等则证明不存在。
再往下执行,得出的mid下标也是指向该元素,再通过与所找元素比较,根据判断结果将left或者right进行最后一次的下标位置移动。
并根据要求的“输出小于x的最大元素的最大下标i和大于x的最小元素的最小下标j”,把right的值给i,left给j,再结束循环,输出的i和j就分别是小于x的最大元素的最大下标和大于x的最小元素的最小下标。
该算法的时间复杂度为O(log(n)),空间复杂度为O(1)
因为算法核心和第一题一样所以复杂度计算也相同。
实验题目三
已知有两个等长的非降序序列S1, S2, 设计函数求S1与S2并集的中位数。有序序列,的中位数指A(N−1)/2的值,即第⌊个数(A0为第1个数)。
输入格式:
输入分三行。第一行给出序列的公共长度N(0<N≤100000),随后每行输入一个序列的信息,即N个非降序排列的整数。数字用空格间隔。
输出格式:
在一行中输出两个输入序列的并集序列的中位数。
输入样例1:
5
1 3 5 7 9
2 3 4 5 6
输出样例1:
4
输入样例2:
6
-100 -10 1 1 1 1
-50 0 2 3 4 5
输出样例2:
1
代码如下
#include<iostream> using namespace std; int MidSerch(int a[], int b[], int la, int ra, int lb, int rb) { int ma = (la + ra) / 2; int mb = (lb + rb+1) / 2; if (la == ra&&lb != rb) { if (a[la] > b[rb])return b[rb]; else return a[la]; } if (lb == rb&&la != ra) { if (b[lb] > a[ra])return a[ra]; else return b[lb]; } if (la == ra&&lb == rb) { if (a[la] > b[lb])return b[lb]; else return a[la]; } else { if (la == ra - 1 && lb == rb - 1) { ma = (la + ra) / 2;//第一个序列求中位数方法不变 mb = (lb + rb) / 2;//第二个序列变为与第一序列相同的方法求得中位数 //但由于元素个数只有两个,所以括号内可写为:mb = mb - 1; } if (a[ma]==b[mb]) { return a[ma]; } else if (a[ma] > b[mb]) { MidSerch(a, b, la, ma ,mb, rb); } else{ MidSerch(a, b, ma, ra, lb, mb); } } } int main() { int n; cin >> n; int *a = new int[n]; int *b = new int[n]; for (int i = 0; i < n; i++) { cin >> a[i]; } for (int i = 0; i < n; i++) { cin >> b[i]; } cout<<MidSerch(a, b, 0, n - 1, 0, n - 1); system("pause"); }
本题本人尚未完全清楚,算法思路是否完全严谨,能看出算法思路是首先判断上下两个数组的中位数大小,然后小的取其数组的右侧剩余元素,大的选择其左侧剩余元素,组成一个新数组,继续二分。
感觉未能够完全处理并集之后元素重复该如何处理的问题
该代码若测试如下数据
5
2 2 2 3 5
2 2 7 8 9
结果数值为2,显然是错误的。要解决这个问题还需要再判断条件上进行补充和修复,亦或是需要新的算法?
算法时间复杂度是O(log(n)),空间复杂度:由于该算法并没有动态申请空间,故空间复杂度为O(1)。
心得体会:本次实验是针对二分算法的上机实验,在结对编程的互相合作中加深了对二分算法的理解,已经粗略地学会了二分算法的应用。
但是对时间复杂度的要求还是未能满足(第三题),目前我认为依照上述算法虽然能够达成要求的时间复杂度(不重新排序)但是未能普及所有情况。
然后感谢有一个很强悍的队友出谋划策并悉心指导,互相帮助,我受益匪浅,特别是学会了一些代码规范以及巧妙的思路。希望以后能更多的进行结对编程。