Java经典算法详解-不来看看可惜咯

正在学Java的各位这几种Java编程中的经典算法千万不要错过,算法在Java开发中会起到非常重要作用,了解这些常用的算法会让你在工作中达到事半功倍的效果!

看看今天为大家整理经典算法详解:

一、插入排序

这是一个很好的理解打麻将或扑克。例如,如果左手中有一套卡1、2、4、7和一张3,则从右向左将此卡插入2、4是来验证否正确。

一次插入排序的操作过程:

将待插元素,依次与已排序好的子数列元素从后到前进行比较,如果当前元素值比待插元素值大,则将移位到与其相邻的后一个位置,否则直接将待插元素插入当前元素相邻的后一位置,因为说明已经找到插入点的最终位置:

Java经典算法详解-不来看看可惜咯

public class InsertSort {

    public static void sort(int[] arr) {
        if (arr.length >= 2) {
            for (int i = 1; i < arr.length; i++) {
                //挖出一个要用来插入的值,同时位置上留下一个可以存新的值的坑
                int x = arr[i];
                int j = i - 1;
                //在前面有一个或连续多个值比x大的时候,一直循环往前面找,将x插入到这串值前面
                while (j >= 0 && arr[j] > x) {
                    //当arr[j]比x大的时候,将j向后移一位,正好填到坑中
                    arr[j + 1] = arr[j];
                    j--;
                }
                //将x插入到最前面
                arr[j + 1] = x;
            }
        }
    }
}

二、选择排序

选择排序的基本思想是遍历数组的过程中,以 i 代表当前需要排序的序号,则需要在剩余的 [i…n-1] 中找出其中的最小值,然后将找到的最小值与 i 指向的值进行交换。因为每一趟确定元素的过程中都会有一个选择最大值的子流程,所以人们形象地称之为选择排序。

如下示例:

初始值为: [38, 17, 16, 16, 7, 31, 39, 32, 2, 11]

i = 0: [2 , 17, 16, 16, 7, 31, 39, 32, 38 , 11] (0th [38]<->8th [2])

i = 1: [2, 7 , 16, 16, 17 , 31, 39, 32, 38, 11] (1st [38]<->4th [17])

i = 2: [2, 7, 11 , 16, 17, 31, 39, 32, 38, 16 ] (2nd [11]<->9th [16])

i = 3: [2, 7, 11, 16, 17, 31, 39, 32, 38, 16] ( 无需交换 )

i = 4: [2, 7, 11, 16, 16 , 31, 39, 32, 38, 17 ] (4th [17]<->9th [16])

i = 5: [2, 7, 11, 16, 16, 17 , 39, 32, 38, 31 ] (5th [31]<->9th [17])

i = 6: [2, 7, 11, 16, 16, 17, 31 , 32, 38, 39 ] (6th [39]<->9th [31])

i = 7: [2, 7, 11, 16, 16, 17, 31, 32, 38, 39] ( 无需交换 )

i = 8: [2, 7, 11, 16, 16, 17, 31, 32, 38, 39] ( 无需交换 )

i = 9: [2, 7, 11, 16, 16, 17, 31, 32, 38, 39] ( 无需交换 )

由例子可以看出,选择排序随着排序的进行( i 逐渐增大),比较的次数会越来越少,但是不论数组初始是否有序,选择排序都会从 i 至数组末尾进行一次选择比较,所以给定长度的数组,选择排序的比较次数是固定的: 1 + 2 + 3 + …. + n = n * (n + 1) / 2 ,而交换的次数则跟初始数组的顺序有关,如果初始数组顺序为随机,则在最坏情况下,数组元素将会交换 n 次,最好的情况下则可能 0 次(数组本身即为有序)。

代码示例:

*  
 * Selection Sorting  
 */  
SELECTION(new Sortable() {   
    public <T extends Comparable<T>> void sort(T[] array, boolean ascend) {   
        int len = array.length;   
        for (int i = 0; i < len; i++) {   
            int selected = i;   
            for (int j = i + 1; j < len; j++) {   
                int compare = array[j].compareTo(array[selected]);   
                if (compare != 0 && compare < 0 == ascend) {   
                    selected = j;   
                }   
            }   
  
            exchange(array, i, selected);   
        }   
    }   
})   

三、快速排序法

简单的说, 就是设置一个标准值, 将大于这个值的放到右边(不管排序), 将小于这个值的放到左边(不管排序), 那么这样只是区分了左小右大, 没有排序, 没关系, 左右两边再重复这个步骤.直到不能分了为止.

代码示例:

public class QuickSort {

