整数二分解析
用二分的思想可以找出这个分界点。
假设我们想二分出来红色(不满足条件的范围)这边的分界点:
- 找一个中间值mid :l+r >> 1
- 写一个函数check()判断时mid时在满足条件(绿色)范围内,还是在不满足条件(红色)范围内。
- (注意:这里为什么是 l+r+1 )
For example:
如果 l = r - 1 (即左右边界只相差1) ,假设为l+r >> 1 (向下取整)假设if(check(mid)) 判断为true (此时mid = l + r >> 1 == l)则执行l=mid; l=mid=l;陷入死循环,左右边界一直为[l, r] 没有变
如果是l+r+1>>1则不会发生这个问题(详情自己参考上面推导一遍即可)。
两个整数二分模板
//模板1
/———————————————————————————————— 整数二分模板1 ————————————————————————————————\
static boolean check(int x) {/* ... */ } // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
public static int bsearch_1(int l, int r) {
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
\———————————————————————————————— 整数二分模板1 ————————————————————————————————/
//模板2
/———————————————————————————————— 整数二分模板2 ————————————————————————————————\
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
public static int bsearch_2(int l, int r) {
while (l < r) {
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
\———————————————————————————————— 整数二分模板2 ————————————————————————————————/
浮点二分模板
浮点数二分解析
用二分的思想可以找出这个分界点。
假设我们想二分出来红色(不满足条件的范围)这边的分界点
- 找一个中间值mid :(l+r) / 2(因为是浮点数,所以可以严格的二分,即mid就是中点)
- 写一个函数check()判断时mid时在满足条件(绿色)范围内,还是在不满足条件(红色)范围内。
- 当区间长度很小的时候,可以近似的认为找到了答案,例:r-l ≤ 10 − 6 ^{-6} −6 (如果题目要求保留4位小数,则应该写到-6次方,比有效小数的位数多2)
实验4
第1题
1、给定数组a[0 : 8]={1, 8, 12, 15, 16, 21, 30, 35, 39}。采用二分搜索算法完成下述任务:
- 查找是否有元素30,若有返回元素在数组中的位置;如没有返回无此元素。
- 查找是否有元素20,若有返回元素在数组中的位置;如没有返回无此元素。
- 当待搜索元素x=10不在数组中时,返回小于x的最大元素位置i和大于x的最小元素位置j。
public class binarySearch {
static int a[] = new int[]{1, 8, 12, 15, 16, 21, 30, 35, 39};
public static int binarySearch(int[] a, int x, int n) {
int l = 0, r = n - 1;
while (l <= r) {
int mid = l + r >> 1;
if (x == a[mid]) return mid;
if (x > a[mid]) l = mid + 1;
else r = mid - 1;
}
return -1;
}
public static int binarySearch3(int[] a, int x, int n) {
int l = 0, r = n - 1;
while (l <= r) {
int mid = l + r >> 1;
if (x == a[mid]) return mid;
if (x > a[mid]) l = mid + 1;
else r = mid - 1;
}
int i = l;
int j = r;
System.out.println("(3)" +'\n' +"a[]中没有10这个元素" + '\n' + "小于10的最大元素位置i为:" + i + '\n' + "大于10的最小元素位置j为:" + j);
return -1;
}
public static void main(String[] args) {
int l = 0, r = 8;
int idx1 = binarySearch(a, 30, r + 1);
int idx2 = binarySearch(a, 20, r + 1);
if (idx1 == -1) System.out.println("(1) a[]中没有30这个元素");
else System.out.println("(1) a[]中30这个元素在:" + idx1);
if (idx2 == -1) System.out.println("(2) a[]中没有20这个元素");
else System.out.println("(2) a[]中20这个元素在:" + idx2);
binarySearch3(a, 10, r + 1);
}
}
第2题
2、给定数组a[ ]={8,4,3,7,1,5,6,2},使用快速排序算法对其进行排序,将算法编程实现。
- 写出算法实现代码并截屏程序的运行结果。
- 分析快速排序算法在最好以及最坏情况下的时间复杂性。
- 快速排序算法的性能与划分是否对称有关,设计随机化的快速排序算法解决划分对称性问题,将算法编程实现。
快速排序:
1.分治 —— 先分出两边再递归
- 确定分界点,一般为三个点: q q q[l](左边界)、 q q q[l+r>>1](中间)或 q q q[r](右边界)(这里取中间为分界点👇)
- 假设分界点为x
分为两个部分,保证第一个部分的位置 ≤ x ≤x ≤x,第二个部分的位置 ≥ x ≥x ≥x - 递归处理左右两端
import java.util.*;
public class quickSort {
static int a[] = new int []{8, 4, 3, 7, 1, 5, 6, 2};
// /————————————————————快排模板————————————————————\
public static void quick_sort(int a[], int l, int r) {
if (l >= r) return;
//取分界点x,可以取a[l]、a[r]或a[l+r>>1](+的优先级高于>>所以不用括号)
// l+r>>1 比 (l+r)/2 的效率高
int x = a[(l + r) >> 1];//取中间为分界点
int i = l - 1, j = r + 1;//选择两个指针i,j,分别为两侧
//每次移动一次交换算迭代
while (i < j) {
do i++; while (a[i] < x);//i指针往后移
do j--; while (a[j] > x);//j指针往前移
//i和j还没有相遇的话,i指向的数应该放到后面,j指向的数应该放到前面
if (i < j) {
int t = a[i];
a[i] = a[j];
a[j] = t;
} else break;
}
//此时已经是左边的数都比x小,右边的数都比x大。
quick_sort(a, l, j);
quick_sort(a, j + 1, r);
//当边界取q[l]时,这里的j不能用i-1来代替,j+1不能用i来代替,会进入死循环一直递归相同的区间
//当边界取q[r]时,这里不能用j和j+1,会进入死循环一直递归相同的区间
}
// \————————————————————快排模板————————————————————/
public static void main(String[] args) {
System.out.println("排序前的结果是:" + Arrays.toString(a));
quick_sort(a, 0, 7);
System.out.println("排序后的结果是:" + Arrays.toString(a));
}
}
快速排序的时间性能取决于快速排序递归的深度
最好的情况下时间复杂度:O(nlogn)。
假设树划分得很均匀,有n个待排序的数字,那么递归树的深度就为log2n+1
时间复杂度就是:O(nlogn)
T ( n ) = { O ( 1 ) , n = 1 2 T ( n 2 ) + O ( n ) , n > 1 T(n)=\left\{ \begin{aligned} O(1) &,n=1 \\ 2T(\frac{n}{2})+O(n)&,n>1 \end{aligned} \right. T(n)=⎩⎨⎧O(1)2T(2n)+O(n),n=1,n>1
所以: T ( n ) = O ( n l o g n ) T(n) = O(nlog n) T(n)=O(nlogn)
假设树划分得很不均匀,就是一颗斜数(待排序的序列为正序或者逆序),此时需要执行n‐1次递归调用,此时时间复杂度就是:O(n2)
T ( n ) = { O ( 1 ) , n = 1 T ( n − 1 ) + O ( n ) , n > 1 T(n)=\left\{ \begin{aligned} O(1) &,n=1 \\ T(n-1)+O(n) &,n>1 \end{aligned} \right. T(n)={O(1)T(n−1)+O(n),n=1,n>1
所以: T ( n ) = O ( n 2 ) T(n) = O(n^2) T(n)=O(n2)
import java.util.Arrays;
import java.util.Random;
public class randomQuickSort {
private static int random(int p, int r) {
Random random = new Random();
int i = random.nextInt(r - p + 1) + p;
return i;
}
private static void quickSort(Comparable[] a, int p, int r) {
if(p < r) {
int q = randomizedPartition(a, p, r);
quickSort(a, p, q - 1);
quickSort(a, q + 1, r);
}
}
private static int randomizedPartition(Comparable[] a, int p, int r) {
int i = random(p, r);
Comparable temp = a[i];
a[i] = a[p];
a[p] = temp;
return partition(a, p, r);
}
private static int partition(Comparable[] a, int p, int r) {
int i = p;
int j = r + 1;
Comparable x = a[p];
while(true) {
while(a[++i].compareTo(x) < 0 && i < r);
while(a[--j].compareTo(x) > 0);
if(i >= j) break;
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
a[p] = a[j];
a[j] = x;
return j;
}
public static void main(String[] args) {
Comparable[] a = {8, 4, 3, 7, 1, 5, 6, 2};
System.out.println("排序前的数组:" + Arrays.toString(a));
quickSort(a, 0, a.length - 1);
System.out.println("排序后的数组:" + Arrays.toString(a));
}
}