啊哈!算法读书笔记
这个只是我学习的一个小记录,没什么特别的地方,书写的很详细建议看书,但书上代码用的c想直接了解java的可以参考下我写的,虽然我写的很low,但有些也是参考了大佬写的,还是可以参考一下的.
目录
注:书中算法例子都是c写的,这里例子我都是用的java实现
-
各种排序
各种排序
看到排序,我第一个想到的就是Arrays.sort()...,可能是最近刷题用的太多了, 麻烦一点就遍历嘛,然后填入新数组,或者冒泡排序,其他的都没接触过,这里的桶排序 虽然不知道名字但也经常会用到 桶排序,就是给定n个容器,每个容器对应一个唯一的序号而且是有序的,然后根据给定 的数据将数据与序号相同的放入容器,使容器内计数加1.然后根据需求依次遍历 例:package Test_Ahsf; import java.util.Scanner; public class Tong_sort { public static void main(String[] args) { Scanner in=new Scanner(System.in); int N=in.nextInt(); int[] n=new int[10]; for(int i=0;i<N;i++){ n[i]=0; } for(int i=0;i<N;i++){ int m=in.nextInt(); for(int j=0;j<10;j++){ if(j==m){ n[j]++; } } } for(int i=0;i<10;i++){ if(n[i]!=0){ for(int j=0;j<n[i];j++){ System.out.print(i+" "); } } } } }
冒泡排序
这个排序就非常熟悉了,先从第一个开始一个个比较将最小或最大的放到最后,然后再从头开始,每次少比一个,这种方法复杂度太高 例:package Test_Ahsf; import java.util.Scanner; public class Maopao_sort { public static void main(String[] args) { Scanner in=new Scanner(System.in); int N=in.nextInt(); int[] n=new int[N]; for(int i=0;i<N;i++){ n[i]=in.nextInt(); } for(int i=0;i<N;i++){ for(int j=1;j<N-i;j++){ if(n[j]>n[j-1]){ int temp=n[j]; n[j]=n[j-1]; n[j-1]=temp; } } } for(int i=0;i<N;i++){ System.out.print(n[i]+" "); } } }
快速排序
快速排序是一个递归的过程,每次从最边上去一个基准数,然后从另一边先开始检索,这边也接着一起检索将比基准数大的放一边,小的方另一边,这个过程用交换实现.处理完一组将基准数与最终检索到的同一位置交换,然后以基准数为界限两边分为两组继续排序. 例:package Test_Ahsf; import java.util.Scanner; public class Fast_sort { public static void main(String[] args) { Scanner sc=new Scanner(System.in); int N=sc.nextInt(); int[] a=new int[N]; for(int i=0;i<N;i++){ a[i]=sc.nextInt(); } fastsort(a,0,N-1); for(int i=0;i<N;i++){ System.out.print(a[i]+" "); } } private static void fastsort(int[] a,int left, int right){ int i,j,t,temp; if(left>right){ return; } temp=a[left]; i=left; j=right; while (i!=j){ while (a[j]>=temp&&i<j){ j--; } while (a[i]<=temp&&i<j){ i++; } if(i<j){ t=a[i]; a[i]=a[j]; a[j]=t; } } a[left]=a[i]; a[i]=temp; fastsort(a,left,i-1); fastsort(a,i+1,right); } }
-
栈,队列,链表
解密QQ号-队列
具体结构就是一边进数据,一边出数据.,队列讲究先进先出,比如书上的例子,先删除第一位,再将第二位插入最后,没有说先删除第二位在将的第一位插入最后,复杂度太高. 例:package Test_Ahsf; import java.util.Scanner; public class QQ_JmDl { public static void main(String[] args) { Scanner sc=new Scanner(System.in); System.out.println("请输入要解密的数的个数"); int N=sc.nextInt(); System.out.println("请输入要解密的数:"); int[] q=new int[N]; for(int i=0;i<N;i++){ q[i]=sc.nextInt(); } int head=1; while (N>1){ System.out.print(q[0]); int temp=q[head]; System.arraycopy(q, 1, q, 0, N - 1); q[N-1]=temp; System.arraycopy(q, 1, q, 0, N - 1); N--; } } }
这里我稍微改动了下不算是解密qq号了就是解密一串数字
- 书中后面讲了一通c结构体队列的实现,队列的结构是明白了.
解密回文-栈
栈的结构应该都清楚,栈只允许一边的出入,而且只能移动最顶部的只能一个个进,再一个个出,遵循先进后出的原则 这里在PTA上做过一个相关的回文题,是判断是否为延迟的回文数,1079延迟的回文数这一题,我写的具体判断回文方法似乎与书中的例子差不多,只是没有从中间断开,直接反转比较,有那个思想就行了.纸牌游戏小猫钓鱼
说实话书上这C++的写法看的不怎么懂,一些专有名词的使用不知道怎么用在java里面于是就查阅网上相关资料找了个例子 例:package Test_Ahsf; import java.util.LinkedList; import java.util.Scanner; import java.util.Stack; public class Puk_game_Stack { public static void main(String[] args) { //a手牌 LinkedList<Integer> a = new LinkedList<>(); //b手牌 LinkedList<Integer> b = new LinkedList<>(); Scanner scanner = new Scanner(System.in); System.out.println("请输入手牌个数:"); //手牌个数 int n = scanner.nextInt(); System.out.println("请输入a的手牌:"); for (int i = 0; i < n ; i++){ a.addLast(scanner.nextInt()); } System.out.println("请输入b的手牌:"); for (int i = 0; i < n ; i++){ b.addLast(scanner.nextInt()); } //定义一个栈,用来放置桌面手牌 Stack<Integer> stack = new Stack(); System.out.println("游戏开始!"); //有一人手牌为空即为游戏结束 while ( !a.isEmpty() && !b.isEmpty()){ int x = a.removeFirst(); System.out.println("a出牌"+x); if (a.isEmpty()){ //a获胜 break; }else { if (stack.contains(x)){ //如果栈中有这张牌,a收牌 System.out.println("a收牌"); System.out.println(); a.addLast(x); int index = stack.search(x); for(int i = 0; i<index;i++){ a.addLast(stack.pop()); } System.out.println("a的手牌"+a); }else { //添加到栈中 stack.push(x); //B出牌 int y = b.removeFirst(); System.out.println("b出牌"+y); if (b.isEmpty()){ //b获胜 break; }else { if (stack.contains(y)){ //如果栈中有这张牌,b收牌 System.out.println("b收牌: "); System.out.println(); b.addLast(y); int index = stack.search(y); for(int i = 0; i<index;i++){ b.addLast(stack.pop()); } System.out.println("b的手牌"+b); }else { stack.push(y); } } } } } if (a.isEmpty()){ System.out.println("a获胜!"); } if (b.isEmpty()){ System.out.println("b获胜!"); } System.out.println("游戏结束!"); } }
这里在原有的基础上我进行了一些改动,将游戏过程输出来了.
链表
关于链表我之前也特别学习过,书里面用的从c涉及到指针什么的我就没细看,java链表有些不一样,但底层结构都是一样的详情见我之前写的ArrayList和LinkedList
第五节我就直接跳过了
枚举,很暴力
坑爹的奥数
这节主要讲了枚举的一些介绍,枚举是什么就不用说了,枚举的缺点人人都知道,毕竟是一种简单暴力的算法,列出所有可能还是太麻烦了,接下来看看下一章是怎么讲解决方法的炸弹人
看完这节不是很明白作者所说的降低枚举复杂度的方法,可能是增加某些条件吧,继续看下一章吧火柴棍等式
刚看到这里时完全没想到怎么用,看来还是我逻辑思维不太行,这里一部分用了桶排序的思想计算一个数用的火柴数量 这里的思路就是先找出拼出数字需要的最小火柴数,从而找出最大可拼出数字,然后枚举abc,但是abc全枚举数量太大,c可以根据条件用a+b得出 例: package Test_Ahsf;
import java.util.Scanner;
public class Huoc_Mj {
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
int N=in.nextInt();
int sum=0;
for(int a=0;a<=1111;a++){
for(int b=0;b<=1111;b++){
int c=a+b;
if(su(a)+su(b)+su(c)==(N-4)){
System.out.printf("%d+%d=%d\n",a,b,c);
sum++;
}
}
}
System.out.println("可拼出等式个数: "+sum);
}
//计算火柴数量
private static int su(int x){
int[] a={6,2,5,5,4,5,6,3,7,6,6};
int sum=0;
while ((x/10!=0)){
sum+=a[x%10];
x=x/10;
}
sum+=a[x];
return sum;
}
}
数的全排列
这个想了挺久没什么枚举想法,唯一的想法就是用字符串包含来做,这个数不管怎么排长度都是固定的就从最小的开始枚举,然后分割判断原数字包含每一位且不重复,还是很复杂,还是看下一章怎么解决的把万能的搜索
深度优先搜索
这个深度优先搜索看的有点迷糊,书中的解释是"当下该如何做",然后通过一个方法判断哪一步该怎么做.感觉还是挺复杂的,这里的方法是解决枚举a+b=c的问题同时运用了全排列.
深度优先的基本模型
void dfs(int step){
判断边界;
//尝试每一种可能
for(i=1;i<=n;i++){
动作;
//继续下一步
dfs(step+1);
}
返回;
}
- 这里我本来打算改进书中的例子,增加计算的数量,但做了才想到,超出9的数就没法满足要求了,但是我没改回去,仔细想想也算是一种特例,把9以上的数字既当一位特殊数,又当正常的两位数…,还是有点不合理就这样吧.
例:
package Test_Ahsf;
import java.util.Scanner;
public class Dsf_Qpl {
private static int sum=0;
public static void main(String[] args) {
//首先这题的等式要满足xxx+xxx=xxx,其中x的数量随意,但三个位置的数长度相同
Scanner sc=new Scanner(System.in);
int N=sc.nextInt();//为X的总数为3的倍数,但不为3
int[] a=new int[N+1];
int[] book=new int[N+1];
if(N%3==0&&N!=3) {
dfs(1, N, a, book);
System.out.println(sum/2);
}else {
System.out.println("不符合要求");
}
}
private static void dfs(int step,int N,int[] a,int[] book){
int A=0,B=0,C=0;
int t=N/3-1;
if(step==N+1){
for(int l=1;l<=N/3;l++){
A+=a[l]*(Math.pow(10,t));
B+=a[l+N/3]*(Math.pow(10,t));
C+=a[l+N/3*2]*(Math.pow(10,t));
t--;
}
if(A+B==C){
sum++;
System.out.printf("%d+%d=%d\n",A,B,C);
}
step=1;
}
for (int i=1;i<=N;i++){
if(book[i]==0){
a[step]=i;
book[i]=1;
dfs(step+1,N,a,book);
book[i]=0;
}
}
}
}
解救小哈
后面这些题是越来越烧脑了,脑子还是不够用...,这题走迷宫用到的同样是深度优先搜索 这里用到了二维数组来模拟上下左右,具体方向按照坐标来决定. 这里我照着书中的代码改了下可能写的有点乱 例: package Test_Ahsf;
import java.util.Scanner;
public class Dsf_MiGong {
private static int min=99999999;
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
System.out.println("请输入迷宫列M和行N: ");
int M=sc.nextInt();
int N=sc.nextInt();
System.out.println("请输入要到达的终点坐标:");
int p=sc.nextInt();
int q=sc.nextInt();
int[][]a=new int[N+1][M+1];
System.out.println("录入地图");
for(int i=0;i<N+1;i++){
for(int j=0;j<M+1;j++){
a[i][j]=sc.nextInt();
}
}
int[][]book=new int[N+1][M+1];
book[1][1]=1;
dfs(N,M,1,1,p,q,0,a,book);
System.out.println("最短路径"+min);
}
private static void dfs(int N,int M,int x,int y,int p,int q,int step,int[][]a,int[][]book){
int[][] next = new int[4][2];//分别为向右,向下,向左,向上
next[0][0]=0;next[0][1]=1;
next[1][0]=1;next[1][1]=0;
next[2][0]=0;next[2][1]=-1;
next[3][0]=-1;next[3][1]=0;
int tx,ty;
if(x==p&&y==q){
if(step<min){
min=step;
return;
}
}
for (int k=0;k<=3;k++){
tx=x+next[k][0];
ty=y+next[k][1];
if(tx<1||tx>N||ty<1||ty>M)
continue;
if(a[tx][ty]==0&&book[tx][ty]==0){
book[tx][ty]=1;
dfs(N,M,tx,ty,p,q,step+1,a,book);
book[tx][ty]=0;
}
}
}
}
层层递进-广度优先搜索
广度优先搜索又称宽度优先搜索,用此方法解决上一节的思路就是每次尝试临近的所有点,然后记录下来树
树的介绍
* 时间原因直接先学习树了
* 树是由任意两个节(结)点有且只有一条路径的无向图构成
* 数有多种形态
* 每棵树都有且只有一个根结点,通过根结点确定一颗树
* 根结点又称祖先,一个节点与其相连节点为父子关系,没有子节点的称为叶节点,既不是叶结点也不是根结点则为内部结点
* 每个结点都有对应的深度,为其到根结点的层数根结点算一层
* 如图
* 左边的根节点为1,右边为3
* 结点4的深度为4
* 4,5,6,7都为叶节点
二叉树
* 二叉树特点:每个结点最多有两个儿子,左儿子和右儿子
* 每个结点最多有两棵子树.
* 满二叉树:每个结点都有两个儿子,也可以说所有的叶结点都有同样的深度
* 定义:一棵深度为h且有2^h-1个结点的二叉树
* 完全二叉树:一个结点有右子结点一定有左结点
* 如果一棵完全二叉树的一个父节点编号为k,那么其左儿子编号为2k,右儿子为2k+1,如果一个儿子结点编号为x,那么其父结点编号为x/2
* 如果一棵二叉树有N个结点,那么这个完全二叉树的高度为logN,
-
二叉树的应用-堆
堆是一种特殊的二叉树
堆分为小堆和大堆
小堆中所有父结点中的数值都比子节点小,大堆与之相反
- 堆可以应用在找最小或最大值
一般情况下如果要找最小值就要扫描所有数时间复杂度太高不合适
这时候就可以用堆,这时候根节点就是最小值了
然后删除根结点替换为另一个数字,这时候不符合小堆的特性了
然后可以对其进行向下调整,先让其与左儿子比较比它小就交换,否则与右儿子比较
以此类推直到比较完毕满足小堆的特性 - 至于如果想要增加一个值可以先将其增加到末尾判断是否需要上移就行了
- 前面这些都是建立在要先建立这个堆的前提下才能实现
建立的方法就是从空堆开始,依次插入元素然后判断根结点是否满足要求依次判断根结点往下
这样复杂度还是有点高 - 这里书中提供了另一个方法建立堆
直接将数放入一个完全二叉树中()还是用数组)
这里可以先以最后面的非叶结点为根结点作为新的树开始判断
完全二叉树有一个特性就是最后一个非叶节点为总结点/2
这样判断起来就快多了
堆排序的例子
其中包含了建堆,排序,交换,向下调整
package Test_Ahsf;
import java.util.Scanner;
public class Heap_sort {
private static int[] h=new int[101];
private static int n;
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
int num=in.nextInt();
for (int i=1;i<=num;i++){
h[i]=in.nextInt();
}
n=num;
//建堆
creat();
//排序
heapsort();
//结果
for (int i=1;i<num;i++){
System.out.print(h[i]+" ");
}
System.out.println(h[num]);
}
//建立堆
private static void creat(){
for (int i=n/2;i>=1;i--){
siftdown(i);
}
}
//交换元素
private static void swap(int x,int y){
int t;
t=h[x];
h[x]=h[y];
h[y]=t;
}
//向下调整
private static void siftdown(int i){
int t,flag=0;//用于标记是否需要向下取整
//当i结点有儿子至少是有左儿子,并且需要调整时
//这里i*2<=n是因为最后一个非叶结点是总结点数/2所以只要判断的结点i是非叶结点就可以了
while (i*2<=n&&flag==0){
if(h[i]<h[i*2]){//判断其与左儿子的关系
t=i*2;
}else {
t=i;
}
if(i*2+1<=n){//如果有右儿子在另外讨论
if(h[t]<h[i*2+1]){
t=i*2+1;
}
}
if(t!=i){//这时候t为最大结点,判断是否为i本身
swap(t,i);
i=t;
}else {
flag=1;
}
}
}
//堆排序
private static void heapsort(){
while (n>1){
swap(1,n);
n--;
siftdown(1);
}
}
}
这种方式的数据结构称为优先队列