提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
AcWing基础算法课程笔记
第一部分快排和归并排序,二分查找。根据模板套入题目调试。
提示:以下是本篇文章正文内容,下面案例可供参考
一、快速排序
快速排序基于分治的一种排序算法
1.基本思想:
(1)确定分界点,为了划分两个区间。x可以取最左边的,右边的,中间的,或者数组中随机的一个数。
(2)根据分界点调整左右两个区间,左边都小于等于x,右边都大于等于x(关键部分)
(3)然后递归处理左右两端,即分别再对左右两个区间排序
2.调整区间思路:
(1)暴力方法,两个数组a[]和b[]分别存放大于x的和小于等于x的值,然后再放入原来的数组
(2)双指针的方法,i和j对应数组下标,i从左边开始移动,移动到第一个大于x的数停止,j从右边开始移动,移动到小于x的数停止。然后交换,i和j指向的数。继续移动交换i和j知道两个数重合。
3.注意事项
取第三步递归分区间取[l,i-1]和[i,r]时,x不可以去q[l]即数组左边值。比如代入[1,2]会陷入死循环。同理分区取[l,j]和[i+1,r]时,x不可以去q[r]即数组右边值。比如代入[1,2]会陷入死循环。
如果取mid时,mid= (l+r)/2,可以用j直接划分,如果用i划分是需要mid+1。计算机计算(0+1)/2等于0。我们需要加1避免mid的值为最左值。因为最左值就会无限循环的划分。这里代入数组[0,1]可以轻松理解。
这里有个典型的错误,比如3,8,6,8两个8交换后,就i,j同时到6,没有完成循环就终止了。
4.代码(JAVA)
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner myScanner = new Scanner(System.in);
int n = myScanner.nextInt();
int []a = new int[n];
for(int i = 0; i < n;i++){
a[i] = myScanner.nextInt(); //创建数组
}
quick_sort(a,0,n-1);
for(int i = 0;i<n ;i++){
System.out.print(a[i]+" ");
}
}
public static void quick_sort(int[] a ,int l,int r){ //快排函数
if(l >= r) return; //就一个数或者没有数,嘿嘿嘿
int x = a[(l+r)>>1] ; //取中间数作为分界点
int i = l - 1;
int j = r + 1;
while(i<j){
while(a[++i]<x);
while(a[--j]>x);
if(i<j){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
quick_sort(a,l,j);
quick_sort(a,j+1,r);
}
}
4.快排一些应用
1.二分类(比如奇数偶数分类O(n)遍历一遍就可以解决)三分类问题(先划分成两个问题,然后对两个子问题递归排序,最后再合并。)
2.找第k小问题:(这里只需要递归需要的部分即可)
比如以某一元素为媒介进行一次快排,小于它的在左边,大于它的在右边,
(1)如果左边数字个数等于k-1,那么第k小就是这个元素了
(2)如果左边个数小于k-1,那么第k小在右面集合
(3)如果左边个数大于k-1,那么第k小就在左面集合
二、归并排序
归并排序也是基于分治的排序算法。
快排是先划分区间,再分别递归两边。
而归并是先递归排序两边,再合并排序。
1.基本思路
(1)确定分界点mid=(l+r)/2
(2)递归排序左边和右边
(3)合并排序(关键),利用双指针合并两个有序链表
2.代码
代码如下(示例):
public static void merge_sort(int[] a,int l ,int r){
if(l>=r) return;
int mid = (l+r)>>1; //进行二分递归
merge_sort(a,l,mid);
merge_sort(a,mid+1,r);
int i = l;
int j = mid+1; // 利用双指针合并
int count =0;
int[] t = new int[r-l+1];
while(i<=mid && j<=r){
if(a[i]<=a[j])
t[count++] = a[i++];
else
t[count++] = a[j++];
}
while(i <= mid) t[count++] = a[i++];
while(j <= r) t[count++] = a[j++];
for(int m = l,q=0;m<=r;m++,q++){
a[m] = t[q];
}
}
3.逆序对的数量
二、二分
最常见的二分就是在排好的有序序列中查找你已知数的位置,每次用mid作为分界点,把一整个序列分成两个部分。通过不断缩写搜寻范围,最终高效查找数据。
二分查找不只可以利用在单调区间的查找上,也可以用于查找边界,比如左侧一部分满足一个条件,右侧一部分不满足这个条件,二分的核心就是每个循环二分区间只搜索一般。
1.基本思路
(1)先找到一个中间值mid=(l+r)/2,或者(l+r+1)/2.
(2)然后根据中间值将原区间划分成两个区间,[l,mid]和[mid+1,r]。([l,mid-1],[mid,r]是另一种划分,根据情况而定)。
(3)划分区间需要写一个check判断条件,选择两个条件中的一个作为新的区间。
注意:[l,mid]和[mid+1,r]这种区间划分时,mid = (l+r+1)/2。另一种区间划分不需要+1。比如当在0,1中查找0时会造成死循环。(即存在l=mid,就必须+1)。
2.查找数的范围
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int e = scanner.nextInt();
int[] a = new int[n];
int i = 0 ,m =0,p=-1,q=-1,lmid,rmid;
for(i = 0;i < n;i++)
a[i] = scanner.nextInt();
for(i = 0;i < e;i++ ){
m = scanner.nextInt(); //有左右边界之分,先找左边界,再找右边界
int l = 0, r =n-1;
while(l < r){ //i==j时就找到我们所需要的解了
lmid = (l + r ) >>1;
if(a[lmid] >= m) //a[lmid]大于等于m,目标在左半区
r = lmid ;
else //a[lmid]<m,目标在右半区
l = lmid+1;
}
if(a[l] != m )
p = -1;
else
p = l;
System.out.print(p+" ");
l = 0;
r = n-1;
while(l < r){ //找寻右边界
rmid = (l + r + 1) >>1;
if(a[rmid] <= m) //a[rmid]<=m 右边界一定在[rmid,r];
l = rmid;
else
r = rmid-1;
}
if(a[l] != m )
p = -1;
else
p = l;
System.out.println(p+" ");
}
}
}
2求一个浮点数的三次开方(-10000到10000之间的数)
这个比较简单,主要注意 l =-10000,而不是0,因为可能是负数。
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner s = new Scanner(System.in);
double b = s.nextDouble();
//double l = 0 ,r = b;
double l = -10000, r=10000;
while(r-l > 1e-8){
double mid = (r+l)/2;
if(mid*mid*mid >= b)
r = mid ;
else
l = mid ;
}
System.out.printf("%.6f",l);
}
}
```c