递归与分治-合并排序、快速排序以及循环赛问题

 合并排序

  合并排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

  递归方法:

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

package com.gqx.arithmetic.Recursion;

public class Recursion_Merge2 {

	private static void mergeSort(int a[],int left,int right) {
		int[] b=new int[a.length];
		if (left<right) {
			int i=(left+right)/2;
			mergeSort(a, left, i);
			mergeSort(a, i+1, right);
			merge(a,b,left,i,right);	//合并数组到b中
			copy(a, b, left, right);	//复制回数组a中
		}

	}

	private static void copy(int[] a, int[] b, int left, int right) {
		// TODO Auto-generated method stub
		for (int i = left; i <= right; i++) {
			a[i]=b[i];
		}
	}

	private static void merge(int[] x, int[] y, int i, int j, int k) {
		// TODO Auto-generated method stub
		//合并c[i:j]和c[j+1:k]
		int t=i,h=i,u=j+1;
		while ( (t <= j) && (u<=k) ) {
			if (x[t]<=x[u]) {
				y[h++]=x[t++];
			}else {
				y[h++]=x[u++];
			}
		}

		//若果两个c[i:j]和c[j+1:k]还有元素没有被比较,
		if (t>j)
			//t>j 说明前面i个数已经加到了y中,而后面的(j-k)还未全部加到y中
			for (int l = u; l <= k; l++) {
				y[h++]=x[l];
			}
		else {
			//如果后面的(j-k)已经加到了y中,而前面的(i-j)还未加到y中
			for(int l=t;l<=j;l++)
				y[h++]=x[l];
		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int a[]={55,11,44,88,67,22,88,33};
		mergeSort(a, 0, a.length-1);
		System.out.println("递归方式实现归并排序:");
		for (int i = 0; i < a.length; i++)
			System.out.print(a[i]+"\t");
	}

}

 递归中只是将待排序的集合一分为二,直至待排序的集合只剩下一个元素为止,然后不断地合并两个已经排好序的数组段。结果如图:

  分治方法:

  消除递归,按照自然排序的基本思想。

package com.gqx.arithmetic.Recursion;

public class Recursion_Merge {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] a={2,45,66,8,22,67,90,33,1};
		for (int i = 0; i < a.length; i++)
			System.out.print(a[i]+"\t");
		System.out.println();
		System.out.println("分治方法排序结果如下:");
		int[] s=mergeSort(a);
		for (int i = 0; i < s.length; i++)
			System.out.print(s[i]+"\t");
	}

	//使用非递归方法
	public static int[] mergeSort(int a[])
	{
		int b[]=new int[a.length];
		int s=1;
		/*
		 * 将数组a分为a.length个独立的数组(每个元素都是一个数组),
		 * 然后(1,2),(3,4)....(a.length-1,a.length)进行排序,即可得到a.lenthg/2个数组(已经排好序)放到b中
		 * 然后在对b进行排序(以每两个元素为一组,然后继续进行排序,此时数组的分组继续减半,重新放到a中去,最后要返回a数组
		 */
		while (s<a.length) {
			mergePass(a,b,s);
			s+=s;
			mergePass(b, a, s);
			s+=s;
		}
		return a;
	}

	private static void mergePass(int[] a, int[] b, int s) {
		// TODO Auto-generated method stub
		//合并大小为s的相邻子数组
		int i=0;
		while(i<=a.length-2*s){
			//合并大小为s的相邻的两个段子数组
			merge(a, b, i, i+s-1, i+s*2-1);
			i=i+2*s;
		}
		//剩余元素少于两个
		if (i+s<a.length) {
			//若最后只剩下n-1个元素和1个元素进行合并排序
			merge(a, b, i, i+s-1, a.length-1);
		}
		else {
			//因为在前面的while中都是以2的幂次方进行分组的,若a为奇数,则剩余部分需要加上
			//复制到y
			for (int j = i; j < a.length; j++) {
				b[j]=a[j];
			}
		}
	}

	private static void merge(int[] x, int[] y, int i, int j, int k) {
		// TODO Auto-generated method stub
		//合并c[i:j]和c[j+1:k]
		int t=i,h=i,u=j+1;
		while ( (t <= j) && (u<=k) ) {
			if (x[t]<=x[u]) {
				y[h++]=x[t++];
			}else {
				y[h++]=x[u++];
			}
		}

		//若果两个c[i:j]和c[j+1:k]还有元素没有被比较,
		if (t>j)
			//t>j 说明前面i个数已经加到了y中,而后面的(j-k)还未全部加到y中
			for (int l = u; l <= k; l++) {
				y[h++]=x[l];
			}
		else {
			//如果后面的(j-k)已经加到了y中,而前面的(i-j)还未加到y中
			for(int l=t;l<=j;l++)
				y[h++]=x[l];
		}

	}

}

  结果如图:


快速排序:

  其基本思想是:在一趟排序中将要排序的数据分割成独立的两部分,其中一部分序列的关键字(这里主要用值来表示)均比另一部分关键字小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。算法如下。

package com.gqx.arithmetic.Recursion;

public class QuickSort {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int a[]={2,45,66,8,22,67,90,33,1};
		new QuickSort().qSort(a, 0, a.length-1);
		for (int i = 0; i < a.length; i++) {
			System.out.print(a[i]+"\t");
		}
	}

	private void qSort(int a[],int p,int r) {
		if (p<r) {
			int q=partition(a,p,r);
			qSort(a, p, q-1);
			qSort(a, q+1, r);
		}
	}

	//对a[p:r]进行划分,以元素x=a[p]作为划分的基准,分别从左右两端开始,扩展两个区域a[p:i]和a[j:r];
	//使得a[p:i]中元素小于或等于x;而a[j:r]中元素大于等于x
	//初始时,i=p,且j=r+1
	private int partition(int a[], int p, int r) {
		// TODO Auto-generated method stub
		int i=p,j=r+1;
		int x=a[p];
		//将<x的元素交换到左边区域
		//将>x的元素交换到右边区域
		while (true) {
			while (a[++i]<x && i<r);
			while(a[--j]>x);
			if (i>=j)
				break;
			swap(a,i,j);
		}
		a[p]=a[j];
		a[j]=x;
		return j;
	}

	private void swap(int[] a, int i, int j) {
		// TODO Auto-generated method stub
		int temp=a[j];
		a[j]=a[i];
		a[i]=temp;
	}

}

  输出如图:


循环赛问题:

问题描述:

设有n=2k个选手要进行网球循环赛,要求设计一个满足以下要求的比赛日程表:

(1)每个选手必须与其他n-1个选手各赛一次;
(2)每个选手一天只能赛一次。

按此要求,可将比赛日程表设计成一个 n 行n-1列的二维表,其中,第 i 行第 j 列表示和第 i 个选手在第 j 天比赛的选手。

 问题分析

  实际上就是将问题的在一个方阵的二位数组中实现值得关于中心点赋值的过程,如图所示,当只有两个选手的时候:

当有三个选手的时候:

同理:当有四个选手的时候,如图:

就由此得到规律。其算法程序如下:

package com.gqx.arithmetic.Recursion;

public class LoopRace {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int x[][]=table(3);
		for (int i = 1; i < x.length; i++) {
			for (int j = 1; j < x[i].length; j++) {
				System.out.print(x[i][j]+"\t");
			}
			System.out.println();
		}
	}

	public static int[][] table(int k){
		int n=1;
		for (int i = 1; i <= k ; i++)
			n*=2;		//初始化选手人数
		int  a[][]=new int[n+1][n+1];
		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;
		}

		return a;
	}

}

输出如图:

时间: 2024-10-11 18:04:40

递归与分治-合并排序、快速排序以及循环赛问题的相关文章

分治——合并排序

