当前位置:   article > 正文

算法入门学习_算法学习

算法学习

1. 算法

1.1 什么是算法?

算法指用系统的方法描述解决问题的策略机制。通俗点讲就是求解问题的方法的方法。

1.2 算法的质量评价指标

1.2.1 时间复杂度

时间复杂度是指问题求解方法的时间效率,由于机器性能等外部环境差异可能会导致同一代码在不同机器上运行的时间不同,所以,严格意义上讲,衡量算法的时间是十分困难的。但是,即使存在这样的外部环境差异,算法的执行时间还是与语句的执行次数成正比的。因此,我们在一定程度上可以通过计算语句的执行次数来推断算法的执行时间。

语句的执行次数被称为时间频度(语句频度),记为T(n)。其中T表示的是最多次数(Times),n表示的是求解问题的规模。所以T(n)表示当问题规模为n时的最多执行次数。

T必须是规模n的最多执行次数才行,这样T(n)才是一个函数,而不是一种相关性。因为如果T不是最多执行次数,那我们还得考虑规模n之下的T到底是什么情况,那么T就可能存在许多值。所以只有当T为最大值时,无论n为多少,T都是一个唯一确定的值,这也就构成了函数关系。

/*案例分析---百元买百鸡
	公鸡5文钱一只,
	母鸡3文钱一只,
	小鸡3只一文钱,
	用100文钱买一百只鸡,
	其中公鸡,母鸡,小鸡都必须要有,
	问公鸡,母鸡,小鸡要买多少只刚好凑足100文钱。
*/

//---A方法---
for(int a=1;a<=100;a++){
	for(int b=1;b<=100;b++){
		for(int c=1;c<=100;c++){
			if(a*5+b*3+c/3==100 && a+b+c==100 && c%3==0){
				System.out.println("公鸡:"+a+"只, 母鸡"+b+", 小鸡"+c);
			}
		}
	}
}
/* A方法时间频度计算:
	第一层for循环:执行100次
	第二层for循环:执行100^2次
	第三层for循环:执行100^3次
*/

