算法之-------求最大子序列和以及快排代码解析

       所谓子序列就是指一个序列,在以它为基础的情况截取的一定长度的数构成的新序列,注序列中的数是不能打乱的,子序列是母序列的局部。



/**
 * 
 * @author sosozha

 *在学习求最大子序列和的时候自己也能写出前两种方法,第三种方法需要费点心思去想,然后引出其与快排类似的用法

        而后引出快排代码的原理解释

第四种方法最简单快捷,却不容易想出来

 */
public class MaxSubSequence {


public static void main(String[] args) {
int[] a = {4,-3,5,-2,-1,2,6,-2};
long first = System.currentTimeMillis();
System.out.println(maxSumRec(a, 0, a.length-1));
long second = System.currentTimeMillis();
System.out.println(maxSumRec(a));
long third = System.currentTimeMillis();
System.out.println(maxSumRec1(a));
long fourth = System.currentTimeMillis();

//数组太小,看不出有什么不同,如果数组很大就会不一样了
System.out.println(second-first);
System.out.println(third-second);
System.out.println(fourth-third);

int[] aaa = {3,1,7,5,9,18,6};
QuickSort(aaa,0,6);

System.out.println(aaa[0]);
System.out.println(aaa[1]);
System.out.println(aaa[2]);
System.out.println(aaa[3]);
System.out.println(aaa[4]);
System.out.println(aaa[5]);
System.out.println(aaa[6]);
}

/**
* 计算所有子序列中总和最大的子序列的和是多少---不能改变数组顺序
* @param a
* @return
*/
private static int maxSumRec(int[] a) {
/**
* 方法一:循环,让所有的相邻元素拼成一个子序列,计算这个序列的总和,并比较所有的总和,找出最大的那个
*/
int maxSum = 0;
//第一个循环将整个数组循环
for(int i=0;i<a.length;i++) {
//第二个循环,设置一个变量用于拼接子序列,从i-j的组合
for(int j = i;j<a.length ;j++){
int thisSum = 0;
//第三个循环,计算拼接出来的子序列中的和
for(int k=i;k<=j;k++){
thisSum += a[k];
}
//比较当前序列总和与已设总大总和的值
if(thisSum > maxSum ){
maxSum = thisSum;
}
}
}
return maxSum;
}

private static int maxSumRec1(int[] a) {
/**
* 方法二:仔细看第一个方法,会发现其中三个循环会有重复的计算,这里对其进行优化
* 重复处:在于计算过程中,你会发现,在计算i-k的和时,会把各个阶段的子序列都顺带的计算了一遍,
* 比如i-k的求和过程中  thisSum += a[j-1]的值就和=i-j-1的序列总和一样的,所以这里有重复计算
* 为了减少这些不必要的计算损耗,对其 优化
* 突破点在于:求和的过程中能够计算出各个序列的和,这些和可以拿来利用,尝试省一个for循环
*/
int maxSum = 0;
for(int i=0 ;i<a.length; i++) {
int thisSum = 0;
for(int j=i;j<a.length;j++){
//这里就需要合理利用这些总和,每一个都相当于一个i-j的序列
thisSum += a[j];
//比较
if(thisSum > maxSum ) {
maxSum = thisSum;
}
}
}
return maxSum;
}
/**
* 和快排相似的算法
* @param a
* @param left
* @param right
* @return
*/
private static int maxSumRec(int[] a,int left,int right) {
//递归的基准出口,如果左边界=右边界,说明这是同一个元素了,并且不可能出现left>right,否则就是你传错值

//只有一个元素的时候就校对 这个数是正是否,正则要,负则不要

                if(left > right ) {

return 0; 
}
if(left == right){
if(a[left]>0){
return a[left];
}else{
return 0;
}
}
//将数组从中间拆分
int center = (left + right)/2;
//递归调用,分别得出左右子数组中的子序列的最大值
int maxLeftSum = maxSumRec(a, left, center);
int maxRightSum = maxSumRec(a, center+1, right);
/**
* 下面这两段
* 因为子序列总和最大的序列无非三种情况,1:全在左边,2全在右边,3,在中间
* 上面已经算出全左或全右的情况,下标记段是用于计算中间的

* 以上面{4,-3,5,-2,-1,2,6,-2}数组为例
* 分析到最后一次调用:左边返回4 (4,-3),右边返回5(5,-2),中间计算为1+5,所以最后返回6,----这个是左边的4个值的结果,即上层的左边最大
* 左边返回2 (-1,2),右边返回6(6,-2),中间计算为2+6,所以最后返回8,----这个是左边的4个值的结果,即上层的左边最大
* 回到上一层即得,左边得6,右边得8,再计算中间序列4+7 = 11 ,所以最后返回的是11
*/
//====================================================
int maxLeftBorderSum = 0 ;
int leftBorderSum = 0;
//包含左边数组以右边位置为边界的子序列,
for(int i = center ; i >= left ; i --) {
leftBorderSum += a[i];
if(leftBorderSum > maxLeftBorderSum) {
maxLeftBorderSum = leftBorderSum;
}
}
//包含右边数组以左边为边界的子序列
int maxRightBorderSum = 0;
int rightBorderSum = 0;
for(int i = center+1 ; i <= right ; i ++ ) {
rightBorderSum += a[i];
if(rightBorderSum > maxRightBorderSum) {
maxRightBorderSum = rightBorderSum;
}
}
//====================================================
//拼接这两个子序列就是第三种子序列
//剩下的就是比较这三种序列了
int max1 = maxLeftSum > maxRightSum ? maxLeftSum : maxRightSum;
int max = max1 > maxLeftBorderSum + maxRightBorderSum ? max1 : maxLeftBorderSum + maxRightBorderSum;
return max;
}


/**
* 方法4的原理就在于:以一个数为始散发拼接子序列
* 如果它的和大于当前maxSum,那么替换,
* 如果它的和小于0,那么说明这段子序列没有拼接的必要了,重新拼接
* 这样,一个完整的序列就被拆分成一个大小不一样的且和为正的子序列
* 最后maxSum持有的就是这些子序列中和最大的那个值
* @param a
* @return
*/
private static int maxSumRec2(int[] a) {

int maxSum = 0;
int thisSum = 0;
for(int i = 0; i<a.length; i++ ){
thisSum += a[i];
if(thisSum > maxSum ){
maxSum = thisSum;
}else if(thisSum < 0) {
thisSum = 0;
}
}
return maxSum;
}

/**
* 快排
* 快排的思路:
* 将数组以最左边的数为基准,从左往右和从右往左比较
* 以左的索引要比右小为前提
* 从右往左j:当出现比基准数小的数时停止 ----- 
* 从左往右i:当出现比基准数大的数时停止 ----- 
* 交换这两个索引的值 ----- 使大的值与小的值互换位置
* 如果j正常查出数据,而i没有查出数据,i会一直++直到=j;  这个时候的j索引数比基准数小
* 将left的数与j的数互换,那么小的数就前移了,那么递归的时候相当于只有除了0号索引外其它数的递归调用。
* 如果j和i都在设定的范围查出了数据,那么i和j的数据互换,虽然没有完成将最小的数放到更前面去
* 但是后续的递归调用中将这些所有的数据都按照这个情况排列下去。    
* 如果j没有查出数据,说明left的值本身就是最小的值,那么递归的时候相当于只有除了0号索引外其它数的递归调用。
*/
public static void QuickSort(int[] a, int left, int right) {
// 如果left等于right,即数组只有一个元素,直接返回
if (left >= right) {
return;
}
// 设置最左边的元素为基准值
int key = a[left];
// 数组中比key小的放在左边,比key大的放在右边,key值下标为i
int i = left; //这里设置变量i,j就是为了不改变left,right的值
int j = right;
while (i < j) {
// j向左移,直到遇到比key小的值
while (a[j] >= key && i < j) {
j--;
}
// i向右移,直到遇到比key大的值,或者i一直++到j一样大,时a[j] 和a[left]互换,就将最小的数换到最左边了
while (a[i] <= key && i < j) {
i++;
}
// i和j指向的元素交换
if (i < j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
a[left] = a[i];
a[i] = key;
//将两截序列继续内调用排序,一直将小于基于数的往前排,大的往后排
QuickSort(a, left, i - 1);
QuickSort(a, i + 1, right);
}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值