20200928—collection与array互转以及递归分治一些例子

今日学习

Java Array、List、Set互相转化

1. Array、List、Set互转实例

1.1 Array、List互转
  • ArrayList

    String[] s = new String[]{"A", "B", "C", "D","E"};
    List<String> list = Arrays.asList(s);12
    

    注意这里list里面的元素直接是s里面的元素( list backed by the specified array),换句话就是说:s的修改,直接影响list

    s[0] ="AA";
    System.out.println("list: " + list);12
    

    输出结果

    list: [AA, B, C, D, E]
    
  • ListArray

    String[] dest = list.toArray(new String[0]);//new String[0]是指定返回数组的类型
    System.out.println("dest: " + Arrays.toString(dest));12
    

    输出结果

    dest: [AA, B, C, D, E]1
    

    注意这里的dest里面的元素不是list里面的元素,换句话就是说:list中关于元素的修改,不会影响dest

    list.set(0, "Z");
    System.out.println("modified list: " + list);
    System.out.println("dest: " + Arrays.toString(dest));123
    

    输出结果

    modified list: [Z, B, C, D, E]
    dest: [AA, B, C, D, E]12
    

    可以看到list虽然被修改了,但是dest数组没有没修改。

1.2 List、Set互转

因为ListSet都实现了Collection接口,且addAll(Collection<? extends E> c);方法,因此可以采用addAll()方法将ListSet互相转换;另外,ListSet也提供了Collection<? extends E> c作为参数的构造函数,因此通常采用构造函数的形式完成互相转化。

//List转Set
Set<String> set = new HashSet<>(list);
System.out.println("set: " + set);
//Set转List
List<String> list_1 = new ArrayList<>(set);
System.out.println("list_1: " + list_1);123456

toArray()一样,被转换的List(Set)的修改不会对被转化后的SetList)造成影响。

1.3 Array、Set互转

1.1 1.2可完成Array和Set的互转

//array转set
s = new String[]{"A", "B", "C", "D","E"};
set = new HashSet<>(Arrays.asList(s));
System.out.println("set: " + set);
//set转array
dest = set.toArray(new String[0]);
System.out.println("dest: " + Arrays.toString(dest));

上述列出的互相转换离不开Arrays.asList()Collection.toArray()两个重要的方法;

递归实例

1. 阶乘函数

package com.seu.recursionclass;

/**
 * @author SJ
 * @date 2020/9/28
 */
public class Factorial {
    public static void main(String[] args) {
        System.out.println(factorial(10));

    }
    //阶乘函数
    public static long factorial(int n){
        if (n==1)
            return 1;
        else
            return n*factorial(n-1);

    }
}

2. Fibonacci数列

package com.seu.recursionclass;

/**
 * @author SJ
 * @date 2020/9/28
 */
public class Fibonacci {
    public static void main(String[] args) {
        System.out.println(fibinacci(5));

    }

    public static int fibinacci(int n) {
        if (n == 0 || n == 1)
            return 1;
        else
            return fibinacci(n - 1) + fibinacci(n - 2);

    }
}

3. 整数划分问题

image-20200928093516526

这道题跟前天做得题一样

方法一:DFS

package com.seu.recursionclass;

/**
 * @author SJ
 * @date 2020/9/28
 */
public class IntergerDistribute {
    public static void main(String[] args) {
        IntergerDistribute intergerDistribute = new IntergerDistribute();
        int a = intergerDistribute.dfs(5, 1, 1);
        System.out.println(a);


    }

    int sum = 0; //方案总数

    //n代表要划分的整数,i代表当前是第几轮划分,m代表划分的数不小于几
    public int dfs(int n, int i, int m) {
        //套dfs模板
        int k;//分解成<=k个整数,本题k=n;

        if (n == 0)
            sum++;
        for (int j = m; j <= n; j++) {
            if (i <= 5)
                dfs(n - j, i + 1, j);

        }
        return sum;


    }
}

测试结果:

"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe"...
7

