Hadoop-HDFS

Hadoop-HDFS

一. 算法

1. 算法复杂度

  • 算法复杂度分为时间复杂度和空间复杂度,其作用:
    • 时间复杂度是指执行这个算法所需要的计算工作量
    • 而空间复杂度是指执行这个算法所需要的内存空间
  • 时间和空间都是计算机资源的重要体现,而算法的复杂性就是体现在运行该算法时的计算机所需的资源

2. 空间复杂度

  • 一个程序的空间复杂度是指运行完一个程序所需内存的大小。
  • 利用程序的空间复杂度,可以对程序的运行所需要的内存多少有个预先估计。
  • 程序执行时所需存储空间包括以下两部分。
    • 固定部分:主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间。
    • 可变空间:这部分空间的主要包括动态分配的空间,以及递旧栈所需的空间等。
  • 如果判断一个年份是平年还是闻年?
    • 方法1:使用年份计算公式进行计算。
    • 方法2:创建一个长度为4000的数组,以年份为下标,直接取值即可如果计算的年份超过1万年?

3. 时间复杂度

  • 一个算法执行所耗费的时间,从理论上是不能算出来的。必须上机运行测试才能知道,但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了,并目一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。
  • 一个算法中的语句执行次数称为语句续度成时间频度,记为T(n)

在这里插入图片描述

  • 为了描述时间频度变化引起的变化规律,引入时间复杂度概念,记为o(…)
  • 时间复杂度计算规则只需要遵循如下守则:
    • 用常数1来取代运行时间中所有加法常数
    • 只要高阶项,不要低阶项
    • 不要高阶项系数
  • 时间频度不同,但时间复杂度可能相同。
    • T(n)=n^2+3n+4 --> n^2+3n—n^2
    • T(n)=4n^2+2n+1 --> 4^2+2n --> 4^2–>በ^2
    • 它们的频度不同但时间复杂度相同,都为0(n^2)
    • T(n)=log2n n+2n+10->log2n n+2n->log2n^n->logN *n
  • 常见的时间复杂度场景
  • O(1)——常数阶
  • O(N)——线性
  • O(logN)——对数阶
  • O(n^2)——平方阶
  • O(N log n)——线性对数阶
  • O(1) < O(log n) < O(n) < O(N log n) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)

在这里插入图片描述

  • 时间复杂度去估算算法优劣的时候注重的是算法的潜力
    • 有100条数据的时候算法a比算法b快
    • 有10000条数据的时候算法b比算法a快
    • 算法b的时间复杂对更优

4. 时间与空间的取舍

  • 日常操作中:我们都是更加注重时间复杂度,有些特殊情况下,空间复杂度可能会更加重要
    • 因为很多时候空间复杂度可以花钱解决
  • 获取明天现在这个时刻 ?
  • 用代码获取明天这个时刻
    • Thread.sleep(3600*24);
    • Date date = new Date();
    • 以后时间复杂度的问题具体情况还要具体分析,两行代码执行一天
  • 编程的精随和美。并不在于一方的退让和妥协。而是在于如何在二者之间取一个平衡点,完成华丽变身。
    • 但是将来工作中一切以时间复杂度为标准

5. 十大排序算法

5.1 冒泡排序

在这里插入图片描述

  • 思路分析
  1. 相邻两个数两两相比,n[i]跟n[j+1]比,如果n[i]>n[j+1],则将连个数进行交换,

  2. j++, 重复以上步骤,第一趟结束后,最大数就会被确定在最后一位,这就是冒泡排序又称大数沉底,小数冒泡。

  3. i++,重复以上步骤,直到i=n-1结束,排序完成。

  • 负杂度分析
  1. 不管原始数组是否有序,时间复杂度都是O(n2),

因为没一个数都要与其他数比较一次,(n-1)2次,分解:n2+2n-1, 去掉低次幂和常数,剩下n2,所以最后的时间复杂度是n2

  1. 空间复杂度是O(1),因为只定义了一个辅助变量,与n的大小无关,所以空间复杂度为O(1)
  • Java 代码如下
import java.util.Arrays;
public class 冒泡 {
   
    public static void main(String[] args) {
   
        int[] n = new int[]{
   1,6,3,8,33,27,66,9,7,88};
        int temp;
        for (int i = 0; i < n.length-1; i++) {
   
            for (int j = 0; j <n.length-1; j++) {
   
                if(n[j]>n[j+1]){
   
                    temp = n[j];
                    n[j] = n[j+1];
                    n[j+1] = temp;
                }
            }
        }
        System.out.println(Arrays.toString(n));
    }
}
5.2 选择排序

在这里插入图片描述

  • 思路分析
  1. 第一个跟后面的所有数相比,如果小于(或小于)第一个数的时候,暂存较小数的下标,第一趟结束后,将第一个数,与暂存的那个最小数进行交换,第一个数就是最小(或最大的数)

  2. 下标移到第二位,第二个数跟后面的所有数相比,一趟下来,确定第二小(或第二大)的数

重复以上步骤

