二分查找
public class BinarySearch {
public static void main(String[] args) {
int[] array={1,5,8,11,19,22,31,35,40,48,49,50};
int target = 40;
int idx = binarySearch(array,target);
System.out.println(idx);
}
private static int binarySearch(int[] a,int t){
int l =0,r=a.length-1,m;
while (l<=r){
m=(l+r)>>>1;//解决整数溢出(推荐)
// 方式2:int = l+(r-l)/2
if (a[m]==t){
return m;
}else if (a[m]>t){
r=m-1;
m=(l+r)>>>1;
}else {
l=m+1;
m=(l+r)>>>2;
}
}
return -1;
}
}
面试填空:
-
比较几次
- 奇数二分取中间(奇数是数的个数,这点自己容易迷)
- 偶数二分取中间靠左
-
求比较次数最多不超过多少次?
- 2的多少次方等于这个数,结果就是几
- 是小数的话,则舍去小数 ,整数部分加1
排序
-
需要手写冒泡、快排的代码
-
了解各个排序算法的特性,如时间复杂度、是否稳定
冒泡排序
文字描述(以升序为例)
- 1.依次比较数组中相邻两个元素大小,若a[i]>a[j],则交换两个元素,两两都比较一遍称为冒泡,结果让最大的元素排至最后
- 2.重复以上步骤,直到整个数组有序
//冒泡排序
public class BubbleSort {
public static void main(String[] args) {
int[] a={5,9,7,4,1,3,2,8};
// int[] a={1,2,3,4,5,6,7,8,9};
bubble(a);
}
public static void bubble(int[] a){
for (int j=0;j<a.length-1;j++){
for (int i=0;i<a.length-1;i++){
if(a[i]>a[i+1]){
swap(a,i,i+1);
}
}
System.out.println("第"+j+"轮冒泡"+ Arrays.toString(a));
}
}
public static void swap(int[] a,int i,int j){
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
优化1
//冒泡排序优化1
public class BubbleSort2 {
public static void main(String[] args) {
int[] a={5,9,7,4,1,3,2,8};
// int[] a={1,2,3,4,5,6,7,8,9};
bubble(a);
}
public static void bubble(int[] a){
for (int j=0;j<a.length-1;j++){
for (int i=0;i<a.length-1-j;i++){
System.out.println("比较次数"+i);
if(a[i]>a[i+1]){
swap(a,i,i+1);
}
}
System.out.println("第"+j+"轮冒泡"+ Arrays.toString(a));
}
}
public static void swap(int[] a,int i,int j){
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
优化2(推荐)
//冒泡排序优化2
public class BubbleSort3 {
public static void main(String[] args) {
// int[] a={5,9,7,4,1,3,2,8};
int[] a={1,2,3,4,5,6,7,8,9};
bubble(a);
}
public static void bubble(int[] a){
for (int j=0;j<a.length-1;j++){
//一轮冒泡
boolean swapped = false;
for (int i=0;i<a.length-1-j;i++){
System.out.println("比较次数"+i);
if(a[i]>a[i+1]){
swap(a,i,i+1);
swapped = true;
}
}
System.out.println("第"+j+"轮冒泡"+ Arrays.toString(a));
if (!swapped){
break;
}
}
}
public static void swap(int[] a,int i,int j){
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
最终优化(推荐)
//冒泡排序:最终优化
public class BubbleSort4 {
public static void main(String[] args) {
// int[] a={5,9,7,4,1,3,2,8};
int[] a={1,2,3,4,5,6,7,8,9};
bubble(a);
}
public static void bubble(int[] a){
int n = a.length-1;
while (true){
int last = 0;
for (int i=0;i<n;i++){
System.out.println("比较次数"+i);
if(a[i]>a[i+1]){
swap(a,i,i+1);
last = i;
}
}
n=last;
System.out.println("第"+"轮冒泡"+ Arrays.toString(a));
if (n==0){
break;
}
}
}
public static void swap(int[] a,int i,int j){
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
选择排序
文字描述(以升序为例)
- 1.将数组分为两个子集,排序的和未排序的,每一轮从未排序的子集中选出最小的元素,放入排序的子集
- 2.重复以上步骤,直到整个数组有序
- 3.优化方式
- 为减少交换次数,每一轮可以找到最小的索引,在每轮最后交换元素
public class SelectionSort {
public static void main(String[] args) {
int[] a={5,3,7,2,1,9,8,4};
selection(a);
}
private static void selection(int[] a){
for (int i=0;i<a.length;i++){
//i代表每轮选择最小元素要交换到的目标索引
int s=i;
for (int j=s+1;j<a.length;j++){
if (a[s]>a[j]){
s=j;
}
}
if (s!=i){
swap(a,s,i);
}
System.out.println(Arrays.toString(a));
}
}
public static void swap(int[] a,int i,int j){
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
冒泡和选择的比较
- 1.二者平均时间复杂度都是O(n2)
- 2.选择排序一般要快于冒泡,因为其交换次数少
- 3.但如果集合有序度高,冒泡优于选择
- 4.冒泡属于稳定的排序算法,而选择属于不稳定的排序
插入排序
文字描述(以升序为例)
- 1.将数组分为两个区域,排序区域和未排序区域,每一轮从未排序区域中取出第一个元素,插入到排序区域(需要保证顺序)
- 2.重复以上步骤,直到整个数组有序
优化方式
-
1.待插入元素进行比较时,遇到比自己小的元素,就代表找到了插入位置,无需进行后续比较-----代码中的这个部分:
else { break;//退出循环,减少比较次数 }
-
2.插入时进行直接移动元素,而不是交换元素
以上优化均在代码中体现
public class InsertSort {
public static void main(String[] args) {
int[] a={9,3,7,2,5,8,1,4};
insert(a);
}
private static void insert(int[] a){
//i代表待插入元素的索引
for (int i=1;i<a.length;i++){
int t=a[i];//代表待插入的元素值
int j=i-1;//代表已排序区域的元素索引
while (j >= 0){
if (t < a[j]){
a[j+1]=a[j];
}else {
break;//退出循环,减少比较次数
}
j--;
}
a[j+1] = t;
System.out.println(Arrays.toString(a));
}
}
}
插入与选择排序进行比较
- 1.二者平均时间复杂度都是O(n2)
- 2.大部分情况下,插入都略优于选择
- 3.有序集合插入的时间复杂度未O(n)
- 4.插入属于稳定排序算法,而选择属于不稳定排序
快速排序-实现方式
1.单边循环快排(lomuto洛穆托分区方案)
- 1.选择最右边元素作为基准点元素
- 2.j指针负责找比基准点小的元素,一旦找到则与i进行交换
- 3.i指针维护小于基准点元素的边界,也是每次交换的目标索引
- 4.最后基准点与i交换,i即为分区位置
public class QuickSort1 {
public static void main(String[] args) {
int[] a ={5,3,7,2,9,8,1,4};
quick(a,0,a.length-1);
}
//递归
public static void quick(int[] a, int l,int h){
if (l>= h){
return;
}
int p =partition(a,l,h);//p 基准点索引值
quick(a,l,p-1);//左边分区的范围缺点
quick(a,p+1,h);//右边分区的范围确定
}
private static int partition(int[] a,int l,int h){
int pv = a[h];//基准点元素
int i =l;
for (int j =l;j<h;j++){
if (a[j]<pv){
if (i!=j){
swap(a,i,j);
}
i++;
}
}
if (i!=h){
swap(a,h,i);
}
System.out.println(Arrays.toString(a)+" i=" + i);
//返回值代表了基准点元素所在的正确索引,用它缺点下一轮分区的边界
return i;
}
public static void swap(int[] a,int i,int j){
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
2.双边循环快排(并不完全等价于hoare霍尔分区方案)
-
1.选择最左边元素作为基准点元素
-
2.j指针负责从右向左找比基准点小的元素,i指针赋值从左到右
找比基准点大的元素,一旦找到二者交换,直至i,j相交
-
最后基准点与i(此时i与j相等)交换,i即为分区位置
public class QuickSort2 {
public static void main(String[] args) {
int[] a ={5,3,7,2,9,8,1,4};
quick(a,0,a.length-1);
}
//递归
public static void quick(int[] a, int l,int h){
if (l>= h){
return;
}
int p =partition(a,l,h);//p 基准点索引值
quick(a,l,p-1);//左边分区的范围缺点
quick(a,p+1,h);//右边分区的范围确定
}
private static int partition(int[] a,int l,int h){
int pv = a[l];
int i = l;
int j = h;
while (i<j){
//必须先从右往左找
//j从右向左找小的
while (i <j && a[j] > pv){
j--;
}
//i从左向右找大的
while (i <j && a[i]<=pv){
i++;
}
swap(a,i,j);
}
swap(a,l,j);
System.out.println(Arrays.toString(a)+"j="+j);
return i;
}
public static void swap(int[] a,int i,int j){
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
3.双边循环几个要点
- 1.基准点在左边,并且要先j后i
- 2.while(i<j && a[j]>pv) j–
- 3.while(i<j && a[i] <= pv) i++
4.快速排序的特点
- 1.平均时间复杂度是O(n log2n),最坏的时间复杂度O(n2)
- 2.数据量较大时,优势非常明显
- 3.属于不稳定排序