Java算法基础和设计模式

目录

数组排序

    冒泡排序

    选择排序

    找出最大值/最小值

    升序排列(从小到大)

    逆序输出

    复制排序

   数组查找算法(搜索)

   线性查找算法(顺序)

   二分查找

   数组插入排序

   快速排序

设计模式

    单例模式(含懒汉、饿汉)

    工厂模式(含普通工厂模式、多个工厂模式、静态工厂模式、抽象工厂模式)

    

    

    

数组排序

代码里标记重点的要记住

冒泡排序

数组最基本的操作算法(这里演示从小到大排序),Arrasy.sort这个相对稳定

这里写图片描述

思路:就是一个个比较,下标0开始跟下标1比较,总共6次循环,每次循环若满足条件就替换,如23>12比,这两个就互换位置,以此类推。

每6次小循环都为一次小循环,每6次循环就可以找出一个最大值(老大),,第二次大循环就可以找出第二大值(老二),以此类推,大循环总共为7次(长度为7)

大概是下面这样:

这样代码太多,我们简化

import java.util.Scanner;
public class MaoPaoPaiXu{
  public static void main(String[] args){
    int[] arr={23,12,45,24,37,65,12,14};

  /*重点:冒泡排序需要两个循环,这就是冒泡排序*/
    for(int i=0;i<arr.length-1;i++){//大循环,每次都能找出一个最大值
      for(int j=0;j<arr.length-1;j++){//嵌套小循环,如6次

         if(arr[j]>arr[j+1]){//一个值跟下一个值对比,这里演示从小到大冒泡排序,反之换<
           //一个值arr[j]>下一个值arr[j+1]交换位置
           int t=arr[j];
           arr[j]=arr[j+1];
           arr[j+1]=t;
         }

      }

    }
   //冒泡后输出该数组内容
   for(int i=0;i<arr.length;i++){
     System.out.println(arr[i]);
   }


  }
}

选择排序

这里写图片描述

思路:先选一个擂主,然后跟后面一个个对比,若是找出最大,那就替换

先选出一个擂主,比如4最大,选出老大后就到外面休息了,在选出老二,又把老二移除,以此类推!

每次找出一个就少一个,不用再比较(冒泡是要一直重复比较)

public class XuanZePaiXu{
  public static void main(String[] args){
    int[] arr={34,54,6,87,98,54,3432,1};

/*重要:需要记忆,也是循环嵌套*/
    for(int i=0;i<arr.length-1;i++){
      for(int j=j+1;j<arr.length;j++){//第一轮第一个擂主跟后面一个个的值比,
        if(arr[i]>arr[j]){//擂主>和我挑战的人,我就跟他互换
          //擂主arr[i]和挑战者arr[j]互换
          int t=arr[i];
          arr[i]=arr[j];
          arr[j]=t;
       
        }

      }
    }
//输出替换后的
    for(int i=0;i<arr.length;i++){
     System.out.println(arr[i]);

    }       

  }
}

冒泡排序很选择排序对比:

比冒泡排序算法效率更高;两种排序的对比

冒泡:相邻的两个下标的比较,进行数组长度的次数循环

选择:选出一个擂台循环跟后面的比较,然后继续换其他的擂台

冒泡:                                                                   选择:

 

找出最大值/最小值

题目:有一组(一串)数字,找出这组数字的最大值,;

思路:最简单的是设定一个擂台,先把一个数放在擂台上,下面一个个数跟擂台对比,若是满足就把擂台和该对比的数字替换下来,知道最后;

import java.util.Scanner;
public class MAXtest{


  public static void main(){

/*非重点:这里是由用户输入一组数字,有些题目是直接给出的一组数字,如:int[] arr={5,2.6.3.1}*/
    Scanner in=new Scanner(System.in);//由用户输入一组数字
    int[] arr=new int[5];//一组数字有五个数
    for(int i=0;i<arr.length;i++){//循环输入一组数字到数组中
      System.out.println("请输入第"+(i+1)+"个数字");
      arr[i]=in.nextInt();//数组接收用户的内容 

    }


/*下面才是重点:“打擂台”的算法*/
     int max=arr[0];//这里就是设置一个擂台max,然后擂台先站一个人arr[0];

     //下面就是让其他的数一个个跟擂台上的数比大小咯,若满足条件,擂台就换人
     for(int i=0;i<arr.length;i++){//循环对比

   //擂台max跟下一个数arr[i]进行对比,如果擂台<下一个数,就替换,这里是找最大值,最小值要换下>
       if(max < arr[i]){
         max=arr[i];//把下一个数字换到擂台上
         
       }

     }
    
System.out.println("所以最大值就是:"+ max );  

   }


}

