算法-学习笔记
1 排序
1.1 插入排序
- 插入排序代码(Java):
/**
* <b> 插入排序
* <br/> 从无序到有序,将无序组的数据 依次插入倒有序组
* @param array 输入数据
* @return int
*/
public static void insertionSort(int[] array){
for (int i = 1; i < array.length; i++) {
int key = array[i], j=i-1;
while(j >= 0 && array[j] > key){
array[j+1] = array[j];
j--;
}
array[j+1]=key;
}
}
- 测试代码(需导入junit包)
public static int[] produceArray(int num){ //工具方法,用于生产num个随机数初始化的数组
Random random = new Random();
int[] a= new int[num];
for (int i = 0; i < num; i++) {
a[i] = random.nextInt(100);
}
return a;
}
@Test
public void insertionSort(){
int []a = Sort.produceArray(10000);
System.out.println(Arrays.toString(a));
long beginTime = System.nanoTime(); //计时,单位纳秒
com.algorithm.sort.Sort.insertionSort(a);
long overTime = System.nanoTime();
System.out.println(Arrays.toString(a));
System.out.println("耗时:"+(overTime-beginTime)/1000000.0+"ms"); //耗时结果
}
1.2 归并排序
- 归并排序递归实现
/**
* <b> 归并排序-递归实现
* <br/>采用分治法(分而治之)
* @param array 待排序数组
* @param left 需排序的 左下标
* @param right 需排序的 右下标
* @return int[] 排序后的数组
*/
public static void mergeSort(int[] array, int left, int right){
if(left<right){
// int middle = (int) Math.round((left+right)/2.0);
int middle = (left+right)/2;
mergeSort(array,left,middle);
mergeSort(array,middle+1,right);
merge(array,left,middle,right);
}
}
/**
* <b> 两堆有序数列合并
* @param array 原数组
* @param left 左下标
* @param middle 中下标
* @param right 右下标
*
*/
public static void merge(int[] array,int left, int middle, int right){
int n1 = middle-left+1; //左边数组个数
int n2 = right-middle-1+1; //右边数组个数,±1可省略,这里为了便于观察,添加上了
int[] arrayLeft = new int[n1+1]; //多添加一个,用于作为堆底
int[] arrayRight = new int[n2+1];
for (int i = 0; i < n1; i++) { //初始化两个有序数组
arrayLeft[i] = array[left+i];
}
for (int i = 0; i < n2; i++) {
arrayRight[i] = array[middle+i +1];
}
arrayLeft[n1] = Integer.MAX_VALUE; //临界量赋值,用作堆底,也可称其为哨兵
arrayRight[n2] = Integer.MAX_VALUE;
int pl=0,pr=0; //左右数组初始指针
for (int i = left; i <= right ; i++) { //从已排好序的两数组中,抽取一个较小值 放到原数组中
if(arrayLeft[pl] < arrayRight[pr]){
array[i] =arrayLeft[pl];
pl++;
}else{
array[i] = arrayRight[pr];
pr++;
}
}
}
- 测试代码(需junit包)
@Test
public void mergeSort(){
int size = 100000; //数据量大小
int []a = Sort.produceArray(size); //与上一测试代码相同的工具方法
System.out.println(Arrays.toString(a));
long beginTime = System.nanoTime();
com.algorithm.sort.Sort.mergeSort(a,0,size-1);
long overTime = System.nanoTime();
System.out.println(Arrays.toString(a));
System.out.println("耗时:"+(overTime-beginTime)/1000000.0+"ms");
}
1.3 算法时间对比
- 插入排序与归并排序所用时间
- 数据量越大两者的效率差距越大
2 分治策略
2.1 求最大数组和
给定一个有正有负的一个整数序列,求其一个子数组,满足数组之和为最大。
- 代码
/**
* <p>分治策略
* <br/>
* 解决方法分可为三个步骤:<br/>
* 1.分解(divide):将问题划分为一些子问题,问题形式与原问题一样,只是规模更小<br/>
* 2.解决(conquer):递归求解出子问题,如果子问题足够小,则停止递归,直接求解(这类问题主要就是找这个突破口)<br/>
* 3.合并(combine):将子问题的解组合成原问题的解。
* @author gu-ppy
* @Package com.algorithm.divide
* @Description: TODO
* @date 2020/7/2 16:45
*/
public class DivideAndConquer {
//集成
public static int[] findMaxArraySum(int[]data){
System.out.println("");
return findMaxSum(data,0,data.length-1);
}
/**
* <b> 查找最大子数组和 问题
* <br/> 总体划分为三个子问题
* @param data 原数据
* @param left 找到的最大数组和的左下标
* @param right 找到的最大数组和的右下标
* @return int[] 共3位数据 1:左下标 2:右下标 3:最大数组和
*/
public static int[] findMaxSum(int[] data, int left, int right){
if(left==right){
return new int[]{left, right, data[left]};
}else{
int mid = (right+left)/2;
int [] leftArray,crossArray,rightArray;
leftArray = findMaxSum(data,left,mid); //递归查找在中点左边部分的最大数组和
rightArray = findMaxSum(data, mid+1, right); //递归查找在中点右边部分的最大数组和
crossArray = findMaxSumCrossing(data,left,mid,right); //查找穿过中点的最大数组和
if (leftArray[2]>rightArray[2] && leftArray[2]>crossArray[2])
return leftArray;
else if (rightArray[2]>leftArray[2] && rightArray[2]>crossArray[2])
return rightArray;
else return crossArray;
}
}
/**
* <b> 查找最大数组和的子模块<br/>
* What:查找穿过中点的最大子数组和<br/>
* Why:由于其之和为最大,那么左边与右边同之和为相应部分的最大
*
* @param data 元数据
* @param left 左下标
* @param mid 中点下标
* @param right 右下标
* @return int[] 共3位数据 1:左下标 2:右下标 3:最大数组和
*/
public static int[] findMaxSumCrossing(int[] data,int left, int mid, int right){
int leftSum=Integer.MIN_VALUE,rightSum=Integer.MIN_VALUE,sum=0; //中间变量,和 计算
int maxLeft = 0,maxRight = 0; //用于接收最大数组的左右两边界下标
for (int i=mid;i>=left;i--){ //遍历左侧数组
sum+=data[i];
if(sum>leftSum){
leftSum=sum;
maxLeft = i;
}
}
sum=0;
for (int j=mid+1;j<=right;j++){ //遍历右侧数组
sum+=data[j];
if(sum>rightSum){
rightSum=sum;
maxRight = j;
}
}
return new int[]{maxLeft,maxRight,rightSum+leftSum};
}
}
- 测试代码(junit包)
@Test
public void findMaxArraySum(){
int[] data ={13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7}; //16个数据
int[]result = com.algorithm.divideDivideAndConquer.findMaxArraySum(data);
System.out.println(Arrays.toString(result));
}
2.2 递归练习
整数划分:
- n=m1+m2+…+mi; (其中mi为正整数,并且1 <= mi <= n),则{m1,m2,…,mi}为n的一个划分。
- 如果{m1,m2,…,mi}中的最大值不超过m,即max(m1,m2,…,mi)<=m,则称它属于n的一个m划分。这里我们记n的m划分的个数为f(n,m);
- 编写程序,输入整数n,m,返回n的所有m的划分个数。
public static int integerDivide(int n, int m){
if (n<1 || m < 1) //不能为负数
return 0;
if (n==1||m==1) //为1时只有一种
return 1;
if (n<=m) //m大于n的部分无意义,而n=m就为一种划分
return 1+integerDivide(n,n-1);
return integerDivide(n-m,m)+integerDivide(n,m-1); //两部分,一部分是包含m,一部分不包含m
}
- 测试代码
@Test
public void integerDivide(){
System.out.println(DivideAndConquer.integerDivide(10,10 ));
}