//---B方法---
for(int a=1;a<=100;a++){
	for(int b=1;b<=100;b++){
		c=100-a-b;
		if(a*5+b*3+c/3==100 && c%3==0){
			System.out.println("公鸡:"+a+"只, 母鸡"+b+", 小鸡"+c);
		}
	}
}
/*B方法时间频度计算:
	第一层for循环:执行100次
	第二层for循环:执行100^2次
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

通过上面这个案例,A、B算法孰优孰劣一览无余,但时间频度不等于时间复杂度。时间复杂度可以看作是时间频度的极限。比如上例,当n趋向于无穷的时候,A、B的时间频度又如何表示呢?我们用O(n)表示时间复杂度,当n趋向于无穷时,T(n)=O(n),所以
A的时间复杂度O(n)=n^3
B的时间复杂度O(n)=n^2

1.2.1.1 常见的时间复杂度量级(效率从高到低)
1.2.1.1.1 常数阶O(1)
int i=1;
i++;
...
  • 1
  • 2
  • 3
1.2.1.1.2 对数阶O(log n)
for(int i=1;i<n;i*=2){
}
  • 1
  • 2
1.2.1.1.3 线性阶O(n)
for(int i=1;i<n;i++){
}
  • 1
  • 2
1.2.1.1.4 线性对数阶O(n * log n)
for(int i=1;i<n;i++){
	for(int j=1;j<n;j*=2){
	}
}
  • 1
  • 2
  • 3
  • 4
1.2.1.1.5 平方阶O(n^2)
for(int i=1;i<n;i++){
	for(int j=1;j<n;j++){
	}
}
  • 1
  • 2
  • 3
  • 4
1.2.1.1.6 立方阶O(n^3)
for(int i=1;i<n;i++){
	for(int j=1;j<n;j++){
		for(int k=1;k<n;k++){
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
1.2.1.1.7 K次方阶O(n^k)

k层循环

1.2.1.1.8 指数阶O(2^n)
long aFunc(int n){
	if(n==1){
		return 1;
	}else{
		return aFunc(n-1)+aFunc(n-2);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

1.2.2 空间复杂度

空间复杂度主要指算法计算所占的存储空间,也用O(n)表示

int i=1;
  • 1

变量i的空间指定为4字节

1.2.2.1 常见的空间复杂度
1.2.2.1.1 O(1)

如冒泡排序的空间复杂度,新产生的变量为常数,并不随n增大而增大

//冒泡排序---升序
        int changeNum=0;//定义一个用于交换的变量
        for(int j=0;j<arr.length-1;j++){
            for(int i=0;i<arr.length-1-j;i++){
                if(arr[i]>arr[i+1]){
                    changeNum=arr[i+1];
                    arr[i+1]=arr[i];
                    arr[i]=changeNum;
                }
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
1.2.2.1.2 O(n)

如归并排序的空间复杂度,随着n的规模增大,

1.2.2.1.3 O(n^2)

1.3 常用排序算法

1.3.1 冒泡排序

示意图
冒泡排序
实现代码

//冒泡排序---升序
        int changeNum=0;//定义一个用于交换的变量
        for(int j=0;j<arr.length-1;j++){
            for(int i=0;i<arr.length-1-j;i++){
                if(arr[i]>arr[i+1]){
                    changeNum=arr[i+1];
                    arr[i+1]=arr[i];
                    arr[i]=changeNum;
                }
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
1.3.2 选择排序

示意图
选择排序
实现代码

//选择排序---升序
        int changeNum=0;//定义一个用于交换的变量
        for(int j=1;j<arr.length;j++){
            for(int i=0;i<arr.length-1;i++){
                if(arr[i]>arr[j]){
                    changeNum=arr[i];
                    arr[i]=arr[j];
                    arr[j]=changeNum;
                }
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
1.3.3 归并排序

归并排序的执行步骤分为两个:递归+合并。为了便于理解,我们先说合并算法再说递归算法。

1.3.3.1 合并排序

合并排序的前提是已经有两个有序的数组,然后将这两个有序数组合并中一一对比后进行排序的算法
图示:
合并排序图示
实现代码:

//合并两个有序数组并生成一个新的有序数组
    public static int[] merge(int[] arr){
        //定义一个新数组用来接收合成的数组
        int[] temp=new int[arr.length];
        //数组索引的初始化
        int i=0,k=0,j=arr.length/2;
        //如果前后两个数组都未遍历结束
        while(i<arr.length/2 && j<arr.length){
            temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
        }
        //如果后面的数组遍历结束,但前面的数组还未遍历结束
        while(i<arr.length/2) temp[k++]=arr[i++];
        //如果前面的数组遍历结束,但后面的数组还未遍历结束
        while(j<arr.length) temp[k++]=arr[j++];
        //返回合并数组
        return temp;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
1.3.3.2 递归拆分数组

使用递归的方法拆分一个数组至单个元素
图示:
递归拆分数组
实现代码

public static void recursion(int[] arr){
        //如果数组长度为1,输出该数组(递归终结)
        if(arr.length==1){
            System.out.println(Arrays.toString(arr));
        //或者执行拆分
        }else{
            //找到中间值
            int mid=arr.length/2;
            //拆分左边数组
            int[] templ=new int[mid];
            for(int i=0;i<templ.length;i++){
                templ[i]=arr[i];
            }
            //判断数组长度的奇偶性,如果是奇数,拆分右边的数组长度要比左边数组长度长1位
            if(arr.length%2!=0){
                int[] tempr=new int[mid+1];
                for(int i=0;i<tempr.length;i++){
                    tempr[i]=arr[mid+i];
                }
                recursion(templ);
                recursion(tempr);
            //如果是偶数,拆分右边的数组长度与左边数组长度长一样
            }else{
                int[] tempr=new int[mid];
                for(int i=0;i<tempr.length;i++){
                    tempr[i]=arr[mid+i];
                }
                recursion(templ);
                recursion(tempr);
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
1.3.3.3 递归合并排序—归并排序
public static int[] mergeSort(int[] arr){
        //找到数组的中间值
        int mid=arr.length/2;

        //定义两个新数组,分别用来接收拆分数组的左右
        int[] templ=new int[mid];
        int[] tempr;

        //如果数组长度为1,返回该数组(递归边界)
        if(arr.length==1){
            return new int[]{arr[arr.length-1]};

        //或者执行拆分
        }else{
            //拆分左边数组
            for(int i=0;i<templ.length;i++){
                templ[i]=arr[i];
            }

            //判断数组长度的奇偶性,如果是奇数,拆分右边的数组长度要比左边数组长度长1位
            if(arr.length%2!=0){
                tempr=new int[mid+1];
                //如果是偶数,拆分右边的数组长度与左边数组长度长一样
            }else {
                tempr = new int[mid];
            }

            //拆分右边数组
            for(int i=0;i<tempr.length;i++){
                tempr[i]=arr[mid+i];
            }
            //左边数组递归
            mergeSort(templ);
            //右边数组递归
            mergeSort(tempr);
        }

        //定义一个新数组用来接收合并的数组
        int[] temp=new int[arr.length];

        //合并数组时数组索引的初始化
        int i=0,k=0,j=0,p=0;

        //如果左右两个数组都未遍历结束
        while(i<templ.length && j<tempr.length){
            temp[k++] = templ[i] <= tempr[j] ? templ[i++] : tempr[j++];
        }

        //如果右边的数组遍历结束,但左边的数组还未遍历结束
        while(i<templ.length) temp[k++]=templ[i++];

        //如果左边的数组遍历结束,但右边的数组还未遍历结束
        while(j<tempr.length) temp[k++]=tempr[j++];

        //返回合并数组
        for(;p<arr.length;p++) arr[p]=temp[p];
        return arr;

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/254920
推荐阅读
相关标签
  

闽ICP备14008679号