线性/顺序查找算法

按照顺序一个个查找的方法;--类似上面打擂台方式

main测试下:

想调用find、()方法先要newFINDTest对象,如果不想加就在find()前加static

-1是表示查找失败,因为数组没有下标为-1的

升序排列(从小到大)

题目:有一组(一串)数字,让这组数字进行升序排列,;

思路:使用java中提供的Arrays方法来实现;

import java.util.Arrays;//java.uril中的方法
import java.util.Scanner;
public class ShengXuPaiLie{

   public static void main(String[] args){

 /*非重点:这里是由用户输入一组数字,有些题目是直接给出的一组数字,如:int[] arr={5,2.6.3.1}*/
    Scanner in=new Scanner(System.in);//由用户输入一组数字
    int[] arr=new int[5];//一组数字有五个数
    for(int i=0;i<arr.length;i++){//循环输入一组数字到数组中
      System.out.println("请输入第"+(i+1)+"个数字");
      arr[i]=in.nextInt();//数组接收用户的内容 
    }


/*下面才是重点:使用Arrays方法来实现升序的方法*/
     Arrays.sort(arr);//这就是自动升序的方法,这个要记住

     //接下来循环输出升序后的内容
     for(int i=0;i<arr.length;i++){
       System.out.println(arr[i]);//循环输出升序后arr[i]的内容
     }

   }

}

三、逆序输出

abcd是正序(从小到大),dcba是逆序(从大到小)

思路:先把这些杂乱的字符排列下,现在从小到大排列(升序),下标很定最小在前,逆序就把下标最大放前面

                                                                                                                   

import java.util.Arrays;//java.uril中的方法
import java.util.Scanner;
public class NiXuPaiLie{

   public static void main(String[] args){

 /*非重点:这里是由用户输入一组数字,有些题目是直接给出的一组数字,如:int[] arr={5,2.6.3.1}*/
    Scanner in=new Scanner(System.in);//由用户输入一组数字
    int[] arr=new int[5];//一组数字有五个数
    for(int i=0;i<arr.length;i++){//循环输入一组数字到数组中
      System.out.println("请输入第"+(i+1)+"个数字");
      arr[i]=in.nextInt();//数组接收用户的内容 
    }


     Arrays.sort(arr);//先把杂乱的字符升序。

/*下面才是重点:使用Arrays方法来实现升序的方法*/
     //接下来逆序输出,逆序就是把最大的坐标在前面先输出,这个for循环要记住
     for(int i=arr.leng-1;i>=0;i--){
/**arr.leng-1这个就是最大的下标,(数组长度-1=最大下标),比如有2个数组就是0,1,1就是最大的下标(2-1=1)
**i>=0这个是数组最少是1个数组;
**i--这个就是逆序不能是++,逻辑要弄清
**/
       System.out.println(arr[i]);//逆序输出
     }

   }

}

java也提供了一个逆序方法,reverse(),

集合的内容https://blog.csdn.net/bbs11007/article/details/89467148


import java.util.ArrayList;
import java.util.Coliections;
import java.util.List;
import java.util.Scanner;
public class Test11
{
    public static void main(String[] args)
    {
        Scanner input=new Scanner(System.in);//由用户输入内容
        List students=new ArrayList();//创建集合
        System.out.println("******** 商品信息 ********");
        for(int i=0;i<5;i++)
        {
            System.out.println("请输入第 "+(i+1)+" 个商品的名称:");
            String name=input.next(); //获取用户的输入内容
            students.add(name);    //将录入的商品名称存到List集合中
        }
        Collections.reverse(students);    //调用reverse()方法对集合元素进行反转排序
        System.out.println("按录入时间的先后顺序进行降序排列为:");
        for(int i=0;i<5;i++)
        {
            System.out.print(students.get(i)+"\t");
        }
    }

复制排序

题目:把一个数组的内容复制到另个数组里

思路:把一个组数的下标的值赋值到对应下标中,如:一个数组0下标2的值赋值到下面数组0下标的值

public class Fuzhi{

