算法笔记2——递归分治

递归的概念:

直接或间接地调用自己的算法称为递归算法。

分治的基本思想

分治的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题互相独立且与原问题相同。递归地解这些子问题,然后将各个子问题的解合并得到原问题的解

递归分治的四个特性:

1、问题缩小到一定程度可以直接求解
2、(最优子结构性)原问题可分解为多个规模较小的相同子问题
3、子问题的解合并成为原问题的解
4、子问题相互独立

分治法的复杂性分析

一个分治法将规模为n的问题分成k个规模为n/k的子问题去解。设分解阀值n0=1,且adhoc解规模为1的问题耗费1个单位时间。再设将原问题分解为k个子问题以及用merge将k个子问题的解合并为原问题的解需用f(n)个单位时间。
用T(n)表示该分治法解规模为|P|=n的问题所需的计算时间,则有:
在这里插入图片描述
在这里插入图片描述
举例说明:
例1:
在这里插入图片描述
解:
f(n)=n2
nlogba=n3
所以n2=o(n3),使用第一条。
T(n)=O((n3)

例2:
在这里插入图片描述
解:
f(n)=n1/2
nlogba=n1/2
所以n1/2=θ(n1/2),使用第二条。
T(n)=O((n1/2logn)

典型问题

1、二分查找

class BinarySearch{
    public static void main(String[] args){
        int[] arr = {1,2,3,4,5,6,7,8};
        int a = 8;
        System.out.println(binarySearch(a,arr));
    }
    static int binarySearch(int a,int[] arr){
        // 最大索引
        int maxIndex = arr.length -1;
        // 最小索引
        int minIndex = 0;
        // 中间索引
        int halfIndex = minIndex+(maxIndex-minIndex)/2;

        while (minIndex<=maxIndex){
            // 找到时
            if (arr[halfIndex]==a){
                return halfIndex;
            }else if (arr[halfIndex]<a){// 比a小时
                minIndex = halfIndex + 1;
            }else {// 比a大时
                maxIndex = halfIndex - 1;
            }
            halfIndex = minIndex+(maxIndex-minIndex)/2;
        }
        return -1;
    }
}

2、合并排序

基本思想:将待排序元素分成大小大致相同的2个子集合,分别对2个子集合进行排序,最终将排好序的子集合合并成为所要求的排好序的集合。

public class MergeSort {

    public static void sort(int[] nums, int low, int high) {
        int mid = (low + high) / 2;
        if (low < high) {
            // 左边
            sort(nums, low, mid);
            // 右边
            sort(nums, mid + 1, high);
            // 左右归并
            merge(nums, low, mid, high);

        }
    }

    public static void merge(int[] nums, int low, int mid, int high) {
        int[] temp = new int[high - low + 1];
        int i = low;// 左指针
        int j = mid + 1;// 右指针
        int k = 0;

        // 把较小的数先移到新数组中
        while (i <= mid && j <= high) {
            if (nums[i] < nums[j]) {
                temp[k++] = nums[i++];

            } else {
                temp[k++] = nums[j++];

            }

        }
        // 把左边剩余的数移入数组
        while (i <= mid) {
            temp[k++] = nums[i++];

        }

        // 把右边边剩余的数移入数组
        while (j <= high) {
            temp[k++] = nums[j++];

        }

        // 把新数组中的数覆盖nums数组
        for (int k2 = 0; k2 < temp.length; k2++) {
            nums[k2 + low] = temp[k2];
        }

    }
    public static void main(String[]args)
    {
    	int arr[]={1,2,7,4,6,5,3};
    	MergeSort mergeSort=new MergeSort();
    	mergeSort.sort(arr, 0, 6);
    	for(int i=0;i<=6;i++)
    	{
    		System.out.println(arr[i]);
    	}
    }

}

3、快速排序

快速排序算法的思想是:找到一个数字作为标准,把比该数小的放左边,比该数大的放右边,然后把左边的数组进行快排,右边的数组进行快排,最后把结果合并。
对数组{a,b,c,…,n}。
1、首先设置一个左指针i指向数组最左边的元素(a),设置一个右指针j只想数组最右边的元素(n)。
2、设置一个基准元素,一般选取数组最左边的元素(a)。
3、将右指针指向的元素和基准元素进行比较。如果比基准元素大,右指针就向左移动一个,即J–,这样一直比较下去。那么什么时候停止这个比较呢?结束条件有两个:1、发现一个元素比基准元素小,右指针指向的元素就和左指针指向的元素互换。2、右指针j与左指针i相逢,直接退出。
4、将左指针指向的元素和基准元素进行比较,如果比基准元素小,左指针就向右移动一个,即i++,这样一直比较下去。那么什么时候停止这个比较呢?结束条件有两个:1、发现一个元素比基准元素大,右指针指向的元素就和左指针指向的元素互换。2、右指针j与左指针i相逢直接退出。

	public class sort {
	public void quickSort(int[] arr, int low, int high) {
		if (low < high) {
			int index = getIndex(arr, low, high);
			//以排好序的元素为界,将原数组分为两部分,递归
			quickSort(arr, 0, index - 1);
			quickSort(arr, index + 1, high);
		}

	}

	public int getIndex(int[] arr, int low, int high) {
		int tmp=arr[low];
		int i=low;
		int j=high;
		int t;
		while (true) {
			// 当队尾的元素大于等于基准数据时,向前挪动high指针
			while(arr[j] >= tmp && i < j)
	    		j--;
	    	while(arr[i] <= tmp && i < j)//再找右边的
	    		i++;  
	    	
	    	if(i < j)//交换两个数在数组中的位置
	    	{
	    		t = arr[i];
	    		arr[i] = arr[j];
	    		arr[j] = t;
	    	}
	    	else
	    		break;

		}
		arr[low] = arr[i];
	    arr[i] = tmp;
		return i;  
	}
	
	
	public static void main (String[]args)
	{
		int a[]={4,5,7,1,3,2,6};
		sort test=new sort();
		test.quickSort(a,0,6);
		for(int i=0;i<=6;i++)
		{
			System.out.println(a[i]);
		}
	}

}

4、线性时间选择问题(随机快排)

问题描述:对于给定的n个元素的数组a[0:n—1],要求从中找出第k小的元素
问题分析:我们知道,快速排序算法的一次排序的思想是:找到一个数字作为标准,把比该数小的放左边,比该数大的放右边。
要找到第k小的元素,最粗暴的就是全部排序,但这样做了很多多余的工作,借鉴快速排序算法的一次排序思想,我们可以随机选择一个元素作为标准,把小于它的放左边,大于它的放右边:
当这个标准左边的元素和它加起来为k的话,就找到第k小的数了
当这个标准左边的元素和它加起来小于k的话,就向右边继续找第(k-1-该数下标)小的数
当这个标准左边的元素和它加起来大于k的话,就向左边继续找第k小的数
这样做的问题是,由于这个标准是随机选择的,这就造成了算法很不稳定。比如选择了最小的数或者最大的数,就导致这次划分没什么用处。所以我们尝试确定一种方法来确定一个随机数,使得这个随机数处在一个恰当的位置。
具体方法为:
将n个输入元素划分成⌈ n/5 ⌉个组,每组5个元素,用任意一种排序算法对每组中的元素排序,然后取出每组的中位数,共⌈ n/5 ⌉个元素,找出这⌈ n/5 ⌉个元素的中位数,如果⌈ n/5 ⌉为偶数,则选择2个中位数中较大的一个,以这个选出的元素作为划分基准。
例如:
按递增顺序,找出下面29个元素的第18个元素:8,31,60,33,17,4,51,57,49,35,11,43,37,3,13,52,6,19,25,32,54,16,5,41,7,23,22,46,29.
(1) 把前面29个元素分为6(=ceil(29/5))组; (8,31,60,33,17),(4,51,57,49,35),(11,43,37,3,13),(52,6,19,25,32),(54,16,5,41,7),(23,22,46,29).
(2) 提取每一组的中值元素,构成集合{31,49,13,25,16,29};
(3) 递归地使用算法求取该集合的中值,得到m=29;
(4) 根据m=29, 把29个元素划分为3个子数组:
P={8,17,4,11, 3,13,6,19, 25,16,5,7,23,22}
Q={29}
R={31,60,33,51,57,49,35,43,37,52,32,54,41,46}
(5) 由于|P|=14,|Q|=1,k=18,所以放弃P,Q,使k=18-14-1=3,对R递归地执行本算法;
(6) 将R划分成3(ceil(14/5))组:{31,60,33,51,57},{49,35,43,37,52},{32,54,41,46}
(7) 求取这3组元素的中值元素分别为:{51,43,46},这个集合的中值元素是43;
(8) 根据43将R划分成3组:
{31, 33, 35,37,32, 41},{43},{60, 51,57, 49, 52,54, 46}
(9) 因为k=3,第一个子数组的元素个数大于k,所以放弃后面两个子数组,以k=3对第一个子数组递归调用本算法;
(10) 将这个子数组分成5个元素的一组:{31,33,35,37,32}、{41},取其中值元素为33;
(11) 根据33,把第一个子数组划分成{31,32},{33},{35,37};
(12) 因为k=3,而第一、第二个子数组的元素个数为3,所以33即为所求取的第18个小元素。
具体代码如下:
在这里插入图片描述

5、循环赛日程表

设有n=2^k个运动员要进行网球循环赛。现要设计一个满足以下要求的比赛日程表:

(1)每个选手必须与其他n-1个选手各赛一次;
(2)每个选手一天只能参赛一次;
(3)循环赛在n-1天内结束。

请按此要求将比赛日程表设计成有n行和n-1列的一个表。在表中的第i行,第j列处填入第i个选手在第j天所遇到的选手。其中1≤i≤n,1≤j≤n-1。8个选手的比赛日程表如下图:
在这里插入图片描述

#define _CRT_SECURE_NO_DEPRECATE;
#define _CRT_SECURE_NO_WARNINGS;
#include<stdio.h>
#define N 100
int a[N][N];

void Scheduled(int i, int size)
{
	int x, y;
	if (size == 2)
	{
		a[i][1] = i;
		a[i + 1][1] = i + 1;
	}
	else {
		Scheduled(i, size / 2);
		Scheduled(i + size / 2, size / 2);
	}

	for (x = i;x<i + size / 2;x++)
		for (y = size / 2 + 1;y <= size;y++) {
			a[x][y] = a[x + size / 2][y - size / 2];
													
		}
	for (x = i + size / 2;x<i + size;x++)
		for (y = size / 2 + 1;y <= size;y++) {
			a[x][y] = a[x - size / 2][y - size / 2];
			
		}
}

void Print(int n) {
	int p, q;
	FILE *fp;
	fp = fopen("D:\\222017321062109_循环赛.txt", "wb");

	for (p = 1;p <= n;p++) {
		for (q = 1;q <= n;q++) {
			printf("%d\t", a[p][q]);
			fprintf(fp, "%d\t", a[p][q]);
		}
		printf("\n");
		fprintf(fp, "\r\n");
	}
	fclose(fp);
}


int main() {
	int n;
	printf("请输入n的值(请确保N为2的K次方,K>=0):\n");
	scanf_s("%d", &n);
	Scheduled(1, n);
	Print(n);
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值