算法(Algorithms)

算法(Algorithms)

第1章 基础

  • break语句,立即从循环中跳出;
  • continue语句,立即开始下一轮循环;

在Java中创建一个数组需要三步:

  • 创建数组的名字和类型;
  • 创建数组;
  • 初始化数组元素;
#完整模式
double[] a;
a=new double[N];
for( int i=0;i<N;i++)
	a[i]=0.0;
	
#简化写法
double[] a=new double[N];

#声明初始化
int[] a={1,1,2,3,5,8}
简化写法中,数组中每个元素的值为0.0,这是数组创建后的默认值
起别名
a = new int[N];
a[i] = 1234;
int[] b=a;
b[i] = 5678; //a[i]的值也会变为5678

起别名有可能会出现一些难以察觉的问题,所以最好将需要的数组重新复制一份。

二维数组初始化时需要借用嵌套的for循环,其第一维是行数,第二维是列数。

静态方法
  1. 静态方法是使用static关键字修饰的方法,属于类的,不属于对象;非静态方法是不使用static关键字修饰的普通方法,属于对象,不属于类。

  2. 静态方法可以直接调用,类名调用和对象调用;非静态方法只能通过对象调用。

  3. 生命周期不同。

静态方法库是定义在一个Java类中的一组静态方法。类的申明是public class加上类名,以及用花括号包含的静态方法。存放类的文件名和类名相同,扩展名是.java。Java开发的基本模式就是编写一个静态方法库来完成一个任务。

递归

编写递归代码时最重要的就是以下三点:

  • 递归总有一个最简单的情况——方法的第一条语句总是一个包含return的条件语句。
  • 递归调用总是去尝试解决一个规模更小的子问题,这样递归才能收敛到最简单的情况。在下面的代码中,第四个参数和第三个参数的差值一直在缩小。
  • 递归调用的父问题和尝试解决的子问题之间不应该有交集。在下面的代码中,两个子问题各自操作的数组部分是不同的。
public static int rank(int key, int[] a)
    {
        return rank(key,a,0,a.length-1);
    }
    public static int rank(int key, int[] a, int lo, int hi){
        //如果key的值存在于a[]中,他的索引不会小于lo且不会大于hi
        if(lo>hi) return -1;
        int mid=lo+(hi-lo)/2;
        if (key<a[mid]) return rank(key,a ,lo,mid-1);
        else if (key>a[mid]) return rank(key,a,mid+1,hi);
        else return mid;
    }
模块化编程

好处:

  • 程序整体的代码量很大时,每次处理的模块大小仍然适中;
  • 可以共享和重用代码而无需重新实现;
  • 很容易用改进的实现替换老的实现;
  • 可以为解决编程问题建立合适的抽象模型;
  • 缩小调试范围;
将对象作为返回值

方法可以将他们的参数对象返回,也可以创建一个对象并返回他的引用。这种能力非常重要,应为Java中的方法只能有一个返回值,有了对象我们的代码实际上就能返回多个值。

public class FlipsMax {
    public static Counter max(Counter x,Counter y){
        if (x.tally()>y.tally()) return x;
        else                     return y;
    }
    public static void main(String[] args){
        int T=Integer.parseInt(args[0]);
        Counter heads= new Counter("heads");
        Counter tails= new Counter("tails");
        for (int t=0;t<T;t++){
            if(StdRandom.bernoulli(0.5))
                heads.increment();
            else
                tails.increment();
        }
        if (heads.tally()==tails.tally())
             StdOut.println("Tie");
        else StdOut.println(max(heads,tails)+" wins");
    }
}


######################
%java FlipsMax 1000000
 500635 heads wins

其中的部分方法是自己定义,并未展示,全篇皆是类似,需要可以私信我
背包、队列和栈
背包

背包是一种不支持从中删除元素的集合数据类型——它的目的就是帮助用例收集元素并迭代遍历所有手机到的元素。迭代的顺序不确定且与用例无关。

背包的典型用例:

public class Stats {
    public static void main(String[] args) {
        Bag<Double> numbers = new Bag<Double>();
        while (!StdIn.isEmpty())
            numbers.add(StdIn.readDouble());
        int N = numbers.size();
        double sum = 0.0;
        for (double x : numbers)
            sum += x;
        double mean = sum / N;
        sum = 0.0;
        for (double x : numbers)
            sum += (x - mean) * (x - mean);
        double std = Math.sqrt(sum / (N - 1));
        StdOut.printf("Mean: %.2f\n", mean);
        StdOut.printf("Std dev: %.2f\n", std);
    }
}

算术表达式求值

