1. 程式人生 > >一維、二維陣列尋找最大子數列-Kadane演算法

一維、二維陣列尋找最大子數列-Kadane演算法

一維陣列求最大子序列

參考部落格 問題: 給定一個數列,例如【−2, 1, −3, 4, −1, 2, 1, −5, 4】, 求一個連續的數列使得數列內的元素和最大, 示例中最大子數列應該是【4, −1, 2, 1】, 求和值為6。

這個問題是可以衍生到一些變種問題, 如尋找數列中最大乘積序列,且要求序列中,相鄰元素間隔不超過限定值等, 常出現在筆試面試程式設計題中。

該問題最早於1977年提出,但是直到1984年才被Jay Kadane 發現了線性時間的最優解法,所以演算法雖然長度很短,但其實並不容易理解。

演算法描述:

遍歷該陣列, 在遍歷過程中, 將遍歷到的元素依次累加起來, 當累加結果小於或等於0時, 從下一個元素開始,重新開始累加。 累加過程中, 要用一個變數(max_so_far)記錄所獲得過的最大值,一次遍歷之後, 變數 max_so_far 中儲存的即為最大子片段的和值。

參考的作者用python實現,如果沒學過,我用C++再描述一下。

#include<iostream>
//#include<array> C++11的標準,我的編譯器竟然沒有這個檔案。
//#include<vector>我們也可以使用容器,就不寫複雜了,具體參看https://blog.csdn.net/qq_29611345/article/details/80958664

using namespace std;

int max(int x, int y){return x > y ? x : y;}

int max_subarray(int *arr, int
size){ //題目中有一個隱含的設定, 最大子片段是可以為空的, 空片段的和值是0 int max_ending_here =0; int max_so_far = 0; for(int i = 0; i != size; i++){ max_ending_here = max(0, max_ending_here + arr[i]); max_so_far = max(max_so_far, max_ending_here); } return max_so_far; } int main(){ int nums[] = {1
,2,3,4,-1,-2}; int numsLen = sizeof(nums) / sizeof(nums[0]); cout<<max_subarray(nums, numsLen)<<endl; return 0; } //由上面改進,增加記錄最大值索引下標的功能。 void MaxIntArray(int *arr, int &max_so_far, int &beginIndex, int &endIndex, int length){ int max_ending_here = 0; max_so_far = 0; beginIndex = -1; endIndex = -1; for(int i = 0; i != length; i++){ max_ending_here = max_ending_here + arr[i]; if( max_ending_here < 0){ beginIndex = i+1; max_ending_here = 0; } if(max_so_far < max_ending_here){ endIndex = i; max_so_far = max_ending_here; } } }

二維陣列尋找最大子數列

題目描述:就是一個二維陣列,裡面連通的的數列的最大和,不要求首尾相接,就和貪吃蛇走迷宮差不多,找到最長的貪吃蛇。 參考部落格截圖 這裡寫圖片描述

這裡寫圖片描述

參考部落格說了兩種方法,第一要用到棧或者佇列,索引記錄較為繁瑣,用了第二種降維的方法。 方案二:

1.按行分組,將二維陣列按行分成n個一維陣列。

2.求出每個一維陣列最大子陣列和,並記錄最大子陣列和的首末位置。(一維陣列的最大子陣列和演算法見上)

3.通過首末位置判斷是否連通。如果連通則直接相加,若不連通則需要判斷連通所需代價如何。 (很明顯,這裡需要思考的地方有兩個、第一更改kadane演算法是計算下標、第二、思考如何計算代價,連通上下兩行,很明顯這裡對於第二個問題的思考魯棒性不強,沒有考慮上下不能直接連通的情形。稍後再修改這部分程式碼。)

#include<iostream>
#include<string>
using namespace std;

void MaxIntArray(int *a,int &max,int &begin,int &end,int n);
/*
先將二維陣列按行分成n個一維陣列,求出每個一維陣列最大子陣列和,
並記錄最大子陣列和的首末位置,再通過首末位置判斷是否連通
*/

void main()
{
    int n,m;//n行m列
    cout<<"請輸入二維陣列的行數和列數:"<<endl;
    cin>>n>>m;
    int a[100][100];
    int b[100];
    cout<<"輸入該二維陣列"<<endl;
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
            cin>>a[i][j];

    //分塊
    int Max[100];
    int Begin[100];
    int End[100];
    for(i=0;i<n;i++)
    {
        //按行分組
        for(int j=0;j<m;j++)
        {
           b[j]=a[i][j];
        }
        //b是第i行的陣列,第i行的最大值,第i行的起始,結束下標,陣列長度。
        MaxIntArray(b,Max[i],Begin[i],End[i],m);
    }

    int max=Max[0];
    for( i=0;i<n-1;i++)//i表示行數
    {
        if((Begin[i]<=End[i+1]&&Begin[i]>=Begin[i+1])||(End[i]<=End[i+1]&&End[i]>=Begin[i+1]))
        {

        /*
        上面的意思就是,上一行到首尾兩端,至少要有一端在下一行兩端的內部,
        這樣上面的子序列可以順利過渡到下一行,不用再新增額外的格子。
        */
            max=Max[i+1]+max;
        }
        else
        {
            //如果不能直接連通,判斷代價是否合適
            //上一行最佳子串在下一行的後面
            if(Begin[i]>End[i+1])
            {
                int t = Begin[i]-End[i+1];//兩行間隔
                int s = Begin[i];   //上一行begin下標
                int temp=0;
                for(int k=0;k<t;k++)//計算上一行begin,往下,往左到下一行end的總和。不包括end。
                {
                  temp+=a[i+1][s-k];
                }
                if(temp+Max[i+1]>0)//如果小於0呢,咋辦?這需要更改整個代價計算的函式,,只計算從i到i+1明顯是不夠的。
                    max=temp+Max[i+1];
            }

            //上一行最佳子串在下一行的前面
            if(End[i]<Begin[i+1])
            {
                int t = Begin[i+1]-End[i];
                int s = End[i];
                int temp=0;
                for(int k=0;k<t;k++)
                {
                  temp+=a[i+1][s+k];
                }
                if(temp+Max[i+1]>0)
                    max=temp+Max[i+1];
            }
        }
    }
    cout<<"最大子陣列塊的值為:"<<max<<endl;
}

//計算一維最大子陣列,並返回起始位置的函式
void MaxIntArray(int *arr, int &max_so_far, int &beginIndex, int &endIndex, int length){

    int max_ending_here = 0;
    max_so_far = 0;
    beginIndex = -1;
    endIndex = -1;

    for(int i = 0; i != length; i++){
        max_ending_here = max_ending_here + arr[i];
        if( max_ending_here < 0){
            beginIndex = i+1;
            max_ending_here = 0;
        }

        if(max_so_far < max_ending_here){
            endIndex = i;
            max_so_far = max_ending_here;
        }   
    }

}