Process finished with exit code 0

方法二:

package com.seu.recursionclass;

/**
 * @author SJ
 * @date 2020/9/28
 */
public class IntergerDistribute2 {
    public static void main(String[] args) {
        IntergerDistribute2 intergerDistribute2 = new IntergerDistribute2();
        int a = intergerDistribute2.huafen(5, 5);
        System.out.println(a);

    }

    public int huafen(int n, int m) {
        if (n == 1 || m == 1)
            return 1;
        else if (n < m)
            return huafen(n, n);
        else if (n == m)
            return 1 + huafen(n, n - 1);

        return huafen(n, m - 1) + huafen(n - m, m);//最大加数不包含m和包含m的情况

    }

}

结果:

"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe" ...
7

Process finished with exit code 0

4. 汉诺塔问题

package com.seu.recursionclass;

/**
 * @author SJ
 * @date 2020/9/28
 */
public class Hanoi {
    public static void main(String[] args) {
        Hanoi hanoi = new Hanoi();
        hanoi.hanoi(3, "a", "b", "c");

    }

    //把n个盘子,从a移到b,借助c
    public void hanoi(int n, String a, String b, String c) {
        if (n > 0) {
            //把n-1个盘子从a移到c 借助b
            hanoi(n - 1, a, c, b);
            //把a上的盘子移到b
            move(a, b);
            //把n-1个盘子从c移回b借助a
            hanoi(n - 1, c, b, a);

        }
    }

    private void move(String a, String b) {
        System.out.println(a + "->" + b);
    }

}

结果:

"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe" ...
a->b
a->c
b->c
a->b
c->a
c->b
a->b

Process finished with exit code 0

分治实例

1. 快排改进

具体做法:不总是选择第一个作为主元,而是先随机选择一个与第一个做交换,然后再拿第一个作主元

package com.seu.recursionclass;

import java.util.Arrays;

/**
 * @author SJ
 * @date 2020/9/28
 */
public class QuickSortVersion2 {
    public static void main(String[] args) {
        int[] nums = {1, 3, 6, 2, 4};
        QuickSortVersion2 quickSortVersion2 = new QuickSortVersion2();
        quickSortVersion2.quicksort2(nums, 0, nums.length - 1);
        for (int i = 0; i < nums.length; i++) {
            System.out.println(nums[i]);
        }
    }

    private void quicksort2(int[] nums, int low, int high) {
        int l = low, h = high;
        //random函数的范围正好是左并右开
        //这里比较容易错,注意课交换的范围要随着数组的下标变,而不是跟着数组的长度变
        int temp = (int) (Math.random() * (high - low + 1) + low);
        exchange(nums, low, temp);
        int beSetted = nums[low];
        while (l < h) {
            while (l < h && nums[h] >= beSetted)
                h--;
            if (nums[h] < beSetted)
                nums[l++] = nums[h];
            while (l < h && nums[l] <= beSetted)
                l++;
            if (nums[l] > beSetted)
                nums[h--] = nums[l];

        }
        nums[h] = beSetted;
        if (low < l)
            quicksort2(nums, low, h - 1);
        if (high > h)
            quicksort2(nums, l + 1, high);


    }

    public void exchange(int[] nums, int a, int b) {
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }

}

结果:

"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe"...
1
2
3
4
6

Process finished with exit code 0

另外:

