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

       所謂子序列就是指一個序列,在以它爲基礎的情況截取的一定長度的數構成的新序列,注序列中的數是不能打亂的,子序列是母序列的局部。



/**
 * 
 * @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);
}

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章