递归与分治策略

概要

直接或间接的调用自身的算法称为递归算法,而分治法是把原先规模为n的问题P(n),分解为k个规模较小,相互独立,结构与原问题结果相同的子问题后,利用子问题的解合并得到原问题的解
因为分治法产生的子问题往往是原问题的较小规模,所以为递归技术的使用提供了便利

递归

学习知乎文章对于递归有没有什么好的理解方法?总结

递归的三大要素

  1. 明确函数想要干什么
  2. 寻找递归的结束条件
    也就是说我们要明确当参数为什么时,可以直接得到函数结果,并结束递归把结果返回
  3. 找出函数的等价关系

举个例子(小青蛙跳台阶)

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

  1. 明确函数想要干什么
    这里的函数是想求小青蛙跳上n级台阶有几种跳法
int frogJump(int n){
//n是台阶数
}
  1. 寻找递归结束的条件
    当只有一节台阶的时候只有一种跳法,即只跳一节
int frogJump(int n){
//n是台阶数
	if(n == 1){
		return 1;
	}
}

所以到怎么选择递归结束条件,我的理解就是找当问题规模小到不能再小的那个地方,直接把结果返回

  1. 找出函数的等价关系
    这步最难,找f(n)和f(n-1)的关系

每次跳的时候,小青蛙可以跳一个台阶,也可以跳两个台阶,也就是说,每次跳的时候,小青蛙有两种跳法。第一种跳法:第一次我跳了一个台阶,那么还剩下n-1个台阶还没跳,剩下的n-1个台阶的跳法有f(n-1)种。第二种跳法:第一次跳了两个台阶,那么还剩下n-2个台阶还没,剩下的n-2个台阶的跳法有f(n-2)种。所以,小青蛙的全部跳法就是这两种跳法之和了,即 f(n) = f(n-1) + f(n-2)。至此,等价关系式就求出来了。于是写出代码:

int f(int n){
    if(n == 1){
        return 1;
    }
    return f(n-1) + f(n-2);
}

注意:观察递归结束条件,当n=2时,无法正常结束,所以应该改为

int f(int n){
    //经过分析,f(2)=2也是一个临界条件。
    if(n <= 2){
        return n;
    }
    return f(n-1) + f(n-2);
}

反转单链表

反转单链表。例如链表为:1->2->3->4。反转后为 4->3->2->1

  1. 定义函数功能
    函数的功能就是要把链表反转,i->j 变成 j->i
Node reverseList(Node head){

}
  1. 寻找递归结束条件
    当只有head节点时,直接返回head就行
Node reverseList(Node head){
    if(head == null || head.next == null){
        return head;
    }
}
  1. 寻找等价关系
//用递归的方法反转链表
public static Node reverseList2(Node head){
    // 1.递归结束条件
    if (head == null || head.next == null) {
             return head;
         }
         // 递归
         Node newList = reverseList2(head.next);
         // 改变 1,2节点的指向。
         // 在递归后这个head不是最初真正的head了,在递归函数里的head
         Node t1  = head.next;
         t1.next = head;      
        head.next = null;
        return newList;
    }

迭代更好理解

定义两个指针: pre 和 cur ;pre 在前 cur在后。
每次让 pre 的 next 指向 cur ,实现一次局部反转
局部反转完成之后, pre 和 cur 同时往前移动一个位置
循环上述过程,直至 pre 到达链表尾部
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null){
            return null;
        }
        ListNode cur = head;
        ListNode pre = null;
        while(cur!=null){
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
}

全排列问题

从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当m=n时所有的排列情况叫全排列

  1. 定义函数功能
    p是待排列数组,perm§ 用来求数组p的全排列

  2. 寻求递归的结束条件
    当p里面只有一个元素,p的全排列就是他自己本身

  3. 寻找等价关系
    ① 首先对于只有一个元素的数组,他的全排列就是它本身
    ② 对于两个元素的数组p{a, b},他的全排列是以a开头的b的全排列,和以b开头的a的全排列
    ③ 对于任意情况数组p{r1, r2, r3, … , rn},他的全排列是r1perm(p1), r2perm(p2), r3perm(p3) … rnperm(pn)
    即将整组数中的所有的数分别与第一个数交换,后n-1个数的全排列

代码实现:

public class Permutate {
    private static int total = 0;//排列数
    private static void swap(int i, int j, String[] str){//交换数组str中的str[i]和str[j]位置
        String temp = str[i];
        str[i] = str[j];
        str[j] = temp;
    }
    //固定第一个元素求剩下元素的全排列,再改变第一个元素
    private static void perm(String[] str, int st, int len){
        //出口条件: 待排列的数组里面只有一个元素
        //st: 待排列的数组的起始点在原数组的位置
        if(st == len-1){
            for(int i = 0; i < len; i++){
                System.out.println(str[i]+" ");
            }
            total++;
        }else {
            for (int i = st; i < len; i++){
                swap(st, i, str);
                perm(str, st+1, len);
                swap(st, i ,str);
            }
        }
    }
}

也可以这样实现

import java.util.List;
import java.util.Arrays;
class Solution {
    public String[] permutation(String S) {
        List<String> res = new ArrayList<String>();
        int len = S.length();
        if (len == 0) return new String[0];
        boolean[] used = new boolean[len];
        char[] sChar = S.toCharArray();

        StringBuilder path = new StringBuilder(len); 

        // 排序是为了后面的剪枝
        Arrays.sort(sChar);

        dfs(res, sChar, len, path, 0, used);
        return res.toArray(new String[0]);
    }
    
    /**
    * @param res 结果集
    * @param sChar 输入字符数组
    * @param len 字符数组长度
    * @param path 根结点到任意结点的路径
    * @param depth 当前树的深度
    * @param used 使用标记数组
    */
    private void dfs(List<String> res
                    , char[] sChar
                    , int len
                    , StringBuilder path
                    , int depth
                    , boolean[] used) {
        // 到达叶子结点
        if (depth == len) {
            res.add(path.toString());
            return;
        }

        for (int i = 0; i < len; i++) {
            if (!used[i]) {

                // 根据已排序字符数组, 剪枝
                //对于两个a,我们计作a1、a2,如果只是正常排列的话,得到aa,有两种情况a1a2和a2a1,会有重复。这个解法再剪枝的时候规定,使用字母的次序,必须是从前往后的。也就是,假如有n个a,用了a1~i,才可以使用 a_i+1。这样达到去重的效果。
                if (i > 0 && sChar[i] == sChar[i-1] && !used[i-1]) {
                    continue;
                }

                path.append(sChar[i]);
                used[i] = true; // 标记选择
                dfs(res, sChar, len, path, depth+1, used);
                path.deleteCharAt(depth);
                used[i] = false; // 撤销选择
            }
        }
    }
}

时间复杂度:

递归的优化

  1. 如果有子问题被重复计算,那么也就是说他的子问题不相互独立,使用动态规划
  2. 如果问题规模过大,自顶向下递归在向上返回可能栈的空间不够,则考虑自底向上递推

分治

分治法的适用条件

  1. 最优子结构:问题可以分解为若干个规模较小的相同问题
  2. 分解出来的子问题相互独立,即子问题之间不包含公共子问题
  3. 子问题的解合并可以得到原问题的解
  4. 问题规模缩小到一定程度可以容易解决

分治法的基本步骤(伪代码)

divide_and_conquer(P){
	if(P <= N){//当子问题的规模小于一定的阈值则解决小规模问题,也就是说在递归步里,子问题小的不能再小了
		adhoc(P);
	}
	把问题P分解为子问题P1,P2,....Pn;
	//再对所有子问题递归求解
	for(i=1; i<=k; i++){
	//对于上次的子问题再分或者得出子问题解
		Yi = divide_and_conquer(Pi);
	}
	return merge(Y1....Yn)
}

分治的时间复杂度分析

k:把原问题分为 k 个小问题,每个小问题的规模为 n/m
f(n):用merge把k个子问题的解合并为原问题的解需要 f(n)个单位时间