直到指针移到倒数第二位,确定倒数第二小(或倒数第二大)的数,那么最后一位也就确定了,排序完成。

  • 负杂度分析
  1. 不管原始数组是否有序,时间复杂度都是O(n2),

因为没一个数都要与其他数比较一次,(n-1)2次,分解:n2-2n+1, 去掉低次幂和常数,剩下n2,所以最后的时间复杂度是n2

  1. 空间复杂度是O(1),因为只定义了两个辅助变量,与n的大小无关,所以空间复杂度为O(1)
  • Java 代码如下
import java.util.Arrays;
public class Main {
   
    public static void main(String[] args) {
   
        int[] n = new int[]{
   1,6,3,8,33,27,66,9,7,88};
        int temp,index = -1;
        for (int i = 0; i < n.length-1; i++) {
   
            index=i;
            //如果大于,暂存较小的数的下标
            for (int j = i+1; j <n.length; j++) {
   
                if(n[index]>n[j]){
   
                    index = j;
                }
            }
            将一趟下来求出的最小数,与这个数交换
            if(index>0){
   
                temp = n[i];
                n[i] = n[index];
                n[index] = temp;
            }
            System.out.println(Arrays.toString(n));
        }
        System.out.println(Arrays.toString(n));
    }
}
5.3 插入排序

在这里插入图片描述

  • 思路分析

例如从小到大排序:

  1. 从第二位开始遍历,

  2. 当前数(第一趟是第二位数)与前面的数依次比较,如果前面的数大于当前数,则将这个数放在当前数的位置上,当前数的下标-1,

  3. 重复以上步骤,直到当前数不大于前面的某一个数为止,这时,将当前数,放到这个位置,

1-3步就是保证当前数的前面的数都是有序的,内层循环的目的就是将当前数插入到前面的有序序列里

  1. 重复以上3步,直到遍历到最后一位数,并将最后一位数插入到合适的位置,插入排序结束。

根据思路分析,每一趟的执行流程如下图所示:

在这里插入图片描述

  • 复杂度分析
  1. 时间复杂度:插入算法,就是保证前面的序列是有序的,只需要把当前数插入前面的某一个位置即可。

    所以如果数组本来就是有序的,则数组的最好情况下时间复杂度为O(n)

    如果数组恰好是倒=倒序,比如原始数组是5 4 3 2 1,想要排成从小到大,则每一趟前面的数都要往后移,一共要执行n-1 + n-2 + … + 2 + 1 = n * (n-1) / 2 = 0.5 * n2 - 0.5 * n次,去掉低次幂及系数,所以最坏情况下时间复杂度为O(n2)

平均时间复杂度(n+n2 )/2,所以平均时间复杂度为O(n2)

  1. 空间复杂度:插入排序算法,只需要两个变量暂存当前数,以及下标,与n的大小无关,所以空间复杂度为:O(1)
  • Java 代码如下
import java.util.Arrays;
public class insertSort {
   
    public static void main(String[] args) {
   
        int[] n = new int[]{
   20,12,15,1,5,49,58,24,578,211,20,214,78,35,125,789,11};
        int temp = 0,j;
        for (int i = 1; i < n.length; i++) {
   
            temp = n[i];
            for (j = i; j >0; j--) {
   
                //如果当前数前面的数大于当前数,则把前面的数向后移一个位置
                if(n[j-1]>temp){
   
                    n[j] = n[j-1];
                    
                    //第一个数已经移到第二个数,将当前数放到第一个位置,这一趟结束
                    if(j==1){
   
                        n[j-1] = temp;
                        break;
                    }

                }else{
   //如果不大于,将当前数放到j的位置,这一趟结束
                
                    n[j] = temp;
                    break;
                }
            }
            System.out.println(Arrays.toString(n));
        }
        System.out.println(Arrays.toString(n));
    }
}
5.4 希尔排序
  • 思路分析

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

简单插入排序很循规蹈矩,不管数组分布是怎么样的,依然一步一步的对元素进行比较,移动,插入,比如[5,4,3,2,1,0]这种倒序序列,数组末端的0要回到首位置很是费劲,比较和移动元素均需n-1次。

而希尔排序在数组中采用跳跃式分组的策略,通过某个增量将数组元素划分为若干组,然后分组进行插入排序,随后逐步缩小增量,继续按组进行插入排序操作,直至增量为1。希尔排序通过这种策略使得整个数组在初始阶段达到从宏观上看基本有序,小的基本在前,大的基本在后。然后缩小增量,到增量为1时,其实多数情况下只需微调即可,不会涉及过多的数据移动。

