二分查找你们都不陌生,能够说除了最简单的顺序查找以外,咱们第二个接触的查找算法就是二分查找了。顺序查找的时间复杂度是O(n),二分查找的时间复杂度为O(logn)。在面试中二分查找被考察的几率仍是比较高的,上次去面试时就遇到手写二分查找的题目。二分查找不难,但咱们要能作到准确、快速地写出二分查找的代码,能对二分查找的效率作出分析,还可以将二分查找的思想来解决其余的问题。html
二分查找要求序列自己是有序的。所以对于无序的序列,咱们要先对其进行排序。如今咱们手头有一有序序列:
array[10] = {2,4,5,7,,8,9,13,23,34,45},则二分查找的过程为:面试
如今咱们在数组{2,4,5,7,,8,9,13,23,34,45}中查找元素23,过程如图:算法
可见,每一次元素比较均可以把待查范围缩小1/2,所以二分查找的时间复杂度为o(logn)。数组
二分查找代码简单,能够递归或迭代地实现。递归容易实现,代码简单,但待查元素数量巨大时,递归深度也随之增大(logn的关系),应考虑是否会发生栈溢出。迭代的实现也不复杂,但咱们力求准确简洁。数据结构
//查找成功时返回查找元素在数组中的下标;查找失败时返回-1 template <typename T> int BinarySearch(const T array[], int start, int end, const T& value) { if (start>end) return -1; int middle = (start + end) / 2; if (array[middle] == value) return middle; if (array[middle] < value) { return BinarySearch(array, middle+1, end, value); } return BinarySearch(array, start, middle-1, value); };
template <typename T> int BinarySearch(const T array[], int start, int end, const T& value) { int result = -1; while (start <= end) { int middle = (start + end) / 2; if (array[middle] == value) { result = middle; break; } if (array[middle] < value) { start = middle + 1; } else end = middle - 1; } return result; }
为了方便用户使用,咱们定义一个接口:函数
//用户使用接口 template <typename T> int BinarySearch(const T array[], int length, const T& value) { if (array == nullptr || length <= 0) return -1; return BinarySearch(array, 0, length - 1, value); }
使用实例:测试
int _tmain(int argc, _TCHAR* argv[]) { int array[10] = { 2, 4, 5, 7, 8, 9, 13, 23, 34, 45 }; int i; while (cin >> i) { cout << BinarySearch(array, 10, i) << endl; } return 0; }
这是对直接插入排序的一种优化策略,可以有效减小插入排序的比较次数。
直接插入排序的思路是对于无序序列的第一个元素,从后至前进行顺序查找 扫描有序序列寻找合适的插入点。改进后的二分插入排序算法使用二分查找在有序序列中查找插入点,将插入排序的比较次数降为O(log2n)。这个思路的实现代码为:优化
/*二分查找函数,返回插入下标*/ template <typename T> int BinarySearch(T array[], int start, int end, T k) { while (start <= end) { int middle = (start + end) / 2; int middleData = array[middle]; if (middleData > k) { end = middle - 1; } else start = middle + 1; } return start; } //二叉查找插入排序 template <typename T> void InsertSort(T array[], int length) { if (array == nullptr || length < 0) return; int i, j; for (i = 1; i < length; i++) { if (array[i]<array[i - 1]) { int temp = array[i]; int insertIndex = BinarySearch(array, 0,i, array[i]);//使用二分查找在有序序列中进行查找,获取插入下标 for (j = i - 1; j>=insertIndex; j--) //移动元素 { array[j + 1] = array[j]; } array[insertIndex] = temp; //插入元素 } } }
若对插入排序不了解,能够看数据结构图文解析之:直接插入排序及其优化(二分插入排序)解析及C++实现。编码
问题描述:把一个数组最开始的若干个元素搬到数组的末尾,咱们称为数组的旋转,输入一个递增排序数组的一个旋转,输出旋转数组的最小元素。例如数组{4,5,6,1,2,3}为数组 {1,2,3,4,5,6}的一个旋转,最小旋转元素为1.3d
解决思路:
寻找数组中最小的元素,咱们能够遍历数组,时间复杂度为O(n)。可是借助二分查找的思想,咱们可以在O(logn)的时间复杂度内找到最小的元素。旋转数组的特色是数组中两个部分都分别是有序的,以{4,5,6,1,2,3}为例,{4,5,6}是非递减的,{1,2,3}也是非递减的:
咱们能够定义两个索引:start指向第一部分的起始位置;end指向第二部分的最后一个元素,如图所示。因为数组在旋转前总体有序,故array[start]>=array[end],而中间值array[middle]知足:
循环执行上述操做,当start与end相邻时,end所指即是数组中的最小元素:
end所指元素即是最小元素。
这个寻址的过程有一个隐喻的要求:中间这个元素必须可以判断它是属于第一部分仍是第二部分。在有些输入下,这个要求不能知足,例如数组:{0,1,1,1,1},它的两个选择数组为:
此时因array[middle] == array[start] == array[end] 而没法判断array[middle]属于哪一部分,咱们只能进行顺序查找找出最小元素。
如图:
此时没法肯定array[middle]是属于第一部分仍是第二部分,这种状况下咱们须要进行顺序查找。
所以,咱们的代码实现为:
//顺序查找函数 int Min(int array[], int length) { int result = array[0]; for (int i = 1; i < length; i++) { if (array[i] < result) result = array[i]; } return result; } int MinInRotation(int array[],int length) { int result = -999; if (array == nullptr || length < 0) return result; int start = 0; int end = length - 1; int middle = start; while (start < end) { if (start + 1 == end) { result = array[end]; break; } middle = (start + end)/2; //若是赶上特殊状况,则须要进行顺序查找 if (array[middle] == array[start] && array[middle] == array[end]) return Min(array,length); //调用顺序查找函数 //不然;中间元素属于第一部分 if (array[middle]>=array[start]) { start = middle; }//中间元素属于第二部分 else if (array[middle] <= array[start]) { end = middle; } } return result; }
测试代码:
int _tmain(int argc, _TCHAR* argv[]) { int array1[6] = { 4, 5, 6, 1, 2, 3 }; int array2[5] = { 1, 0, 1, 1, 1 }; int array3[5] = { 1, 1, 1, 0, 1 }; cout << MinInRotation(array1, 6)<<endl; cout << MinInRotation(array2, 5) << endl; cout << MinInRotation(array3, 5) << endl; getchar(); return 0; }
运行结果:
1 0 0
问题描述:统计一个数组在排序数组中出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5}和数字3,因为3在数组中出现了4次,所以输出4.
解决思路:假设咱们是统计数字k在排序数组中出现的次数,只要找出排序数组中第一个k与最后一个k的下标,就可以计算出k的出现次数。
寻找第一个k时,利用二分查找的思想,咱们老是拿k与数组的中间元素进行比较。若是中间元素比k大,那么第一个k只有可能出如今数组的前半段;若是中间元素等于k,咱们就须要判断k是不是前半段的第一个k,若是k前面的元素不等于k,那么说明这是第一个k;若是k前面的元素依旧是k,那么说明第一个k在数组的前半段中,咱们要继续递归查找。
这个过程的代码:
int getFirstIndex(int array[], int start ,int end, int k) { if (start > end) return -1; int middle = (start + end) / 2; int middleData = array[middle]; if (middleData == k) { if (middle == 0 || array[middle - 1] != k) return middle; else end = middle - 1; } else if (middleData>k) { end = middle - 1; } else start = middle + 1; return getFirstIndex(array, start, end, k); }
一样的思路,咱们在数组中寻找最后一个k,若是中间元素比K大,那么k只能出如今数组的后半段;若是中间元素比K小,那么K只能出如今数组的前半段。若是中间元素等于k,而k后面的元素等于k,那么最后一个k只能在后半段出现;不然k为数组中最后的一个k。
代码以下:
///获取最后一个K的下标 int getLastIndex(int array[], int start,int end,int length, int k) { if (start > end) return -1; int middle = (start + end) / 2; int middleData = array[middle]; if (middleData == k) { if (middle == length-1 || array[middle+ 1] != k) //最后一个k return middle; else start = middle + 1; } else if (middleData>k) { end = middle - 1; } else start = middle + 1; return getLastIndex(array, start, end,length, k); }
把第一个坐标与最后一个坐标算出来后,咱们能够进行元素出现次数的计算:
//计算元素出现个数 int CountKInArray(int array[], int length, int k) { int result=-1; if (array != nullptr && length > 0) { int firstPos = getFirstIndex(array, 0, length - 1, k); int lastPos = getLastIndex(array, 0, length - 1, length, k); if (firstPos != -1 && lastPos != -1) { result = lastPos - firstPos+1; } } return result; }
测试:
int _tmain(int argc, _TCHAR* argv[]) { int array[8] = { 1, 2, 3, 3, 3, 4, 5 }; cout << "数组array中数字3出现的次数为:"<<CountKInArray(array, 7, 3) << endl; getchar(); return 0; }
运行结果:
数组array中数字3出现的次数为:3
原创文章,转载请注明:http://www.cnblogs.com/QG-whz/p/5194627.html