根据以下四种情况从左到右逐个将这些实体送入栈处理:

  • 将操作数压入操作数栈;
  • 将运算符压入运算符栈;
  • 忽略左括号;
  • 在遇到右括号时,弹出一个运算符,弹出所需数量的操作数,并将运算符和操作数的运算结果压入操作数栈。

Dijkstra的双栈算法表达式求值算法:

public class Evaluate {
    public static void main(String[] args){
        Stack<String> ops= new Stack<String>();
        Stack<Double> vals=new Stack<Double>();
        while(!StdIn.isEmpty()){
            //读取字符,如果是运算符则压入栈中
            String s =StdIn.readString();
            if (s.equals("("));
            else if(s.equals("+"))  ops.push(s);
            else if(s.equals("-"))  ops.push(s);
            else if(s.equals("*"))  ops.push(s);
            else if(s.equals("/"))  ops.push(s);
            else if(s.equals("sqrt")) ops.push(s);
            else if (s.equals(")"))
            {
                //如果字符为")",弹出运算符和操作数,计算结果并压入栈中
                String op=ops.pop();
                double v=vals.pop();
                if (op.equals("+")) v=vals.pop()+v;
                else if (op.equals("-")) v=vals.pop()-v;
                else if (op.equals("*")) v=vals.pop()*v;
                else if (op.equals("/")) v=vals.pop()/v;
                else if(op.equals("sqrt")) v=Math.sqrt(v);
                vals.push(v);
            }
            else vals.push(Double.parseDouble(s));
        }
        StdOut.println(vals.pop());
    }
}
案例研究:union-find算法

问题的输入是一列整数对,其中每个整数都表示一个某种类型的对象,一对整数p,q可以被理解为“p和q是相连的”。我们假设相连是一种等价关系。

我们的目标是编写一个程序来过滤掉序列中所有无意义的整数对。换句话说,当程序从输入中读取了整数对p q时,如果已知的所有整数对都不能说明p和q是相连的,那么则将这一对整数写入到输出中。如果已知的数据可以说明p和q是相连的,那么程序应该忽略p q这对整数并继续处理输入中的下一对整数。(找出对连通性有影响的整数对,将其输出到屏幕上,整数对前后出现的顺序会对结果产生影响)

我们将这个问题通俗的叫做动态连通性问题。

union-find算法框架:
public class UF {
    private int[] id;    //分量ID(以触点作为索引
    private int count;  //分量数量
    public UF(int N){
        //初始化分量id数组
        count=N;
        id =new int[N];
        for(int i=0;i<N;i++){
            id[i]=i;
        }
    }
    public int count()
    {
        return count;
    }
    public boolean connected(int p,int q)
    {
        return find(p)==find(q);
    }
    public int find(int p)  
    {
        return p;
    }
    public void union(int p,int q)    //这两个函数是UF算法的主题,将在后面讨论实现
    {

    }
    public static void main(String[] args)
    {
        //解决由StdIn得到的动态连通性问题
        int N=StdIn.readInt();      //读取触点数量
        UF uf=new UF(N);
        while(!StdIn.isEmpty())
        {
            int p=StdIn.readInt();
            int q=StdIn.readInt();              //读取整数对
            if(uf.connected(p,q)) continue;     //如果已经连通则忽略
            uf.union(p,q);                      //归并分量
            StdOut.println(p+" "+q);            //打印连接
        }
        StdOut.println(uf.count()+"components");
    }
}
实现

我们将讨论三种不同的实现,他们均是根据以触点为索引的**id[]**数组来确定两个触点是否存在于相同的连通分量中。

quck-find算法

一种方法保证当且仅当id[p]等于id[q]时p和q是连通的。换句话说,在同一个连通分量中的所有触点在id[]中的值必须全部相同。这意味着connected(p,q)只需要判断id[p]==id[q],当且仅当p和q在同一连通分量中该语句才会返回true。调用union(p,q),如果是处于同一分量,不需要采取任何行动,否则将p有关的id[]值改为id[p]

public int find(int p) {
    return id[p];
}

public void union(int p, int q)    //这两个函数是UF算法的主题,将在后面讨论实现
{
    //将p和q归并到相同的分量中
    int pID = find(p);
    int qID = find(q);

    //如果p和q已经在相同的分量中则不需要采取任何行动
    if (pID == qID) return;

    //将p的分量重命名为q的名称
    for (int i = 0; i < id.length; i++) {
        if (id[i] == pID) id[i] = qID;
    }
    count--;
}
算法分析

find()操作的速度很快,因为它只需要访问**id[]数组一次。但quick-find算法一般无法处理大型问题,因为对于每一对输入union()都需要扫描整个id[]**数组。如果算法最后只得到了一个连通分量,那么至少需要调用N-1次union(),即至少(N+3)(N-1)~ N 2 N^2 N2次数组访问。需要寻找更好的算法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值