来看下希尔排序的基本步骤,在此选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择可以用一个序列来表示,{n/2,(n/2)/2…1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。此处做示例使用希尔增量。
在这里插入图片描述

  • 复杂度分析
  1. 时间复杂度:最坏情况下,每两个数都要比较并交换一次,则最坏情况下的时间复杂度为O(n2), 最好情况下,数组是有序的,不需要交换,只需要比较,则最好情况下的时间复杂度为O(n)。

经大量人研究,希尔排序的平均时间复杂度为O(n1.3)(这个我也不知道咋来的,书上和博客上都这样说,也没找到个具体的依据,,,)。

  1. 空间复杂度:希尔排序,只需要一个变量用于两数交换,与n的大小无关,所以空间复杂度为:O(1)。
  • Java 代码如下
import java.util.Arrays;

public class shell {
   
    public static void main(String[] args) {
   
        int[] arr = new int[]{
   10,6,3,8,33,27,66,9,7,88};
        shellSort(arr);
        System.out.println(Arrays.toString(arr));
    }
    private static void shellSort(int[] arr) {
   
        int temp;
        //控制增量序列,增量序列为1的时候为最后一趟
        for (int i = arr.length/2; i >0; i/=2) {
   
            //根据增量序列,找到每组比较序列的最后一个数的位置
            for (int j = i; j < arr.length; j++) {
   
                //根据该比较序列的最后一个数的位置,依次向前执行插入排序
                for (int k = j-i; k >=0; k-=i) {
   
                    if(arr[k]>arr[k+i]){
   
                        temp = arr[k];
                        arr[k]  = arr[k+i];
                        arr[k+i] = temp;
                    }
                }
            }
        }
    }
}
5.5 快速排序

在这里插入图片描述

  • 思路分析

快速排序的思想就是,选一个数作为基数(这里我选的是第一个数),大于这个基数的放到右边,小于这个基数的放到左边,等于这个基数的数可以放到左边或右边,看自己习惯,这里我是放到了左边,

一趟结束后,将基数放到中间分隔的位置,第二趟将数组从基数的位置分成两半,分割后的两个的数组继续重复以上步骤,选基数,将小数放在基数左边,将大数放到基数的右边,在分割数组,,,直到数组不能再分为止,排序结束。

例如从小到大排序:

  1. 第一趟,第一个数为基数temp,设置两个指针left = 0,right = n.length,

①从right开始与基数temp比较,如果n[right]>基数temp,则right指针向前移一位,继续与基数temp比较,直到不满足n[right]>基数temp

②将n[right]赋给n[left]

③从left开始与基数temp比较,如果n[left]<=基数temp,则left指针向后移一位,继续与基数temp比较,直到不满足n[left]<=基数temp

④将n[left]赋给n[rigth]

⑤重复①-④步,直到left==right结束,将基数temp赋给n[left]

  1. 第二趟,将数组从中间分隔,每个数组再进行第1步的操作,然后再将分隔后的数组进行分隔再快排,

  2. 递归重复分隔快排,直到数组不能再分,也就是只剩下一个元素的时候,结束递归,排序完成

根据思路分析,第一趟的执行流程如下图所示:
在这里插入图片描述

  • 负杂度分析
  1. 时间复杂度:

最坏情况就是每一次取到的元素就是数组中最小/最大的,这种情况其实就是冒泡排序了(每一次都排好一个元素的顺序)

这种情况时间复杂度就好计算了,就是冒泡排序的时间复杂度:T[n] = n * (n-1) = n^2 + n;

最好情况下是O(nlog2n),推导过程如下:

(递归算法的时间复杂度公式:T[n] = aT[n/b] + f(n) )

在这里插入图片描述

所以平均时间复杂度为O(nlog2n)

  1. 空间复杂度:

快速排序使用的空间是O(1)的,也就是个常数级;而真正消耗空间的就是递归调用了,因为每次递归就要保持一些数据:
  最优的情况下空间复杂度为:O(log2n);每一次都平分数组的情况
  最差的情况下空间复杂度为:O( n );退化为冒泡排序的情况
所以平均空间复杂度为O(log2n)

  • Java 代码如下
import java.util.Arrays;
public class quick{
   
    public static void main(String[] args) {
   
        int[] arr = new int[]{
   10,6,3,8,33,27,66,9,7,88};
//        int[] arr = new int[]{1,3,2};
        f(arr,0,arr.length-1);
        System.out.println(Arrays.toString(arr));
    }
    public static void f(int[] arr,int start,int end){
   
        //直到start>=end时结束递归
        if(start<end){
   
            int left = start;
            int right = end;
            int temp = arr[start];
            
            while(left<right){
   
                
                //右面的数字大于标准数时,右边的数的位置不变,指针向左移一个位置
                while(left<right && arr[right]>temp){
   
                    right--;
                }
                
                
                //右边的数字及下标小于或等于基本数,将右边的数放到左边
                if(left<right) {
   
                     arr[left] = arr[right];
                     left++;
                }
                
                左边的数字小于或等于标准数时,左边的数的位置不变,指针向右移一个位置
                while(left<right && arr[left]<=temp){
   
                    left++;
                }
                
                //左边的数字大于基本数,将左边的数放到右边
                arr[right] = arr[left];
            }
            
            //一趟循环结束,此时left=right,将基数放到这个重合的位置,
            arr[left] = temp;

            //将数组从left位置分为两半,继续递归下去进行排序
            f(arr,start,left);
            f(arr,left+1,end);
        }<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

♪-Interpretation

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值