编程之美-第3章 结构之法
3.1. 字符串移位包含問(wèn)題
方法1:
分別對(duì)字符串進(jìn)行循環(huán)移1位,2位,3位…,來(lái)判斷給定的字符串是否是其中一個(gè)字串.
復(fù)雜度是O(n^3)
方法2:
這也是一種利用空間換時(shí)間的方法.
代碼如下, 為了簡(jiǎn)便實(shí)現(xiàn),采用了C庫(kù)中的字符串操作函數(shù):
#if 0 /** 3.1*/ bool isRotate(char *s1,char* s2){int len1=strlen(s1),len2=strlen(s2);char *ss=new char[2*len1+1];strncpy(ss,s1,len1);strncat(ss,s2,len1);char *p=strstr(ss,s2);bool flag;if(p==0)flag=false;elseflag=true;delete[] ss;return true; } int main(){char *s1="ABCD";char *s2="CDAB";if(isRotate(s1,s2))printf("OK");elseprintf("NO");}#endif也可以采用KMP算法進(jìn)行加快查找: http://blog.csdn.net/linyunzju/article/details/7745553
3.2 電話號(hào)碼對(duì)應(yīng)英語(yǔ)單詞問(wèn)題
這個(gè)問(wèn)題可以直觀的用下面的圖來(lái)表示
這樣對(duì)于一個(gè)號(hào)碼所對(duì)應(yīng)的單詞, 可以使用對(duì)樹(shù)進(jìn)行搜索的方式進(jìn)行.
這種情況下采用的是DFS的方式.
下面的代碼中采用了兩種方式, 遞歸和非遞歸的方式. 其實(shí)這就是一個(gè)組合問(wèn)題, 需要遍歷所有情況. 對(duì)于非遞歸的情況, 其實(shí)有些類似非遞歸的排列組合的方式.
#if 0 char *c[10]{"","","ABC","DEF","GHI","JKL","MNO","PQRS","TUV","WXYZ" }; int total[10]={0,0,3,3,3,3,3, 4,3,4}; void transform(char *number){int sz=strlen(number);int *answer=new int[sz]();while(1){for(int i=0;i<sz;i++)printf("%c",c[number[i]-'0'][answer[i]]);printf("\n");int k=sz-1;while(k>=0){if(answer[k]<total[number[k]-'0']-1){answer[k]++;break;}else{answer[k]=0;k--;}}if(k<0)break;}delete[] answer; } void dfs(char *number,int *answer,int k,int sz){if(k==sz){for(int i=0;i<sz;i++)printf("%c",c[number[i]-'0'][answer[i]]);printf("\n");}else{for(answer[k]=0;answer[k]<total[number[k]-'0'];answer[k]++)dfs(number,answer,k+1,sz);}} void transform2(char *number){int sz=strlen(number);int *answer=new int[sz]();dfs(number,answer,0,sz);delete[] answer; } int main(){char *number="2345";transform(number);transform2(number); }#endif對(duì)于問(wèn)題二,可以采用下面的解法:
3.3 計(jì)算字符串相似度問(wèn)題(編輯距離問(wèn)題)
這個(gè)問(wèn)題就是編輯距離的問(wèn)題.
可以有兩種解決方式: 遞歸的方式和動(dòng)態(tài)規(guī)劃的方式.
方法1: 遞歸的方式
這樣就得到了下面的遞歸程序:
#if 0 /** 3.3*/ int calc_edit_dist(char *s1,char *s2){if(*s1=='\0'||s1==0)return strlen(s2);if(*s2=='\0'||s2==0)return strlen(s1);if(*s1==*s2){return calc_edit_dist(s1+1,s2+1);}else{int ed1=calc_edit_dist(s1,s2+1);int ed2=calc_edit_dist(s1+1,s2);int ed3=calc_edit_dist(s1+1,s2+1);if(ed1<ed2)return (ed1<ed3?ed1:ed3)+1;elsereturn (ed2<ed3?ed2:ed3)+1;} } int main(){char *s1="ab";char *s2="bb";printf("%d\n",calc_edit_dist(s1,s2));}#endif但是遞歸程序計(jì)算比較慢, 因?yàn)橹虚g存在很多的重復(fù)計(jì)算.
\
方法2: 動(dòng)態(tài)規(guī)劃
這個(gè)問(wèn)題其實(shí)可以歸結(jié)為尋找兩個(gè)序列的最長(zhǎng)公共子序列問(wèn)題.
參考: http://blog.csdn.net/linyunzju/article/details/7747718
3.5 最短摘要生成問(wèn)題
給定一段產(chǎn)品的英文描述,包含M個(gè)英文字母,每個(gè)英文單詞以空格分隔,無(wú)其他標(biāo)點(diǎn)符號(hào);再給定N個(gè)英文單詞關(guān)鍵字,請(qǐng)說(shuō)明思路并編程實(shí)現(xiàn)方法String extractSummary(String description,String[] key words),目標(biāo)是找出此產(chǎn)品描述中包含N個(gè)關(guān)鍵字(每個(gè)關(guān)鍵詞至少出現(xiàn)一次)的長(zhǎng)度最短的子串,作為產(chǎn)品簡(jiǎn)介輸出。(不限編程語(yǔ)言)20分。
#if 0 bool allExisted(const map<string,int>& mp){map<string,int>::const_iterator ite;for(ite=mp.begin();ite!=mp.end();++ite)if(ite->second==0)return false;return true; } void reset(map<string,int>& mp){map<string,int>::iterator ite;for(ite=mp.begin();ite!=mp.end();++ite)ite->second=0; } int find_min_len_digest(const vector<string>& vec,const vector<string>& keywords){map<string,int> mp;int len=1000000;vector<string>::const_iterator ite,pbeg,pend;for(ite=keywords.begin();ite!=keywords.end();++ite)mp[*ite]=0;pbeg=vec.begin();while(pbeg!=vec.end()){while(pbeg!=vec.end()&&mp.find(*pbeg)==mp.end())++pbeg;if(pbeg==vec.end())break;mp[*pbeg]++;pend=pbeg+1;while(pend!=vec.end()&&!allExisted(mp)){if(mp.find(*pend)==mp.end()) {++pend;}else{mp[*pend]++;++pend;}}if(allExisted(mp)){if(pend!=vec.end()){if(len>pend-pbeg){len=pend-pbeg;}for(ite=pbeg;ite!=pend;++ite)cout<<*ite<<' ';cout<<'\n';}else{if(len>vec.size()-(pbeg-vec.begin()))len=vec.size()-(pbeg-vec.begin());for(ite=pbeg;ite!=vec.end();++ite)cout<<*ite<<' ';cout<<'\n';break;}reset(mp);}pbeg=pend;}return len; } int main(){ // string str[]={"i","am","a","big","boy",".","i","is","i","boy"}; // string key[]={"i","boy"}; string key[] = { "微軟", "計(jì)算機(jī)", "亞洲"};string str[] = { "微軟","亞洲","研究院","成立","于","1998","年",",","我們","的","使命","是","使","未來(lái)","的","計(jì)算機(jī)","能夠","看","、","聽(tīng)","、","學(xué)",",","能","用","自然語(yǔ)言","與","人類","進(jìn)行","交流","。","在","此","基礎(chǔ)","上",",","微軟","亞洲","研究院","還","將","促進(jìn)","計(jì)算機(jī)","在","亞太","地區(qū)","的","普及",",","改善","亞太","用戶","的","計(jì)算","體驗(yàn)","。","”"};vector<string> keywords(key,key+sizeof(key)/sizeof(string));vector<string> vec(str,str+sizeof(str)/sizeof(string));// copy(vec.begin(),vec.end(),ostream_iterator<string>(cout," ")); // cout<<endl; find_min_len_digest(vec,keywords);}#endif3.6 判斷兩個(gè)鏈表是否相交
參考: http://blog.csdn.net/linyunzju/article/details/7753548
3.7 隊(duì)列中取最大值操作問(wèn)題
參考: http://blog.csdn.net/linyunzju/article/details/7765324
解法三:
利用兩個(gè)STL中的stack適配器來(lái)實(shí)現(xiàn)帶有最大值操作的棧類, 然后利用兩個(gè)新實(shí)現(xiàn)的棧來(lái)實(shí)現(xiàn)帶有最大值操作的隊(duì)列.
#if 0 class Stack{public:void push(int x){sta.push(x);if(stb.empty()) {stb.push(x);}else{int t=stb.top();stb.push(t>x?t:x);}}void pop(){if(!sta.empty()){sta.pop();stb.pop();}}int top(){assert(sta.empty()==false);return sta.top();}int maxV(){assert(sta.empty()==false);return stb.top();}int size(){return sta.size();}bool empty(){return sta.empty();}private:stack<int> sta;stack<int> stb; }; class Queue{public:void push(int x){sta.push(x);}void pop(){if(stb.empty()) {while(!sta.empty()) {int t=sta.top(); sta.pop();stb.push(t);}}stb.pop();}int front(){if(stb.empty()) {while(!sta.empty()){int t=sta.top(); sta.pop();stb.push(t);}}return stb.top();}int maxV(){if(stb.empty()){return sta.maxV();}if(sta.empty()){return stb.maxV();}return max(sta.maxV(),stb.maxV());}private:Stack sta;Stack stb; }; int main(){Queue q; int a[] = {2, 3, 4, 9, 4, 5, 10, 6}; for(int i = 0; i < sizeof(a)/sizeof(int); ++i) { q.push(a[i]); } q.push(101);cout<<q.front()<<endl;cout<<"queue maxvalue = "<<q.maxV()<<endl; q.push(590);cout<<"queue maxvalue = "<<q.maxV()<<endl; int deq = q.front(); cout<<"deq = "<<deq<<endl; cout<<"queue maxvalue = "<<q.maxV()<<endl; q.pop();cout<<"queue maxvalue = "<<q.maxV()<<endl; }#endif3.8 求二叉樹(shù)中節(jié)點(diǎn)的最大距離
計(jì)算一個(gè)二叉樹(shù)的最大距離有兩個(gè)情況:
情況A: 路徑經(jīng)過(guò)左子樹(shù)的最深節(jié)點(diǎn),通過(guò)根節(jié)點(diǎn),再到右子樹(shù)的最深節(jié)點(diǎn)。
情況B: 路徑不穿過(guò)根節(jié)點(diǎn),而是左子樹(shù)或右子樹(shù)的最大距離路徑,取其大者。
只需要計(jì)算這兩個(gè)情況的路徑距離,并取其大者,就是該二叉樹(shù)的最大距離。
//思路:注意指針聲明了一定要賦值,否則會(huì)報(bào)錯(cuò)。
// 方法一:遞歸法
//距離相差最遠(yuǎn)的兩個(gè)結(jié)點(diǎn),共有以下兩種情況:
// (1)路徑經(jīng)過(guò)根結(jié)點(diǎn),所以兩個(gè)結(jié)點(diǎn)在根結(jié)點(diǎn)的不同分支上
// (2)路徑不經(jīng)過(guò)根結(jié)點(diǎn),所以兩個(gè)結(jié)點(diǎn)應(yīng)該是次根結(jié)點(diǎn)中相聚最遠(yuǎn)的兩個(gè)結(jié)點(diǎn)。(遞歸思想)
// 遞歸本質(zhì)上還是采用了后續(xù)遍歷的方法。由于是從葉子結(jié)點(diǎn)開(kāi)始的所以每次處理都是第一種情況。
// 方法二:非遞歸法
//采用后序遍歷二叉樹(shù)的同時(shí)對(duì)二叉樹(shù)的結(jié)點(diǎn)進(jìn)行更新,每次更新都要更新最大距離。
struct Node{char data;Node* left;Node* right;int nMaxLeft;int nMaxRight; }; int maxLen=0; void findMaxLength(Node* root){if(root==0)return ;if(root->left==0)root->nMaxLeft=0;if(root->right==0)root->nMaxRight=0;if(root->left!=0)findMaxLength(root->left);if(root->right!=0)findMaxLength(root->right);if(root->left!=0) {root->nMaxLeft=(root->left->nMaxLeft>root->left->nMaxRight?root->left->nMaxLeft:root->left->nMaxRight)+1;}if(root->right!=0){root->nMaxRight=(root->right->nMaxLeft>root->right->nMaxRight?root->right->nMaxLeft:root->right->nMaxRight)+1;}if(root->nMaxLeft+root->nMaxRight>maxLen)maxLen=root->nMaxLeft+root->nMaxRight; } void findMaxLength2(Node* root){stack<Node*> st;Node *p=root,*visited=0;while(p!=0||!st.empty()){while(p!=0){st.push(p);p=p->left;}p=st.top();if(p->right==0||visited==p->right){if(p->left!=0) {p->nMaxLeft=(p->left->nMaxLeft>p->left->nMaxRight?p->left->nMaxLeft:p->left->nMaxRight)+1;}if(p->right!=0){p->nMaxRight=(p->right->nMaxLeft>p->right->nMaxRight?p->right->nMaxLeft:p->right->nMaxRight)+1;}if(p->nMaxLeft+p->nMaxRight>maxLen)maxLen=p->nMaxLeft+p->nMaxRight;st.pop();visited=p;p=0;}elsep=p->right;} }這段代碼有幾個(gè)缺點(diǎn):
算法加入了侵入式(intrusive)的資料nMaxLeft, nMaxRight
使用了全局變量 nMaxLen。每次使用要額外初始化。而且就算是不同的獨(dú)立資料,也不能在多個(gè)線程使用這個(gè)函數(shù)
邏輯比較復(fù)雜,也有許多 NULL 相關(guān)的條件測(cè)試。
一種不改變樹(shù)本身結(jié)構(gòu)的方法:
我認(rèn)為這個(gè)問(wèn)題的核心是,情況A 及 B 需要不同的信息: A 需要子樹(shù)的最大深度,B 需要子樹(shù)的最大距離。只要函數(shù)能在一個(gè)節(jié)點(diǎn)同時(shí)計(jì)算及傳回這兩個(gè)信息,代碼就可以很簡(jiǎn)單:
struct RESULT{int nMaxDistance;int nMaxDepth; }; RESULT findMaxLength3(Node* root){if(root==0){RESULT empty={0,-1};return empty;}RESULT lhs=findMaxLength3(root->left);RESULT rhs=findMaxLength3(root->right);RESULT res;res.nMaxDepth=max(lhs.nMaxDepth,rhs.nMaxDepth)+1;res.nMaxDistance=max(max(lhs.nMaxDistance,rhs.nMaxDistance),lhs.nMaxDepth+rhs.nMaxDepth+2);return res; }void postorder(Node* root){stack<Node*> st;Node* p=root,*visited=0;while(p!=0||!st.empty()){while(p!=0){st.push(p);p=p->left;}p=st.top();if(p->right==0||visited==p->right){cout<<(int)p->data<<' ';st.pop();visited=p;p=0;}else{p=p->right;}}cout<<endl; }計(jì)算 result 的代碼很清楚;nMaxDepth 就是左子樹(shù)和右子樹(shù)的深度加1;nMaxDistance 則取 A 和 B 情況的最大值。
為了減少 NULL 的條件測(cè)試,進(jìn)入函數(shù)時(shí),如果節(jié)點(diǎn)為 NULL,會(huì)傳回一個(gè) empty 變量。比較奇怪的是 empty.nMaxDepth = -1,目的是讓調(diào)用方 +1 后,把當(dāng)前的不存在的 (NULL) 子樹(shù)當(dāng)成最大深度為 0。
除了提高了可讀性,這個(gè)解法的另一個(gè)優(yōu)點(diǎn)是減少了 O(節(jié)點(diǎn)數(shù)目) 大小的侵入式資料,而改為使用 O(樹(shù)的最大深度) 大小的棧空間。這個(gè)設(shè)計(jì)使函數(shù)完全沒(méi)有副作用(side effect)。
測(cè)試代碼如下:
Node* initTree() { Node* tree[10]; for(int i=0;i<10;i++) { tree[i]=(Node*)malloc(sizeof(Node)); tree[i]->nMaxLeft=0; tree[i]->nMaxRight=0; tree[i]->left=NULL; tree[i]->right=NULL; tree[i]->data=(char)i; } for(int i=0;i<=2;i++) { tree[i]->left=tree[2*i+1]; tree[i]->right=tree[2*i+2]; } tree[3]->left=tree[7]; tree[5]->right=tree[8]; return tree[0]; } int main(){findMaxLength(initTree()); printf("遞歸法:%d\n",maxLen); maxLen=0;findMaxLength2(initTree()); printf("非遞歸:%d\n",maxLen); maxLen=0;RESULT r=findMaxLength3(initTree());printf("new Method:%d\n",r.nMaxDistance); }參考: http://blog.csdn.net/zuiaituantuan/article/details/6210317
http://www.cnblogs.com/miloyip/archive/2010/02/25/1673114.html
3.9 重建二叉樹(shù)
主要是給出前序和中序或中序和后序來(lái)構(gòu)造出二叉樹(shù).
這種題一般有二種形式,共同點(diǎn)是都已知中序序列。如果沒(méi)有中序序列,是無(wú)法唯一確定一棵樹(shù)的,證明略。
一、已知二叉樹(shù)的前序序列和中序序列,求解樹(shù)。
1、確定樹(shù)的根節(jié)點(diǎn)。樹(shù)根是當(dāng)前樹(shù)中所有元素在前序遍歷中最先出現(xiàn)的元素。
2、求解樹(shù)的子樹(shù)。找出根節(jié)點(diǎn)在中序遍歷中的位置,根左邊的所有元素就是左子樹(shù),根右邊的所有元素就是右子樹(shù)。若根節(jié)點(diǎn)左邊或右邊為空,則該方向子樹(shù)為空;若根節(jié)點(diǎn)左邊和右邊都為空,則根節(jié)點(diǎn)已經(jīng)為葉子節(jié)點(diǎn)。
3、遞歸求解樹(shù)。將左子樹(shù)和右子樹(shù)分別看成一棵二叉樹(shù),重復(fù)1、2、3步,直到所有的節(jié)點(diǎn)完成定位。
二、已知二叉樹(shù)的后序序列和中序序列,求解樹(shù)。
1、確定樹(shù)的根。樹(shù)根是當(dāng)前樹(shù)中所有元素在后序遍歷中最后出現(xiàn)的元素。
2、求解樹(shù)的子樹(shù)。找出根節(jié)點(diǎn)在中序遍歷中的位置,根左邊的所有元素就是左子樹(shù),根右邊的所有元素就是右子樹(shù)。若根節(jié)點(diǎn)左邊或右邊為空,則該方向子樹(shù)為空;若根節(jié)點(diǎn)左邊和右邊都為空,則根節(jié)點(diǎn)已經(jīng)為葉子節(jié)點(diǎn)。
3、遞歸求解樹(shù)。將左子樹(shù)和右子樹(shù)分別看成一棵二叉樹(shù),重復(fù)1、2、3步,直到所有的節(jié)點(diǎn)完成定位。
舉例說(shuō)明:根據(jù)已知求解二叉樹(shù)
中序序列 HLDBEKAFCG
后序序列 LHDKEBFGCA
1、在后序序列LHDKEBFGCA中最后出現(xiàn)的元素為A,HLDBEK|A|FCG
2、在后序序列LHDKEB中最后出現(xiàn)的元素為B,HLD|B|EK|A|FCG
3、在后序序列LHD中最后出現(xiàn)的元素為D,HL|D|B|EK|A|FCG
4、在后序序列LH中最后出現(xiàn)的元素為H,H|L|D|B|EK|A|FCG
5、在后序序列KE中最后出現(xiàn)的元素為E,H|L|D|B|E|K|A|FCG
5、在后序序列FGC中最后出現(xiàn)的元素為C,H|L|D|B|E|K|A|F|C|G
6、所有元素都已經(jīng)定位,二叉樹(shù)求解完成。
A
/ \
B C
/ \ / \
D E F G
/ \
H K
\
L
下面的代碼中也包括了根據(jù)兩個(gè)遍歷結(jié)果輸出另一個(gè)結(jié)果.
#if 0 //查找根節(jié)點(diǎn)的位置 char* find_root(char *start,char *end,char c){while(start<=end){if(*start==c)return start;start++;}return NULL; } //根據(jù)前序和中序來(lái)輸出后序:核心函數(shù) void pre_in_post_(char *pre_start,char*pre_end,char* in_start,char *in_end){if(in_start>in_end)return;if(in_start==in_end) {printf("%c",*in_start);return ;}char c=*pre_start++;char *p=find_root(in_start,in_end,c);if(p!=NULL){int len=p-in_start;pre_in_post_(pre_start,pre_start+len-1,in_start,p-1);pre_in_post_(pre_start+len,pre_end,p+1,in_end);}printf("%c",c); } //調(diào)用上面的遞歸函數(shù) void pre_in_post(char*pre,char *in){int len1=strlen(pre);int len2=strlen(in);char *pre_end=pre+len1-1;char *in_end=in+len2-1;pre_in_post_(pre,pre_end,in,in_end); } //根據(jù)后序和中序來(lái)輸出前序 void post_in_pre_(char *post_start,char*post_end,char* in_start,char *in_end){if(in_start>in_end)return;if(in_start==in_end) {printf("%c",*in_start);return ;}char c=*post_end--;char *p=find_root(in_start,in_end,c);printf("%c",c);if(p!=NULL){int len=p-in_start;post_in_pre_(post_start,post_start+len-1,in_start,p-1);post_in_pre_(post_start+len,post_end,p+1,in_end);}} //調(diào)用上面的遞歸函數(shù) void post_in_pre(char*post,char *in){int len1=strlen(post);int len2=strlen(in);char *post_end=post+len1-1;char *in_end=in+len2-1;post_in_pre_(post,post_end,in,in_end); } //二叉樹(shù)節(jié)點(diǎn)定義 struct Node{char data;Node* left;Node* right;Node(){};Node(char c,Node* l=0,Node* r=0):data(c),left(l),right(r){} }; //根據(jù)前序和中序來(lái)重建二叉樹(shù) Node* rebuild_pre_in_post_(char *pre_start,char*pre_end,char* in_start,char *in_end){if(in_start>in_end)return 0;if(in_start==in_end)return new Node(*in_start);char c=*pre_start++;char *p=find_root(in_start,in_end,c);Node* r=new Node(c);if(p!=NULL){int len=p-in_start;r->left=rebuild_pre_in_post_(pre_start,pre_start+len-1,in_start,p-1);r->right=rebuild_pre_in_post_(pre_start+len,pre_end,p+1,in_end);}return r; } // Node* rebuild_pre_in_post(char*pre,char *in){int len1=strlen(pre);int len2=strlen(in);char *pre_end=pre+len1-1;char *in_end=in+len2-1;Node* root=rebuild_pre_in_post_(pre,pre_end,in,in_end);return root; } //根據(jù)后序和中序來(lái)重建二叉樹(shù) Node* rebuild_post_in_pre_(char *post_start,char*post_end,char* in_start,char *in_end){if(in_start>in_end)return 0;if(in_start==in_end)return new Node(*in_start);char c=*post_end--;char *p=find_root(in_start,in_end,c);Node* r=new Node(c);if(p!=NULL){int len=p-in_start;r->left=rebuild_post_in_pre_(post_start,post_start+len-1,in_start,p-1);r->right=rebuild_post_in_pre_(post_start+len,post_end,p+1,in_end);}return r; } // Node* rebuild_post_in_pre(char*post,char *in){int len1=strlen(post);int len2=strlen(in);char *post_end=post+len1-1;char *in_end=in+len2-1;Node* root=rebuild_post_in_pre_(post,post_end,in,in_end);return root; } //前序遍歷函數(shù) void PreOrder(Node* root){if(root!=0) {printf("%c ",root->data);PreOrder(root->left);PreOrder(root->right);} } //后序變量函數(shù) void PostOrder(Node* root){if(root!=0) {PostOrder(root->left);PostOrder(root->right);printf("%c ",root->data);} } int main(){char *pre="ACBGEDF";char *in="GBCEADF";char *post="GBECFDA";pre_in_post(pre,in);printf("\n");post_in_pre(post,in);printf("\n");PreOrder(rebuild_pre_in_post(pre,in));printf("\n");PostOrder(rebuild_post_in_pre(post,in)); }#endif參考: http://www.cnblogs.com/bmrs/archive/2010/08/19/SloveTree.html
轉(zhuǎn)載于:https://www.cnblogs.com/xkfz007/archive/2012/11/07/2758363.html
總結(jié)
以上是生活随笔為你收集整理的编程之美-第3章 结构之法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 地址运算符:
- 下一篇: [Winodows Phone 7控件详