    public static void sort(int[] arr,int begin,int end) {
        //先定义两个参数接收排序起始值和结束值
        int a = begin;
        int b = end;
        //先判断a是否大于b

        if (a >= b) {
            //没必要排序
            return;
        }
        //基准数,默认设置为第一个值
        int x = arr[a];

        //循环
        while (a < b) {
            //从后往前找,找到一个比基准数x小的值,赋给arr[a]
            //如果a和b的逻辑正确--a<b ,并且最后一个值arr[b]>x,就一直往下找,直到找到后面的值大于x
            while (a < b && arr[b] >= x) {
                b--;
            }
            //跳出循环,两种情况,一是a和b的逻辑不对了,a>=b,这时候排序结束.二是在后面找到了比x小的值
            if (a < b) {
                //将这时候找到的arr[b]放到最前面arr[a]
                arr[a] = arr[b];
                //排序的起始位置后移一位
                a++;
            }

            //从前往后找,找到一个比基准数x大的值,放在最后面arr[b]
            while (a < b && arr[a] <= x) {
                a++;
            }
            if (a < b) {
                arr[b] = arr[a];
                //排序的终止位置前移一位
                b--;
            }
        }
        //跳出循环 a < b的逻辑不成立了,a==b重合了,此时将x赋值回去arr[a]
        arr[a] = x;
        //调用递归函数,再细分再排序
        sort(arr,begin,a-1);
        sort(arr,a+1,end);
    }
}

四、冒泡排序 基础版

每个冒泡过程从序列的第一个元素开始,然后依次将其与其余元素进行比较。与队列一样,两个相邻元素的大小之比从左到右改变高和低的位置。最后,最高(最大值)必须排在后面

但是这只是把最高的一个放在后面,我们必须找到第二个最高的,所以我们比较第一个的两个,高的一个向后,然后第二个最高的一个落后

然后是第三高的再往后排…

代码示例:

public class MaoPao {