  public static void main(String[] args){
      int[] arrA={1,2,3,4,5,6,7,8};//一个数组的值
      int[] arrB=new int{arrA.length}//新建一个数组的长度跟arrA一样
     
/*重点来了:下面就开始一个个把arrA的值赋值给arrB中,对应下标*/
      for(int i=0;i<arrA.length;i++){//循环arrA的长度下标
         arrB[i]=arrA[i];//把arrA的下标i赋值给arrB的下标i,如:arrA[0]的值赋值给arr[0]

      }

  }


}

java还提供了一个复制的方法,copy()

集合的内容https://blog.csdn.net/bbs11007/article/details/89467148


public class Test12
{
    public static void main(String[] args)
    {
        Scanner input=new Scanner(System.in);
        List srcList=new ArrayList();
        List destList=new ArrayList();
        destList.add("苏打水");
        destList.add("木糖醇");
        destList.add("方便面");
        destList.add("火腿肠");
        destList.add("冰红茶");
        System.out.println("原有商品如下:");
        for(int i=0;i<destList.size();i++)
        {
            System.out.println(destList.get(i));
        }
        System.out.println("输入替换的商品名称:");
        for(int i=0;i<3;i++)
        {
            System.out.println("第 "+(i+1)+" 个商品:");
            String name=input.next();
            srcList.add(name);
        }
        //调用copy()方法将当前商品信息复制到原有商品信息集合中
        Collections.copy(destList,srcList);
        System.out.println("当前商品有:");
        for(int i=0;i<destList.size();i++)
        {
            System.out.print(destList.get(i)+"\t");
        }
    }
}

数组查找算法(搜索)

题目:存在数组的名字,查找出某个同学(如壁虎)

思路:从0下标开始一个个找,找到最后为止,如果最后还找不到,就判断没有。(注意,这里是查找有没有这个人,若是有两个一样的名字,也不管,只负责是否判断有这个人)

import java.util.Scanner;
public class SouSuo{
  public static void main(String[] args){
    Scanner in = new Scanner(System.in);
    System.out.println("请输入你要找的学生的姓名:");
    String name = in.next();//由用户输入要找的人,比如用户输入:壁虎
    String[] arr={"张三","李四","王二","麻子","壁虎"};//若是有重名,下面就判断输出两次就没意义

    boolean flag=false;//这里做个标记,防止有重名,不要输出多次
  
 /*重点:开始在数组里面一个个找,判断是不是要找的人*/
   for(int i=0;i<arr.length;i++){//循环数组里面的人
     if(name.equals(arr[i])){
       flag=true;//我们就判断flag
       break;//如果找到了,下面就不用再找了,就跳出for循环
     }

   }
   
   if(flag==true){//如果有这个学生,就提示找到了,后面就没有找了
    System.out.printl(“找到了”);  

   }else{//否则整个数组都没有该学生
    System.out.printl(“没有找到该学生”);
   }

  }

}

 

二分查找

必须是从小到大排列,有序的数组才能用此方法;

题目:使用二分查在数组中一个数

折半查找,也称二分查找、二分搜索、折半查找,是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组已经为空,则表示找不到指定的元素。这种搜索算法每一次比较都使搜索范围缩小一半,其时间复杂度是O(logN)。

思路如下:二分查找前提是要在有序的数组里,所以先会使用Arrays.sort()方法先进行排序,使用java提供的方法

import java.util.Arrays;

public class ErFenPaiXu{
  public static void main(String[] args){
    int data= new int[]{1,4,64,65,34,23,21312,2};
    java.uitl.Arrays.sort(data);//先进行排序
    System.out.println(Arrays.binarySearch(data,23));//二分查找binarySearch()方法

  }

}

第二种非递归实现:

public static int biSearch(int []array,int a){
        int lo=0;
        int hi=array.length-1;
        int mid;
        while(lo<=hi){
            mid=(lo+hi)/2;
            if(array[mid]==a){
                return mid+1;
            }else if(array[mid]<a){
                lo=mid+1;
            }else{
                hi=mid-1;
            }
        }
        return -1;
    }

第三种递归方式

public static int sort(int []array,int a,int lo,int hi){
        if(lo<=hi){
            int mid=(lo+hi)/2;
            if(a==array[mid]){
                return mid+1;
            }
            else if(a>array[mid]){
                return sort(array,a,mid+1,hi);
            }else{
                return sort(array,a,lo,mid-1);
            }
        }
        return -1;
    }

高逼格写法

import java.util.Comparator;
 
public class MyUtil {
 
   public static <T extends Comparable<T>> int binarySearch(T[] x, T key) {
      return binarySearch(x, 0, x.length- 1, key);
   }
 
   // 使用循环实现的二分查找
   public static <T> int binarySearch(T[] x, T key, Comparator<T> comp) {
      int low = 0;
      int high = x.length - 1;
      while (low <= high) {
          int mid = (low + high) >>> 1;
          int cmp = comp.compare(x[mid], key);
          if (cmp < 0) {
            low= mid + 1;
          }
          else if (cmp > 0) {
            high= mid - 1;
          }
          else {
            return mid;
          }
      }
      return -1;
   }
 