参考分治法时间复杂度求解秘籍
递推求解法
以合并排序为例
T(n) = 2T(n/2) + O(n)
= 2(2(T(n/4) + O(n/2)) + O(n)
= …
= 2xT(n/2x) + xO(n)
因为递归后的最小规模为1,所以
n/2x = 1
T(n) = O(nlogn)

众数问题

给定含有n个元素的多重集合s,每个元素在s中出现的次数称为该元素的重数,多重集s中重数最大的元素称为众数,给定多重集合s,求s中的众数集重数
如:S = {1,2,2,2,3,5}中的重数为3,众数为2

解题思路

计算其中每个元素的出现次数在进行比较可以暴力求解
但在这里只要使用分治的思想,先对序列中的元素进行排序,然后找到中间的元素middle,计算middle的重数n,如果middle左边的元素个数大于n,则所要寻找的众数可能在左边(因为之前排好序的原因,相同的数都被放在一起);对于左边的子序列按照之前的方法进行递归求解

算法实现

public class DivideAndConquer {
    public static int left;//确定左边的子序列到哪里结束
    public static int right;//确定右边的子序列从哪里开始
    public static int maxCnt = 0;//重数
    public static int maxNum = 0;//众数

    public static void split(int[] arr, int n){
        int middleIndex = n/2;
        int middle = arr[middleIndex];
        for(left = 0; left<n; left++){
            if(arr[left] == middle)
                break;
        }

        for(right = left+1; right<n; right++){
            if(arr[right] != middle)
                break;
        }
    }

    public static void getMaxCnt(int[] arr, int n) {
        //递归结束的条件是middle左边的和右边的子序列的长度都比middle的重数小
        //也就是说只有满足上述条件之一才会进行递归求解
        //maxCnt是重数
        //n是要被split的arr的长度
        left = 0;
        right = 0;
        //要重点理解split的意思
        split(arr,n);
        int num = n/2;//num:取中间的数为众数
        int cnt = right - left;//cnt是当前所取数的重数
        if(cnt > maxCnt){
            maxCnt = cnt;
            maxNum = arr[num];
        }

        if(left>maxCnt){
            getMaxCnt(arr,left+1);
        }

        if(n-right>maxCnt){
            //数组要改变
            int[] splitArr = new int[n-right+1];
            for(int i = 0; i<n-right+1; i++){
                splitArr[i] = arr[right+i];
            }
            getMaxCnt(splitArr,n-right);
        }
    }
    
}

数组一定要先排好序
其实用c++写更方便
c++实现

时间复杂度

T(n) = n+lognO(n)

二分查找问题

给出按照升序排列好的n个元素a[0:n-1],找特定元素x在不在a中

解题思路

将查找的键和子数组的中间键作比较,如果被查找的键小于中间键,就在左子数组继续查找;如果大于中间键,就在右子数组中查找,否则中间键就是要找的元素

递归算法实现

public class BinarySearch {

    public static boolean binarySearch(int[] arr, int n,int left,int right) {
        int middleIndex = (left+right)/2;
        if(middleIndex<=0||middleIndex>=arr.length-1){
            return false;
        }
        int middle = arr[middleIndex];
        if (middle == n) {
            return true;
        }
        if(middle>n){
            //左边搜索
            return binarySearch(arr,n,left,middleIndex-1);
        }else{
            //右边搜索
            return binarySearch(arr,n,middleIndex+1,right);
        }

    }
}

非递归算法实现

static int binarySerach(int[] array, int key) {
    int left = 0;
    int right = array.length - 1;

    // 这里必须是 <=
    while (left <= right) {
        int mid = (left + right) / 2;
        if (array[mid] == key) {
            return mid;
        }
        else if (array[mid] < key) {
            left = mid + 1;
        }
        else {
            right = mid - 1;
        }
    }

    return -1;
}
时间复杂度分析

学习

T( n ) = T( n/2 ) + O( 1 )(每次都丢弃了一半)
T( n ) = T( n/4 ) + 2 O( 1 )
T( n) = T( n/8 ) + 3 O( 1 ) …
…[共 logN 次] …
T( n ) = T( 1 ) + logN·O( 1 )
T( n ) = O(logN)

棋盘覆盖

在一个2k×2k个方格组成的棋盘中,若有一个方格与其他方格不同,则称该方格为一特殊方格,且称该棋盘为一个特殊棋盘
在这里插入图片描述
在棋盘覆盖问题中,要用下图中 4 中不同形态的 L 型骨牌覆盖一个给定的特殊棋牌上除特殊方格以外的所有方格,且任何 2 个 L 型骨牌不得重叠覆盖
在这里插入图片描述

① 定义全局变量size,sr,sc 用来分别表示棋盘的大小规模,特殊棋子的横纵坐标,二维数组broad用来存储棋盘信息

private static int Color = 0;//表示牌骨的颜色
    static int size = 0;
    static int sr = 0;
    static int sc = 0;

    static int [][] Broad ;

② 在main方法中要求用户自定义输入棋盘的大小,特殊方格的坐标

public static void main(String[] args){
        //OverGride overGride = new OverGride();
        System.out.println("请输入棋盘大小:");
        Scanner Sc = new Scanner(System.in);
        size = Sc.nextInt();
        Broad = new int[size][size];
        System.out.println("请输入特殊方格位置:");
        System.out.println("横坐标:");
        sr = Sc.nextInt();
        System.out.println("纵坐标:");
        sc = Sc.nextInt();

         ChessBoard(0,0,sr,sc,size);
         for(int i = 0; i<size; i++){
             for(int k = 0; k<size; k++){
                 System.out.print(Broad[i][k]);
             }
             System.out.println();
        }

③ ChessBroad(int lr, int lc, int sr, int sc, int size)方法:利用递归分治的思想解决问题,大体思路自于把问题规模缩小,即把划分棋盘使其从先前规模2k* 2k 变为2(k-1)* 2(k-1)的问题进行求解(其中参数lr代表该块区域左上角横坐标,lc代表该块区域左上角纵坐标,sr,sc代表特殊方格的坐标,size表示问题的规模)
首先判断size是否等于1(size表示期盼规模)如果等于1代表已经划分到不能划分即1个方格为一个问题规模,返回(这是递归的出口),如果size不等于1,即进行划分size = size/2,并且要把需要用到的颜色改变

 if(size == 1){
            return ;
        }
        int color_temp = Color++;//颜色在每次划分棋盘后要改变
        //划分
        int size_temp = size/2;//即2^k*2^k的问题变为4个2^(k-1)*2^(k-1)问题进行求解
        

④ 判断特殊方格是否在划分后的左上角区域,如果在,则递归调用ChessBoard()函数,(此时的参数size是划分后的size)
如果不在该区域则覆盖右下角的方格让他变成特殊方格再递归调用ChessBoard()(其参数特殊方格坐标要相应改变)

 //判断特殊方格是否在划分后的左上角
        if(sr <lr+size_temp && sc <lc+size_temp){
            ChessBoard(lr,lc,sr,sc,size_temp);//递归
        }else{
            //如果不在这个区域,则去覆盖该区域右下角的方格
            Broad[lr+size_temp-1][lc+size_temp-1] = color_temp;
            ChessBoard(lr,lc,lr+size_temp-1,lc+size_temp-1,size_temp);//递归
        }

⑤ 同理判断右上角,左下角,右下角

//判断特殊方格是否在划分后的右上角
        if(sr <lr+size_temp && sc >=lc+size_temp){
            ChessBoard(lr,lc+size_temp,sr,sc,size_temp);//递归
        }else{
            //如果不在这个区域,则去覆盖该区域左下角的方格
            //同时该左下角方格变成特殊方格
            Broad[lr+size_temp-1][lc+size_temp] = color_temp;
            ChessBoard(lr,lc+size_temp,lr+size_temp-1,lc+size_temp,size_temp);//递归
        }
        //判断特殊方格是否在划分后的左下角
        if(sr >=lr+size_temp && sc <lc+size_temp){
            ChessBoard(lr+size_temp,lc,sr,sc,size_temp);//递归
        }else{
            //如果不在这个区域,则去覆盖该区域右下角的方格
            //同时该右下角方格变成特殊方格
            Broad[lr+size_temp][lc+size_temp-1] = color_temp;
            ChessBoard(lr+size_temp,lc,lr+size_temp,lc+size_temp-1,size_temp);//递归
        }

        if(sr >=lr+size_temp && sc >=lc+size_temp){
            ChessBoard(lr+size_temp,lc+size_temp,sr,sc,size_temp);//递归
        }else{
            //如果不在这个区域,则去覆盖该区域右下角的方格
            //同时变为特殊方格
            Broad[lr+size_temp][lc+size_temp] = color_temp;
            ChessBoard(lr+size_temp,lc+size_temp,lr+size_temp,lc+size_temp,size_temp);//递归
        }

完整代码如下:

import java.awt.*;
import java.util.Scanner;

import javax.swing.*;


public class OverGride extends JFrame{
    //棋盘覆盖问题
//4.	用分治策略实现棋盘覆盖问题。
//(1)	选择合适的数据结构来表示问题;
//(2)	根据分治法的基本原理,写出棋盘覆盖问题的伪码算法;
//(3)	编制C++或JAVA等高级语言程序实现伪码算法;
//(4)	上机运行程序,验证算法的正确性,并分析算法的时空复杂性。

    private static int Color = 0;//表示牌骨的颜色
    static int size = 0;
    static int sr = 0;
    static int sc = 0;

    static int [][] Broad ;
    public static void main(String[] args){
        //OverGride overGride = new OverGride();
        System.out.println("请输入棋盘大小:");
        Scanner Sc = new Scanner(System.in);
        size = Sc.nextInt();
        Broad = new int[size][size];
        System.out.println("请输入特殊方格位置:");
        System.out.println("横坐标:");
        sr = Sc.nextInt();
        System.out.println("纵坐标:");
        sc = Sc.nextInt();

         ChessBoard(0,0,sr,sc,size);
         for(int i = 0; i<size; i++){
             for(int k = 0; k<size; k++){
                 System.out.print(Broad[i][k]);
             }
             System.out.println();
        }
        JButton[][] butt = new JButton[size][size];
        OverGride overGride = new OverGride(butt);

    }

    //绘制棋盘
    public OverGride(JButton[][] butt){

        for(int i=0; i<size; i++){
            for(int c = 0;c<size;c++){
                butt[i][c] = new JButton();
                switch (Broad[i][c]){

                    case 0:{
                        butt[i][c].setBackground(java.awt.Color.CYAN);
                        break;
                    }
                    case 1:{
                        butt[i][c].setBackground(java.awt.Color.ORANGE);
                        break;
                    }
                    case 2:{
                        butt[i][c].setBackground(java.awt.Color.yellow);
                        break;
                    }
                    case 3:{
                        butt[i][c].setBackground(java.awt.Color.black);
                        break;
                    }
                    case 4:{
                        butt[i][c].setBackground(java.awt.Color.MAGENTA);
                        break;
                    }
                    case 5:{
                        butt[i][c].setBackground(java.awt.Color.LIGHT_GRAY);
                        break;
                    }
                    case 6:{
                        butt[i][c].setBackground(java.awt.Color.DARK_GRAY);
                        break;
                    }
                    case 7:{
                        butt[i][c].setBackground(java.awt.Color.green);
                        break;
                    }
                    case 8:{
                        butt[i][c].setBackground(java.awt.Color.RED);
                        break;
                    }case 9:{
                        butt[i][c].setBackground(java.awt.Color.WHITE);
                        break;
                    }case 10:{
                        butt[i][c].setBackground(java.awt.Color.BLUE);
                        break;
                    }case 11:{
                        butt[i][c].setBackground(java.awt.Color.GRAY);
                        break;
                    }case 12:{
                        butt[i][c].setBackground(java.awt.Color.magenta);
                        break;
                    }case 13:{
                        butt[i][c].setBackground(java.awt.Color.pink);
                        break;
                    }case 14:{
                        butt[i][c].setBackground(java.awt.Color.gray);
                        break;
                    }case 15:{
                        butt[i][c].setBackground(java.awt.Color.red);
                        break;
                    }case 16:{
                        butt[i][c].setBackground(java.awt.Color.GREEN);
                        break;
                    }case 17:{
                        butt[i][c].setBackground(java.awt.Color.YELLOW);
                        break;
                    }case 18:{
                        butt[i][c].setBackground(java.awt.Color.blue);
                        break;
                    }case 19:{
                        butt[i][c].setBackground(java.awt.Color.lightGray);
                        break;
                    }default:{
                        butt[i][c].setBackground(java.awt.Color.PINK);

                    }



                }

            }
        }



        this.setLayout(new GridLayout(size, size, 0, 0));
        for(int i=0; i<size; i++){
            for(int c = 0;c<size;c++){


                this.add(butt[i][c]);
            }
        }


        this.setTitle("棋盘覆盖!");
        this.setLocation(100,200);
        this.setSize(400,400) ;
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true) ;


    }

    public static void ChessBoard(int lr,int lc, int sr, int sc, int size){
        //主要思想在于把问题缩小,即2^k*2^k的问题变为4个2^(k-1)*2^(k-1)问题进行求解
        //lr是左上角的横坐标,lc是左上角的纵坐标,用于定位
        //sr,sc是特殊方格的坐标
        //size是棋盘大小
        if(size == 1){
            return ;
        }
        int color_temp = Color++;//颜色在每次划分棋盘后要改变
        //划分
        int size_temp = size/2;//即2^k*2^k的问题变为4个2^(k-1)*2^(k-1)问题进行求解
        //判断特殊方格是否在划分后的左上角
        if(sr <lr+size_temp && sc <lc+size_temp){
            ChessBoard(lr,lc,sr,sc,size_temp);//递归
        }else{
            //如果不在这个区域,则去覆盖该区域右下角的方格
            Broad[lr+size_temp-1][lc+size_temp-1] = color_temp;
            ChessBoard(lr,lc,lr+size_temp-1,lc+size_temp-1,size_temp);//递归
        }
        //判断特殊方格是否在划分后的右上角
        if(sr <lr+size_temp && sc >=lc+size_temp){
            ChessBoard(lr,lc+size_temp,sr,sc,size_temp);//递归
        }else{
            //如果不在这个区域,则去覆盖该区域左下角的方格
            //同时该左下角方格变成特殊方格
            Broad[lr+size_temp-1][lc+size_temp] = color_temp;
            ChessBoard(lr,lc+size_temp,lr+size_temp-1,lc+size_temp,size_temp);//递归
        }
        //判断特殊方格是否在划分后的左下角
        if(sr >=lr+size_temp && sc <lc+size_temp){
            ChessBoard(lr+size_temp,lc,sr,sc,size_temp);//递归
        }else{
            //如果不在这个区域,则去覆盖该区域右下角的方格
            //同时该右下角方格变成特殊方格
            Broad[lr+size_temp][lc+size_temp-1] = color_temp;
            ChessBoard(lr+size_temp,lc,lr+size_temp,lc+size_temp-1,size_temp);//递归
        }

        if(sr >=lr+size_temp && sc >=lc+size_temp){
            ChessBoard(lr+size_temp,lc+size_temp,sr,sc,size_temp);//递归
        }else{
            //如果不在这个区域,则去覆盖该区域右下角的方格
            //同时变为特殊方格
            Broad[lr+size_temp][lc+size_temp] = color_temp;
            ChessBoard(lr+size_temp,lc+size_temp,lr+size_temp,lc+size_temp,size_temp);//递归
        }


    }
}

时间复杂度:当k = 0(k就是2^k中的k)时,T(k) = O(1); 当k大于0时T(k) = 4*T(k-1) + O(1); 所以可以得到T(k) = O(4k)

合并排序

基本思路:
参考图解排序算法之归并排序
采用分治的基本思想,把原问题不断缩小为规模更小的问题,再把规模更小的问题的解决结果进行合并
在这里插入图片描述

代码实现

 public static void mergeSort(int[] arr, int left, int right, int[] temp){
        /*
         arr[] 表示要排序的序列
         left是排序的起点
         right是终点
         */
         
        //结束条件其实是
		//if(left>=right){
		//	return;
		//}
        //分
        if(left<right){

            int mid = (left+right)/2;
            //分
            mergeSort(arr,left,mid,temp);//左边进行归并排序使得左子序列有序
            mergeSort(arr,mid+1,right,temp);//右边归并排序使得右子序列有序
            //合
            merge(arr,left,mid,right,temp);
        }
    }

    private static void merge(int[] arr,int left,int mid,int right,int[] temp){
        int i = left;//左序列指针
        int j = mid+1;//右序列指针
        int t = 0;//临时数组指针
        while (i<=mid && j<=right){
            if(arr[i]<=arr[j]){
                temp[t++] = arr[i++];
            }else {
                temp[t++] = arr[j++];
            }
        }
        while(i<=mid){//将左边剩余元素填充进temp中
            temp[t++] = arr[i++];
        }
        while(j<=right){//将右序列剩余元素填充进temp中
            temp[t++] = arr[j++];
        }
        t = 0;
        //将temp中的元素全部拷贝到原数组中
        while(left <= right){
            arr[left++] = temp[t++];
        }
    }

**结束递归的条件是当leftright的时候**==
其实代码和图解不太一样,并不是说全部分完再一个一个合并,而是交叉着进行,这点再当初理解代码的时候给我造成了困扰

时间复杂度:O(nlogn) 学习

快速排序

参考快速排序
基本思路:通过一趟排序将要排序的数据分割成独立的两部分:分割点左边都是比它小的数,右边都是比它大的数。然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
在这里插入图片描述

public class QuickSort {
    public int division(int[] list, int left, int right){
        int base = list[left];
        while(left<right){
            //从左往右找找比基准数小的数
            while(left<right && list[right] >= base){
                right--;
            }
            //把比基准数小的数放到left指向位置
            list[left] = list[right];
            //从右往左找,找比基准数大的数
            while(left<right && list[left] <= base){
                left++;
            }
            list[right] = list[left];
        }
        //最后将base放到left位置。此时,left位置的左侧数值应该都比left小,右侧的数都比left大
        list[left] = base;
        return left;
    }

    public void quickSort(int[] list, int left, int right){
        // 左下标一定小于右下标,否则就越界了
        if (left < right) {
            // 对数组进行分割,取出下次分割的基准标号
            int base = division(list, left, right);

            System.out.format("base = %d:\t", list[base]);

            // 对“基准标号“左侧的一组数值进行递归的切割,以至于将这些数值完整的排序
            quickSort(list, left, base - 1);

            // 对“基准标号“右侧的一组数值进行递归的切割,以至于将这些数值完整的排序
            quickSort(list, base + 1, right);
        }
    }
}

时间复杂度:O(nlogn)

整数因子分解

参考递归分治

大于1 的正整数n 可以分解为:n=x1 x 2…xm 。 例如,当n= 12时,共有8 种不同的分解式: 12= 12; 12=62; 12=43; 12=34; 12=322; 12=26;12=232; 12=22*3;对于给定的正整数n,编程计算n共有多少种不同的分解式

思路:递归所有可能被分解的因子,然后一次除尽到1总的个数就加一

代码实现:

public static void solve(int n){
        if(n == 1) sum ++;
        else{
        	for(int i = 2; i <= n; i++){
        		if(n % i == 0){
        			solve(n/i);
        		}
        	}
        }
    }

循环赛日程表

参考循环赛日程表
设有n=2^k个运动员,要进行网球循环赛。现在要设计一个满足以下要求的比赛日程表
(1)每个选手必须与其他n-1个选手各赛一场
(2)每个选手一天只能赛一次
(3)循环赛一共进行n-1天
将比赛日程表设计成n行n列,表中除了第一列,其他n-1列才是我们要的,数组下标行列都从0开始,第i行j列代表第(i+1)位选手在第j天的对手:
在这里插入图片描述

package cn.com.zfc.everyday.test;

import java.util.Scanner;

/**
 * 
 * @title RoundRobinSchedule
 * @describe 循环赛日程表:
 *           设有n=2^k个运动员要进行网球循环赛。
 *           现要设计一个满足以下要求的比赛日程表:
 *           (1)每个选手必须与其他n-1个选手各赛一次;
 *           (2)每个选手一天只能参赛一次;
 *           (3)循环赛在n-1天内结束。
 *           按此要求将比赛日程表设计成有n行和n-1列的一个表。
 *           在表中的第i行,第j列处填入第i个选手在第j天所遇到的选手。
 * @author 张富昌
 * @date 2017年4月9日下午9:22:42
 */
public class RoundRobinSchedule {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入 k 的值(2^k个运动员)");
        int k = scanner.nextInt();
        scanner.close();
        // 求运动员人数
        int n = 1;
        for (int i = 1; i <= k; i++) {
            n = n * 2;
        }
        // 创建二维数组作为日程表
        int[][] array = new int[n + 1][n + 1];
        // 制作日程表
        table(k, array, n);
        // 输出日程表
        printTable(array, n);
    }

    /**
     * 
     * @param k:2^k个运动员
     * @param a:循环赛日程表
     * @param n:运动员的人数
     */
    private static void table(int k, int[][] a, int n) {
        // 设置日程表第一行
        for (int i = 1; i <= n; i++) {
            a[1][i] = i;
        }
        // 每次填充时,起始填充位置
        int m = 1;
        for (int s = 1; s <= k; s++) {
            n /= 2;
            for (int t = 1; t <= n; t++) {
                // 控制行
                for (int i = m + 1; i <= 2 * m; i++) {
                    // 控制列
                    for (int j = m + 1; j <= 2 * m; j++) {
                        // 右下角等于左上角的值
                        a[i][j + (t - 1) * m * 2] = a[i - m][j + (t - 1) * m * 2 - m];
                        // 左下角等于右上角的值
                        a[i][j + (t - 1) * m * 2 - m] = a[i - m][j + (t - 1) * m * 2];
                    }
                }
            }
            m *= 2;
        }
    }

    // 输出日程表
    private static void printTable(int[] array[], int n) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                System.out.print(array[i][j] + " ");
            }
            System.out.println();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值