一維、二維陣列尋找最大子數列-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;
}
}
}