日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

算法--职前算法复习

發布時間:2023/12/14 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 算法--职前算法复习 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
1,strcpy//返回的是目標串的地址,這樣支持連續的運算表達式,已測試char *strcpy(char *strDest, const char *strSrc){//源串一定要const修飾assert((strDest!=NULL)&&(strSrc!=NULL));//此類題型基本判斷,指針是否空if(strDest==strSrc)return strDest;//編程題型的基本判斷,相等時可直接返回char *tempptr=strDest;//因為傳進來的指針在復制時會移動,因此先備份while((*strDest++=*strSrc++)!=’/0’);//空循環,如是其它條件一定注意’/0’的處理return tempptr;//上一行的while中(*strDest++=*strSrc++)括號不能少}2,memcpy//對內存塊的復制,需要著重考慮的是,內存塊重疊的情況,已測試void memcpy(void *pDest, const void *pSrc, size_t size){assert((pDest!=NULL)&&(pSrc!=NULL)); //此類題型基本判斷,指針是否空//任何對void*指針的運算,都需要先類型轉換!if((pDest>pSrc)&&((char *)pSrc+size>pDest)){//有重疊,且目標內存塊在后char* pstrSrc=( char*)pSrc+size-1;//一定要注意-1,因為如果size是1char* pstrDest=( char*)pDest+size-1;//則不需要加,因此注意要-1while(size--)//直接用size控制循環,另一處用size是else中,不可能同時會執行* pstrDest --=* pstrSrc --;//自減用的是pstrSrc,而不是pSrc}else{//無重疊或者有重疊但目標內存塊在前char* pstrSrc =(char*)pSrc;char* pstrDest =(char*)pDest; while(size--)* pstrDest ++=* pstrSrc ++;}}3, 鏈表建立,合并有序鏈表,鏈表逆置,已測試typedef struct Node{int val; Node *next;}Node;Node* createLink(){//建立一個有序鏈表int val;Node *head=NULL;while(true){cin>>val;if(val==-1)return head;Node *tmp=new Node;tmp->val=val;tmp->next=NULL;if(head==NULL)head=tmp;//空鏈表情況else{Node *q=NULL;for(Node *p=head;p!=NULL&&(p->val<=tmp->val);q=p,p=p->next) ;if(q==NULL){tmp->next=head;head=tmp;}//插表頭情況else{tmp->next=p;//其它情況,表尾也包括在此中q->next=tmp;}} }return head;}Node *mergeLink(Node *a,Node *b){//合并有序鏈表Node *p,*head; if(a==NULL)return b;//這個邊界條件一定要處理if(b==NULL)return a;if(a->val<=b->val){//確定表頭head=a;a=a->next;}else {head=b;b=b->next;}p=head;while(a!=NULL&&b!=NULL){//當兩表都不空時確定結點if(a->val<b->val){p->next=a;p=p->next;a=a->next;}else {p->next=b;p=p->next;b=b->next;}}while(a!=NULL){//處理余下的一個鏈尾p->next=a;a=a->next;}while(b!=NULL){//處理另一鏈尾p->next=b;b=b->next;}return head;}Node *reverseLink(Node *head){ if(head==NULL)return NULL;//空鏈表Node *p,*q,*r;p=head;if(p->next==NULL)return p;//只一個結點q=p->next;p->next=NULL;//一定要有這句,否則逆序后找不到尾了if(q->next==NULL){q->next=p; return q;//只兩個結點}r=q->next; while(r!=NULL){//多于兩個情況q->next=p;p=q;q=r;r=r->next;}q->next=p;//處理表尾情況return q;}4,二叉樹的前中后序遍歷的遞歸,非遞歸寫法!已測試過void InOrderTraverse(NODE* root){//中序非遞歸stack<NODE*> s;s.push(root);//根進棧NODE *p=NULL;while(!s.empty()){while((p=s.top())!=NULL)s.push(p->pLeft);//左一直進棧,空的也進s.pop();//彈出空指針if(!s.empty()){p=s.top();s.pop();//彈出棧頂元素并訪問之cout<<p->chValue;s.push(p->pRight);//往右一步}}}void PreOrderTraverse(NODE* root){//前序非遞歸stack<NODE*> s;s.push(root);NODE *p=NULL;while(!s.empty()){while((p=s.top())!=NULL){//此處會處理樹空的情況cout<<p->chValue;//先訪問自己再左子樹進棧,這是與中序的唯一區別s.push(p->pLeft);}s.pop();//此處會彈出空指針if(!s.empty()){p=s.top();s.pop();s.push(p->pRight);}}}void PostOrderTraverse(NODE* root){//后序,需要增加目前已經訪問是左或右的標記stack<stackNODE> s;stackNODE x;NODE* p=root;do{while(p!=NULL){//非空左子樹一直進棧,并標記為已訪問左子樹,直達最左葉子x.ptr=p;x.tag='L';s.push(x);p=p->pLeft;}while(!s.empty()&&s.top().tag=='R'){//如果棧中元素已經訪問過右子樹,則訪問根x=s.top();s.pop();cout<<x.ptr->chValue;}if(!s.empty()){//進入棧中元素的右子樹,只是賦值,右子樹是否空,交由外層循環s.top().tag='R';p=s.top().ptr->pRight;}}while(!s.empty());}5,折半查找int halfSearch(int *a,int e,int start,int end){//二分查找,e為待找元素的值if(start>end)return -1;//處理找不到的情況,start=end最終也會遞歸到start>endint mid=(start+end)/2;if(a[mid]==e)return mid;else if(a[mid]>e)return halfSearch(a,e,start,mid-1);else return halfSearch(a,e,mid+1,end);}6,最大公約數,已測試int gcb(int a,int b){ //輾轉相除法if(a<b)swap(a,b);//先判斷大小,估計a大于bif(b==0)return a;//如果小的數為0返回a,不管a是不是0int r=a%b;//否則求余while(r!=0){//余數不為零時一直循環,至余數為0為止a=b;b=r;r=a%b;}return b;//余數為0則返回兩數中較小的}int gcb(int a, int b){// 遞歸算法if(a<b)swap(a,b);if(b!=0)return gcb(b,a%b);else return a;}7,棧的操作(InitStack,GetTop,Push,Pop)如果用鏈表的話,插刪表頭,如果用數組的話,用base,top保存數組下標最簡單實現如下:已測試,但未必充分template <typename T>class MyStack{private:int size;int top;int base;T *stack;public:MyStack(int size=100){this->size=size;stack=new T[size];top=base=0;}void push(T elem){if(top<size-1)stack[top++]=elem;}T pop(){if(!(top==base))return stack[--top];}~MyStack(){delete []stack;}};8,隊列操作(入隊出隊),已測試typedef struct QNode{int data;struct QNode *next;}QNode,*QueuePtr;typedef struct{QueuePtr front,rear;//隊首尾指針}LinkQueue;void InitQueue(LinkQueue &Q){//初始化只分配一個不存元素的空頭結點Q.front=Q.rear=(QueuePtr)malloc(sizeof(QNode));if(!Q.front){printf("OVERFLOW");return;}Q.front->next=NULL;}void EnQueue(LinkQueue &Q,int e){QueuePtr p=( QueuePtr)malloc(sizeof(QNode));if(!p){printf("OVERFLOW");return;}p->data=e;p->next=NULL;Q.rear->next=p;Q.rear=p;}void DeQueue(LinkQueue &Q,int &e){if(Q.front==Q.rear){printf("Empty Queue!");return;}QueuePtr p=Q.front->next;e=p->data;Q.front->next=p->next;if(Q.rear==p)Q.rear=Q.front;//刪最后一個結點的情況下,必須處理,以防rear指針懸空free(p);}9,破壞棧返回鏈的代碼,已測試void breakDown(){char str[3]=”/0”;//str放在棧上strcat(str,”AAAAA”);//越界的賦值把返回鏈頂亂了,因此程序崩潰return;}int main(int argc, char *argv[]){breakDown ();return 0;}10,各種排序算法,已驗證(一)堆排序int heapSize = 0;int Parent(int index) { //返回父母的下標,左孩子的父母除2即可,右孩子除后-1return (index % 2) ? (index >> 1) : ((index >> 1) - 1);}int Left(int index) { //返回左孩子下標,乘2之后+1return ((index << 1) + 1);}int Right(int index) { //返回右孩子下標,乘2后+2return ((index << 1) + 2);}//主要函數一,堆調整,調整index為根的子樹,前提是,index的左右子樹已經調整過。void maxHeapify(int * array, int index) {int largest = 0;int left = Left(index);int right = Right(index);//第一步,先找到根與左右孩子中最大者的下標if ((left <= heapSize) && (array[left] > array[index]))//heapSize來控制是否到葉子了largest = left;elselargest = index;if ((right <= heapSize) && (array[right] > array[largest]))largest = right;if (largest != index) {//第二步,如果最大的不是根,則交換最大者與根,遞歸調整swap(&array[index], &array[largest]);maxHeapify(array, largest);}}//主要函數二,建堆void buildMaxHeap(int * array, int length) {int i;heapSize = length;//初始化堆大小for (i = (length >> 1); i >= 0; i--)//從有孩子的結點開始,調整至樹根為止即建好了maxHeapify(array, i);}//主要函數三,排序,調用前兩個函數int heapSort(int * array, int length) {int i;buildMaxHeap(array, (length - 1));//建堆for (i = (length - 1); i >= 1; i--) {printf("%d,",array[0]);//輸出堆頂元素swap(&array[0], &array[i]);//交換它與堆的最末尾一個元素,排完序后a升序heapSize--;//堆大小減1maxHeapify(array, 0);//只需要調整樹根}return 0;}(二)快速排序int part(int *a,int low,int high){//劃分函數int x=a[low];//此處是a[low]而不是a[0],我自己寫代碼時犯了此錯誤//int i=low,j=high;不需要,直接用low,highwhile(low<high){while((a[high]>=x)&&(low<high))--high;//要重復low<high這一條件!a[low]=a[high];while((a[low]<=x)&&(low<high))++low;a[high]=a[low]; }a[low]=x;//x最后是給a[low]return low;//返回的是low}void quickSort(int *a,int low,int high){//遞歸函數if(low<high){//這是遞歸終結條件,即在只有一個元素時或者low>high時,不排序!int mid=part(a,low,high);quickSort(a,low,mid-1);quickSort(a,mid+1,high);}}快速排序擴展:(1),隨機化版本,即每次進行劃分之前,隨機生成一個low至high之間的下標,將此下標中元素與a[low]交換,然后再進行劃分;(2)快排的第二次遞歸非必須,可用尾遞歸技術,用循環代替遞歸,代碼如下:while(low<high){int mid=part(a,low,high);quickSort(a,low,mid-1);low=mid+1;}(3)在劃分不均衡時,會導致遞歸深度為n,解決方法是,每次劃分后,小的一段先遞歸,可使棧深度最大為logn,或者小的一段用遞歸而大的一段用循環,參考“尾遞歸”方法;(4)快速排序的非遞歸版本思想:1)將low,high包裝為position結構體,先將初始position(low,high)進棧2)while(棧不空){彈出棧頂的position,如果low<high,則q=partition(a,low,high);if(q>low+1)將(low,q-1)包裝成position進棧if(high>q+1)將(q+1,high)包裝成position進棧}原理是,每次棧中有較長的序列,都會彈出,分成更小的序列,直至某序列少到一個元素時,不再push進新的position。具體代碼如下,已驗證:void quickSort2(int *a,int low,int high){MyStack<position> stack(150);//這個棧是自己寫的,只有pop,push,empty及析構函數position pos;//自己定義的結構體pos.x=low;pos.y=high;stack.push(pos);//初始上下界進棧while(!stack.empty()){position poped=stack.pop();//棧不空進,彈出棧頂元素if(poped.x<poped.y){//如果可劃分,則劃分成兩段int q=part(a,poped.x,poped.y);if(q>poped.x+1){//劃后的第一段可進棧否?position lowpart;lowpart.x=poped.x;lowpart.y=q-1;stack.push(lowpart);}if(poped.y>q+1){//劃分的第二段可進棧否?position highpart;highpart.x=q+1;highpart.y=poped.y;stack.push(highpart);}}}}(三)插入排序,遞歸算法void insertSort(int *a,int n){//其中n代表待排序元素的個數if(n>1){//遞歸的邊界條件insertSort(a,n-1);//先遞歸調用一下int x=a[n-1]; //最末一個元素備份int i=n-2;for(i=n-2;i>=0;i--){if(a[i]>x)a[i+1]=a[i];else break;//一定要這句,也許用while循環,條件為a[i]>x&&i>=0,更清晰}a[i+1]=x;}}非遞歸算法void insertSort2(int *a,int n){for(int i=1;i<n;i++){int x=a[i];for(int j=i-1;(j>=0)&&a[j]>x;j--)a[j+1]=a[j];a[j+1]=x;}}(四)選擇排序:每次從余下的數中,選擇一個最小的放在當前位置,當前位置后移一位;初始時,余下數為n個,當前位置為0。(五)冒泡排序:思想是每次一個大數沉底。具體C代碼如下:void bubbleSort(int *a,int n){//每次一個大數沉底for(int i=n-1;i>=0;i--)//每次比較時,要到達的下標for(int j=0;j<=i;j++)//每次從0到i之間比較if(a[j]>a[j+1]){int temp=a[j];a[j]=a[j+1];a[j+1]=temp;} }(六)歸并排序void merge(int *a,int *b,int l,int m,int r){//主要輔助函數一int i=l,j=m+1,k=l;//合并a[l...m]和a[m+1...r]到b[l...r]while((i<=m)&&(j<=r))if(a[i]<=a[j])b[k++]=a[i++];else b[k++]=a[j++];if (i>m)for(int q=j;q<=r;q++)b[k++]=a[q];else for(int q=i;q<=m;q++)b[k++]=a[q];}void copy(int *a,int *b,int left,int right){//遞歸輔助函數二while(left<=right){a[left]=b[left];left++;}}//遞歸主算法void mergeSort(int *a,int *b,int left,int right){//b數組是輔助空間if(left<right){//至少2個元素int i=(left+right)/2;mergeSort(a,b,left,i);mergeSort(a,b,i+1,right);merge(a,b,left,i,right);//合并到數組bcopy(a,b,left,right);//復制回數組a}}//非遞歸主要輔助函數一void mergePass(int *a,int *b,int s,int n){//合并大小為s的相鄰2段子數組int i=0;while(i<=n-2*s){//先合并前若干對長度為s的相鄰子數組merge(a,b,i,i+s-1,i+2*s-1);i+=2*s;}if(i+s<n)merge(a,b,i,i+s-1,n-1);//剩下的數少于2*s個,如是>s個情況else for(int j=i;j<=n-1;j++)b[j]=a[j];//剩下不足s個}//非遞歸主函數void mergeSort2(int *a,int n){int *b=new int[n];int s=1;while(s<n){mergePass(a,b,s,n);//合并數組到bs+=s;mergePass(b,a,s,n);//合并數組到as+=s;}}(七)計數排序。它假設輸入的n個元素是介于0…k之間的整數,此處k為某個整數,當k=O(n)時,計數排序的運行進行為O(n),它需要k個用來存放計數的空間C[0…k]及n個存放結果的區間,偽代碼如下:countSort(A,B,k){//A為待排數組,B為結果數組,k的意義如前所述for(i=0…k)C[i]=0;//初始化計數器for(j=0…length(A))C[A[j]]=C[A[j]]+1;//計數for(i=1…k)C[i]=C[i]+C[i-1];//C[i]表示值所有小于或等于i的元素個數for(j=length(A) downto 1){B[C[A[j]]]=A[j];//所有小于等于A[j]的元素有x個,便把A[j]放在位置xC[A[j]]--;//然后把小于等于A[j]元素的元素數減1}}(八)希爾排序,已驗證:void shellInsert(int *a,int n,int dk){//dk為步長for(int i=dk;i<n;i++){//一趟希爾排序,對步長分成的若干子序列插入排序if(a[i]<a[i-dk]){int x=a[i];for(int j=i-dk;j>=0&&(x<a[j]);j-=dk)a[j+dk]=a[j];a[j+dk]=x;}}}void shellSort(int *a,int n,int *dlta,int t){//dlta為步長數組,t為dlta長度for(int k=0;k<t;++k){//步長數組shellInsert(a,n,dlta[k]);//步長數組生成方法不同,效率也不同常見的有}//...9,5,3,2,1 dlta[k]=2^(t=k)+1等等,總之,最后一個一定是1,而且}//應該使增量序列中的值沒有除1之外的公因子(九)基數排序,偽代碼Radix-Sort(A,d){//最大有d位數for(i=1…d)對每一個數字,以第i位進行排序(調用任何一種穩定排序算法)}效率為O((n+k)*d)//其中n表示待排序數字數目,d表示位數,k表示進制。基數排序對內存要求更高,無法原地排序,而且不能有效利用緩存,故對大數據量排序時,顯然不如快排,但數據量不大時,誰更快一些,取決于底層機器實現特性。11,KMP算法int Index_KMP(char *S,char *T,int pos){//利用模式串T的next函數求T在S中S[pop]//字符之后的位置的KMP算法,其中T非空,1<pos<S.lengthint i=pos,j=0;int sLength=strlen(S);int tLength=strlen(T); while(i<sLength&&j<tLength){if(j==0||S[i]==T[j]){++i;++j;}else j=next[j];//如果是窮舉法,則此處next[j]=j++;}if(j>=tLength)return i-tLength;//找到了串的位置,退出時應該j==tLengthelse return 0;}void get_next(char *T,int *next){//這個函數是重點int i=0, j=-1;//j在前,i在后,next[i]存的是i之前的串的最長公共前后綴長度next[0]=-1;//初始時計算i=0,即考慮第0個字符,此時j應設為-1,next[i]=-1表i之前//的串沒有公共前后綴while(i<strlen(T)){if(j==-1||T[i]==T[j]){++i;++j;next[i]=j;}//j先自增所以next[i]至少=0else j=next[j];//j最小=-1,利用已求得的next部分}}12,尋找質數(打印小于x的所有質數)常規方法:2為質數,從3至x,每個能被2整除的數不是質數,余下的可能質數中緊臨2的為下一質數(這里是3),再從4至x,把被個能被3整除的數置為非質數……。最后留下的為質數,代碼如下:for(int i=3;i<=n;i+=2)a[i]=true;//去掉2的倍數,認為3以后的奇數是質數for(i=2;i*i<=n;i++){//對已找出來的每個數,第一次時是2if(a[i]){//如果i是質數for(int j=i;j*i<=n;j++)a[j*i]=false;//為什么是j=i,而不是j=1呢?因為小于i*i的數//一定是小于i的因子可供分解。}}另一方法(摘自維基百科)是:根據質數分布特性,把x除以6得余數,有:x=6n+0偶數;x=6n+1可能是質數;x=6n+2;偶數;x=6n+3能被3整除;x=6n+4偶數;x=6n+5可能是質數。因此,只需要考慮6n+1,6n+5及2,3,5三個數即可,搜索空間一下減少了2/3,運行時間是常規方法的一半,代碼如下:findPrime(){int LIMIT=1000000int nLIMIT=LIMIT-6for(int i=7;i<=nLIMIT;i+=6){sieve[i]=true;//6n+1sieve[i+2]=false;//6n+3sieve[i+4]=true;}int p=5;//2,3不考慮,從5開始while(int j=(p*p)<LIMIT){//從p*p開始,因為小于p*p的數一定有一因子小于pp2=p<<1;//每次加2p,因為p為奇數,加p之后是偶數,不用考慮,這表已知質數while(j<=LIMIT){//j表示p的倍數,每次增2psieve[j]=false;//j+=p2;}do{p+=2;}while(sieve[p]==false)//找下一質數}}13,八皇后問題(n后問題),代碼已測試對第i個棋子(肯定是在第i行)進行放置的時候,x[i]表示第i列是否已經放置棋子,而對于兩個對角線,有如下規律:主對角線上的兩個元素,其x,y坐標之差相等,副對角線上的元素,其x,y坐標之和相等,即x1-y1=x2-y2或x1+y1=x2+y2,這兩個式子分別等價于x1-x2=y1-y2和x1-x2=y2-y1。因此,只要abs(x1-x2)==abs(y1-y2)就表示有對角線已經放置棋子了,此問題不可放。由此可得解n后問題的遞歸回溯算法如下:void Queeen(int t,int *x){//當前在第t行嘗試放棋子if(t>n) for(int i=1;i<=n;i++)cout<<"("<<i<<","<<x[i]<<") ";cout<<endl;//得到一種解else for(int i=1;i<=n;i++){//這個循環保證找到所有解,而不是一個解時退出x[t]=i;//int *x用來記錄結果i,x[i]分別表示第i行棋子放置在x[i]列if(place(t))Backtrack(t+1);//如果放置合法,則繼續遍歷}}bool place(int k){for(int j=1;j<k;j++){if((abs(k-j)==abs(x[j]-x[k]))||(x[j]==x[k]))return false; }return true;}用循環代替遞歸的回溯法代碼如下:void Queue(int *x){x[1]=0;//列的初始值在第一列之左int k=1;//行的初始值在第一行while(k>0){//當未回溯到根以上時,持續找解x[k]+=1;//列++,這也是為什么初始列設為0的原因while((x[k]<=n)&&!(place(k)))x[k]+=1;//找目前第一個能放棋子的位置(1)if(x[k]<=n)//如果找到if(k==n) for(int i=1;i<=n;i++)cout<<"("<<i<<","<<x[i]<<") ";cout<<endl;//輸出解else{k++;x[k]=0;}//試下一行,列號歸為0else k--;//當前方式在第k行找不到最優解了,回溯;注意x[k]不用置0,(1)處會接//著往后找可放置的位子。}}初始時,調用Queue(1,x)即可。這里的代碼省去了初始化等問題,x的下標從1開始。14,大整數乘法void reverse(char chr[],int l){//顛倒數組 int i;char temp;for(i=0;i <= l/2;i++){//首尾交換temp=chr[i];chr[i]=chr[l-i];chr[l-i]=temp;}}int multiple(char a[],char b[],int result[]){//函數返回值是乘積的長度 int la,lb,lresult;//a,b及result的位數-1int i,j;//循環控制變量int temp; la=strlen(a)-1;//初始化最后一位lasta,lastblb=strlen(b)-1; reverse(a,la);//將a,b顛倒,下面從個位數開始算起reverse(b,lb); for(i=0;i <= la;i++)for(j=0;j <= lb;j++){//精華所在,+=用于處理進位result[i+j]+=(a[i]-48)*(b[j]-48);//-48是減去字符'0'的ASCII值result[i+j+1]+=result[i+j]/10;//進位result[i+j]%=10;//自己留的數}lresult=i+j+1;//結果長度最多為i+j+1while(result[lresult] == 0) lresult--;//沒有達到長度最多的情況,逆序,高位在后if(lresult < 0 ){result[0]=0;lresult=0;}//如果是0for(i=0;i <= lresult/2;i++){ //返回正序的結果temp=result[i];result[i]=result[lresult-i];result[lresult-i]=temp;} return lresult; }15,一臺計算機,只能進行賦值,+1,固定次數的循環三種原子操作,只能操作0和正整數。請用偽代碼寫出四則運算的實現。1)加法:add1(x){return x+1;}//+1為原子運算add(x,y){for(i=1…y)x=add1(x);return x;}2)乘法:mul(x,y){ret=0;for(i=1…y) ret=add(ret,x);return ret;}3)減法:sub1(x){ //只能操作0和正整數,則令0-1=0ret=0; //不能直接用if(x==0)判斷,因為if不是原子操作for(i=2…x)ret=add1(x);return ret;}sub(x,y){ //因為0-1=0,因此x<y時,令x-y=0for(i=1…y)x=sub1(x);return x;}4)除法:div(x,y){ //令某數除以0仍為原數quotient=0; //保存除的結果tmp=add1(x); //先x增1,運算時,每一個x-y結果不為0時,除的結果增1for(i=1…x) //最多只可能循環x次,因為除去y=0的情況,每次至少減1tmp=sub(tmp,y);//如果y=0,則每次tmp都不為0,會加x次1得原數 quotient=add(quotient,sgn(tmp));//如果y!=0,則tmp到0之前quotient都會增return quotient;}sgn(x){ //在x=0時返回0,否則返回1ret=0;for(i=1…x){ret=1;}return ret;}16,memset函數實現。加速原理:32位機器一次傳四個字節與傳一個字節耗時差不多#define CPU_ALIGN_MASK 0x03//用來判斷所分內存地址是否對齊(4的倍數) #define CPU_BYTE 4//機器字節數為4,表32位機器//整數長度一般是機器上的字長,已測試void memset(void *paddr, unsigned int size, char value){char *pcaddr = 0; //按字節復制時用到的指針unsigned int *piaddr = (unsigned int *)paddr;//按一個個整數復制時,用到的指針unsigned int iValue = 0;//iValue表示按一個個整數復制時的值int iValueTmp = (unsigned int)value;//iValueTmp表示一個字節中的值unsigned int iPtrNumber = (unsigned int )paddr;if( iPtrNumber & CPU_ALIGN_MASK ){//看地址是否對齊(4的倍數)pcaddr = ( char* )piaddr;while( size > 0 ){*pcaddr++ = value;-- size;}//while}//ifiValue |= iValueTmp;//將iValueTmp賦給第一個字節,0|x,結果為x #if CPU_BYTE > 1//采用條件編譯iValue |= iValueTmp<<8;//將iValueTmp賦給第二個字節 #if CPU_BYTE > 2iValue |= iValueTmp<<16;//將iValueTmp賦給第三,第四個字節iValue |= iValueTmp<<24; #if CPU_BYTE > 4iValue |= iValueTmp<<32;iValue |= iValueTmp<<40;iValue |= iValueTmp<<48;iValue |= iValueTmp<<56; #endif #endif #endifwhile( size >= CPU_BYTE ){//之前判斷過地址是4的倍數(對齊)*piaddr++ = iValue;//一個個整數賦值size -= CPU_BYTE;} }//memset17,全排列問題遞歸方法:perm(int a[],int k,int m){//產生a[k…m]之間所有的全排列,已驗證if(k==m)輸出a[0…m];else for(int i=k;i<=m;i++){swap(a[i],a[k]);//大家輪流來做這個位子,大家都坐過了,也就代表都排過了perm(a,k+1,m);swap(a[i],a[k]);//回溯}}非遞歸方法思路:第一次拿一個數,一種排法,第二個數有兩種(在第一個數的左/右),第三個數有三種……,總共有n!種排法,編程實現偽碼為:for(i=1…n!){//n!種排列,一個i值可對應一種排列第一個數放1for(j=2…n){if(i%j==0)j插到原隊列尾 //每種排列的計算方法else j插到第i%j個位置之前i=i/j;}}18,棋盤覆蓋問題,分治法void ChessBoard(int tr,int tc,int dr,int dc,int size){//tr,tc表示棋盤左上角方格的行列號;dr,dcif(size==1)return;//表示特殊方格的行列號,size表示棋盤大小int t=tile++;//L型骨牌編號,tile是全局變量,初始為1s=size/2;//處理左上if(dr<tr+s&&dc<tc+s){//如果特殊方格在左上角ChessBoard(tr,tc,dr,dc,s);//遞歸處理左上的四分之一塊}else{//如果不在,則填充左上四分之一塊中的最右下角,然后遞歸處理Board[tr+s-1][tc+s-1]=t;ChessBoard(tr,tc,tr+s-1,tc+s-1,s);}….//其實三個四分之一類似處理…打印出ChessBoard即可看到解!}19,生產者消費者問題;哲學家進餐問題;讀者寫者問題生產者消費者偽代碼:Var mutex=1,empty=n,full=0//分別表示互斥信號量,可用空間及已生產產品數var buffer[o…n-1];//存放產品的緩沖區var in=0,out=0;//存放生產者放產品的指針及消費者取產品的指針,用隊列的形式producer: while(true){produce a item next;;wait(empty);//P先看是否有空間存放,沒有就一直掛起,等消費者signal的消息wait(mutex);//實現對緩沖區的互斥訪問buffer[in]=nextp;in=(in+1)%n;signal(mutex);signal(full);//進行V操作,通知消費者,也即full++}consumer:while(true){wait(full);wait(mutex);nextc=buffer[out];out=(out+1)%n;signal(mutex);signal(empty);consumer the item in nextc;}哲學家進餐問題的解決方法為:方法一,奇數號先拿左邊的筷子,偶數號先拿右邊的筷子;方法二,兩個筷子都可用才能拿讀者寫者問題用偽碼描述如下:Var rmutex=1,wmutex=1//用來實現對讀寫變量的互斥訪問var Readcount=0;Reader:while(true){wait(rmutex);//對Readcount的互斥訪問if readcount=0 then wait(wmutex);//如果沒有人讀,則還需要對wmutex進行P操作Readcount=Readcount+1;signal(rmutex);…..進行讀操作…….wait(rmutex);//對Readcount的互斥訪問if readcount=0 then signal(wmutex);//如果沒有人讀,指示別人可寫了Readcount=Readcount-1;signal(rmutex);}Writer:while(true){wait(wmutex);perform write operation;signal(wmutex);}20,來自于多米諾牌完美覆蓋的組合數學知識。下面給出通解。臺階問題:某樓梯有n級臺階,某人一步最多邁三級,問有多少種不同的方式上樓?引進遞歸思想,我們就可找到簡單的方法:設上n級臺階的方法有f(n)種,某人第1步可邁一至三級。第1步邁一級,而后上n-1級,有f(n-1)種上法;第1步邁二級,而后上n-2級,有f(n-2)種上法;第1步邁三級,而后上n-3級,有f(n-3)種上法。從而得f(n)=f(n-1)+f(n-2)+f(n-3)且f(1)=1;f(2)=2,f(3)=4;同時規定f(0)=1。更一般情況:一般臺階問題:某樓梯有n(n≥1)級臺階,某人一步最多邁m(n≥m≥1)級,有多少種不同的方案上樓。設上n級臺階的方案有f(n)種,則f(n)=f(n-1)+f(n-2)+f(n-3)+...+f(n-m) (n≥m),且有f(0)=1;f(1)=1;f(2)=2;...;f(k)=f(0)+f(1)+f(2)+...+f(k-1);22,Tribonaci數列,斐波那契數列斐波那契數列,遞歸時,會重復計算,遞推則不會,不過要一數組來存儲結果。遞歸時也可以采用備忘錄的方法避免重復計算。另一方法是利用矩陣,先整理出如下公式:由此公式遞推有(Fn Fn-1)=(Fn-1 Fn-2)*A=(Fn-2 Fn-3)*A^2=……=(F1 F0)*A^(n-1)剩下的問題,就是求解矩陣A的方冪n。矩陣的冪,在冪n很大時,可采用如下方法進行加速,將乘的次數,從n減少到log2n,用二進制表示n:舉個例子:由于以上分析,可得偽代碼如下:class Matrix;//假設我們已經有了實現乘法操作的矩陣類Matrix MatrixPow(const Matrix& m,int n){//求解矩陣m的n次方Matrix result=Matrix::Identity;//賦初值為單位矩陣Matrix tmp=m;//tmp每次循環都會增長,依次表示A1,A2,A4,A8for(;n;n>>=1){// 每次右移一位,即依次判斷每位二進制是否是1if(n&1)result*=tmp;//如果最低位是1,tmp*=tmp;}}int Fibnacci(int n){Matrix an=MatrixPow(A,n-1);//調用上面的方法return F1*an(0,0)+F0*an(1,0);//an(0,0)表示an矩的元素(0,0)}Tribonaci數列求解方式類似,只是增加了一項。23,快速尋找滿足條件的兩個數,即從數組中找出兩個數,使其和等于指定值方法,首先對數組排序,時間復雜度為O(nlgn);然后令i,j分別指向數組首尾,看a[i]+a[j]是否等于指定的值n,是則返回,否則,比較a[i]+a[j],如果大于n則j--,如果小于n則i++,循環結束條件為i>=j。另一擴展的類似問題: 已經兩個已經排好序的,且長度都為N的數組,找出兩個數組合起來第N大的數,即找出它們的中間大數字,如:x數組: 1, 7,9,10,30 y數組:3,5,8,11,12中間大數為: 8 解法為:先比較兩個數組的中間元素,如果一個比另一個大,如x[mid]<y[mid], 則y[mid+1...end]和x[0...mid-1]可排除掉,因為第N大數不可能在這些數中間,再對剩余的元素x[mid...end]和y[0...mid]調用相同的過程,即在它們之中尋找第N/2大的元素24,字符串替換(王丙林整理)已測試//用新子串newstr替換源字符串src中的oldstr子串// @return char* dest 返回新串的地址 char *strreplace(char *dest, char *src, const char *oldstr, const char *newstr){if (dest==src)return dest; //這類題的常規步驟char *needle;//方法是,初始時dest為src,每次循環用strstr從中找到oldstr的位置char *temp;//然后把它之前的部分,newstr部分以及之后的部分連接起來放到temp里面dest=src; //循環末了把temp中復制為dest(用庫函數strdup)while(needle=strstr(dest,oldstr)){ //strstr函數返回串中找到的第一個子串的char*地址temp=(char *)malloc(strlen(dest)+strlen(newstr)-strlen(oldstr)+1);//三截共需內存長度strncpy(temp,dest,needle-dest);//第一截,strncpy把源串中的n個字符復制到目標串temp[needle-dest]=’/0’;//標識串的結束, 從中間截斷的沒有帶’/0’,所以要處理strcat(temp,newstr);//第二截,newstr自己帶了’/0’strcat(temp,needle+strlen(oldstr));//第三截dest=strdup(temp);//strdup()會另辟一塊內存,并不會釋放temp的內存free(temp);//strstr函數}return dest;}25,要把cpp原文件中的注釋去掉,存入到新文件中,要求寫出完整代碼#include<fstream>//一定要有#include<string>using namespace std;int main(int argc,char *argv[]){ifstream in("findPrime.cpp");ofstream out("findPrime2.cpp");string s;bool start=false;while(getline(in,s)){if(!start){//如果沒有開始多行注釋,則找注釋開始int oneLinePos=s.find("//");int mulLinePosStart=s.find("/*");//兩個注釋都有時要看哪種注釋開始得早?或者只有其中一種注釋開始if(((oneLinePos!=string::npos)&&(mulLinePosStart!=string::npos)&&(oneLinePos<mulLinePosStart))||((oneLinePos!=string::npos)&&(mulLinePosStart==string::npos))){s=s.substr(0,oneLinePos);//子串不包括oneLinePos元素}else if(((oneLinePos!=string::npos)&&(mulLinePosStart!=string::npos)&&(mulLinePosStart<oneLinePos))||((oneLinePos==string::npos)&&(mulLinePosStart!=string::npos))){s=s.substr(0,mulLinePosStart); start=true;} if(s.size()!=0)out<<s<<"/n";}else{//已經開始了多行注釋的情況下,找結尾int mulLinePosEnd=s.find("*/");if(mulLinePosEnd!=string::npos){//如果找到,則輸出余下字符s=s.substr(mulLinePosEnd+2,s.size());if(s.size()!=0)out<<s<<"/n";//如果更智能化start=false;}//未找到則沒有輸出,該行認為是注釋掉了}}return 0;}26,大數階乘的估算公式如求10的7次方階乘的位數,此題其實是純粹的數學問題。 由斯特林[striling]公式可得: lnN!=NlnN-N+0.5ln(2N*pi) ,而10的7次方階乘的位數等于: log10(N!)取整后加1 , log10(N!)=lnN!/ln(10) ,C代碼如下:#include <stdlib.h>#include <math.h>#define N 10000000#define PI 3.14159265int main(int argc,char *argv){int len;len=ceil((N*log(N))-N+0.5log(2*N*PI)/log(10));printf(“%d”,len);// 結果是65657060return 0;}27,數的補碼的求法:(1)正數的補碼:與原碼相同。例如,+9的補碼是00001001。 (2)負數的補碼:符號位為1,其余位為該數絕對值的原碼按位取反;然后整個數加1。 例如,-7的補碼:因為是負數,則符號位為“1”,整個為10000111;其余7位為-7的絕對值+7的原碼0000111按位取反為1111000;再加1,所以-7的補碼是11111001。已知一個數的補碼,求原碼的操作分兩種情況: (1)如果補碼的符號位為“0”,表示是一個正數,所以補碼就是該數的原碼。 (2)如果補碼的符號位為“1”,表示是一個負數,求原碼的操作可以是:符號位為1,其余各位取反,然后再整個數加1。 例如,已知一個補碼為11111001,則原碼是10000111(-7):因為符號位為“1”,表示是一個負數,所以該位不變,仍為“1”;其余7位1111001取反后為0000110;再加1,所以是10000111。28,二叉排序樹中的查找和插入刪除bool SearchBST(BiTree T,int key,BiTree f,BiTree &p){//二叉排序樹查找//在根T所指二叉排序樹中找關鍵字為key的節點,找到則p指向該結果并返回true//否則p指向查找路徑上訪問的最后一個結點并返回false,f指向T的雙親,初始為NULLif(!T){p=f;return false;}else if(T->data==key){p=T;return true;}else if(key<T->data)return SearchBST(T->lchild,key,T,p);else return SearchBST(T->rchild,key,T,p);}bool InsertBST(BiTree &T,int e){//二叉排序樹插入//當二叉排序樹中不存在關鍵字e時插入e并返回true,否則返回falseif(!SearchBST(T,e,NULL,p)){s=(BiTree)malloc(sizeof(BiTNode));s->data=e;s->lchild=s->rchild=NULL;if(!p)T=s;//如果是空樹else if(e<p->data)p->lchild=s;//插入的位置一定是葉子else p->rchild=s;return true;}elsereturn false;}bool DeleteBST(BiTree &T,int key){//二叉查找樹中如存在關鍵字key則刪除之,返回trueif(!T)return false;//不存在關鍵字等于key的數據元素else{if(key==t->data)return Delete(T);else if(key<T->data)return DeleteBST(T->lchild,key);else return DeleteBST(T->rchild,key);}}//DeleteBSTbool Delete(BiTree &p){//從二叉排序樹中刪除結點p,并重接它的左或右子樹if(!p->rchild){//作了Delete(p)后刪除了結點p,并p指向代替p的結點q=p;p=p-> lchild;free(q);//右子樹空則只需要重接它的左子樹}else if(!p->lchild){q=p;p=p-> rchild;free(q);}else{q=p;s=p->lchild;//找前驅代替p,往左一步,然后一直往右while(s->rchild){q=s;s=s->rchild};//前驅一定是只有一個子樹的!!!p->data=s->data;//把p的值用它前驅的值代替if(q!=p)q->rchild=s->lchild;//p的左孩子有右子樹else q->lchild=s->lchild;//表示p往左一步之后,p的左孩子沒有右子樹free(s);}}29,哈希表的查找與插入!(各種處理沖突的方法要理解)int hashsize[]={997,…};//哈希表容量遞增表,一個合適的素數列表typedef struct{//哈希表結構體ElemType *elem;//存儲元素的數組int count;//當前含有元素個數int sizeindex;//hashsize[sizeindex]為當前容量}HashTable;Status SearchHash(HashTable H,KeyType K,int &p,int &c){在H中查找關鍵碼為K的元素p=Hash(K);//求得哈希地址,哈希地址一般即為數組元素下標while(H.elem[p].key!=NULLKEY&&!EQ(K,H.elem[p].key))//如果沒有找到,但值得再找collision(p,++c);//求得下一哈希地址,如何解決沖突,有許多方法,如再哈希if(EQ(K,H.elem[p].key))return SUCCESS;//上一行c表示沖突次數,初始為0else return UNSUCCESS;}Status InsertHash(HashTable &H,Elemtype e){ //查找不成功時,插入數據e到哈希表中 c=0;if(SearchHash(H,e.key,p,c))return DUPLICATE;//查找成功else if(c<MAXC){//如果沖突次數未達到最大H.elem[p]=e;++H.count;return OK;//p值在SearchHash函數中已經定位好}else{RecreateHashTable(H);return UNSUCCESS;}//沖突到上限,重建Hash表}30,B樹查找過程的偽碼:int SearchBTree(BTree T,int K){//在T中找關鍵字K,返回結點或返回K應插入的位置p=T;q=NULL;found=FALSE;i=0;//p指向待查找結點,q指向p的雙親while(p&&!found){i=Search(p,K);//在p->key[1…keynum]中查找if(i>0&&p->key[i]==K)found=TRUE;else{q=p;p=p->ptr[i];}} if(found)return(p,i,1);//1表示在p結點的第i個key處找到了else return(q,i,0);//0表示在q結點的第i個key處應該插入K}每個結點的形狀如下所示:(n,A0,K1,A1,K2,…,Kn,An)//Ai表示指針,也即p->ptr[i]中的ptr[i]。Ki表示關鍵字,即p->key[1..n]中的k[i],n表示該結點中有的關鍵字的個數。B樹的插入刪除情況,看《數據結構》,要能夠畫出圖。31,求二叉樹中節點的最大距離(遞歸,思考非遞歸如何實現)根據相距最遠的兩個節點一定是葉子結點這個規律,我們可以分兩種情況:如果經過根結點,則該兩個結點U和V一定是左右子樹中離根結點最遠的結點;如果不經過根結點,則它們一定屬于左右子樹之一,問題遞歸。根據以上分析,可用分治法來解,代碼如下:struct NODE{NODE* pLeft;//左孩子NODE* pRight;//右孩子int nMaxleft;//左子樹中的葉子到根最長距離int nMaxright;//右子樹中的葉子到根最長距離char chValue;//本結點的值};int nMaxLen=0;//尋找樹中最長的兩段距離void FindMaxLen(NODE* pRoot){if(pRoot==NULL)return;//如果是葉子結點,返回if(pRoot->pLeft==NULL)pRoot->nMaxLeft=0;//左子樹葉子到根最長距離為0if(pRoot->pRight==NULL)pRoot->nMaxRight=0;//右子樹為空情況if(pRoot->pLeft!=NULL)FindMaxLen(pRoot->pLeft);//左右子樹不空時分別遞歸if(pRoot->pRight!=NULL)FindMaxLen(pRoot-> pRight);// 遞歸時可能會改變nMaxLenif(pRoot->pLeft!=NULL){//計算左子樹葉子到根最長距離int nTempMax=0;if(pRoot->pLeft->nMaxLeft> pRoot->pLeft->nMaxRight)nTempMax= pRoot->pLeft->nMaxLeft;elsenTempMax= pRoot->pLeft->nMaxRight;pRoot->nMaxLeft=nTempMax+1;//取左子樹的左右子樹中較長者加1}if(pRoot-> pRight!=NULL){//計算右子樹葉子到根最長距離int nTempMax=0;if(pRoot-> pRight ->nMaxLeft> pRoot-> pRight ->nMaxRight)nTempMax= pRoot-> pRight ->nMaxLeft;elsenTempMax= pRoot-> pRight ->nMaxRight;pRoot->nMaxLeft=nTempMax+1;//取右子樹左右子樹中較長者加1}if(pRoot->nMaxLeft+pRoot->nMaxRight>nMaxLen)//更新最長距離nMaxLennMaxLen=(pRoot->nMaxLeft+pRoot->nMaxRight;}32,由前序中序或者中后序建二叉樹(遞歸,思考非遞歸實現)給定前序中序序列,要求重建二叉樹。方法是:先掃前序的第一個字符,建根,然后以前序的第一個字符為分界,把中序劃成兩段,分別遞歸。代碼如下:#define TREELEN 6//為后序調用實現簡單,直接用宏定義結點數目struct NODE{NODE* pLeft;NODE* pRight;char chValue;};void ReBuild(char* pPreOrder,char* pInOrder,int nTreeLen,NODE** pRoot){//已經驗證if(pPreOrder==NULL||pInOrder==NULL)return;//檢查邊界條件,前中序是否有一為空NODE* pTemp=new NODE;pTemp->chValue=*pPreOrder;pTemp->pLeft=NULL;pTemp->pRight=NULL;if(*pRoot==NULL)*pRoot=pTemp;//如果節點為空,把當前結點復制到根結點if(nTreeLen==1)return;//如果當前樹長度為1,那么已經是最后一個結點了char* pOrgInOrder=pInOrder;//記錄初始中序串起始位置char*pLeftEnd=pInOrder;//記錄中序序列中樹以左最末的位置int nTempLen=0;//臨時記錄左子樹的長度while(*pPreOrder!=*pLeftEnd){if(pPreOrder==NULL||pLeftEnd==NULL)return;nTempLen++;if(nTempLen>nTreeLen)break;pLeftEnd++;} int nLeftLen=0;nLeftLen=(int)(pLeftEnd-pOrgInOrder);//計算左子樹長度int nRightLen=0;nRightLen=nTreeLen-nLeftLen-1;//計算右子樹長度if(nLeftLen>0)ReBuild(pPreOrder+1,pInOrder,nLeftLen,&((*pRoot)->pLeft));if(nRightLen>0)ReBuild(pPreOrder+nLeftLen+1,pInOrder+nLeftLen+1,nRightLen,&((*pRoot)->pRight));//想想為什么pRoot是NODE**類型?}33,分層遍歷二叉樹(用隊列)void PrintNodeByLevel(NODE* root){//已驗證if(root==NULL)return;vector<NODE*>vec;vec.push_back(root);int cur=0;int last=1;while(cur<vec.size()){last=vec.size();while(cur<last){cout<<vec[cur]->chValue<<” “;if(vec[cur]->pLeft)vec.push_back(vec[cur]->pLeft);if(vec[cur]->pRight)vec.push_back(vec[cur]->pRight);cur++;}cout<<endl;}}34,哈夫曼編碼算法,已測試void buildHuffmanTree(HTNode *HT,int n){//經過測試的,呵呵,不錯for(int i=n;i<2*n-1;i++){//得到哈夫曼樹int s1,s2;select(HT,i-1,s1,s2);//從HT的前i-1個元素中,選擇parent為0的最小兩個HT[s1].parent=i;HT[s2].parent=i;HT[i].lchild=s1;HT[i].rchild=s2;HT[i].val=HT[s1].val+HT[s2].val;}}string* buildHCode(HTNode *HT,int n){//由哈夫曼構造哈夫曼編碼string *HC=new string[n];char *cd=new char[n];for(int j=0;j<n-1;j++)cd[j]='*';//初始化cd[n-1]='/0';//字符串尾for(int i=0;i<n;i++){int start=n-1;//從葉子開始往根走,對應編碼從尾往前int c,f;//c表示當前結點,f表示其父親for(c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent){//當其父不是樹根時退出if(HT[f].lchild==c)cd[--start]='0';//如是其父的左孩子,置’0’else cd[--start]='1';否則置1}for(j=0;j<n&&cd[j]=='*';j++);//去掉未滿的編碼內存HC[i]=(char*)&cd[j];}delete cd;return HC;}35,打十槍中90環的各種可能性打印解答:方法一,用10層的循環;方法二,用遞歸。用一個全局的數組store[10]存儲每次的環數,一個全局整數sum記錄可能性的種數。代碼如下://代碼已驗證#include <iostream>using namespace std;int sum=0;int store[10];void output(){sum++;for(int i=0;i<10;i++)cout<<store[i]<<’ ’ ;cout<<endl;}void compute(int score,int num){//score表示剩余的環數,num表剩余可打的槍數,初值是9if(score<0||score>(num+1)*10)return;if(num==0){store[num]=score;;output();return;}for(int i=0;i<=10;i++){store[num]=i;compute(score-i,num-1);}}36,求一個串的各所有子集輸出(組合)算法四步:第一步,從首字符到尾字符循環;第二步,循環到的當前字符加到out串尾,輸出當前的out串;第三步,如果循環到的字符i后面還有字符,遞歸,后面的字符為起始字符,重復二三步;第四步,如果循環到的字符i后面沒有其它字符了,退出循環。void combine(char in[],char out[],int length,int rec int start){ //代碼已驗證//in[],out[]為輸入輸出串,length為輸入串的長度,rec為當前遞歸時,out串尾位置//start為本次遞歸時in[]的循環開始位置int i;//start表示此次遞歸,只考慮in[start]后的元素for(i=start;i<length;i++){out[rec]=in[i];//選擇的起點為i時,輸出,然后遞歸,完事后起點后移out[rec+1]=’/0’;//標記串尾,為輸出用。起點后移時rec并未移,因此printf(“%s”,out);if(i<length-1)combine(in,out,length,rec+1,i+1);}}37,輸m,n兩個數,從數列1,2,3,……中隨意選幾個數,使其和為m求所有的可能,并輸出之(0-1背包類似,重量(可取的數的個數)不限,只要求價值總和等于m)void calFun(int m,int n){ //代碼已驗證if(m<1||n<1||(n==1&&m!=1))return;//遞歸退出條件if(m==n){//如果找到了一個解(n本身即為m)pOut[n]=1;//pOut[i]=1表示整數i選,為0表不選,初始全為0for(int i=1;i<=nVal;i++)if(pOut[i])cout<<i<<”,”;cout<<endl;pOut[n]=0;//回溯,因要求所有可能,而pOut[]為全局變量,所以不能影響后續求解}calFun(m,n-1);//不取n時進行遞歸,因為初始pOut[n]=0,所以不需要管pOut[n]=1;//取n,如果m==n,這里遞歸也會在下一輪時因為m<1返回,所以沒關系calFun(m-n,n-1);//進行遞歸pOut[n]=0;//回溯,防止影響后續求解}38,漢諾塔的非遞歸,遞歸實現遞歸實現,不需要多說: void Hannoi(int n,char a,char b,char c) {if(n==1)Move(1,a,c);else{Hannoi(n-1,a,c,b);Move(n,a,c);Hannoi(n-1,b,a,c);} } 非遞歸實現的方法思想:當盤子的個數為n時,移動的次數應等于2^n - 1(有興趣的可以自己證明試試看)。后來一位美國學者發現一種出人意料的簡單方法,只要輪流進行兩步操作就可以了。首先把三根柱子按順序排成品字型,把所 有的圓盤按從大到小的順序放在柱子A上,根據圓盤的數量確定柱子的排放順序:若n為偶數,按順時針方向依次擺放 A B C;若n為奇數,按順時針方向依次擺放 A C B。 (1)按順時針方向把圓盤1從現在的柱子移動到下一根柱子,即當n為偶數時,若圓盤1在柱子A,則把它移動到B;若圓盤1在柱子B,則把它移動到C;若圓盤1在柱子C,則把它移動到A。 (2)接著,把另外兩根柱子上可以移動的圓盤移動到新的柱子上。即把非空柱子上的圓盤移動到空柱子上,當兩根柱子都非空時,移動較小的圓盤。這一步沒有明確規定移動哪個圓盤,你可能以為會有多種可能性,其實不然,可實施的行動是唯一的。 (3)反復進行(1)(2)操作,最后就能按規定完成漢諾塔的移動。代碼如下: struct st{//用來表示每根柱子的信息,其實就是棧int s[MAX]; //柱子上的圓盤存儲情況int top; //棧頂,用來最上面的圓盤char name; //柱子的名字,可以是A,B,C中的一個int Top(){//取棧頂元素 return s[top];}int Pop(){//出棧 return s[top--];}void Push(int x){//入棧 s[++top] = x;}};void Creat(st ta[], int n){ta[0].name = 'A';ta[0].top = n-1;//把所有的圓盤按從大到小的順序放在柱子A上for (int i=0; i<n; i++)ta[0].s[i] = n - i;//柱子B,C上開始沒有沒有圓盤ta[1].top = ta[2].top = 0;for (i=0; i<n; i++)ta[1].s[i] = ta[2].s[i] = 0;//若n為偶數,按順時針方向依次擺放 A B Cif (n%2 == 0){ta[1].name = 'B';ta[2].name = 'C';}else{ //若n為奇數,按順時針方向依次擺放 A C Bta[1].name = 'C';ta[2].name = 'B';}}void Hannuota(st ta[], long max){int k = 0; //累計移動的次數int i = 0;int ch;while (k < max){//按順時針方向把圓盤1從現在的柱子移動到下一根柱子ch = ta[i%3].Pop();ta[(i+1)%3].Push(ch);cout << ++k << ": " <<"Move disk " << ch << " from " << ta[i%3].name <<" to " << ta[(i+1)%3].name << endl;i++;//把另外兩根柱子上可以移動的圓盤移動到新的柱子上if (k < max){ //把非空柱子上的圓盤移動到空柱子上,當兩根柱子都空時,移動較小的圓盤if (ta[(i+1)%3].Top() == 0 ||ta[(i-1)%3].Top() > 0 &&ta[(i+1)%3].Top() > ta[(i-1)%3].Top()){ch = ta[(i-1)%3].Pop();ta[(i+1)%3].Push(ch);cout << ++k << ": " << "Move disk "<< ch << " from " << ta[(i-1)%3].name<< " to " << ta[(i+1)%3].name << endl;}else {ch = ta[(i+1)%3].Pop();ta[(i-1)%3].Push(ch);cout << ++k << ": " << "Move disk "<< ch << " from " << ta[(i+1)%3].name<< " to " << ta[(i-1)%3].name << endl;}//else}//if}//while}int main(void){int n;cin >> n; //輸入圓盤的個數st ta[3]; //三根柱子的信息用結構數組存儲Creat(ta, n); //給結構數組設置初值long max = Pow(2, n) - 1;//動的次數應等于2^n - 1Hannuota(ta, max);//移動漢諾塔的主要函數return 0;}39,謎宮求解typedef struct{int ord;//通道塊在路徑上的序號PopType seat;//通道在謎宮中的坐標位置int di;//從此通道塊走向下一塊通道塊的“方向”}SElemType;bool MazePath(MazeType maze,PosType start,PosType end){//找謎宮maze從start到end的路InitStack(S);curpos=start;//設定當前位置為startcurstep=1;//探索的第一步do{if(Pass(curpos)){//如果當前位置是可通過的FootPrint(curpos)//留下路跡,打印一下遍歷過程,這行可不要e= SElemType (curstep,curpos,1);push(S,e);//加入到路徑棧中if(curpos==end)return true;//到達出口curpos=NextPos(curpos,1);//下一位置是當前位置的東鄰curstep++;}else{//如果當前位置是不可通過的,看棧頂元素再作決定if(!StackEmpty(S)){//如果棧上有元素pop(s,e);while(e.di==4&&! StackEmpty(S)) pop(s,e);//直到棧頂元素有下一方向為止if(e.di<4){//如果棧中彈出的元素有方向可走e.di++;push(S,e);//標記為探索下一個方面curpos=NextPos(e.seat,e.di);//將該方向上的相鄰塊置為當前塊}}}while(!StackEmpty(S));}return false;}40,后綴表達式求值OperandType EvaluateExpression(){InitStack(OPTR);Push(OPTR,’#’);//初始化符號棧InitStack(OPND);c=getchar();//初始化操作數棧while(c!=’#’||GetTop(OPTR)!=’#’){如果得到的不是結束符if(!In(c,OP)){Push(OPND,c);c=getchar();}//如果不是操作符,則直接進棧elseswitch(Precede(GetTop(OPTR),c)){//如是操作符則比較它與棧頂符的優先級case ‘<’:Push(OPTR,c);c=getchar();break;//棧頂元素優先級低case ‘=’:Pop(OPTR,x);c=getchar();break;//如果是括號,則消去一對case ‘>’:Pop(OPTR,theta);//如果優先級大則原來的部分先運行Pop(OPND,Operate(a,theta,b)); //注意此處沒有getchar();break;//所以會繼續判斷所得符號與棧頂元素的優先級}}//whilereturn GetTop(OPND);//返回最后的操作數棧頂元素,正確時應該棧只有一個元素}41,十進制數N轉為d進制數,一個簡單的實現方法基于如下原理:N=(N/d)*d+N%d,代碼借助棧實現如下:void conversion(){stack<int> s;int N;cin>>N;while(N){s.push(N%8);N=N/8;} while(!s.empty()){cout<<s.top();s.pop();}}42,int maxPrimeSub(int n); n<10,000,000。求n的所有子串中的最大的質數。如 n=5617,則為617。int maxPrimeSub(int n){// 明顯應該從最長的串開始判斷起!先是n,然后減去首尾遞歸if(isPrime(n))return n;else{int higher=n/10;int lower;int highest=1;while((lower=n/highest)!=0)highest*=10;lower=n-n/(highest/10)*(highest/10);lower=maxPrimeSub(lower);higher=maxPrimeSub(higher);return lower>higher?lower:higher;}}bool isPrime(int n){if(n%2==0&&n>2)return false;//大于2的偶數else for(int i=3;i*i<=n;i+=2){if(n%i==0)return false;}return true;}另:EMC筆試題,求一個非負整型數組非鄰接子序列的最大和,要求O(N)一遍掃瞄,記住前兩個位置的解f(i)=max(a[i]+f(i-2),f(i-1))43,一棵樹,每個節點有三個域,data域,child域指向孩子節點,next域指向右邊最近的兄弟節點。 寫程序,將所有的堂兄弟節點都 連起來。(比如一個節點A及其右兄弟節點B,A的子節點有CDEF,B的子節點有GH,初始狀態是沒有F到G的指針的,現在讓你把這樣的空缺都連起來)。劉光遠寫出遞歸的形式,讓改成非遞歸的。用層序遍歷的隊列方式。44, 有一個矩形區域,分成N*M個格子,求從最左上角的格子走到最右下角的格子的最短時間,不能斜著走,只能豎著或者橫著走,而且格子與格子之間花費的時間是不同的 。同樣,不讓用遞歸。三種方法:一,遞歸,右下角的一定是通過前兩個格子中最小路徑得到的;二,是用動態規劃的填表法;三,這其實是一個求單源最短路徑的問題,因此可用dijstra最短路徑遞增的貪心算法,用圖表示寫代碼可能會難一點,動態規劃的填表法更易寫代碼。45,匹配字符串,模式串為正則串,待匹配串為一長字符串,如果待匹配串中包含模式串,則返回1,否則返回0; 正則串包含字母,'.' 匹配任意字符,'*"匹配0或者任意多個前一個字符。我的非遞歸解法:int next(const char *t,int pos,char anych){//在串t之后找第一個不等于anych的位置距pos的長度for(int i=pos;i<strlen(t);i++)if(t[i]!=anych)break;return i-pos;}int match(const char *t,const char *p){ //p為模式串,帶有正則表達式符號,t為待匹配字符串//匹配成功返回位置,否則返回-1if(t==NULL||p==NULL)return -1;//對空串的處理,當然也可以其它處理while(*p==’*’)p++;//之前必須增加對p中若第一個是*號的跳過處理for(int i=0;i<strlen(t);i++){int oldi=i;char anych;for(int j=0;j<strlen(p);j++){if(t[i]==p[j]){i++;}//如果是普通字符相等else if(p[j]=='.'){anych=t[i];i++;}//如果是任意字符跳一步else if(p[j]=='*'&&p[j-1]=='.'){i+=next(t,i,anych);}//如果是*else if(p[j]=='*'&&p[j-1]!='.'){i+=next(t,i,t[i-1]);}//則跳過若干字符else break;} if(j==strlen(p))return oldi;i=oldi;}return -1;}int main(int argc,char *argv[]){cout<<match("fdasfldjaklfjaf","j*.f")<<endl;cout<<match("","")<<endl;cout<<match("ababbccddeefghijfg","ab.c*d..e*f")<<endl;cout<<match("ababcddeefgh","ab.c*d..e.*f")<<endl;cout<<match("sccas",".*c*")<<endl;cout<<match("aac341bb",".c.*...")<<endl;cout<<match("dfdcc234ad",".*c*...*")<<endl;return 0;}不難寫成遞歸解法,把里面一個for循環寫成遞歸的形式,就是對于t的每一個后綴串,看是否某個串的前綴能完全匹配p,循環到第一個這樣的后綴時,返回此后綴首下標;遞歸中判斷當前字符,以決定跳若干步之后再遞歸調用。為保留next函數中可訪問p[i-1]的內容,可在遞歸時,把p[i-1]的地址作為參數。46, 兩個相同長度的整型數組A、B,長度為n?,F從兩個數組中,各抽出S個數,形成兩個新的數組C、D。實現一個算法,保證數組CD的內積最大,并分析時間和空間復雜度。題目其實就是要求在兩個數組中找出積最大的前S對數:1. 對兩個數組按降序排列,2. 設置兩對指針,分別指向兩個數組的首尾元素,3. 計算首尾指針對指向元素的積,比較兩個積的大小,取乘積較大的兩個元素,4. 指向乘積較大的元素對的指針向前移動(或往回移動,對于尾指針對),元素對計數器加15. 重復1-4,直到找到S對元素。這是基于在給定數組中,對于所有正數,兩個最大正數的乘積必定是最大,對于所有負數,兩個最小負數的乘積必定是最大,但是它們哪個更大則不確定;至于時間復雜度主要看排序算法47,一個網頁,在每天記錄一個訪問量,然后給你一個時間區間,找出這個時間區間內訪問量最多的那一天??赡芙洺W鲞@個查詢操作,而且區間大小是隨意的。請問如果設計 以保證高性能。(歐陽劉彬說對時間做B+索引)。我想到的一個方法是:RMQ(Range Minimum/Maximum Query)問題是求區間最值問題。你當然可以寫個O(n)的,但是萬一要詢問最值1000000遍,估計你就要掛了。這時候你可以放心地寫一個線段樹(前提是不寫錯)O(logn)的復雜度應該不會掛。但是,這里有更牛的算法,就是ST算法,它可以做到O(nlogn)的預處理,O(1)地回答每個詢問。來看一下ST算法是怎么實現的(以最大值為例): 首先是預處理,用一個DP解決。設a[i]是要求區間最值的數列,f[i,j]表示從第i個數起連續2^j個數中的最大值。例如數列3 2 4 5 6 8 1 2 9 7 ,f[1,0]表示第1個數起,長度為2^0=1的最大值,其實就是3這個數。f[1,2]=5,f[1,3]=8,f[2,0]=2,f[2,1]=4……從這里可以看出f[i,0]其實就等于a[i]。這樣,Dp的狀態、初值都已經有了,剩下的就是狀態轉移方程。我們把f[i,j]平均分成兩段(因為f[i,j]一定是偶數個數字),從i到i+2^(j-1)-1為一段,i+2^(j-1)到i+2^j-1為一段(長度都為2^(j-1))。用上例說明,當i=1,j=3時就是3,2,4,5 和 6,8,1,2這兩段。f[i,j]就是這兩段的最大值中的最大值。于是我們得到了動規方程F[i,j]=max(F[i,j-1],F[i+2^(j-i),j-1])。接下來是得出最值,也許你想不到計算出f[i,j]有什么用處,一般毛想想計算max還是要O(logn),甚至O(n)。但有一個很好的辦法,做到了O(1)。還是分開來。如在上例中我們要求區間[2,8]的最大值,就要把它分成[2,5]和[5,8]兩個區間,因為這兩個區間的最大值我們可以直接由f[2,2]和f[5,2]得到。擴展到一般情況,就是把區間[l,r]分成兩個長度為2^n的區間(保證有f[i,j]對應)。直接給出表達式:k:=ln((r-l+1)/ln(2)); ans:=max(F[l,k],F[r-2^k+1,k]);這樣就計算了從i開始,長度為2^t次的區間和從r-2^i+1開始長度為2^t的區間的最大值(表達式比較煩瑣,細節問題如加1減1需要仔細考慮48,有一個整數n,將n分解成若干個整數之和,問如何分解能使這些數的乘積最大,輸出這個乘積m。例如:n=12(1)分解為1+1+1+…+1,12個1, m=1*1*1……*1=1(2)分解為2+2+…+2,6個2, m=64(3)分解為3+3+3+3,4個3, m=81(4)分解為4+4+4,3個4, m=64。。。。。。。。。。。。。。。顯然,3最好。解答: 假設輸入為N,可以分解成為K個相同的數R以及一個余數W的集合。N = K*R + W;那么問題轉化為求:Val = R^K * W的極值問題,條件是N=K*R+W。為使問題簡化,先不考慮W,之后再作討論。于是問題轉化為:條件為N1=K*R時,求Val=R^K的極值(心中應該記住,終極任務是求R,因此K可以試著換成N1/R表示,也即,在Val中,只把R當成變量)。Val= R^K; 兩邊取對數,有ln(Val) = (N1/R)ln(R) ,令F(R) = (N1/R)ln(R),對這個式子取導數利用求導公式(u/v)'=(u'v-uv')/v^2,得到下式 :f(R) = N1*(1-lnR)/(R^2) ,當這個f(R) 為零的時候,F(R)取到極值 。因此,當R = e 的時候,取到極值(e= 2.7*****),因此取到R =3現考慮余數情況,因除數為3,余數只有兩種情況,1或2,當為1時,顯示應該讓其中一個數為4,而當余數為2時,顯示應該把2提出來,乘以其余分解得到的3。因為2*3>2+349,數據結構134頁中序遍歷線索二叉樹,及二叉樹的中序線索化!bool InOrderTraverse_Thr(BiThrTree T){// 中序遍歷線索二叉樹p=T->lchild;//T指向頭結點,p指向根結點while(p!=T){while(p->LTag==’LINK’)p->plchild;//中序的第一個結點,第一個沒有左子樹的結點if(!visit(p))return false;//訪問自己while(p->RTag==’Thread’&&p->rchild!=T){//若右子樹無則用線索訪問p=p->rchild;visite(p);}p=p->rchild;//至某個有右子樹的結點后,移到右子樹上,繼續回到第一步循環return true;}}bool InOrderThreading(BiThrTree &Thrt,BiThrTree T){//中序遍歷二叉樹T并完成中序線索化,Thrt指向線索化后的頭結點if(!(Thrt=(BiThrTree)malloc(sizeof(BiThrNode))))exit(OVERFLOW);Thrt->LTag=”Link”;Thrt->RTag=”Thread”;//左孩子指向根Thrt->rchild=Thrt;//右孩子指針對性先回指,if(!T)Thrt->lchild=Thrt;//如果樹空,則左孩子指針也回指else{Thrt->lchild=T;pre=Thrt;//pre指向前驅InThreading(T);//中序遍歷進行線索化pre->rchild=Thrt;pre->RTag=Thread;//pre指向最后一結點,最后一個結點線索化Thrt->rchild=pre;//Thrt與末結點互指}return true;}void InThreading(BiThrTree p){if(p){InThreading(p->lchild);//左子樹線索化if(!p->lchild){p->LTag=Thread;p->lchild=pre;}//前驅線索化if(!pre->rchild){pre->RTag=Thread;pre->rchild=p;}//后繼線索化pre=p;//切換前驅InThreading(p->rchild);//右子樹線索化}}50,電話號碼對應英語單詞number[i]存放電話電碼的第i個數字;answer[i]用來存放這個電話在按鍵中的第幾個字母;c[10][10]={“”,””,”ABC”,”DEF”,”GHI”,”JKL”,”MNO”,”PQRS”,”TUV”,”WXYZ”};int total[10]={0,0,3,3,3,3,3,4,3,4};如果電話號碼第一位數字是4,answer[0]是0,c[number[0]][answer[0]]=G;于是遍歷各種可能的字母串的代碼如下:while(true){ //代碼已驗證//n為電話號碼長度,初始化answer[n]數組各元素均為0for(int i=0;i<n;i++)cout<<ca[number[i]][answer[i]];//先輸出一種可能cout<<endl;int k=n-1;//從最后一位電話號碼的各個可能answer[i]開始遍歷起while(k>=0){if(answer[k]<total[number[k]]-1){answer[k]++;break;//把第k的號碼的可能對應的字符換一下,繼續輸出,輸出后k=n-1}else{answer[k]=0;k--;//把answer[k]置0,然后,開始考慮前一個電話號碼,注//意此處無break;}}if(k<0)break;}遞歸算法也許更好理解:void RecursiveSearch(int *number,int *answer,int index,int n){//已經讀取到了number[index]if(index==n){//下標最大為n-1,所以為n時表找到一個解,直接輸出,并且返回for(int i=0;i<n;i++)printf(“%c”,c[number[i]][answer[i]]);printf(“/n”);return;}for(answer[index]=0;answer[index]<total[number[i]];answer[index]++){RecursiveSearch(number,answer,index+1,n);//考慮當前字符各種情況,然后遞歸//注意用answer[n]數組來保存遞歸時各種分支的選擇,傳給葉子}}51,編程之美:不要被階乘嚇倒階乘(Fctorial)N!。問題為:1),給定一個整數N,那么N!末尾有多少個零呢?例如N=10,N!=3628800,答案為2;2),求N!中二進制表示中最低位1的位置解答:(1)規律是,每出現一個能被5整除的數,便會往末尾貢獻一個0,因為5之前肯定出現了2,4等數字,相乘之后一定是10的倍數,每出現一個能被5^2整除的數貢獻兩個0……。因此,表示為公式是:Z=[N/5]+[N/5^2]+[N/5^3]+……[N/5]表示小于N的數中,能被5整除的數的個數,[N/5^2] 表示小于N的數中,能被5^2整除的數的個數……。代碼求解零的個數如下:ret=0;while(N){ret+=N/5;//N/5表示小于N的數中,5的倍數有多少個N/=5;//N/=5之后第二輪循環計算的是小于N的數中5^2的倍數}解答:(2)問題與(1)一樣,只是數制變為2進制,代碼如下:int lowerstOne(int N){int Ret=0;while(N){N>>=1;//順序跟上面不一樣,上面也可以寫成這樣,結果是一樣的Ret+=N;}return Ret;}52,編程之美:1的數目(十進制里面,分個位數,十位數,百位數等一個個分析,得出規律),第二問記住答案!問題是:(1),寫一個函數f(N),返回1到N之間,出現的“1”的個數,比如f(12)=5;(2)在32位整數范圍內,滿足條件”f(N=N)”的最大的N是多少?解答:(1)分析如下,假設N=abcde,如現在需要計算百位數上出現1的次數,分三種情況:如果百位數上為0,則可知道百位數上可能出現1的次數由更高位決定,比如12013,百位數上出現1的情況可能是100—199,1100—1199,2100—2199,……共1200個。也就是由比百分位高的部分的數字12所決定,并且等于12*100(百位數代表的值);如果百位數上為1,則可知百位上出現1的情況不僅包括剛才的,還包括百位數為1時的情況,比如12113比12013多了12100—12113這14個1,這是受低位決定的,等于低位數字的值+1(13+1)。如果百位上的數字大于1(即為2—9),則百位上可能出現1的情況也只由高位數字決定,為(高位數字+1)*100。由于以上分析,代碼如下:long Sum1s(ulong n){ulong iCount=0;//1的個數計數器ulong iFactor=1;//iFactor每次循環按10,100,1000等遞增ulong iLowerNum=0;//低位數字,即從當前位往右的所有數字之值ulong iCurrNum=0;//當前位ulong iHigerNum=0;//高位數字,即從當前位往左的所有數字之值while(n/iFactor!=0){//n大于iFactoriLowerNum=n-(n/iFactor)*iFactor;iCirrNum=(n/iFactor)%10;iHigerNum=n/(iFactor*10);switch(iCurrNum){case 0:iCount+=iHigerNum*iFactor;break;case 1:iCount+=iHigerNum*iFactor+iLowerNum+1;break;default:iCount+=(iHigerNum+1)*iFactor;break;}iFactor*=10;//每次循環改變當前位數}return iCount;}53,編程之美:子數組中N-1項的最大乘積(掃瞄一遍,記住符號,最小負數,0的個數等,分析符號即可得解)54,判斷鏈表相交的幾種方法:尾結點是否相同;結點的地址計數;一個尾鏈到另一個首,看是否會出現環;55,將中綴表達式轉換為逆波蘭式,方法:1) 構造一運算符棧,此棧中運算符遵循越往棧頂,優先級越高的原則。2) 中綴表達式首尾為’#’字符,優先級最低,各符號的優先級關系必須有個優先級表。3) 從左到右分析中綴串,如遇數字,則輸出;如遇運算符,則比較優先關系,:如果該運算符優先級高于棧頂元素則入棧,否則將棧頂元素彈出并輸出,直至棧頂元素優先級低于掃到的運算符,再將掃到的運算符進棧。4) 重復3)直至串尾,最后連續將棧退空。56,整數分割問題。將一個正整數表示為一系列整數之和,如3=3,3=1+2,3=1+1+1有3種表示法,4=4,4=3+1,4=2+1+1,4=2+2,4=1+1+1+1共有5種表示法。6有: 65+14+2,4+1+13+3,3+2+1,3+1+1+12+2+2,2+2+1+1,2+1+1+1+1+11+1+1+1+1+1共11種表示方法。解答:用q(n,m)表示找出n劃分為不大于m的整數的劃分種數,如:q(6,4)=9遞歸式有:1 m=1或n=1q(n,n) n<mq(n,m)= 1+q(n,n-1) n=mq(n,m-1)+q(n-m,m) n>m>157,歸并排序求逆序對思想:歸并排序的同時,記錄逆序對的數目,每次將某兩相鄰字段s1和s2進行插入歸并時,若所選最小元素來自于s2的第j個元素(記作s2j)時,逆序對數目Total+=s1.length-i(其中i為歸并時s1中當前的下標),因為s1中i之后的元素也必與s2中的j元素是逆序對(比s2j大)。58,1,0序列生成(有道難題)一個二進制序列由下面的偽代碼生成: String A = "0" While (A的長度小于等于n)創建一個和A一樣長度的字符串BFor i=0,1,...length(A)-1If (i 是完全平方數)B[i] = 1-A[i]ElseB[i] = A[i]令A = A + B (即將B拼接在A后面) End While Return A 請注意,在上面的偽代碼中,A[i]和B[i]分別表示字符串A和B中下標為 i 的字符(下標編號從0開始)。對“完全平方數”的定義是,對于整數 i,存在整數 j,使得 i = j * j,則稱 i 為完全平方數。下面具體說明序列生成的過程:如果n=7,則在每一輪迭代中,A的取值依次為:0, 01, 0110, 01101010,所以最終產生的二進制序列就是0,1,1,0,1,0,1,0 請返回上述序列中下標為n的數字(該序列的下標從0開始)n=0 輸出0 n=1 輸出1 如果n不是2的冪時,它會夾在兩個2的冪中間,設為k<n<m,那么n就是由位于(n-k)的字符變化而來,所以可以先得到(n-k),再取n。public int getValue(int n){ if(n==0) return 0;else if(n==1) return 1;else{long h = 1;while(h < n) h=h<<1;int m = (int)(n-(h>>1)); if(isSquare(m)) return 1-getValue(m);else return getValue(m);}}以下部分主要為動態規劃:59,矩陣連乘問題此類問題屬于動態規劃法解決范圍。動態規劃法能解決的問題,基本上都可以用遞歸方法解決。使用動態規劃法的好處在于,動態規劃法是自底向上計算,在計算過程中保存子問題的解,避免了重復計算,直接的遞歸調用則會導致重復計算,浪費時間。因此,動態規劃法的兩個重要特點是:最優子結構的遞歸特性和重疊子問題。遞歸方法解決時,可以采用備忘錄的方法,同樣達到動態規劃的目的。動態規劃法的另一個重要特征是“選擇”,每一步都是作一次選擇,在前一次已經計算過子問題的最優選擇的基礎上,當前選擇如何得到(可能通過一次循環來嘗試各個值),以及如何將這一選擇描述成問題的解。根據這些問題的特點可知。求解動態規劃法類型的問題,第一步就是抽象成數學表示并列出遞歸式,有了遞歸式,便可寫出偽代碼。矩陣連乘問題抽象成數學問題:第一步,使用一個數學式子來表示最優解。令m[i][j]表示矩陣A[i…j]連乘時,所需要的最小相乘次數,p[n+1]存儲的是每個矩陣的行數及最后一個矩陣的列數。第二步,由第一步可得遞歸式為:第三步,寫出(偽)代碼:void MatrixChain(int *p,int n,int **m,int **s){//s用來保存m[i][j]的劃分點for(int i=1;i<=n;i++)m[i][i]=0;//初始化邊界條件for(int r=2;r<=n;r++){//鏈長從2到n時,依次考慮,即自底向上for(int i=1;i<=n-r+1;i++){//i為每個鏈首,最后一個鏈首是n-r+1int j=i+r-1;//j為鏈尾,因為p下標從0開始,所以m下標從1開始m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j];//對每個鏈,賦初始劃分值為在鏈首劃分s[i][j]=i;//存儲劃分點for(int k=i+1;k<j;k++){//依次考察從其它點劃分的情況int t=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];if(t<m[i][j]){ //如果從k點劃分更優,則更新解并記下劃分點m[i][j]=t;s[i][j]=k;}}}}}第四步,構造最優解。第三步的算法,只是記錄了最優時的運算次數,并不知從哪劃分,不過由二維數組s可構造出最優解;void TraceBack(int i,int j,int **s){//構造A[i…j]連乘時的各個矩陣運算次序if(i==j)return;TraceBack(i,s[i][j],s);//s[i][j]記錄的是A[i…j]中間的那個劃分點kTraceBack(s[i][j]+1,j,s);cout<<”乘A”<<i<<”,”<<s[i][j]<<”和A”<<s[i][j]+1<<”,”<<j<<endl;}補充一,遞歸解法偽代碼:int RecurMatrixChain(int i,int j){if(i==j)return 0;//遞歸邊界條件int u=RecurMatrixChain(i,i)+ RecurMatrixChain(i+1,j)+ p[i-1]*p[i]*p[j];s[i][j]=i; //上一行最后一項會不斷加起來,而0是遞歸邊界for(int k=i+1;k<j;k++){int t= RecurMatrixChain(i,k)+ RecurMatrixChain(k+1,j)+ p[i-1]*p[k]*p[j];if(t<u){u=t;s[i][j]=k;}}return u;}補充二,備忘錄解法,也是用遞歸,過程與遞歸解法一樣只是增加判斷及記錄兩樣int MemorizedMatrixChain(int n,int **m,int **s){for(int i=1;i<=n;i++)for(int i=1;i<=n;i++)m[i][j]=0;//初始化,遞歸時,判斷到m[i][j]>0則表示計算過,直接返回return LookupChain(1,n);}int LookupChain(int i,int j){if(m[i][j]>0)return m[i][j];//判斷!if(i==j)return 0;int u= LookupChain (i,i)+ LookupChain (i+1,j)+ p[i-1]*p[i]*p[j];s[i][j]=i; //上一行最后一項會不斷加起來,而0是遞歸邊界for(int k=i+1;k<j;k++){int t= LookupChain (i,k)+ LookupChain (k+1,j)+ p[i-1]*p[k]*p[j];if(t<u){u=t;s[i][j]=k;}}m[i][j]=u;//記錄!遞歸時沒有m數組記錄中間結果return u;}時間效率,動態規劃解法為O(n^3),遞歸解法為O(2^n)。60,最長公共子序列問題最長公共子序列中,子序列的定義是一個給定序列的子序列,是在該序列中刪去若干元素后得到的序列。問題為,求給定序列X{x1,x2,…,xm}和Y{y1,y2,…yn},求解其最長的公共子序列。動態規劃法第一步,抽象出數學公式并給出遞歸結構表示。c[i][j]用來表示x1,x2,…xi與y1,y2,…yj的最長公共子序列,則遞歸式表示為:第二步,寫出(偽)代碼:void LCSLength(int m,int n,char *x,char *y, int **c,char ** b){int i,j;//寫出遞歸解法是容易的,但動態規劃用的是自底向上的方式,也即求c[i][j]for(i=1;i<=m;i++)c[i][0]=0;//c數組長度為[m+1]*[n+1]。for(i=1;i<=n;i++)c[0][i]=0;//先初始化其中一個串為0時的解for(i=1;i<=m;i++)for(j=1;j<=n;j++){if(x[i]==y[j]){c[i][j]=c[i-1][j-1]+1;b[i][j]=’↖’;//b[i][j]用來記錄c[i][j]是由哪個子問題得來}else if(c[i-1][j]>=c[i][j-1]){c[i][j]=c[i-1][j];b[i][j]=’^’;}else{c[i][j]=c[i][j-1];b[i][j]=’<’;}}}第三步,構造最優解void LCS(int i,int j,char *x,char **b){if(i==0||b==0)return;if(b[i][j]==’↖’){LCS(i-1,j-1,x,b);cout<<x[i];}else if(b[i][j]==’^’)LCS(i-1,j,x,b);else LCS(i,j-1,x,b);}61,求數組的最大子段和(及二維情況的解答思想)最大子段和的遞歸式思想:每次從中間mid劃開,分別遞歸求兩邊最大子段和,再從中間開始,分別往左右遍歷,得到從…mid和mid+1…(包括mid和mid+1)的最大子段和,二者相加,看它比前兩個遞歸結果是否大,三者取最大者即為解,分治法(遞歸)偽代碼如下:int MaxSubSum(int *a,int left,int right){//調用MaxSumSum(a,1,n)即為整個數組最大子段和if(left==right)return a[left];else{int mid=(left+right)/2;int leftsum=MaxSubSum(a,left,center);int rightsum=MaxSubSum(a,center+1,right);int maxl=maxLeftWithCenter(a,center);//從a[center]往前遍歷求和,記下最大者int maxr=maxRightWithCenter(a,center+1);//從a[center+1]往后求和,記下最大者return max(leftsum,rightsum,max1+maxr);}}動態規劃法算法分析如下。第一步,數學表示及導出遞歸表示。記b[j] (1<=j<=n)表示以元素j結尾的最大子段和,也即由b[j]往左加可得的最大子段和。于是,題目要求的解即是:max(b[j]) (1<=j<=n)。由b[j]的定義易知,當b[j-1]>0時b[j]=b[j-1]+a[j],否則b[j]=a[j]。由此可得計算b[j]的動態規劃遞歸式:b[j]=max{b[j-1]+a[j],a[j]} , 1<=j<=n;據此,可得最大子段和的動態規劃算法。int MaxSum(int n,int *a){int sum=0;//如果最大子段和是負數,則返回0b=0;//b初始時為0for(int i=1;i<=n;i++){if(b>0)b+=a[i];//因為b初始為0所以,第一步執行時,會執行elseelse b=a[i];//第一次執行時,會將a[1]賦給bif (b>sum);sum=b;//只用一個b即可代替b[]的作用,因為每個b[j]只引用b[j-1]}return sum;}時間復雜度僅為O(n)。如果是二維的情況,則需要用兩重循環來確定其中一維的上下邊界。復雜度為O(n*m^2)。62,編程之美:求數組中最長遞增子序列動態規劃法求解的遞歸表示為:LIS[i+1]=max{1,LIS[k]+1},array[i+1]>array[k],for any k<=i;其中LIS[i]表示以array[i]結尾的最長遞增公共子序列的長度,公式意思為,求以array[i+1]結尾的遞增子序列,等于i+1之前的所有遞增子序列加上array[i+1](如果加上之后子序列仍是增的話)之后中最長者,或者1(初始時的值)。代碼如下:int LIS(int *array,int n){int *LIS=new int[n];for(int i=0;i<n;i++){LIS[i]=1;//初始時的值,遞增子序列長度至少為1for(int j=0;j<i;j++){if(array[i]>array[j]&&LIS[j]+1>LIS[i])LIS[i]=LIS[j]+1;}}return Max(LIS);//取LIS數組中的最大值}63,計算串的相似度(編輯距離計算)第一步,數學表示:設六種操作代價分別為c1…c6,令c[i][j]表示為由xj,…,xm變到yi,…,yn的最小代價,從m,n往左匹配,可有如下規律:若當前操作為delete則此前操作xj+1,…,xm變到yi,…,yn的代價最小;若為replace或copy則之前xj+1,…,xm變到yi+1,…,yn的代價最小;若為insert則表示此前xj,…,xm變到yi+1,…,yn的代價最小;twiddle(交換相鄰字符)操作之前xj+2,…,xm變到yi+2,…,yn的代價最小,因為每次都是五選一(kill為邊界條件,計算時決定),所以每步都是比較5種操作的代價,選最小者。第二步,遞歸式為:c[i][j]=min{c[i][j+1]+c1,c[i+1][j+1]+c2, c[i+1][j+1]+c3,c[i+1][j]+c4, c[i+2][j+2]+c5}另設一k[i][j]用來記錄c[i][j]最小時,的操作序號ci(1<=i<=5)顯然,c[i][j]遞歸式的邊界條件有三個:1, c[i][m+1]=(n-i+1)*c4;//源串為空,則變為目標串需要插入n-i+1次2, [n+1][j]=(m-i+1)*c1;//目標串為空,則源串需要刪除m-i+1次,即kill操作3, c[n+1][m+1]=0;//源與目標串均空,不需要操作64,汽車裝配問題,算法導論P195動態規劃法求解第一步,數學表示及遞歸公式如下:這個公式變量含義如下:e1,e2分別表示初始時裝配上1號2號線的時間,a1,j和a2,j分別表示產品在1,2號線上的j個結點上加工的時間,f1[j]和f2[j]分別記錄產品推進到1,2號線上的第j個結點時所費最少時間。t1[j]和t2[j]分別表示從1,2號線的結點j切換到另一線所耗費的時間。由遞歸式,寫出偽代碼解法如下:FATEST-WAY(a,t,e,x,n){//e表示e1,e2;x表示x1,x2,分別表示初始上線及最后下線的代價f[1][1]=e1+a[1][1];//初始化邊界條件,即第一個結點的情況f[2][1]=e2+a[2][1];for j=2…n{if((f[1][j-1]+a[1][j])<=(f[2][j-1]+t[2][j-1]+a[1][j])){f[1][j]=f[1][j-1]+a[1][j];l[1][j]=1;//l[1][j]表示在線1上第j個結點加工的產品上一結點是哪條線}else {f[1][j]= f[2][j-1]+t[2][j-1]+a[1][j];l[1][j]=2;}if((f[2][j-1]+a[2][j])<=(f[1][j-1]+t[1][j-1]+a[2][j])){f[2][j]=f[2][j-1]+a[2][j];l[1][j]=2;//l[2][j]表示在線2上第j個結點加工的產品上一結點是哪條線}else {f[2][j]= f[1][j-1]+t[1][j-1]+a[2][j];l[1][j]=1;}if((f[1][n]+x1)<=(f[2][n]+x2)){//處理最后一個結點,x1表示從線1卸下的時間finish_cost=f[1][n]+x1;//如果從線1結束的時間比從線2早,則在線一完成line_finish=1;}else{//如果從線2結束時間比從線1早,則在線二完成finish_cost=f[2][n]+x2;line_finish=2;}} }構造最優解PRINT-STATIONS(l,line_finish,n){int i=line_finish;print “line”+i+”,station”+nfor(j=n downto 2){i=l[i][j];print “line”+i+”,station”+j-1;}}65,雙調旅行商問題:對平面上給定的n個點,確定一條連接各點的最短閉合旅程的問題。這是NP完全的,有人提議只考慮“雙調旅程”來簡化問題。即先從最左到最右,再回到最左。換言之,設兩個A,B從最左,以不同路徑到達最右,求此路徑之和的最小值。嚴格從左至右,即到某點i時,i左邊的點已經全部走過了。假設兩人A,B,不失一般性,設A在B后,Pij表示A走到點i而B走到點j時的最短路徑,根據假設有i<=j,設b[i][j]表示最短雙調路徑Pij的長度,d[i][j]表示i,j之間的距離。則:b[1][2]=d[1][2];當i=j時,即A,B處在同一點,b[i][j]=b[i][i]=b[i-1][i]+d[i-1][i];當i=j-1時,即A在B相鄰的靠左一點,b[i][j]=b[j-1][j]=min{b[k][j-1]+d[k][j]}1<=k<=j-1當i<j-1時,即A在B后的多個點處b[i][j]=b[i][j-1]+d[j-1][j]。此為動態規劃法。67,漂亮打印問題:考慮在一張紙上打印一段文章,正文為n個分別長為l1,l2,…,ln的單詞構成的序列,漂亮打印的標準為,如果一行包括從第i到第j個單詞(i<j),且單詞之間只留一個空格,則每行末多余空格數立方和最小。用動態規劃法求解:記單詞為w1,w2,…,wn,記Pi為將wi至wn的單詞漂亮打印出來的方案,則本題為求P1。對任意的Pi,若在wj處結尾(該行包括從i到j所有單詞),則Pi中,對于wj+1,wj+2,…,wn即Pj+1是最優的,符合最優子結構性質。容易發現重疊子問題(重復計算)也存在。使用動態規劃法從Pn求到P1,求Pi時,只用處理與wi可能在同一行的所有情況,而求同一行的情況與行寬M相關,所以,求單個Pi的時間為O(M),整個算法的時間復雜度為O(m*n),遞歸條件(自底向上的起始條件)為,Pn=0;68,計劃一個公司聚會算法導論P219公司的管理層次結構可用樹形結構表示,每個雇員有一個喜歡聚會的程度(實數表示),為每大家都喜歡這個聚會,總裁不希望一個雇員和他的直接上司同時參加。樹的結構用左子女,右兄弟表示法。樹中每個結點除了包含指針,還包含了雇員的名字及該雇員喜歡聚會程度的排名。描述一個算法,它生成一張客人列表,使得喜歡聚會的程度之總和為最大。用動態規劃法解答:轉化為樹的著色問題,如果點x著紅色,代表參加,白色代表不參加,則從葉子起,記住每個結點著(不著色)時的喜歡程度總和,遞歸定義如下:求max(T(root,WHITE),T(root,RED))即為解。69,0-1背包問題的動態規劃算法:有n件物品和一個容量為c的背包。第i件物品的費用是w[i],價值是v[i]。求解將哪些物品裝入背包可使價值總和最大。基本思路:這是最基礎的背包問題,特點是:每種物品僅有一件,可以選擇放或不放。遞歸關系公式表示為:其中m(i,j)表示,選的物品范圍為i,…,n,而剩余容量為j。用動態規劃求解的(偽)代碼如下:void Knapsack(int *v,int *w,int c,int n,int **m){int jMax=min(w[n]-1,c);//jMax為下面的初始化時用,jMax表所剩容量的一個邊界值//一開始試第n個時,容器裝不下n或者剩余容量j在jMax以下時,初始化該情況for(int j=0;j<=jMax;j++)m[n][j]=0;for(int j=w[n];j<=c;j++)m[n][j]=v[n];//一開始試第n個時,如能裝下,則計算矩陣值for(int i=n-1;i>1;i--){//從第n-1個物品考察起,直至第2個jMax=min(w[i]-1,c);//考察第i個物品時,c裝不下它,或者j(所剩容量)在它之下for(int j=0;j<=jMax;j++)m[i][j]=m[i+1][j];//這些j都裝不下for(int j=w[i];j<=c;j++){m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);//這些j都裝得下}}m[1][c]=m[2][c];//處理第一個,因是數組,所以所有值都填!if(c>=w[1])m[1][c]=max(m[1][c],m[2][c-w[1]]+v[1]);//如果是裝得下,則對這些數組元素賦值}構造最優解:void Traceback(int **m,int w,int c,int n,int *x){for(int i=1;i<n;i++){//生成一個x[1…n]向量,用1表示該物品選了,否則表示未選if(m[i][c]==m[i+1][c])x[i]=0;else{x[i]=1;c-=w[i];}x[n]=(m[n][c])?1:0;}}0-1背包動態規劃的另一種解法(從第0個物品開始考慮起)遞歸式為:偽代碼如下:for(j=0…W)m[0][j]=0;//大寫W表背包總容量for(i=1…n){m[i][0]=0;for(j=1…W){if(w[i]<j){//裝得下時,看哪個選擇好if(v[i]+m[i-1][j-w[i]]>m[i-1][j])m[i][j]= v[i]+m[i-1][j-w[i]];else m[i][j]= m[i-1][j];}else m[i][j]= m[i-1][j];//裝不下}}return m[0][W];//可裝得下的最大價值,構造最優解的方向,也應與考慮時方向一致0-1背包問題擴展之一。數組分割,要求將長度為2n的數組分割成兩個長度為n的數組,并使它們之間的和最接近。假設S=(a[1]+a[2]+...+a[n]+a[n+1]+...+a[2n])/2,那么這是個和0-1背包類似的動態規劃問題,區別之處就是取的個數受到限制,必須得取足n個元素。用dp(i,j,c)來表示從前i個元素中取j個、且這j個元素之和不超過c的最佳(大)方案,在這里i>=j,c<=S 狀態轉移方程: dp(i,j,c)=max{dp(i-1,j-1,c-a),dp(i-1,j,c)}dp(2n,n,S)就是題目的解。 整體復雜度是O(S*n^2)0-1問包問題擴展之二。最優裝載,要把n個重量分別為wi的物品,裝到2艘船上,使之完成裝載,2艘船的載重分別為c1,c2。容易證明,如果給定的一人裝載問題有解,則采用如下的策略一定能得到一個最優裝載方案:1、 首先將第一艘船盡可能地裝滿;2、 然后將剩余的物品裝到第二艘船上。這樣,便演化成了0-1背包問題。0-1背包問題擴展之三。回溯法解0-1背包void Backtrack(int i){if(i>n){bestp=cp;return;}//如果找到了一個最優解,則返回if(cw+w[i]<=c){//嘗試裝x[i]=1(如果裝得下)cw+=w[i];//重量加c[p]+=p[i];//價值加Backtrack(i+1);//遞歸調用cw-=w[i];//一定要復位,因為cw和cp是共用的變量cp-=p[i];}//其實所謂回溯,就是遞歸調用,加了一個剪枝步,Bound(int i)判斷把余下的}//按單位價值排序后依次裝上,零頭也裝上,是否能得到更優的解,也即估上界if(Bound(i+1)>bestp)Backtrack(i+1);//不裝x[i]=0}70,編程之美:買書問題令F(Y1,Y2,Y3,Y4,Y5)表示買五冊書分別為Y1,…,Y5時所花的最少價格。其中Y1>Y2…>Y5。每次求解之前,都要對那五個數進行排列,大的放前面。動態規劃解法的遞歸式為:F(Y1,Y2,Y3,Y4,Y5)=0 (Y1=Y2=Y3=Y4=Y5=0)F(Y1,Y2,Y3,Y4,Y5)=min(5*8*(1-25%)+F(Y1-1,Y2-1,Y3-1,Y4-1,Y5-1),//如果Y5>14*8*(1-20%)+F(Y1-1,Y2-1,Y3-1,Y4-1,Y5), //如果Y4>13*8*(1-10%)+F(Y1-1,Y2-1,Y3-1,Y4,Y5), //如果Y3>12*8*(1-5%)+F(Y1-1,Y2-1,Y3,Y4,Y5), //如果Y2>18+ F(Y1-1,Y2,Y3,Y4,Y5)) //如果Y1>1以下部分主要為圖與幾何相關部分71,圖的基本數據結構,主要表示方法(鄰接表),深度優先遍歷算法,廣度優先遍歷算法與層序遍歷樹一樣,用隊列!#define MAX_VERTEX_NUM 20typedef struct ArcNode{//弧結構體int adjvex;//該弧所指向的頂點位置struct ArcNode *nextarc;//下一條弧int weight;//權重}ArcNode;typedef struct VNode{int data;//頂點信息ArcNode *firstArc;//指向第一條依附該結點的弧的指針}VNode,AdjList[MAX_VERTEX_NUM];typedef struct{AdjList vertices;//結點數組int vexnum,arcnum;//圖的當前頂點數和弧數int kind;//圖的種類標志}Graph;void DFS(Graph G,int v){//深度優先遍歷結點vvisited[v]=true;cout<<v<<endl;//訪問函數for(ArcNode *w=G.vertices[v].firstArc;w!=NULL;w=w->nextarc)if(!visited[w->adjvex])DFS(G,w->adjvex);}void DFSTraverse(Graph G){//深度優先遍歷圖for(int v=0;v<G.vexnum;v++)visited[v]=false;for(v=0;v<G.vexnum;v++)if(!visited[v])DFS(G,v);}圖的連通性判斷。無向圖只需要只以一個結點出發進行一種(深度或廣度)遍歷,然后檢查是否所有結點均被visited了即可,有向圖則采用如下算法:(1)對圖調用深度優先遍歷,并記住每個頂點完成時間(比如用棧)(2)對圖轉置為GT(3)從最晚完成的頂點開始,對GT調用深度優先遍歷,當找不到新頂點時,從余下頂點中選一個最晚完成的頂點繼續,直到全部完成;每次換選一個頂點之前得到的頂點集即為一個強連通分支。圖的拓撲排序。入度為0的結點入隊,當隊不空時循環:彈出隊首,并對隊首元素可達的每個頂點入度減1,將此輪減1后入度為0者插入隊尾。最后出隊順序即拓撲有序。最小生成樹算法。普里姆算法(prim),從某一點出發,每次從余下的點集中選擇離它最近的點作為下一點,并入已選點集中(設置距離為0)并更新已選點集與未選點集的距離(如果因新加入的點引起了變化的話),時間復雜度為n^2,與邊無關;克魯斯卡爾(Kruskal)算法,每次從屬于不同的兩個點集中的兩點所連的邊中選擇一條最短者,然后將此兩點集合并,直至所有點集同屬一個集合即完成算法,初始點集數為n,每個集一個點,時間復雜度為eloge。由以上分析可知,前者適用于點少邊多的圖,后者則相反。圖的單源最短路徑算法。迪杰斯特拉(dijstra)算法,按最短路徑遞增的方式求單源最短路徑,偽碼如下:Dijstra(G,w,s){//s為源點,w為權值矩陣init();//初始化d[s]=0;其余d[v]=MAXINTQ=V(G)-s;//未用來計算最短路徑的點集S=s;//已用來計算最短路徑的點集while(Q!=NULL){u=extract-min(Q)//從Q中選擇d[u]最小的點(并從Q中刪除該點)S=S并u;for each vertex v in Adj[u];//對于u可達的每個頂點Relax(u,v,w);}}Relax(u,v,w){if(d[u]+w[w][v]<d[v])d[v]= d[u]+w[w][v];}圖的每對頂點間最短路徑。方法一,執行n次單源最短路徑算法,復雜度O(n^3);方法二,Floy-Warshall算法,復雜度O(n^3);對任意兩個點i,j給定k表示i,j之間,所有經過的頂點序號不大于k的時候,i,j之間的最短路徑,考慮已經求出k-1時的情況,對于k:(1) 若k在i,j最短路徑中,則路徑為(2) 如果k不是i,j最短路徑中,則k-1的解即為k的解,用數學形式表示為:由此得Floy-Warshall算法偽代碼:Floy-Warshall(W){n=rows(W);D0=W;//D代表每輪計算之后的權值矩陣Wfor(k=1…n)for(i=1…n)for(j=1…n);return Dn;}有向圖的傳遞閉包問題。即要求兩點i,j(任意)是否可達。方法:對每條邊賦值1,若運行Floy-Warshall之后dij<n則可達,否則為不可達;改進之處,在求Floyd時公式改為:;進行或運算即可關鍵路徑求法。AOV網加上權值便得AOE(Activity On Edge)網,對AOE網的一項典型計算是計算關鍵路徑,即從源點到終點的最長路徑,關鍵路徑上的活動延期會影響整個工程!求關鍵路徑的方法是:(1)從源點開始,求各個活動的最早發生時間(事先拓撲排序);入一棧中為第二步作準備(2)從終點開始向前,求各個活動的最晚發生時間;(3)輸出最早發生時間等于最晚發生時間的活動,便是關鍵活動。72,尋找最近點對!一維情況,用排序,然后掃瞄一遍即可;二維時的算法偽代碼:bool cpair2(s,&d){n=|s|;if(n<2){d=MAXINT;return false;}m=s中各點x坐標的中位數;由x=m分割點集s為s1,s2;cpair2(s1,d1);cpair2(s2,d2);dm=min(d1,d2);設p1為s1中與x=m小于dm的所有點集設p2為s2中與x=m小于dm的所有點集將p1和p2中的點按y值排序,結果為X,Y對X中的每個頂點,檢查Y中與其在y軸上投影距離小于dm的所有頂點(最多6個),最終求得的最小距離為dl;d=min(dm,dl);return true;}73,九宮格,可以上下左右方向走,不可斜走,任意走n步(1<=n<=8)且不可以已經走過的方格,只要走的步數不一樣,或者走的路線不一樣,就算一種走法,問題是任意給一格作為起點,請問有多少走走法?/* 0 1 23 4 56 7 8 ********/#include <iostream>using namespace std;int ninemap[9][4]={{1,3,-1,-1},{0,2,4,-1},{1,5,-1,-1},{0,4,6,-1},{1,3,5,7},{2,4,8,-1},{3,7,-1,-1},{4,6,8,-1},{5,7,-1,-1}};void findPathNum(int source, int ninemap[9][4],int gone[],int &goneNum,int&sum,int gonePath[]){if(gonePath[0]!=-1){for(int i=0;i<9;i++)if(gonePath[i]!=-1)cout<<gonePath[i];cout<<endl; sum++;}gone[source]=1; // 表示我已經來過這里gonePath[goneNum]=source;// 當前狀態下路徑位置if(goneNum==9) return;int nextPoint=-1;for(int i=0;i<4;i++){nextPoint =ninemap[source][i];if (nextPoint!=-1&&gone[nextPoint]!=1){// 存在下一頂點,且之前沒有走過gone[nextPoint]=1;++goneNum;gonePath[goneNum]=nextPoint;findPathNum(nextPoint,ninemap,gone,goneNum,sum,gonePath); //遞歸gonePath[goneNum]=-1;--goneNum;gone[nextPoint]=0;}}gonePath[goneNum]=-1; // 重置位gone[source]=0;// 重置位}int main(){cout<<"請輸入九宮格的初始節點(0-8,結束請輸入-1)"<<endl;int source;cin>>source;if(source==-1){return 0;}while(source<0 || source>8){cout<<"輸入參數不規范,請重新輸入"<<endl;cout<<"請輸入九宮格的初始節點(0-8,結束請輸入-1)"<<endl;cin>>source;}while(source>-1 && source <9){int goneNum=0;// 當前節點位置int gonePath[9]={-1,-1,-1,-1,-1,-1,-1,-1,-1}; // 保存走過的路徑int sum=0;// 解法個數int gone[9]={0,0,0,0,0,0,0,0,0};// 走過該節點否,1走過,0未走findPathNum(source,ninemap,gone,goneNum,sum,gonePath);cout<<"以"<<source<<"為起點的解法共有"<<sum<<"個"<<endl;cout<<"請輸入九宮格的初始節點(0-8,結束請輸入-1)"<<endl;cin>>source;}return 0;}74,計算凸包Graham掃瞄法:第一步,選取y值最小的點P0,其余點以P0為原點按逆時針掃過的順序排序;第二步,按序P1,P2進棧;第三步,對于余下的每個Pi,按序依次{while(next-to-top(Stack),top(Stack),Pi三點(兩線段)非左拐)pop(Stack);push(Pi,Stack);}javis法:第一步,與Graham一樣選取P0;第二步,對于剩下的點,按逆時針掃過的第一個點為P1;第三步,P1為新端點,除P0,P1外,以新端點逆時針掃過的第一個點為P2;……到最高點時,右鏈掃瞄完畢,第四步,左鏈由P0開始與右鏈對稱執行一次,兩鏈合并即完成掃瞄。75,確定兩線段是否相交,看算法導論筆記知識一,兩點P1(x1,y1),P2(x2,y2)的叉積定義為:x1*y2-x2*y1。如果叉積P1×P2為正數,則相對于原點(0,0)來說,P1在P2的順時針方向,如果是負數則表示在逆時針方向,如果是0則表示這兩個向量是共線的。確定連續線段是向左轉還是向右轉?給定點p0,p1,p2求線段p1p2在p0p1之后是向左轉了還是向右轉了。方法就是求p0p2和p0p1這兩個向量的叉積。確定兩線段是否相交,有兩種情況:一是,每個線段都跨越包含了另一線段的直線;二是,一個線段的某一端點位于另一線段上。(這一情況來自于邊界情況)SEGMENTS-INTERSECT(p1,p2,p3,p4){//判斷p1p2和p3p4是否相交d1=DIRECTION(p3,p4,p1);//計算p3p4與p3p1的叉積d2=DIRECTION(p3,p4,p2);d3=DIRECTION(p1,p2,p3);d4=DIRECTION(p1,p2,p4);if((d1*d2<0)&&(d3*d4<0))return true;else if((d1==0)&&ON-SEGMENT(p3,p4,p1))return true;//d1=0表在p1在p3p4的直線上else if((d2==0)&&ON-SEGMENT(p3,p4,p2))return true;//是否在線段上,仍需要判斷else if((d3==0)&&ON-SEGMENT(p1,p2,p3))return true;//ON-SEGMENT判斷范圍即可else if((d4==0)&&ON-SEGMENT(p1,p2,p4))return true;//else return false;}DIRECTION(pi,pj,pk){//計算pipj與pipk的叉積return (pk-pi)×(pj-pi)}ON-SEGMENT(pi,pj,pk){//計算點pk是否在線段pipj上,因之前已經判斷了在直線上if((min(xi,xj)<=xk<=max(xi,xj))&&(min(yi,yj)<=yk<=max(yi,yj)))return true;else return false;}76,求給定的一組線段中,是否有相交的方法大意:第一步,將線段所有端點排序(按x值,相同時再按y值)第二步,設一掃瞄線,從左到右掃瞄每一個端點。對于每個掃到的端點,若其為左端點,則將線段加入到掃瞄線線段集中,判斷此時緊臨其掃瞄線上方或下方存在的線段,若有與之相交的,則返回true;若其為右端點,則判斷掃瞄線此時其上下方均有線段并相關時返回true,否則從掃瞄線線段集中刪除該右端點所在的線段。以下主要為幾個矩陣輸出題77,void Matrix(int n) 打印出如下一個矩陣:n=55 6 3 4 54 7 2 7 63 2 1 2 36 7 2 7 45 4 3 6 5n=77 8 9 4 5 6 76 13 10 3 12 13 85 12 11 2 11 10 94 3 2 1 2 3 49 10 11 2 11 12 58 13 12 3 10 13 67 6 5 4 9 8 7....以此類推 n<20, n為奇數十字架,劃分成四個區域,然后各個區域再分別打印!void printMatrix2(int n){if(n>20||n<0||n%2==0)return;//偶數,及其它不符合條件的數返回int **a=new int*[n];for(int i=0;i<n;i++)a[i]=new int[n];int center=n>>1;a[center][center]=1;//賦中心點int j;for(i=center+1,j=2;i<n;i++,j++){//賦十字架a[center][i]=j;//十字架的右a[i][center]=j;//十字架的下a[n-i-1][center]=j;//十字架的上a[center][n-i-1]=j;//十字架的左}printRightUpQuarter(a,0,center+1,center+2,center);//打印數組a的右上角四分之一printRightDownQuarter(a,center+1,n-1,center+2,center);//打印數組a的右上角四分之一printLeftUpQuarter(a,center-1,0,center+2,center);//打印數組a的右上角四分之一printLeftDownQuarter(a,n-1,center-1,center+2,center);//打印數組a的右上角四分之一for( int s = 0; s < n ; ++s ){for( int j = 0; j < n ; ++j )cout<< a[s][j]<< "/t" ;cout<< endl;}delete []a;}void printRightUpQuarter(int **matrix, int x, int y, int start, int n) {//右上,其它三塊類似int i, j;//打印螺旋隊列的遞歸算法if (n <= 0) return;if (n == 1) {matrix[x][y] = start;return;}for (j = y; j < y + n; j++) /* 上部 */matrix[x][j] = start++;for (i = x+1; i < x + n; i++) /* 右邊 */matrix[i][y+n-1] = start++;for (j = y+n-2; j >= y; j--) /* 底部 */matrix[x+n-1][j] = start++;for (i = x+n-2; i > x; i--) /* 左邊 */matrix[i][y] = start++;printRightUpQuarter(matrix, x+1, y+1, start, n-2); /* 遞歸 */}擴展,打印螺旋隊列的非遞歸算法如下:void printRightUpQuarter(int **matrix, int x, int y, int start, int n) {//右上if (n <= 0)return;if (n == 1) {matrix[x][y] = start;return;} int round=1;for(round=1;round<=n/2;round++){for (int j = y+round-1; j < y + n-round+1; j++) //* 上部matrix[x+round-1][j] = start++;for (int i = x+round; i < x + n-round+1; i++) //* 右邊matrix[i][y+n-round] = start++;for (j = y+n-1-round; j >= y+round-1; j--) //* 底部matrix[x+n-round][j] = start++;for (i = x+n-1-round; i > x+round-1; i--) //* 左邊matrix[i][y+round-1] = start++;}}78,有道筆試題。打印矩陣void printmatrix(int n) n<20打印出如下一個矩陣:n=31 2 11 2 11 1 1n=41 2 2 11 2 2 11 2 2 11 1 1 1n=51 2 2 2 11 2 3 2 11 2 3 2 11 2 3 2 11 1 1 1 1n=61 2 2 2 2 11 2 3 3 2 11 2 3 3 2 11 2 3 3 2 11 2 3 3 2 11 1 1 1 1 1n=71 2 2 2 2 2 11 2 3 4 3 2 11 2 3 4 3 2 11 2 3 4 3 2 11 2 3 4 3 2 11 2 3 3 3 2 11 1 1 1 1 1 1....以此類推,//規律是分四類,第一行,最后一行,倒數第二行,其它void printMatrix( int n ) //已運行驗證{if(n<2)return;//邊界條件int **a=new int*[n];//=int[n][n]不行!for(int k=0;k<n;k++)a[k]=new int[n];a[0][0]=1;//以下三行代碼給第一行賦值a[0][n-1]=1;for(int j=1;j<n-1;j++)a[0][j]=2;for(j=0;j<n;j++)a[n-1][j]=1;//給最后行賦值for(j=0;j<3;j++)a[n-2][j]=j+1;//給倒數第二行賦值for(j=3;(j<=n-4)&&j<(n/2+n%2);j++)a[n-2][j]=3;for(j=n-1;(j>n-4)&&j>=(n/2+n%2);j--)a[n-2][j]=n-j;//for(int i=1;i<n-2;i++){//賦其它行for(j=0;j<(n/2+n%2);j++)a[i][j]=j+1;for(j=n-1;j>=(n/2+n%2);j--)a[i][j]=n-j;}for( int s = 0; s < n ; ++s ){for( int j = 0; j < n ; ++j )cout<< a[s][j]<< " " ;cout<< endl;}delete []a;}79,輸出矩陣,n<20;n為奇數;n=5;1444414333140231112322223n=7;1444444143333314322231430123144412311111232222223提示:從外圍開始轉圈!代碼如下:void printMatrix3(int n){if(n>20||n<0||n%2==0)return;//偶數,及其它不符合條件的數返回int **a=new int*[n];for(int i=0;i<n;i++)a[i]=new int[n];int x,y;//一圈圈地轉,與螺旋隊列一樣,只是填的數不一樣而已int filledNumber=1;a[n/2][n/2]=0;for(int round=1;round<=n/2;round++){for(x=round-1;x<n-round;x++)//左邊a[x][round-1]=filledNumber;if(filledNumber<4)filledNumber++;else filledNumber=1;for(y=round-1;y<n-round;y++)//下邊a[n-round][y]=filledNumber;if(filledNumber<4)filledNumber++;else filledNumber=1;for(x=n-round;x>round-1;x--)//右邊a[x][n-round]=filledNumber;if(filledNumber<4)filledNumber++;else filledNumber=1;for(y=n-round;y>round-1;y--)//上邊a[round-1][y]=filledNumber; }for( int s = 0; s < n ; ++s ){for( int j = 0; j < n ; ++j )cout<< a[s][j]<< "/t" ;cout<< endl;}delete []a;}

總結

以上是生活随笔為你收集整理的算法--职前算法复习的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。