   // 使用递归实现的二分查找
   private static<T extends Comparable<T>> int binarySearch(T[] x, int low, int high, T key) {
      if(low <= high) {
        int mid = low + ((high -low) >> 1);
        if(key.compareTo(x[mid])== 0) {
           return mid;
        }
        else if(key.compareTo(x[mid])< 0) {
           return binarySearch(x,low, mid - 1, key);
        }
        else {
           return binarySearch(x,mid + 1, high, key);
        }
      }
      return -1;
   }
}
/*
说明:上面的代码中给出了折半查找的两个版本,一个用递归实现,一个用循环实现。需要注意的是计算中间位置时不应该使用(high+ low) / 2的方式,因为加法运算可能导致整数越界,这里应该使用以下三种方式之一:low + (high – low) / 2或low + (high – low) >> 1或(low + high) >>> 1(>>>是逻辑右移,是不带符号位的右移)
*/

数组插入算法

题目:插入一个数字

思路:最大下标是100,若是我要插入33,那么久把100替换了,但是整个数组顺序就打乱了(从小到大排序),所以我们33要插入在前面,这就慢慢的跟之前的比较大小,若满足就替换。

从小到大排序:

插入33(把最后数组100给替换了): arr(arr.length-1)=num;

最后结果:

import java.util.Scanner;
public class ChaRu{
  public static void main(String[] args){
    Scanner in = new Scanner(System.in);
    int arr={1,32,45,65,67,76,100};
    System.out.println("请输入你要插入的数值:");//由用户输入,如:33
    int num = in.next();

/*重点:插入数字后,要对数字进行排序*/
    arr(arr.length-1)=num;//33把最后数组100给替换了
//接下来就对33插入在哪个位置更合适,也就是进行排列。
    for(int i=arr..length-1;i>0;i--){//下面从后写起都是这样
      if(arr[i]<arr[i-1]){//如果插入的数值比前面的小,那就进行替换,如:33<76两者替换
         //两个值交换
         int t=arr[i];
         arr[i]=arr[i-1];
         arr[i-1]=t
      }else{//如果不小于就不往下面执行了
        break;
       
       }

    }
   
    //输出排列后的数组
   for(int i=0;i<arr.length;i++){
      System.out.println(arr[i]);
   }

  }
}

还有其他的插入排序

这里写图片描述

代码实现如下:

   /**
    *插入排序
    */
    public static void chaRu(int []array) {
        int temp;
        System.out.println("\n插入排序:");
        for(int i=1;i<array.length;i++) {
            int j=i;
            temp = array[i];
            while( j>0 && temp < array[j-1]) {
                array[j] = array[j-1];
                j--;
            }
            array[j] = temp;
            System.out.print("第"+i+"次:");
            show(array);
        }

快速排序

特点:高效,时间复杂度为nlogn。 
原理:采用分治法的思想:首先设置一个中间值,然后以这个中间值为划分基准将待排序序列分成比该值大和比该值小的两部分,将这两部分再分别进行快速排序 
直到序列只剩下一个元素

这里写图片描述

/**快速排序
     * */
    public static void kuaiSu(int []array,int left,int right){
        if(left < right) {
            int i=left,j=right;
            int pivot = array[left];//选择最左边的元素作为中间值

        /**
         * 分治
         */
        while(i < j) {
            while(i < j && array[j] >= pivot) {
                j--;
            }
            if(i < j) {
                array[i] = array[j];
                i++;
            }
            while(i < j&& array[i] < pivot){
                i++;
            }
            if(i < j) {
                array [j] =array [i];
                j--;
            }
        }
        array [i]=pivot;
        //递归
        kuaiSu(array,left,i-1);
        kuaiSu(array,i+1,right);
        }
    }

设计模式

单例模式:

    单例类整个程序只能有一个实例,这个类负责创造自己的对象,并确保只有一个类被创建。

 一个类只有一个实例,即一个类只有一个对象实例。

代码要点:

1.私有构造器

2.持有该类的属性

3.对外提供获取实例的静态方法

     懒汉单例模式:

                 线程不安全,致命的是在多线程不能正常工作,在第一次调用的时候实例化 

线程不安全、延迟加载(两种加同步,效率低)

public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static synchronized Singleton getInstance(){
        if (instance == null) instance = new Singleton();
        return instance;
    }
}

     饿汉单例模式:

   避免了多线程的同步问题,在类初始化时,已经自行实例化 

线程安全、反射和反序列化不安全;

public class Singleton {
    private Singleton(){}
    private static Singleton instance = new Singleton();
    public static Singleton getInstance(){
        return instance;
    }
}

 

工厂模式

工厂方法模式的好处就提高扩展性和维护性。自己体会。

一个小米、华为不是自己建厂,而是外包给富士康。

普通工厂方法模式

根据类图去写代码,这里就不介绍怎么看了,编写步骤如下:

先写右边的,先新建一个sender接口

Sender无返回值(所以用void)

然后照着类图写两个实现类

第一个实现类,新建这个类,这个要重写send方法,来模拟下发送邮件

第二个实现类,新建这个类,这个要重写send方法,来模拟下发送短信

最后来写工厂类:,新建这个类,

工厂就是生产两个对象,如果两个都没有就返回null;

写个main测试下:

如果要调用工厂类的方法Produce(),那先引用SendrFacetory类(就是new它),见下图

运行结果:

这个框架就写完了,一个Send接口,两个实现接口的实现类,一个工厂(里面一个方法,相当于生产线生产产品),

上面的效果相当于,比较麻烦,明明可以new对象来实现,还要建一个工厂,为什么这么复杂还要建呢?

这样的方法简单简洁,比上面简洁;

一个对象看不出来,要是一会创建一个对象,一会创建一个,要200个,这种要new200次对象,但是用工厂的好处。

如果在s2 new之前输出同一句System.out.print(“helloworld),每次new对象都要写这个,如果客户要改动,你要改两百次

有一种场景,如果main函数中,Sender s= sf.produce(“maill”);//手抖多加了一个l

任何传到工厂类判断不满足条件,所以return null;那么把空值给了s,所以s.send()调用就会出现空指针异常;

解决这个问题采用下面的多个工厂方法模式;

多个工厂方法模式

右边跟普通工厂模式一样,左边有变动,增加了一个类,所以添加就行

把原来一个方法生产200种产品,拆分成两个方法各自生产一种产品。优点就是不用再传字符串了,也不考虑用户字符串传错的

问题;

main函数也修改下,不用传参数的好处

运行:

这个模式还不够好,创建是为了调用produce方法;

我们可以采用以下的静态工厂方法,可以跳过这一步(代码能越简洁越好)

静态工厂方法模式

简单的加上static静态,这就是静态的,很容易理解吧

main中就不都不需要创建对象了,这就更优秀了。

没有特殊要求一律写静态工厂模式;

一个工厂类两个工厂模式去生产不同产品,如果需要新增一个包裹,就要增加一个方法,增加方法就要修改工厂类,这就违背了开闭原则,所以就引用下面的抽象工厂

抽象工厂方法模式

工厂模式讲求开闭原则,就是不修改工厂类中已测试完毕的代码,违背了开闭原则,(也就是工厂模式写完测试通过的代码能不修改就不修改),这就是引用抽象工厂方法模式

右侧的类图跟上面一样,没有变化,左侧提供了Provider的接口,produce方法...

那根据类图写代码,左侧的新增工厂类接口

这个就是抽象行为,抽象的概念是相当于一个模板,比如我们知道一个对象的行为,但是具体是哪个行为要自己去定义,我们先把这种行为的格式先写好放在那里(如:动物都要吃着行为,但要怎么吃,坐着吃还是躺着吃,我都不管,我先把这个行为放那里),不理解的去看看抽象的概念。抽象写好后,因为没有具体实现,所以后面一般不会修改它。

然后在写实现类的接口

最后写第二个实现类接口

两个工厂类2个公共接口写完了。

我们写个main函数测试下:

运行:

跟上面的功能一模一样

小结:

 普通工厂模式:按照框架类图写代码,缺点是根据参数传值,容易误传错,所以采用下面的多个工厂模式

多个工厂模式:多写一些方法,不用一个方法负责所有的生产,可以各负责各的;优点是不用传参数,不用担心传错参数

静态工厂模式:在多个工厂模式的方法前加上static修饰,就不需要new对象,也不需要先new工厂才能调用方法,让代码简化

                         缺点是要增加一个方法就要修改工厂类,都测试通过了还要改;

抽象工厂模式:工厂模式遵循开闭原则,也就是不能随便改变工厂类的代码(特别是测试通过后),抽象就是相当于一个模板,

                         优点是要增加方法不用修改工厂类,新增一个,且与其他的互不影响。

猜你喜欢

转载自blog.csdn.net/bbs11007/article/details/100136297