网络上也给出了其他的改进方法:

  1. 用随机数产生器来选择pivot,是希望pivot能尽量将数组划分得均匀一些,但随机数产生器选择pivot带来的开销太大,可以选 择一个替代方案来替代随机数产生器来选择pivot。比如三数取中,通过对序列的first、middle和last做比较,选择三个数的中间大小的那一 个做pivot,从概率上可以将比较次数下降到12/7 ln(n)。
    median-of-three对小数组来说有很大的概率选择到一个比较好的pivot,但是对于大数组来说就不足以保证能够选择出一个好的pivot, 因此还有个办法是所谓median-of-nine,这个怎么做呢?它是先从数组中分三次取样,每次取三个数,三个样品各取出中数,然后从这三个中数当中 再取出一个中数作为pivot,也就是median-of-medians。取样也不是乱来,分别是在左端点、中点和右端点取样。什么时候采用 median-of-nine去选择pivot,这里也有个数组大小的阀值,这个值也完全是经验值,设定在40。大小大于40的数组使用median-of-nine选择pivot,大小在7到40之间的数组使用three-of-median选择pivot,大小等于7的数组直接选择中数作为pivot,大小小于7的数组则直接使用插入排序。

  2. 插入排序在小数组的排序上是非常高效的,这给我们一个 提示,在快速排序递归的子序列,如果序列规模足够小,可以使用插入排序替代快速排序,因此可以在快排之前判断数组大小,如果小于一个阀值就使用插入排序。

2.斐波那契数列改进

package com.seu.recursionclass;

/**
 * @author SJ
 * @date 2020/9/28
 */
public class Fibonacci2 {
    public static void main(String[] args) {
        System.out.println(fibo2(4));

    }
    public static int fibo2(int n){
        int[] nums=new int[n+1];
        nums[1]=1;
        nums[2]=1;
        for (int i = 3; i < nums.length; i++) {
            nums[i]=nums[i-1]+nums[i-2];
        }
        return nums[n];
    }
}

利用迭代把空间复杂度优化到O(1)

此时时间复杂度O(n),空间复杂度O(1),很妙。暴力递归的话时间复杂度是O(2^n)

    public static int fibo3(int n){
        int a=1;
        int b=1;
        for (int i = 3; i <= n; i++) {
            b=a+b;//下一项数据
            a=b-a;//前一项数据

        }
        return b;

    }

迭代:利用变量的原值推算出变量的一个新值.如果递归是自己调用自己的话,迭代就是A不停的调用B.

一半来说,递归都能转换成迭代,但是迭代比较晦涩难懂,但是斐波那契疏解比较简单,转换成迭代也很好懂。

法二:利用矩阵乘法可以把时间复杂度降到O(logn)

image-20200928205300498

此时,求斐波那契数列第N项的问题就变成了如何用最快的方法求一个矩阵N次方的问题,而求矩阵N次方的问题明显是一个能够在O(logN)时间内解决的问题。

为了表示方便,用求一个整数N次方的例子来说明,因为只要理解了如何在O(logN)的时间复杂度内求整数N次方的问题,对于求矩阵N次方的问题是同理的,区别是矩阵乘法和整数乘法在细节上有些不一样,但两者道理相同。

将设一个整数10,如何最快的求10的11次方

\1. 11的二进制形式为1011.

\2. 10的11次方=10^8 * 10^2 * 10^1。

在这个过程中,我们先求出101,然后根据101求出102,再根据102求出104,…,最后求出108,即11的二进制数形式总共多少位,就使用了几次乘法。

\3. 在步骤2进行的过程中,把应该累乘的值相乘即可,比如108、102、101应该累乘,因为8,2,1对应到11的二进制数中,相应位上是1;而104不应该累乘,因为4对应到11的二进制数中,相应位上是0。

参考:https://blog.csdn.net/u012684062/article/details/76330075

#include <iostream>
#include<stdlib.h>
 
using namespace std;
 
 struct matrix{
        int m[2][2];
    }ans;
 
    matrix mulimatrix(matrix &m1, matrix &m2){
        matrix res0;
        for(int i=0; i<2; i++){
            for(int j=0; j<2; j++){
                res0.m[i][j]=0;
                for(int k=0; k<2; k++){
                    res0.m[i][j] += m1.m[i][k]*m2.m[k][j];
                }
            }
        }
        return res0;
    }
 
    matrix matrixpower(matrix &mat, int p){
        matrix tmp=mat;
        matrix res1;
        for(int i=0; i<2; i++){
            res1.m[i][i]=1;
        }
        res1.m[0][1]=0;
        res1.m[1][0]=0;
        for( ; p != 0; p >>= 1){
            if((p&1)!=0){
                res1 = mulimatrix(res1,tmp);
            }
            tmp = mulimatrix(tmp,tmp);
        }
        return res1;
    }
 
    int Fibonacci(int n) {
        if(n<1)return 0;
        if(n==1 || n==2) return 1;
        matrix base = {1,1,1,0};
 
        matrix res= matrixpower(base, n-2);
 
 
        int result= res.m[0][0] + res.m[1][0];
 
        return result;
    }
 