分治策略中有一个经典的算法就是合并排序,这个算法的精髓也是分治二字,分而治之.将一个大规模的问题分割成若干个同样的小问题,小问题的规模很小,很容易解决,解决了小的问题后再对这些小问题的结果进行合并得到大规模问题的解答. 合并排序便是分治策略中比较经典的算法,首先是合并,两个排列有序的数列经过合并后成为有序的数组:代码如下: void _merge(int *A,int left,int middle,int right) { int i=left,j=middle+1,k=0; int *C;

3种sort:insertion_sort,merge_sort,quick_sort 插入排序 合并排序 快速排序

插入排序,普通排序 一般 前端够用,样本容量小于1000,根本看不出性能问题 function insertion_sort(arr){ var len=arr.length; for(var j=1;j<len;j++){ var key=arr[j]; var i=j-1; while(i>=0&&arr[i]>key){ arr[i+1]=arr[i]; i=i-1; } arr[i+1]=key; } } 合并排序 merge_sort function _mer

Python 归并排序(递归、非递归、自然合并排序)

归并排序仍然是利用完全二叉树实现,它是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用.将已有序的子序列合并,得到完全有序的序列. 基本过程:假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2个长度为2或1的有序子序列,再两两归并,最终得到一个长度为n的有序序列为止,这称为2路归并排序. 归并排序是典型的分治算法,它不断地将某个数组分为两个部分,分别对左子数组与右子数组进行排序

算法重拾之路——合并排序

***************************************转载请注明出处:http://blog.csdn.net/lttree******************************************** 隶属于递归与分治 合并排序 问题描述:实现对n个元素的排序. 算法描述:不断将待排序的元素分成大小大致相同的两个子集合各自排序,最终将排序好的两个子集合合并.集合不断递归分下去,直至集合元素个数为1. 算法程序: <span style="font-fam

分治法-合并排序和快速排序

分治法是按照以下方案工作的: 将问题的实例划分为同一个问题的几个较小的实例,最好拥有同样的规模 对这些较小的实例求解(一般使用递归方法,但在问题规模足够小的时候,有时会利用另一种算法以提高效率) 如果必要的话,合并较小问题的解,以得到原始问题的解 分治法的流程: 4.1 合并排序 合并排序是成功应用分治技术的一个完美例子(书上说的). 对于一个需要排序的数组,合并排序把它一分为二,并对每个子数组递归排序,然后把这两个排好序的子数组合并为一个有序数组. 代码实现: /** * 合并排序 * @au

快速排序(递归与分治的思想)

快排具有递归和分治的思想,实现步骤如下. 第一步:对数组A中的第一个元素x执行操作,使得数组变成新的数组B,B中C段表示小于x的元素,D段表示大于x的元素 第二步:把C段,D段,当成2个独立的数组,然后对这2个数组执行类似于第一步中A的操作 第三步:这样B和D数组又同样被分成了三部分,依次类推反复执行相同的操作. 代码: #include<iostream> using namespace std; void swap(int &a,int &b) { int c; c=a;

使用合并排序和快速排序对字符串按长度排序

前段时间要对字符串集合进行按长度排序,字符串长度长的排在队列前面,最短的排在最后,可以使用两种排序方法进行排序,其中快速排序的效能会好些,但快速排序在字符串的集合非常大的时候,有时会得不到正确的结果,具体原因还不清楚. 1.合拼排序的代码 using System; using System.Collections.Generic; using System.Text; namespace FtpproxyDownRule.DBUtility { public class sortbyStrin

合并排序和快速排序

/* 合并排序 O(n*lgn) */ #include <iostream> using namespace std; #define MAXN 100 int a[MAXN]; void Merge(int a[MAXN],int left,int mid,int right) { int i,j,k; int n1=mid-left+1; int n2=right-mid; int L[MAXN],R[MAXN]; for(i=1;i<=n1;i++) L[i]=a[left+i-

算法有插入排序,堆排序,合并排序,快速排序和stooge排序

比较的算法有插入排序,堆排序,合并排序,快速排序和stooge排序, 先说一下比较结果 1,比较插入和stooge排序,stooge的表现如此之差,数组大小在2000时 InsertSort VS StoogeSort 's Running Time:     16ms:47672ms; Terribly! Isn't It? 所以在后面的比较中,没有带stooge这个垃圾算法 2,插入排序,堆排序,合并排序,快速排序运行时间对比 (网易博客的表格功能太差了,不爽,只好以文本对齐展现给大家了):