二分查找法
二分查找法
二分查找法的前提是这个数据必须是有序的
二分查找思路
1、数据中间的值V与想查找的元素比较,如果没有查找到,分成两个区间,然后选择一部分查找。
2、将标定点与查找元素比较。
二分查找法(递归实现)
public class BinarySearch{
public BinarySearch() {
}
public static <E extends Comparable<E>> int search(E[] data,E target){
return search(data,0,data.length-1,target);
}
private static <E extends Comparable<E>> int search(E[] data,int l,int r,E target){
if(l>r) return -1;
int mid = l +(r-l)/2 ;
if(data[mid].compareTo(target)==0)
return mid;
if(data[mid].compareTo(target)<0)
return search(data,mid+1,r,target);
return search(data,l,mid-1,target);
}
}
二分查找非递归
public class BinarySearch2 {
public BinarySearch2() {
}
public static <E extends Comparable<E>> int search(E[] data,E target){
int l = 0, r=data.length-1;
//在data[l,r]的范围查找target
while (l<=r){
int mid = l+(r-l)/2;
if(data[mid].compareTo(target)==0)
return mid;
if(data[mid].compareTo(target)<0)
l = mid +1;
else
r = mid - 1;
}
return -1;
}
}
不同的边界条件
public static boolean exist(int[] sortedArr, int num) {
if (sortedArr == null || sortedArr.length == 0) {
return false;
}
int L = 0;
int R = sortedArr.length - 1;
int mid = 0;
// L..R
while (L < R) { // L..R 至少两个数的时候
mid = L + ((R - L) >> 1);//=mid=(l+r)/2,数组长度到达20亿,会超标。
//R-L/2 = L+(R-L)/2
if (sortedArr[mid] == num) {
return true;
} else if (sortedArr[mid] > num) {
R = mid - 1;
} else {
L = mid + 1;
}
}
//只有一个元素的时候比较是否等于num.
return sortedArr[L] == num;
}
查找target的(最大或最小值)
例如查找比target大的最小的值
1、取mid为72,此时不能将72过滤,可能存在比target大的最小值。
2、将mid=r
3、再取mid =65 mid = r
4、当前mid=65,l = mid +1 ,当l=r 时,找到了解。
Ceil
如果数组中存在元素,返回最大索引
如果数组中不存在元素,返回upper
Lower
查找小于target 的最大值。
搜索范围[l,r]
当target远大于数组的最大值,那查找值就是数组的最后一个值。
当target远小于数组的最小值,那没有查找的值。
局部最小值问题
题目描述
给定一个不包含相同元素的整数数组,求一个局部最小值
题解
1)数组第一个元素比第二个元素小,即为局部最小值。
2)数组最后一个元素比它前一个元素小,即为局部最小值。
3)若不满足,那么局部最小值必可在数组首尾两元素之间的某个位置取得。此时可以采用二分法思想,看中间位置是否符合条件,不符合就分成两部分,从不符合的那一边继续操作。
两种情况
值只会有一个。
这种情况取决于mid所在度区间,求最值。
public class LocalMinimum {
public static int findLM(int[] arr) {
if (arr == null || arr.length == 0) { //数组为空或数据长度为0
return -1;
}
if (arr.length == 1) { //数组只有一个元素即为局部最小值
return 0;
}
if (arr[0] < arr[1]) { //数组第一个元素比第二个元素小,即为局部最小值
return 0;
}
if (arr[arr.length - 1] < arr[arr.length - 2]) {//数组最后一个元素比它前一个元素小,即为局部最小值
return arr.length - 1;
}
//上述条件均不满足,局部最小值必在数组内部取得
//用二分法思想求解
int left = 0, right = arr.length - 1;
int mid;
while (left < right) {
mid = left + (right - left) / 2;
if (arr[mid] > arr[mid - 1]) {
right = mid;
} else if (arr[mid] > arr[mid + 1]) {
left = mid + 1;
} else {
return mid;
}
}
return -1;
}
public static void main(String[] args) {
//测试
int[] arr = {4,3,6,8,2,1,5,7};
int res = findLM(arr);
if(res < 0) {
System.out.println("最小值不存在");
} else {
System.out.println(arr[res]);
}
}
}
时间复杂度
比如:总共有n个元素,每次查找的区间大小就是n,n/2,n/4,…,n/2^k(接下来操作元素的剩余个数)其中k就是循环的次数。
在最坏情况下是在排除到只剩下最后一个值之后得到结果,所以为
n/(2^m)=1; 最坏的情况下是为1 即查找到了最后一个元素。
2^m=n;
所以时间复杂度为:log2(n) = logn+ log2
二分查找的模板
冒泡排序
冒泡排序
冒泡排序(Bubble Sort)一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。
冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始) ,依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒
算法实现
外层循环:要循环的次数,n个元素的循环次数为n-1
内层循环:元素要比较的次数,i层循环的比较次数为n-1-j
public static void main(String[] args) {
int arr[] = {3, 9, -1, 10, -2};
int temp = 0;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
System.out.println(Arrays.toString(arr));
}
冒泡优化
Flag:增加falg标志位判断是否经历过排序,没有交换过值,直接返回。
public static void main(String[] args) {
int arr[] = {3, 9, -1, 10, -2};
int temp = 0;
//增加标志位,校验是否做过排序
boolean flag = false;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
flag = true;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
//没有做过排序
if(!flag){
break;
} else {
flag = false;
}
}
System.out.println(Arrays.toString(arr));
}
冒泡排序的时间复杂度
分析一下它的时间复杂度。当最好的情况,也就是要排序的表本身就是有序的,那么我们比较次数,根据最后改进的代码,可以推断出就是n-1次的比较,没有数据交换,时间复杂度为0(n)。当最坏的情况,即待排序表是逆序的情况,此时需要比较.
并作等数量级的记录移动。因此,总的时间复杂度为0(n2)。
选择排序算法
选择排序
选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的。
简单选择排序法(Simple Selection Sort)就是通过n-i次关键字间的比较,从n-1+1个记录中选出关键字最小的记录,并和第i (1<isn)个记录交换之。
选择排序的思路
算法实现
外层循环:获取最小值的次数,n个元素要循环n-1次.
内存循环:要比较最小值得次数,为i+1->n次
public class Selection {
public static void main(String[] args) {
int[] arr = {1,4,2,3,6,5};
selection(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
private static void selection(int[] arr){
for (int i = 0; i < arr.length; i++) {//length
int maxIndex = i;
for (int j = i; j < arr.length ; j++) {
if(arr[j]>arr[maxIndex])//lending-i
//大到小,直接修改为<
maxIndex = j;
}
swap(arr,i,maxIndex);
}
}
private static void swap(int[] arr, int i, int j) {
int t= arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
算法实现(升级版)
public class Selection2 {
public static void main(String[] args) {
Student[] students = {
new Student("alice",123),
new Student("bob",1223),
new Student("jack",142)
};
selection(students);
for (int i = 0; i < students.length; i++) {
System.out.println(students[i]);
}
}
private static <E extends Comparable<E>> void selection(E[] arr) {
for (int i = 0; i < arr.length; i++) {//length
//索引值当前循环的最大或最小索引,是由compareTo方法决定的
int index = i;
for (int j = i; j < arr.length; j++) {
if (arr[j].compareTo(arr[index]) < 0)//lending-i
index = j;
}
swap(arr, i, index);
}
}
private static <E extends Comparable<E>> void swap(E[] arr, int i, int j) {
E t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
插入排序
插入排序
插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。
插入排序思路
插入排序(Insertion Sorting)的基本思想是:把n个待排序的元素看成为个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
arr[0,i) 已经排好序, arr(i,n]未排好
插入流程
代码
public class InsertSort {
public static void main(String[] args) {
int[] dataSize = {10000, 100000};
for (int n : dataSize) {
Integer[] integers = ArrayGenerator.generateRandomArray(n, n);
SortingHelper.sortTest("InsertSort", integers);
}
// InsertSort,n=10000:0.104666 s
// InsertSort,n=100000:13.700619 s
}
public static <E extends Comparable<E>> void sort(E[] arr) {
//一层循环控制排序号的数,[0,i)排序好的数,i代表需要进行向前排序的数。
for (int i = 0; i < arr.length; i++) {
//j为排序号最后一位数,
//j>=0 i=0的情况下,j为i左边的第一个数据,小于0,不满足条件,说明已经已经有序。
//比较j和j+1 进行交换。依次比较完。
for (int j = i; j - 1 >= 0; j--) {
if (arr[j].compareTo(arr[j - 1]) < 0) {
swap(arr, j, j - 1);
} else {
break;
}
}
}
}
private static <E extends Comparable<E>> void swap(E[] arr, int i, int j) {
E t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
插入排序的优化
减少swap的次数
1、记录当前元素的值。
2、如果当前元素小于比较的元素,将比较的元素向后移位。
3、直到找到适合插入的位置.
public class InsertSort2 {
public static void main(String[] args) {
int[] dataSize = {10000, 100000};
for (int n : dataSize) {
Integer[] arr = ArrayGenerator.generateRandomArray(n, n);
Integer[] arr1 = Arrays.copyOf(arr, arr.length);
SortingHelper.sortTest("InsertSort", arr);
SortingHelper.sortTest("InsertSort2", arr1);
// InsertSort,n=100000:14.144661 s
// InsertSort2,n=100000:8.994257 s
}
}
public static <E extends Comparable<E>> void sort(E[] arr) {
for (int i = 0; i < arr.length; i++) {
E t = arr[i];
int j;
for (j = i; j - 1 >= 0 && t.compareTo(arr[j - 1]) < 0; j--) {
arr[j] = arr[j - 1];
}
arr[j] = t;
}
}
private static <E extends Comparable<E>> void swap(E[] arr, int i, int j) {
E t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
复杂度分析
由于嵌套循环的每一个都花费N次迭代,因此插入排序为O(N2)
Sum = 1+2+3+4+5+…+n = n(n+1)/2 = O(N2)
如果输入数据已预线排序,name运行时间为O(N),因此,对应近乎有序的数据,适合插入排序。 要比较的元素>有序数组的元素 此时跳出有序数组比较,此时已经数组已经有序