入位演算法設計
1. 弄清楚題目的意思,列出題目的輸入、輸出、約束條件
其中又一道題目是這樣的:「有一個mxn的矩陣,每一行從左到右是升序的,每一列從上到下是升序的。請實現一個函數,在矩陣中查找元素elem,找到則返回elem的位置。」題設只說了行和列是升序的,我在草稿紙上畫了一個3x4的矩陣,裡面的元素是1~12,於是我就想當然的認為矩陣的左上角是最小的元素,右下角是最大的元素。於是整個題目的思考方向就錯了。
2. 思考怎樣讓演算法的時間復雜度盡可能的小
繼續以上面的題目為例子。可以有如下幾種演算法:
a. 遍歷整個矩陣進行查找,那麼復雜度為O(m*n);
b. 因為每一行是有序的,所以可以對每一行進行二分查找,復雜度為O(m*logn)。但是這樣只用到了行有序的性質。
c. 網上查了一下,最優的演算法是從矩陣的左下角開始,比較左下角的元素(假設為X)與elem的大小,如果elem比X大,那麼X所在的那一列元素就都被排除了,因為X是該列中最大的了,比X還大,那麼肯定比X上面的都大;如果elem比X小,那麼X所在的那一行就可以排除了,因為X是這一行里最小的了,比X還小那麼肯定比X右邊的都小。每迭代一次,矩陣的尺寸就縮小一行或一列。復雜度為O(max(m,n))。
可以先從復雜度較高的實現方法入手,然後再考慮如何利用題目的特定條件來降低復雜度。
3. 編寫偽代碼或代碼
2. 2.設計演算法:將一個元素插入到有序的順序表中,使順序表仍有序,並編寫主函數測試算
#include<stdio.h>
#defineMAX5//預定義數組容量
intInsNum(int*a,intlen,intins)
{
inti=0,j;
while(ins>a[i]&&i<len)//找合適的位置插入ins
宏擾i++;
if(i==len)//到了數組末尾
a[i]=ins;
else//數組中間i處
{
for(j=len;j>i;j--)//從i起所有數亂凳據往後移一位
a[j]=a[j-1];
a[i]=ins;//插入位置i處
}
嘩絕旅returnlen+1;
}
intmain()
{
inti,j,n,m;
inta[MAX]={2,5,11,15,17};//注意定義長度一定要比實際長度大
scanf("%d",&n);//輸入要插入的數字
m=InsNum(a,5,n);//插入數組中
for(i=0;i<m;i++)//輸出結果
{
printf("%d",a[i]);
}
return0;
}
3. 演算法設計:輸入N個只含一位數字的整數,試用基數排序的方法,對這N個數排序.
typedef struct
{
int key;
int next;
}SLRecType;
SLRecType R[N+1];
typedef struct
{
int f,e;
}SLQueue;
SLQueue B[10];
int Radixsort(SLRecType R[],int n)//設關鍵森鉛字已輸入到R數組
{
int k,t;
for(i=1;i<n;i++)R[i].next=i+1;
R[n].next=-1;p=1; //-1表示靜態鏈表的結束
for(i=0;i<=9;i++){B[i].f=-1;b[i].e=-1} //設置隊頭隊衫余尾指針初值
while(p!=-1) //一趟分配
{
k=R[p].key; //取關鍵字
if (B[k].f==-1)B[k].f=p; //修改隊頭指針
else R[B[k].e].next=p;
B[k].e=p;
p=R[p].next; /此塌好/一趟收集
}
i=0;
while(B[i].f==-1)i++;
t=B[i].e;p=B[i].f;
while(i<9)
{
i++;
if( B[i].f!=-1)
{
R[t].next=B[i].f;
t= B[i].e;
}
}
R[].next=-1;
return p; //返回第一個記錄指針
}
4. 設計演算法輸入一個5位的整數n,輸出n的各位數字的和(如n=13546,由於1+3+5+4+6=1
int n,s,temp;
n=12345;鏈散
temp=10000;
s=0;
for(int i=0;i<5;i++){
s+=n/temp;
n=n%temp;
temp=temp/跡喚雹姿帆10;
}
System.out.println(s);
5. 演算法設計原則是什麼
原則:首先說設計的演算法必須是"正確的",其次應有很好的"可讀性",還必須具有"健壯性",最後應考慮所設計的演算法具有"高效率與低存儲量"。
所謂演算法是正確的,除了應該滿足演算法說明中寫明的"功能"之外,應對各組典型的帶有苛刻條件的輸入數據得出正確的結果。
在演算法是正確的前提下,演算法的可讀性是擺在第一位的,這在當今大型軟體需要多人合作完成的環境下是換重要的,另一方面,晦澀難讀的程序易於隱藏錯誤而難以調試。演算法的效率指的是演算法的執行時間,演算法的存儲量指的是演算法執行過程中所需最大存儲空間。
演算法是程序設計的另一個不可缺的要素,因此在討論數據結構的同時免不了要討論相應的演算法。這里有兩重意思,即演算法中的操作步驟為有限個,且每個步驟都能在有限時間內完成。
確定性表現在對演算法中每一步的描述都沒有二義性,只要輸入相同,初始狀態相同,則無論執行多少遍,所得結果都應該相同。
可行性指的是,序列中的每個操作都是可以簡單完成的,其本身不存在演算法問題,例如,"求x和y的公因子"就不夠基本。
輸入值即為演算法的操作對象,但操作的對象也可以由演算法自身生成,如"求100以內的素數",操作對象是自然數列,可以由變數逐個增1生成。
演算法的健壯性指的是,演算法應對非法輸入的數據作出恰當反映或進行相應處理,一般情況下,應向調用它的函數返回一個表示錯誤或錯誤性質的值。
6. 演算法設計-幫幫忙
這個估計要用文件輸入
否則太累了
--我們必須盡量優化程序才能達到題目的要求。顯然n實在是太大了,所以我們不但不可能忍受O(n2)的復雜度,甚至連n前面的系數過大都會造成超時。為了盡量減小時間復雜度,我們只能依靠自動機的理論。
自動機理論
一個DFA(確定性有限自動機)是一個五元組<∑, U, s, T, φ>,其中∑是字元集U 是一個有限的狀態集合, s 是 U 的一個元素表示初始狀態, T 是 U的一個子集,表示終止狀態and φ : U × ∑ → U 是轉移函數。
開始時自動機在初始狀態s,每步它讀入一個字元c,然後根據state=φ(state,c)進行狀態的轉移。如果當字元串結束時它達到了終止狀態,那麼就說它接受了字元串。
NFA(非確定性自動機)與DFA的主要區別就是,NFA從一個狀態按照一個字元轉移到的狀態可能有多個。因此NFA需要使用一個狀態集合來表示當前可能處在的狀態。
把自動機用圖來表示的話,一般用結點表示狀態,而結點之間有標有某個字元的有向邊,表示可以從邊的起點通過這個字元轉移到邊的終點。
此外NFA還可以包含標有ε的邊,這種邊表示不需要任何字元就可以直接進行狀態的轉移。
容易想到,如果我們建立了一個只接受符合正則表達式的自動機,就可以通過它來較快的判斷哪些位置是可匹配的。
具體應用
比較常見的字元串匹配自動機大多都是DFA。但是由於有+,*,把正則表達式直接轉變成DFA是非常困難的。因此我們只能先把它轉化成NFA。
先看一下一般的轉換規則:
如果正則表達式是一個字元,那麼對應的NFA是:
p0-a->p1
如果正則表達式是A+B的形式,那麼只要把它們的起始狀態和終止狀態合並就可以:
p0-a->p1
p0-b->p1
如果正則表達式是AB的形式,那麼只要把A和B對應的自動機首尾相接:
p0-a->p1-b->p2
如果正則表達式是A*的形式,那麼就需要按下面的圖來進行處理:
p0-a->p0
p0->p1
通過上面的步驟可以得出:NFA的狀態數不會超過正則表達式長度。
NFA中的狀態轉移
在NFA中進行狀態轉移比DFA要困難一些。我們可以定義兩個函數,一個叫做epsilon-closure,用來計算一組NFA狀態通過epsilon邊能夠到達的NFA狀態集合。另外一個move用來在NFA上進行狀態的轉移。
設當前的狀態集合是now,那麼狀態轉移的過程就是:
new = {}
for every state s in now do
new = new ∪ {t | (s,t) = c}
new = epsilon_closure(new)
其中的(s,t)=c表示從s狀態經過一個字元c可以轉化到t狀態。因為這個語句需要的集合總數也不會太多,所以可以通過預先計算出它們來加快速度。預處理後這個過程的復雜度是O(m2),而系數是比較小的。
但是由於在NFA中,我們必須把狀態的集合作為一個真正的狀態,每次狀態轉移都是對這個集合的處理。這樣轉移一次的復雜度是O(m2),這是不可忍受的(最大的數據需要大約2分鍾才能出解)。
既然NFA速度過慢,我們自然想要把它轉化為DFA。因為DFA中的狀態轉移是O(1)的。可是並沒有一個多項式復雜度的方法可以把NFA轉換成DFA。一般介紹的NFA轉換到DFA的方法都是通過類似BFS的方法來把NFA中所有可能出現的狀態集合對應成DFA的一個狀態。
這種轉換在本題中顯然是不可行的,因為NFA的結點數最多是500,而轉化成的DFA則可能有多達2500個狀態,即使實際有許多狀態不能達到,也是無論如何不可以忍受的。
看來把NFA轉換成DFA是行不通的。但是我們還是從NFA轉DFA的過程中受到了一些啟發:NFA之所以速度慢,是因為我們在進行狀態轉移的時候可能進行許多重復操作:比如從{1,2}沿一個字元1轉移後是{2,3},以後我們還可能遇到相同的情況,而這時我們還會用O(m2)的時間去進行狀態轉移。這是一種很嚴重的浪費。因此我們每進行一次狀態轉移,就把這個狀態轉移的情況放到hash表中,以後使用的時候就可以查找了。這相當於只把我們需要的一部分子集轉移成DFA,雖然一般情況下並不能降低時間復雜度,但是在實際應用中確實很有效。(特別是對於沒有*和括弧的自動機,這樣做可以保證線形的時間復雜度)
演算法回顧和細節
我們重新來看一下演算法的框架:
根據正則表達式建立NFA
now = start
while not eof do begin
read(c);
if 曾經進行過now,c的轉移 then 利用以前的結果
else 進行狀態轉移並記錄結果
if 終止狀態 in now then 輸出當前位置
end
建立NFA有各種方法,由於NFA並不會很大,所以我們可以使用遞歸的方法來實現。為了盡量減小系數,我們使用位壓縮的方式來實現集合,並採用較為簡化的hash函數(比如把一個集合位壓縮後的一系列整數加起來取對220的余數),並在輸入和輸出的時候通過緩沖區來加快速度。這樣就基本可以達到題目的時間要求。
但是注意到字元串可能有10M,而產生的NFA的狀態集合最多也可能有10M個,而每個集合都需要100到200位元組的存儲空間,大大超出了允許的空間范圍。因此我們需要對hash表的規模加以嚴格限制。如果規模過大就不得不放棄存儲狀態的想法,犧牲一定的時間來換取空間。
7. 演算法設計題
1.遍歷字元串,如果遇到「(」字元則把「(」push入棧,如果遇到「)」字元則pop,(pop前檢查棧是否為空,如果為空則停止遍歷,返回0)。便利完後檢查棧是否為空,如果為空返回1,否則返回0。
2.用兩個指針p1,p2。p1開始向後移動(p1=p1->next),等p1移動到k個元素時p2開始移動,以後p1,p2同步向後移,等p1移動到尾節點時(即p1->next==NULL)停止,此時p2所指即倒數第k個節點。
8. 影響演算法設計的因素有哪些
影響演算法設計的有以下因素:
針對機器:空間復雜性和時間復雜性。
針對程序員:演算法表達和實現的簡單性。
針對問題:演算法對問題及問題輸入規模的普適性。
影響演算法效率的因素
1、從大的方面來講,所選擇的語言對演算法的效率影響很大。一般來說,使用越高級的語言所需要的時間和空間就越大。另外,不同編譯器產生的代碼質量不同,這對演算法的效率也會有影響。
2、存儲結構
數據的存儲結構,分為順序存儲結構和鏈式存儲結構。順序存儲結構的特點是藉助元素在存儲器中的相對位置來表示數據元素之間的邏輯關系;鏈式存儲結構則是藉助指示元素存儲地址的指針表示數據元素之間的邏輯關系。不同的問題求解選用不同的存儲結構。
3、指針操作
在使用指針時,指針的有秩序掃描非常重要。例如在模式匹配中,如果直接進行匹配,當有不完全匹配時,主串的指針需要回溯。
在KMP演算法中,我們先可以求出每個元素的next函數值,從而在發生不完全匹配時,主串的指針不必要回溯,只需要模式串的元素回到當前元素的next函數值所指的元素再進行匹配即可。當主串和模式串有很多不完全匹配時,KMP演算法可以大大提高效率。
4、查找的效率
有很多快速查找的演算法都可以提高查找的效率,如建立索引,折半查找等,都是在記錄和關鍵字之間進行比較,從而尋求關系。這一類查找建立在比較的基礎之上。查找的效率依賴於查找過程中所進行的比較次數。
在哈希表中,使得記錄的存儲位置和關鍵字之間建立一個確定的存儲關系,因而在查找時,只需要根據這個對應的關系f 找到給定值K 的像f(k)。用這個思想建立哈希表。如在基因組匹配時,用哈希表非常方便。
5、數據類型的選擇
數據類型的選擇也會影響演算法效率,在對時間和空間要求非常嚴格時,盡可能的使用佔用空間較小的數據類型。使用動態開辟空間會使得效率降低,所有在能確定或估計出需要的空間大小的情況下盡量使用靜態數字。個人覺得用vector雖然方便,但是效率並不高。
6、存儲方式
用堆操作還是用棧操作,對於不同的問題需要仔細選擇。在串和隊列的有關操作中用堆操作合適,在樹的操作中用棧操作合適,如建立二叉樹中序遍歷的遞歸演算法或非遞歸演算法,用棧操作好。
9. C語言程序設計,初級的~設計演算法輸入一個四位正整數,將它們倒排,例如輸入1234,輸出4321
「希望寫的詳細些,我就可以直接打上去了,連空格啊,標點啊也寫一下.」....學習不是這樣的。念亮。。。
給個思路就行了。用除法求出千位、求余再除法求出百位、再除法+求余求出十位,最後用求余得到個位。然後告宏按照個位十位百位千位一個一個襪高冊輸出即可。
10. 演算法設計題:輸入一個任意的非負數十進制整數,輸出與其等值的八進制數
最近好多這樣的問題哦,如果不能用%o來輸出的話,那麼一個很好的方法就是用堆棧,我用的是鏈式堆棧:
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
typedef int DataType;
#include "LSNode.h";
void main()
{
LSNode *head;
float a;//為了判斷輸入是否為整數而設的變數
int x,y;
StackInitiate(&head);//初始化
printf("請輸入一個非負十進制整數: ");
scanf("告芹%f",&a);
x=(int)a;
if(x<0||x!=a){printf("您輸入的是負數或小數!\n");return;}
do
{
y=x%8;//除攜餘8取余
if(StackPush(head,y)==0)//入棧所得余數y
{
printf("錯誤辯友滾!\n");
return;
}
x=x/8;//取得本循環的商數作為下一循環被除數
}while(x!=0);
printf("轉換成八進制為: ");
while(StackNotEmpty(head))
{
StackPop(head,&x);//出棧,次序剛好與入棧時相反
printf("%d",x);//顯示各位元素
}
printf("\n");
Destroy(head);//撤銷鏈式堆棧
}
然後用堆棧還需要自己定義一個名為"LSNode.h"的頭文件。其內容是:
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
typedef struct snode
{
DataType data;
struct snode *next;
}LSNode;
void StackInitiate(LSNode **head)
{
if((*head=(LSNode *)malloc(sizeof(LSNode)))==NULL)exit(1);
(* head)->next=NULL;
}
int StackNotEmpty(LSNode *head)
{
if(head->next==NULL)return 0;
else return 1;
}
int StackPush(LSNode *head,DataType x)
{
LSNode *p;
if((p=(LSNode *)malloc(sizeof(LSNode)))==NULL)
{
printf("內存空間不足無法插入!\n");
return 0;
}
p->data=x;
p->next=head->next;
head->next=p;
return 1;
}
StackPop(LSNode *head,DataType *d)
{
LSNode *p =head->next;
if(p==NULL)
{
printf("堆棧已空出錯!");
return 0;
}
head->next=p->next;
*d=p->data;
free(p);
return 1;
}
int StackTop(LSNode *head,DataType *d)
{
LSNode *p=head->next;
if(p==NULL)
{
printf("堆棧已空出錯!");
return 0;
}
*d=p->data;
return 1;
}
void Destroy(LSNode *head)
{
LSNode *p,*p1;
p=head;
while(p!=NULL)
{
p1=p;
p=p->next;
free(p1);
}
}
編好了保存到與程序同個文件夾里就好了。