int main()
{
    int result = Fibonacci(30);
    cout<<result<<endl;
    return 0;
}

关于矩阵相乘的Strassen算法https://sites.google.com/a/chaoskey.com/algorithm/02/03

3.最大元最小元问题

用分治法找最大最小元。

我觉得红黑树比较好,红黑树的插入删除查找的最坏时间复杂度都是O(logn)

所以我打算测一下,分治法查找和红黑树查找哪个更快。

用红黑树的思路是,把数组装到TreeSet里,装的过程系统会自动帮你排序,我只需要取出第一个数字和最后一个数字就行了。

package com.seu.recursionclass;

import java.util.*;

/**
 * @author SJ
 * @date 2020/9/28
 */

public class PrintMaxMin {
    public static void max_min(Integer[] array,int left,int right,int[] max,int[] min) {
        if (left==right){
            //当只有一个元素时候,直接得出最大值和最小值
            max[0]=array[left];
            min[0]=array[right];
        } else if (left+1==right){
            //当数组中有两个元素时,直接判断哪个元素大
            if (array[left]>array[right]) {
                max[0]=array[left];
                min[0]=array[left];
            } else{
                max[0]=array[right];
                min[0]=array[left];
            }
        } else{
            //当数组元素的个数大于2以上的操作
            int m=(left+right)/2;
            int[] lmax={0};
            int[] lmin={0};
            int[] rmax={0};
            int[] rmin={0};
            max_min(array,left,m,lmax,lmin);
            max_min(array,m+1, right, rmax, rmin);
            if(lmax[0]>rmax[0])
            {
                max[0]=lmax[0];
            } else {
                max[0]=rmax[0];
            }

            if(lmin[0]<rmin[0]) {
                min[0]=lmin[0];
            } else {
                min[0]=rmin[0];
            }
        }
    }
    public void test2(Integer[] nums,int[] Max,int[] Min){

      //  TreeSet<Integer> objects = new TreeSet<>(Arrays.asList(nums));
        TreeSet<Integer> objects = new TreeSet<>();

        for (int i = 0; i < nums.length; i++) {
            objects.add(nums[i]);
     }
        Min[0]=objects.first();
        Max[0]=objects.last();



    }
    public static void main(String[] args) {
        Integer[] array=new Integer[10000];
        for (int i = 0; i <array.length; i++) {
            array[i]=(int)(Math.random()*10000);
        }
        int []Max=new int[1];  //java对于实参传递,是采用值传递,所以要改成数组,不能直接使用变量去计算最大值和最小值。
        int []Min=new int[1];
        Long start = System.currentTimeMillis();
        new PrintMaxMin().max_min(array,0,array.length-1,Max,Min);

        System.out.println("最大值:"+ Max[0]);
        System.out.println("最小值:"+ Min[0]);
        Long end = System.currentTimeMillis();
        System.out.println("用时:"+(end-start));

        Long start2 = System.currentTimeMillis();
        new PrintMaxMin().test2(array,Max,Min);

        System.out.println("最大值:"+ Max[0]);
        System.out.println("最小值:"+ Min[0]);
        Long end2 = System.currentTimeMillis();
        System.out.println("用时:"+(end2-start2));

    }
}

结果:

"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe" ...
最大值:9999
最小值:3
用时:2
最大值:9999
最小值:3
用时:11

Process finished with exit code 0

红黑树的用时比分治法慢了4倍哈哈哈,看来建树是一个比较耗时的过程。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值