最大子序列
最大子序列是要找出由數組成的一維數組中和最大的連續子序列。比如{5,-3,4,2}的最大子序列就是 {5,-3,4,2},它的和是8,達到最大;而 {5,-6,4,2}的最大子序列是{4,2},它的和是6。你已經看出來了,找最大子序列的方法很簡單,只要前i項的和還沒有小於0那麼子序列就一直向後擴充,否則丟棄之前的子序列開始新的子序列,同時我們要記下各個子序列的和,最後找到和最大的子序列。
#include<iostream>using namespace std;int MaxSubSeq(const int *arr,int len,int *start,int *end){ int max=0; //記錄目前找到的最大子序列的和 int sum=0; //記錄當前子序列的和 int begin=0,finish=0; //記錄當前子序列的起始下標 *start=begin;*end=finish; //記錄最長子序列的起始下標 for(int i=0;i<len;i++){ sum+=arr[i]; finish=i; if(sum>max){ max=sum; *end=finish; *start=begin; } if(sum<=0){ sum=0; begin=i+1; } } return max;}int main(){ int arr[6]={5,-3,-2,12,9,-1}; int start,end; int max=MaxSubSeq(arr,6,&start,&end); cout<<"The MaxSubSeq is from position "<<start<<"to position "<<end<<"."<<endl; cout<<"Sum of MaSubSeq: "<<max<<endl; return 0;}
最長公用子串(LCS)
找 兩個字串的最長公用子串,這個子串要求在原字串中是連續的。其實這又是一個序貫決策問題,可以用動態規劃來求解。我們採用一個二維矩陣來記錄中間的結 果。這個二維矩陣怎麼構造呢?直接舉個例子吧:"bab"和"caba"(當然我們現在一眼就可以看出來最長公用子串是"ba"或"ab")
b a b
c 0 0 0
a 0 1 0
b 1 0 1
a 0 1 0
我們看矩陣的斜對角線最長的那個就能找出最長公用子串。
不過在二維矩陣上找最長的由1組成的斜對角線也是件麻煩費時的事,下面改進:當要在矩陣是填1時讓它等於其左上方元素加1。
b a b
c 0 0 0
a 0 1 0
b 1 0 2
a 0 2 0
這樣矩陣中的最大元素就是 最長公用子串的長度。
在構造這個二維矩陣的過程中由於得出矩陣的某一行後其上一行就沒用了,所以實際上在程式中可以用一維數組來代替這個矩陣。
#include<iostream>#include<cstring>#include<vector>using namespace std;//str1為橫向,str2這縱向const string LCS(const string& str1,const string& str2){ int xlen=str1.size(); //橫向長度 vector<int> tmp(xlen); //儲存矩陣的上一行 vector<int> arr(tmp); //當前行 int ylen=str2.size(); //縱向長度 int maxele=0; //矩陣元素中的最大值 int pos=0; //矩陣元素最大值出現在第幾列 for(int i=0;i<ylen;i++){ string s=str2.substr(i,1); arr.assign(xlen,0); //數組清0 for(int j=0;j<xlen;j++){ if(str1.compare(j,1,s)==0){ if(j==0) arr[j]=1; else arr[j]=tmp[j-1]+1; if(arr[j]>maxele){ maxele=arr[j]; pos=j; } } }// {// vector<int>::iterator iter=arr.begin();// while(iter!=arr.end())// cout<<*iter++;// cout<<endl;// } tmp.assign(arr.begin(),arr.end()); } string res=str1.substr(pos-maxele+1,maxele); return res;}int main(){ string str1("21232523311324"); string str2("312123223445"); string lcs=LCS(str1,str2); cout<<lcs<<endl; return 0;}
最長公用子序列
最長公用子序列與最長公用子串的區別在於最長公用子序列不要求在原字串中是連續的,比如ADE和ABCDE的最長公用子序列是ADE。
我們用動態規劃的方法來思考這個問題如是求解。首先要找到狀態轉移方程:
等號約定,C1是S1的最右側字元,C2是S2的最右側字元,S1‘是從S1中去除C1的部分,S2'是從S2中去除C2的部分。
LCS(S1,S2)等於下列3項的最大者:
(1)LCS(S1,S2’)
(2)LCS(S1’,S2)
(3)LCS(S1’,S2’)--如果C1不等於C2; LCS(S1',S2')+C1--如果C1等於C2;
邊界終止條件:如果S1和S2都是空串,則結果也是空串。
下面我們同樣要構建一個矩陣來儲存動態規划過程中子問題的解。這個矩陣中的每個數字代表了該行和該列之前的LCS的長度。與上面剛剛分析出的狀態轉移議程相對應,矩陣中每個格子裡的數字應該這麼填,它等於以下3項的最大值:
(1)上面一個格子裡的數字
(2)左邊一個格子裡的數字
(3)左上方那個格子裡的數字(如果 C1不等於C2); 左上方那個格子裡的數字+1( 如果C1等於C2)
#include<iostream> #include<cstring> #include<stack> #include<utility> #define LEFTUP 0 #define LEFT 1 #define UP 2 using namespace std; int Max(int a,int b,int c,int *max){ //找最大者時a的優先順序別最高,c的最低.最大值儲存在*max中 int res=0; //res記錄來自於哪個儲存格 *max=a; if(b>*max){ *max=b; res=1; } if(c>*max){ *max=c; res=2; } return res; } //調用此函數時請注意把較長的字串賦給str1,這主要是為了在回溯最長子序列時節省時間。如果沒有把較長的字串賦給str1不影響程式的正確執行。 string LCS(const string &str1,const string &str2){ int xlen=str1.size(); //橫向長度 int ylen=str2.size(); //縱向長度 if(xlen==0||ylen==0) //str1和str2中只要有一個為空白,則返回空 return ""; pair<int,int> arr[ylen+1][xlen+1]; //構造pair二維數組,first記錄資料,second記錄來源 for(int i=0;i<=xlen;i++) //首行清0 arr[0][i].first=0; for(int j=0;j<=ylen;j++) //首列清0 arr[j][0].first=0; for(int i=1;i<=ylen;i++){ char s=str2.at(i-1); for(int j=1;j<=xlen;j++){ int leftup=arr[i-1][j-1].first; int left=arr[i][j-1].first; int up=arr[i-1][j].first; if(str1.at(j-1)==s) //C1==C2 leftup++; int max; arr[i][j].second=Max(leftup,left,up,&arr[i][j].first); // cout<<arr[i][j].first<<arr[i][j].second<<" "; } // cout<<endl; } /*矩陣構造完畢*/ //回溯找出最長公用子序列 stack<int> st; int i=ylen,j=xlen; while(!(i==0&&j==0)){ if(arr[i][j].second==LEFTUP){ if(arr[i][j].first==arr[i-1][j-1].first+1) st.push(i); --i; --j; } else if(arr[i][j].second==LEFT){ --j; } else if(arr[i][j].second==UP){ --i; } } string res=""; while(!st.empty()){ int index=st.top()-1; res.append(str2.substr(index,1)); st.pop(); } return res; } int main(){ string str1="GCCCTAGCG"; string str2="GCGCAATG"; string lcs=LCS(str1,str2); cout<<lcs<<endl; return 0; }
精確字串匹配BM演算法
所謂精確字串匹配問題,是在文本 T 中找到所有與查詢 P 精確匹配的子串。而 BM 演算法可以非常有效地解決這個問題,讓時間複雜度降到低於線形的水平。
BM 演算法主要用了三種巧妙而有效方法,即從右至左掃描,壞字元規則和好尾碼規則。
從右至左掃描的意思是從最後一個字元開始向前匹配,而不是習慣上的從開頭向後匹配。
壞字元規則是,從右至左的掃描過程中,發現 Ti 與 Pj 不同,如果P 中存在一個字元 Pk 與 Ti 相同,且 k<i 那麼就將直接將 P 向右移使 Pk 與 Ti 對齊,然後再從右至左進行匹配。如果 P 中不存在任何與 Ti 相同的字元,則直接將P 的第一個字元與 Ti 的下一個字元對齊,再從右至左進行比較。
T: a b c b a d f t a t e
P: c b a x a d
P: c b a x a d
用 R(x) 表示字元 x 在 P 中出現的最右位置,此例中 R(b)=2。
可以看出使用從右至左掃描和壞字元規則可以跳過 T 中的很多位置不去檢查,從而使時間複雜度低於線性。
好尾碼規則是,從右至左的掃描過程中,發現 Ti 與 Pj 不同,檢查一下相同的部分 t 是否在 P 中的其他位置 t'出現,a) 如果 t 與 t' 的前一個字母不相同,就將 P 向右移,使 t' 與 T 中的 t 對齊。b) 如果 t' 沒有出現,則找到與 t 的尾碼相同的 P 的最長首碼 x,向右移動P ,使 x 與 T 中 t 的尾碼相對應。
a):
N: 1
N: 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8
T: a b c b a d f t b c f a q v t b c e...
P: c b c a b c e a b c
P: c b c a b c e a b c f
可見,並不是將 P 向右移讓 P5 與 T9 對齊,而是讓 P2 與 T9 對齊,因為 P1 與 P8 不相同。用 L(i) 表示 t' 的最大位置,此例中, L(9)= 3。
b):
N: 1
N:
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8
T: a b c b a d f t b c f a q v t b c e...
P: b c c a b c e t b c
P: b c c a b c e t b c
可見,當 P 向左找不到 “tbc”時,就找到 “tbc”的最長與 P 的首碼匹配的尾碼,並將 P 向右移。用 l(i) 表示這個最長尾碼的長度,這個例子中 i=8。
[預先處理] 輸入查詢字串 P, 計算 P 中每個位置的 L(i) 和 l(i),並計算 R(i)。[查詢] k:=n; // n 是 T 中字元的總數 while k<=m do begin i :=n; // i 表示 P 中字元的位置 h :=k; // h 表示 T 中字元的位置 while i>0 and P(i)=T(i) do begin i:=i-1; h:=h-1; end; if i=0 then begin 輸出 T 的這個位置上的字串; k:= k+n-l(2); end else 移動 P(增加 k),k 取 好尾碼規則和壞字元規則決定的最大值 end;
字串拷貝函數
#include "stdafx.h"using namespace std;/* * 說明:字串拷貝 * 參數:dest目標地址,src源地址 * 返回:返回拷貝好的地址;如果出錯或者有重疊,無定義 * 異常:可能出現字串溢出,及dest所佔空間不如src所佔空間大。 */char *strcpy(char *dest , const char *src){ //調試時,使用斷言,入口檢測 assert( (dest!=NULL) && (src!=NULL) ); //注意這裡的記憶體指向參數dest所在的記憶體,不是棧記憶體,因而可以在函數中返回 char *to = dest; //主要操作在while條件中完成 while( (*dest++ = *src++)!='\0'); //返回拷貝字串首地址,方便連綴,比如strlen(strcpy(dest,"hello")) return -1;}
字串的倒序輸出
常規方法
#include <stdio.h>#include <string.h>void StringReverse(char *str){if(NULL == str){return ;}char *pBegin = str;char *pEnd = str + strlen(str) - 1;while(pBegin < pEnd){char tmp = *pBegin;*pBegin = *pEnd;*pEnd = tmp;pBegin ++, pEnd --;}}int main(){char str[] = "123456789";StringReverse(str);printf("%s\n",str);}鏈表形式
void PrintListReversely(ListNode* pListHead) { if(pListHead != NULL) { // Print the next node first if (pListHead->m_pNext != NULL) { PrintListReversely(pListHead->m_pNext); } // Print this node printf("%d", pListHead->m_nKey); } }