    public static void  sort(int[] arr){
        for (int i = 1; i < arr.length; i++) {  //第一层for循环,用来控制冒泡的次数
            for (int j = 0; j < arr.length-1; j++) { //第二层for循环,用来控制冒泡一层层到最后
                //如果前一个数比后一个数大,两者调换 ,意味着泡泡向上走了一层
                if (arr[j] > arr[j+1] ){
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }
}

五、冒泡排序 进阶版

补充、改进,参见下面测试结果发现提升并不大,这是正常的,因为改进的一步是省略了成功后的判断,甚至如果没有改进,成功后的排序和只是数组遍历,没有数据更新,我们知道读取数组快更新慢,所以它看起来并没有比之前的版本有多大的改进

在这个版本中,改动了两点分别是:

第一点是加入了一个布尔值,判断第二层循环中的调换有没有执行,如果没有进行两两调换,说明后面都已经排好序了,已经不需要再循环了,直接跳出循环,排序结束.

第二点是第二层循环不再循环到arr.length - 1,因为外面的i循环递增一次,说明数组最后就多了一个排好序的大泡泡.第二层循环也就不需要到最末尾一位了,可以提前结束循环

代码示例:

 /**
 * 进阶冒泡排序
 * 加入一个布尔变量,如果内循环没有交换值,说明已经排序完成,提前终止
 * @param arr
 */
public static void sortPlus(int[] arr){
    if(arr != null && arr.length > 1){
        for(int i = 0; i < arr.length - 1; i++){
            // 初始化一个布尔值
            boolean flag = true;
            for(int j = 0; j < arr.length - i - 1 ; j++){
                if(arr[j] > arr[j+1]){
                    // 调换
                    int temp;
                    temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                    // 改变flag
                    flag = false;
                }
            }
            if(flag){
                break;
            }
        }
    }
}

六、希尔排序

希尔排序的诞生是由于插入排序在处理大规模数组的时候会遇到需要移动太多元素的问题。

希尔排序的思想是将一个大数组划分为几个较小的数组,按间gap划分,例如数组[1,2,3,4,5,6,7,8]。如果 gap = 2,可以将其划分为两个数组[1,3,5,7]和[2,4,6,8](对应的,如gap = 3,被划分的数组为[1,4,7],[2,5,8],[3,6]),然后分别对被划分的数组插入排序。对每个子数组进行排序后,减小gap值,重复前面的步骤,直到gap = 1,即对整个数组插入排序。此时,数组几乎已经排好序了,所以需要移动的元素非常非常小,这就解决了在处理大型数组时插入排序需要更多移动的问题。

具体实例请参照插入排序。

希尔排序是插入排序的改进版本,在数据量较大的情况下,Hill排序有助于提高效率。当数据量很小时,建议直接使用插入排序。

代码示例:

/**  
 * Shell Sorting  
 */  
SHELL(new Sortable() {   
    public <T extends Comparable<T>> void sort(T[] array, boolean ascend) {   
        int length = array.length;   
        int gap = 1;   
  
        // use the most next to length / 3 as the first gap   
        while (gap < length / 3) {   
            gap = gap * 3 + 1;   
        }   
  
        while (gap >= 1) {   
            for (int i = gap; i < length; i++) {   
                T next = array[i];   
                int j = i;   
                while (j >= gap) {   
                    int compare = array[j - gap].compareTo(next);   
                    // already find its position   
                    if (compare == 0 || compare < 0 == ascend) {   
                        break;   
                    }   
  
                    array[j] = array[j - gap];   
                    j -= gap;   
                }   
                if (j != i) {   
                    array[j] = next;   
                }   
            }   
            gap /= 3;   
        }   
  
    }   
})  

 


顺便在列据出几道经典的算法面试题:

第一

问题:有一对兔子。从出生后的第三个月开始,他们每月生一对兔子。当小兔子长到第三个月时,它们每个月都会产下另一对兔子。如果兔子没有死,每个月兔子的总数是多少?

代码演示:

//这是一个菲波拉契数列问题
public class test01 {
    public static void main(String[] args) {
        int f1=1,f2=1,f;
        int M=30;
        System.out.println(f1);
        System.out.println(f2);
        for(int i=3;i<M;i++) {
            f=f2;
            f2=f1+f2;
            f1=f;
            System.out.println(f2);
        }
    }
}

第二

问题:判断101-200之间有多少个素数,并输出所有素数。

程序分析:判断素数的方法:用一个数分别去除2到sqrt(这个数),如果能被整除, 则表明此数不是素数,反之是素数。

public class test02 {
    public static void main(String[] args) {
        int count=0;
        for(int i=101;i<200;i+=2) {
            boolean flag=true;
            for(int j=2;j<=Math.sqrt(i);j++) {
                if(i%j==0) {
                    flag=false;
                    break;
                }
            }
            if(flag==true) {
                count++;
                System.out.println(i);
            }
        }
        System.out.println(count);
    }
}

第三

问题:打印出所有的 "水仙花数 ",所谓 "水仙花数 "是指一个三位数,其各位数字立方和等于该数本身。例如:153是一个 "水仙花数 ",因为153=1的三次方+5的三次方+3的三次方。

public class test03 {
    public static void main(String[] args) {
        int a,b,c;
        for(int i=101;i<1000;i++) {
            a=i%10;
            b=i/10%10;
            c=i/100;
            if(a*a*a+b*b*b+c*c*c==i)
                System.out.println(i);
            }
        }
} 

第四:

问题:将正整数分解为素因子。例如,输入90并打印90=2*3*3*5。

程序分析:要分解N的素数因子,首先找到一个最小素数k,然后完成以下步骤:

(1) 如果素数正好等于N,则表示分解素数因子的过程已经结束,您可以将其打印出来。

(2) 如果n>k,但n可以被k除,则打印出k的值,将n除以k的商作为新的正整数,然后重复第一步。

(3) 如果n不能除以K,则以K+1作为K的值重复第一步。

import java.util.Scanner;
public class test04 {
    public static void main(String[] args) {
        Scanner input=new Scanner(System.in);
        int n=input.nextInt();
        int k=2;
        while(n>=k) {
            if(n==k) {
                System.out.println(k);
                break;
            }else if (n%k==0) {
                System.out.println(k);
                n=n/k;
            }else {
                k++;
            }
        }
    }
}

第五:

问题:使用条件运算符嵌套完成此问题:学业成绩>=90分的学生用a表示,60-89分的学生用B表示,60分以下的学生用C表示。

import java.util.Scanner;
public class test05 {
    public static void main(String[] args) {
        Scanner input=new Scanner(System.in);
        int score=input.nextInt();
        char grade=score>=90?‘A‘:score>=60?‘B‘:‘C‘;
        System.out.println(grade);
    }
}

第六:

问题:输入一行字符,分别统计出其中英文字母、空格、数字和其它字符的个数。

import java.util.Scanner;
public class test07 {
    public static void main(String[] args) {
        int abccount=0;
        int spacecount=0;
        int numcount=0;
        int othercount=0;
        Scanner input=new Scanner(System.in);
        String toString=input.nextLine();
        char [] ch=toString.toCharArray();
 
        for(int i=0;i<ch.length;i++) {
            if(Character.isLetter(ch[i])) {
                abccount++;
            }else if(Character.isDigit(ch[i])) {
                numcount++;
            }else if(Character.isSpaceChar(ch[i])){
                spacecount++;
            }else {
                othercount++;
            }
        }
        System.out.println(abccount);
        System.out.println(spacecount);
        System.out.println(numcount);
        System.out.println(othercount);
    }
}

以上就本次的全部内容, 文章内容部分摘抄与网络,数据共享时代为正在学习的你,添加一份提醒

历史文章推荐:

必读的10本有关Java的书籍

Java-程序员:最为常用的开发工具 详解

javase2021最强学习线路没有之一

“Git”在 IntelliJ IDEA中这样使用贼方便

狙击面试官-21条 Linux 常用命令

Java经典算法详解-不来看看可惜咯

上一篇:Using PowerCLI to build multiple VMs


下一篇:静默授权与主动授权区别、使用场景以及常见问题。