常用的幾種數據結構
數據的邏輯結構常分為四大類:
(1)集合結構
(2)線性結構
(3)樹形結構
(4)圖結構(網結構)
存儲結構可以分為:連續存儲和鏈式存儲。連續存儲又可以分為:靜態存儲和動態存儲
連續存儲和鏈式存儲比較
順序存儲的優點:
(1) 方法簡單,各種高級語言中都提供數組結構,容易實現。
(2) 不用為表示結點間的邏輯關系而增加額外的存儲開銷。
(3) 順序表具有按元素序號隨機訪問的特點。
順序存儲的缺點:
(1) 在順序表中做插入刪除操作時,平均移動大約表中一半的元素,因此對n較大的順序表效率低。
(2) 需要預先分配足夠大的存儲空間,估計過大,可能會導致順序表后部大量閑置;預先分配過小,又會造成溢出。
在選取存儲結構時權衡因素有:
1)基於存儲的考慮
順序表的存儲空間是靜態分配的,在程序執行之前必須明確規定它的存儲規模,也就是說事先對“MAXSIZE”要有合適的設定,過大造成浪費,過小造成溢出。可見對線性表的長度或存儲規模難以估計時,不宜采用順序表;鏈表不用事先估計存儲規模,但鏈表的存儲密度較低,
2)基於運算的考慮
在順序表中按序號訪問ai的時間性能時O(1),而鏈表中按序號訪問的時間性能O(n),所以如果經常做的運算是按序號訪問數據元素,顯然順序表優於鏈表;
3)基於環境的考慮
順序表容易實現,任何高級語言中都有數組類型,鏈表的操作是基於指針的,操作簡單。
【例1】某次選舉,要從五個候選人(編號分別為1、2、3、4、5)中選一名廠長。請編程完成統計選票的工作。
算法設計:
1)雖然選票發放的數量一般是已知的,但收回的數量通常是無法預知的,所以算法采用隨機循環,設計停止標志為“-1”。
2)統計過程的一般方法為:先為五個候選人各自設置五個“計數器”S1,S2,S3,S4,S5,然后根據錄入數據通過多分支語句或嵌套條件語句決定為某個“計數器”累加1, 這樣做效率太低。
現在把五個“計數器”用數組代替,選票中候選人的編號xp正好做下標,執行A(xp)=A(xp)+1就可方便地將選票內容累加到相應的“計數器”中。也就是說數組結構是存儲統計結果的,而其下標正是原始信息。
3)考慮到算法的健壯性,要排除對1——5之外的數據進行統計。
算法如下:
vote( )
{ int i,xp,a[6];
print(“input data until input -1”);
input(xp );
while(xp!=-1)
{if (xp>=1 and xp<=5 )
a[xp]=a[xp]+1;
else
print(xp, "input error!");
input(xp );
}
for (i=1;i<=5;i++)
print(i,"number get", a[i], "votes");
}
【例2】編程統計身高(單位為厘米)。統計分150——154;155——159;160——164;165——169;170——174;175——179及低於是150、高於是179共八檔次進行。
算法設計:
輸入的身高可能在50——250之間,若用輸入的身高數據直接作為數組下標進行統計,即使是用PASCAL語言可設置上、下標的下界,也要開辟200多個空間,而統計是分八個檔次進行的,這樣是完全沒有必要的。
由於多數統計區間的大小是都固定為5,這樣用“身高/5-29”做下標,則只需開辟8個元素的數組,對應八個統計檔次,即可完成統計工作。
算法如下:
main( )
{ int i,sg,a[8];
print(“input height data until input -1”);
input(sg );
while (sg<>-1)
{if (sg>179) a[7]=a[7]+1;
else
if (sg<150) a[0]=a[0]+1;
else a[sg/5-29]=a[sg/5-29]+1;
input( sg );
}
for (i=0;i<=7;i=i+1)
print(i+1 ,“field the number of people: ”,a[i]);
}
【例3】一次考試共考了語文、代數和外語三科。某小組共有九人,考后各科及格名單如下表,請編寫算法找出三科全及格的學生的名單(學號)。

方法一:
從語文及格名單中逐一抽出各及格學生學號,先在代數及格名單核對,若有該學號(說明代數也及格了),再在外語及格名單中繼續查找,看該學號是否也在外語及格名單中。若仍在,說明該號屬全及格學生的學號,否則就是至少有一科不及格的。若語文及格名單中就沒有某生的號,顯然該生根本不在比較之列,自然不屬全及格學生 。
該方法采用了枚舉嘗試的方法
A,B,C三組分別存放語文、代數、外語及格名單,嘗試范圍為三重循環:
I循環, 初值0, 終值6, 步長1
J循環, 初值0, 終值5, 步長1
K循環, 初值0, 終值7, 步長1
定解條件:
A[I]=B[J]=C[K]
共嘗試7*6*8=336次。
方法一算法如下:
main( )
{int a[7], b[6], c[8],i,j,k,v,flag;
for( i =0;i<=6;i=i+1) input(a[i]);
for( i =0;i<=5;i=i+1) input(b[i]);
for( i =0;i<=7;i=i+1) input(c[i]);
for( i =0;i<=6;i=i+1)
{v=a[i];
for( j =0;j<=5;j=j+1)
if ( b[j]=v )
for(k =0;k<=7;k=k+1)
if(c[k]=v)
{print(v); break;}
}
}
方法二 :
用數組A的九個下標分量作為各號考生及格科目的計數器。將三科及格名單共存一個數組,當掃描完畢總及格名單后,凡下標計數器的值為3的就是全及格的,其余則至少有一科不及格的。
該方法同樣也采用了枚舉嘗試的方法。
算法步驟為:
1)用下標計數器累加各學號學生及格科數,
2)嘗試、輸出部分。
累加部分為一重循環,初值1 ,終值為三科及格的總人數,包括重復部分。計7+6+8=21,步長1。
嘗試部分的嘗試范圍為一重循環,初值1 ,終值9,步長1。
定解條件:A(i)=3
方法二算法如下:
main( )
{int a[10],i,xh;
for(i =1;i<=21;i=i+1)
{input(xh);
a[xh]=a[xh]+1;}
for(xh =1;xh<=9;xh=xh+1)
if(a[xh] =3 )
print(xh);
}
【例4】統計找數字對的出現頻率
算法說明:
輸入N(2≤N≤100)個數字(在0與9之間),然后統計出這組數中相鄰兩數字組成的鏈環數字對出現的次數。例如:
輸入:N=20 {表示要輸入數的數目}
0 1 5 9 8 7 2 2 2 3 2 7 8 7 8 7 9 6 5 9
輸出:(7,8)=2 (8,7)=3 {指(7,8)、(8,7)數字對出現次數分別為2次、3次
(7,2)=1 (2,7)=1
(2,2)=2
(2,3)=1 (3,2)=1
算法設計:
其實並不是只有一維數組這樣的數據結構可以在算法設計中有多采的應用,根據數據信息的特點,二維數組的使用同樣可以使算法易於實現,此題就正是如此。
用10*10的二維數組(行、列下標均為0-9),存儲統計結果,i行j列存儲數字對(i,j)出現的次數,無需存儲原始數據,用其做下標,在數據輸入的同時就能統計出問題的結果,
算法如下:
main( )
{int a[10][10],m,i,j,k1,k0;
print(“How many is numbers?”);
input(n);
print(“Please input these numbers:”);
input(k0);
for (i=2;i<=n;i=i+1)
{input(k1); a[k0][k1]=a[k0][k1]+1; k0=k1;}
for (i=0;i<=9;i=i+1)
for (j=0;j<=9;j=j+1)
if (a[i][j]<>0 and a[j][i]<>0); print(“(”,i,j,”)=“,a[i][j],”,(”,j,i,”)=”,a[j][i])
}
數組使信息有序化
當題目中的數據缺乏規律時,很難把重復的工作抽象成循環不變式來完成,但先用數組結構存儲這些地信息后,問題就迎刃而解了,
【例1】編程將編號“翻譯”成英文。例35706“翻譯”成three-five-seven-zero-six。
算法設計1:
算法設計1:
1) 編號一般位數較多,可按長整型輸入和存儲。
2) 將英文的“zero——nine”存儲在數組中,對應下標為0——9。這樣無數值規律可循的單詞,通過下標就可以方便存取、訪問了。
3) 通過求余、取整運算,可以取到編號的各個位數字。用這個數字作下標,正好能找到對應的英文數字。
4)考慮輸出翻譯結果是從高位到低位進行的,而取各位數字,比較簡單的方法是從低位開始通過求余和整除運算逐步完成的。所以還要開辟一個數組來存儲從低位到高位翻譯好的結果,並記錄編號的位數,最后倒着從高位到低位輸出結果。
算法如下:
main( )
{int i,a[10], ind; long num1,num2;
char eng[10][6]={“zero”,”one”,”two”,”three ”,” four”,
” five”,”six”,”seven”,“eight”,”nine”};
print(“Input a num”);
input(num1); num2=num1; ind =0;
while (num2<>0)
{a[ind]=num2 mod 10; ind= ind +1; num2=num2/10; }
print(num1, “English_exp:”, eng[a[ind-1]]);
for( i=ind-2;i>=0;i=i-1)
print(“-”,eng[a[i]]);
}
【例2】一個顧客買了價值x元的商品,並將y元的錢交給售貨員。售貨員希望用張數最少的錢幣找給顧客。
問題分析:無論買商品的價值x是多大,找給他的錢最多需要以下六種幣值:50,20,10,5,2,1。
算法設計:
1)為了能達到找給顧客錢的張數最少,先盡量多地取大面額的幣種,由大面額到小面額幣種逐漸進行。
2)六種面額不是等到差數列,為了能構造出循環不變式,將六種幣值存儲在數組B。這樣,六種幣值就可表示為B[i],i=1,2,3,4,5,6。為了能達到先盡量多地找大面額的幣種,六種幣值應該由大到小存儲。
3)另外還要為統計六種面額的數量,同樣為了能構造出循環不變式,設置一個有六個元素的累加器數組S,這樣操作就可以通過循環順利完成了。
算法如下:
main( )
{int i,j,x,y,z,a,b[7]={0,50,20,10,5,2,1},s[7];
input(x,y);
z=y-x;
for(i=1;i<=6;i=i+1)
{a=z\b[i]; s[i]=s[i]+a; z=z-a*b[i];}
print(y,”-”x,”=”,z:);
for(i=1;i<=6;i=i+1)
if (s[i]<>0)
print(b[i], “----”, s[i]);
}
算法說明:
1)每求出一種面額所需的張數后,一定要把這部分金額減去: “y=y-A*B[j];”,否則將會重復計算。
2)算法無論要找的錢z是多大,我們都從50元開始統計,所以在輸出時要注意合理性,不要輸出無用的張數為0 的信息。
算法分析:問題的規模是常量,時間復雜性肯定為O(1)。
數組記錄狀態信息
問題提出:
有的問題會限定在現有數據中,每個數據只能被使用一次,怎么樣表示一個數據“使用過”還是沒有“使用過”?
一個朴素的想法是:用數組存儲已使用過的數據,然后每處理一個新數據就與前面的數據逐一比較看是否重復。這樣做,當數據量大時,判斷工作的效率就會越來越低。
【例1】求X,使X2為一個各位數字互不相同的九位數。
分析:只能用枚舉法嘗試完成此題。由X2為一個九位數,估算X應在10000——32000之間。
算法設計:
1) 用一個10 個元素的狀態數組p,記錄數字0——9在X2中出現的情況。數組元素都賦初值為1,表示數字0——9沒有被使用過。
2) 對嘗試的每一個數x,求x*x,並取其各個位數字,數字作為數組的下標,若對應元素為1,則該數字第一次出現,將對應的元素賦為0,表示該數字已出現一次。否則,若對應元素為0,則說明有重復數字,結束這次嘗試。
3) 容易理解當狀態數組p中有9個元素為0時,就找到了問題的解。但這樣判定有解,需要掃描一遍數組p。為避免這個步驟,設置一個計數器k,在取x*x各個位數字的過程中記錄不同的數字的個數,當k=9時就找到了問題的解。
算法如下:
main( )
{long x, y1, y2; int p[10], 2, i, t, k, num=0;
for (x=10000;x<32000; x=x+1)
{ for(i=0; i<=9; i=i+1) p[i]=1;
y1=x*x ; y2=y1; k=0;
for(i=1; i<=9. i=i+1)
{t=y2 mod 10;
y2=y2/10;
if(p[t]=1) {k=k+1; p[t]=0;}
else break;
}
if(k=9)
{num=num+1;
print (“No.”,num , “:n=”, x, “n2=”, y1);}
}
}
【例2】游戲問題:
12個小朋友手拉手站成一個圓圈,從某一個小朋友開始報數,報到7的那個小朋友退到圈外,然后他的下一位重新報“1”。這樣繼續下去,直到最后只剩下一個小朋友,求解這個小朋友原來站在什么位置上呢?
算法設計:
這個問題初看起來很復雜,又是手拉手,又是站成圈,報數到7時退出圈……似乎很難入手。
細細分析起來,首先是如何表示狀態的問題。開辟12個元素的數組,記錄12個小朋友的狀態,開始時將12個元素的數組值均賦為1,表示大家都在圈內。這樣小朋友報數就用累加數組元素的值來模擬,累加到7時,該元素所代表的小朋友退到圈外,將相應元素的值改賦為0,這樣再累加到該元素時和不會改變,又模擬了已出圈外的狀態。
為了算法的通用性,算法允許對游戲的總人數、報數的起點、退出圈外的報數點任意輸入。其中n表示做游戲的總人數,k表示開始及狀態數組的下標變量,m表示退出圈外的報數點,即報m的人出隊,p表示已退出圈外的人數。
算法如下:
main( )
{int a[100],i,k,p,m;
print(“input numbers of game:”);
input(n);
print(“input serial number of game start:”);
input(k);
print(“input number of out_ring:”);
input(m);
for( i=1;i<=n;i++)
a[i]=1;
p=0;
while (p<n-1)
{x=0;
while x<m
{k=k+1;
if(k>n) k=1;
x=x+a[k];}
print(k);
a[k]=0;
p=p+1;
}
for( i=1;i<=n;i++)
if ( a[i]=1)
print( “i=”,i);
}
算法說明:
1) 算法中當變量k >n時,賦k=1,表示n號報完數就該1號報數。模擬了將n個人連成了一個“圈”的情況(2.10“算術運算的妙用”中還介紹了其它技巧,請參考)。
2) x為“報”出的數,x=m時輸出退出圈外人的下標k,將a[k]賦值為0;
3) p=n-1時游戲結束;
4) 最后檢測還在圈上a[i]=1的人,輸出其下標值即編號(原來位置)。
思考:可選擇順時針或反時針方向報數。
大整數的存儲及運算
計算機存儲數據是按類型分配空間的。在微型機上為整型提供2個字節16位的存儲空間,則整型數據的范圍為-32768——32767;為長整型提供4個字節32位的存儲空間,則長整型數據的范圍為-2147483648——2147483647;為實型也是提供4個字節32位的存儲空間,但不是精確存儲數據,只有六位精度,數據的范圍±(3.4e-38~3.4e+38) ;為雙精度型數據提供8個字節64位的存儲空間,數據的范圍±(1.7e-308~1.7e+308),其精確位數是17位。
雖然現在工作站或小型機等機型上都有更高精度的數值類型,但這些機型價格昂貴,只有大型科研機構才有可能擁有,一般不易接觸。當我們需要在個人機上,對超過長整型的多位數(簡稱為“高精度數據”)操作時,只能借助於數組才能精確存儲、計算。
在用數組存儲高精度數據時,從計算的方便性考慮,決定將數據是由低到高還是由高到低存儲到數組中;可以每位占一個數組元素空間,也可幾位占一個數組元素空間。
若需從鍵盤輸入要處理的高精度數據,一般用字符數型組存儲,這樣無需對高精度數據進行分段輸入。當然這樣存儲后,需要有類型轉換的操作,不同語言轉換的操作差別雖然較大,但都是利用數字字符的ASCII碼進行的。其它的技巧和注意事項通過下面的例子來說明。本節只針對大 整數的計算進行討論,對高精度實數的計算可以仿照進行。
【例】編程求當N<=100時,N!的准確值
問題分析:問題要求對輸入的正整數N,計算N!的准確值,而N!的增長速度僅次於指數增長的速度,所以這是一個高精度計算問題。
例如:
9!=362880
100! = 93 326215 443944 152681 699263
856266 700490 715968 264381 621468 592963
895217 599993 229915 608914 463976 156578
286253 697920 827223 758251 185210 916864
000000 000000 000000 000000
算法設計:
1)乘法的結果是按由低位到高位存儲在數組ab中,由於計算結果位數太多,若存儲結果的數組,每個存儲空間只存儲一位數字,對每一位進行累乘次數太多。所以將數組定義為長整型,每個元素存儲六位。
2)對兩個高精度數據的乘法運算,比較簡單的方法就是兩個高精度數據的按元素交叉相乘,用二重循環來實現。其中一個循環變量i代表累乘的數據,b存儲計算的中間結果,數組ab存儲每次累乘的結果,每個元素存儲六位數據,d存儲超過六位數后的進位。
算法如下:
main( )
{long ab[256],b,d; int m,n,i,j,r;
input(n);
m=log(n)*n/6+2; ab[1]=1;
for( i=2; i<=m;i++)
ab[i]=0;
d=0;
for( i=2; i<=n;i++)
for (j=1;j<=m;j++)
{b=ab[j]*i+d; ab[j]=b%1000000; d=b/1000000;}
for (i=m ;i>=1;i--)
if( ab[i]==0) continue;
else {r=i; break;}
print(n,“!=”); print(ab[r]);
for(i=r-1;i>=1;i--)
{if (ab[i]>99999) {print(ab[i]); continue;}
if (ab[i]>9999) {print( “0”,ab[i]); continue;}
if (ab[i]> 999) {print( “00”,ab[i]); continue;}
if (ab[i]>99 ) {print( “000”,ab[i]); continue;}
if (ab[i]>9) {print( “0000”,ab[i]); continue;}
print( “00000”,ab[i]);
}//for
}
算法說明:
1)算法中“m=log(n)*n/6+2;”是對N!位數的粗略估計。
2)輸出時,首先計算結果的准確位數r,然后輸出最高位數據,在輸出其它存儲單元的數據時要特別注意,如若計算結果是123000001,ab[2]中存儲的是123,而ab[1]中存儲的不是000001,而是1。所以在輸出時,通過多個條件語句保證輸出的正確性。
構造趣味矩陣
趣味矩陣 經常用二維數組來解決
根據趣味矩陣中的數據規律,設計算法把要輸出的數據存儲到一個二維數組中,最后按行輸出該數組中的元素。
基本常識:
1)當對二維表按行進行操作時,應該“外層循環控制行;內層循環控制列”;反之若要對二維表按列進行操作時,應該“外層循環控制列;內層循環控制行”。
2)二維表和二維數組的顯示輸出,只能按行從上到下連續進行,每行各列則只能從左到右連續輸出。所以,只能用“外層循環控制行;內層循環控制列”。
3)用i代表行下標,以j代表列下標(除特別聲明以后都遵守此約定),則對n*n矩陣有以下常識:
主對角線元素i=j;
副對角線元素: 下標下界為1時 i+j=n+1,
下標下界為0時i+j=n-1;
主上三角◥元素: i <=j;
主下三角◣元素: i >=j;
次上三角◤元素:下標下界為1時i +j<=n+1,
下標下界為0時i+j<=n-1;
次下三角◢元素:下標下界為1時i +j>=n+1,
下標下界為0時i+j>=n-1;
【例1】編程打印形如下規律的n*n方陣。
例如下圖:使左對角線和右對角線上的元素為0,它們上方的元素為1,左方的元素為2,下方元素為3,右方元素為4,下圖是一個符合條件的階矩陣。
0 1 1 1 0
2 0 1 0 4
2 2 0 4 4
2 0 3 0 4
0 3 3 3 0
算法設計:根據數據分布的特點,利用以上關於二維數組的基本常識,在只考慮可讀性的情況下,
算法如下:
main( )
{int i,j,a[100][100],n;
input(n);
for(i=1;i<=n;i=i+1)
for(j=1;j<=n;j=j+1)
{if (i=j or i+j=n+1) a [i][j]=0;
if (i+j<n+1 and i<j) a [i][j]=1;
if (i+j<n+1 and i>j) a [i][j]=2;
if (i+j>n+1 and i>j) a [i][j]=3;
if (i+j>n+1 and i<j) a [i][j]=4;}
for(i=1;i<=n;i=i+1)
{print( “換行符”);
for( j=1;j<=n;j=j+1)
print(a[i][j]);
}
}
【例2】螺旋陣:任意給定n值,按如下螺旋的方式輸出方陣:
n=3 輸出:
1 8 7
2 9 6
3 4 5
n=4 輸出:
1 12 11 10
2 13 16 9
3 14 15 8
4 5 6 7
算法設計:
算法設計1:此例可以按照“擺放”數據的過程,逐層(圈)分別處理每圈的左側、下方、右側、上方的數據。以n=4為例詳細設計如下:
把“1——12”看做一層(一圈)“13-16”看做二層……以層做為外層循環,下標變量為i。由以上兩個例子,n=3、4均為兩層,用n\2表示下取整,這樣(n+1)/2下好表示對n/2上取整。所以下標變量i的范圍1——(n+1)/2。
i層內“擺放”數據的四個過程為(四角元素分別歸四個邊):
1) i列(左側),從i行到n-i行
( n=4,i=1時 “擺放1,2,3”)
2) n+1-i行(下方),從i列到n-i列
( n=4,i=1時 “擺放4,5,6”)
3) n+1-i列(右側),從n+1-i行到i+1行
( n=4,i=1時 “擺放7,8,9”)
4) i行(上方),從n+1-i列到i+1列
( n=4,i=1時 “擺放10,11,12”)
四個過程通過四個循環實現,用j表示i層內每邊中行或列的下標。
算法如下:
main( )
{int i,j,a[100][100],n,k;
input(n); k=1;
for(i=1;i<=n/2;i=i+1)
{for( j=i;j<=n-i;j=j+1) { a [j][i]=k; k=k+1;} /左側/
for( j=i;j<=n-i;j=j+1) {a [n+1-i][j]=k; k=k+1;} /下方/
for( j= n-i+1;j>=i+1;j=j-1){a[j][n+1-i]=k;k=k+1;} /右側/
for( j= n-i+1;j>=i+1;j=j-1) {a[i][j]=k; k=k+1;} /上方/
}
if (n mod 2=1)
{i=(n+1)/2; a[i][i]=n*n;}
for(i=1;i<=n;i=i+1)
{print(“換行符”);
for( j=1;j<=n;j=j+1)
print(a[i][j]);
}
}
算法設計2為了用統一的表達式表示循環變量的范圍,引入變量k,k表示在某一方向的上數據的個數,k的初值是n,每當數據存放到左下角時,k就減1,又存放到右上角時,k又減1;此時的k 值又恰好是下一圈左側的數據個數。
同樣,為了將向下和向上“擺放”數據時,行下標i 的變化用統一的表達式表示;同時也將向右向左“擺放”數據時, 列下標j的變化用統一的表達式表示。引入符號變量t,t的初值為1,表示處理前半圈:左側i向下變大,j向右變大;t就變為-1;表示處理后半圈:右側i向上變小,j向左變小。於是一圈內下標的變化情況如下:
j=1 i=i+t 1----n
i=n j=j+t 2----n 前半圈共2*k-1個
t= -t k=k-1
j=n i=i+t n-1----1
i=1 j=j+t n-1----2 后半圈共2*k-1個
t= -t
用x模擬“擺放”的數據;用y(1——2*k-1)作循環變量,模擬半圈內數據的處理的過程。
算法如下:
main( )
{int i,j,k,n,a[100][100],b[2],x,y;
b[0]=0; b[1]=1; k=n; t=1; x=1;
while x<=n*n
{for (y=1;y<=2*k-1;y++) // t=1時處理左下角
{ b[y/(k+1)]=b[y/(k+1)]+t; //t= -1時處理右上角
a[b[0]][b[1]]=x;
x=x+1;}
k=k-1; t=-t;
}
for( i=1;i<=n;i++)
{print(“換行符”);
for(j=1;j<=n;j++)
print(a[i][j]);
}
}
算法說明:
在“算法設計”中我們沒有介紹數組b,由算法中的“a[b[0]][b[1]]=x;”語句可以體會到,數組元素b[0]表示存儲矩陣的數組a的行下標,數組元素b[1]是數組a的列下標。那么為什么不用習慣的i,j作行、列的下標變量呢?使用數組元素作下標變量的必要性是什么?表達式“b[y/(k+1)]= b[y/(k+1)]+t;”的意義又是什么?
“算法設計”中已說明,y用作循環變量,模擬半圈內數據的處理的過程。變化范圍是1——2*k-1。而在半圈里,當y=1——k時,是行下標在變化,列下標不變;注意:此時y/(k+1)的值為0,確實實現了行下標b[0]的變化(加1或減1,由t決定)。當y=k+1——2*k-1時, 是行下標在不變,列下標在變化;注意:此時y/(k+1)的值為1,確實實現了列下標b[1]的變化。這又驗證了2.5.4節介紹的利用一維數組“便於循環”的技巧,當然這離不開對數組下標的構造。
綜上所述,引入變量t,k和數組b后,通過算術運算將一圈中的上下左右四種不同的變化情況,歸結構造成了一個循環“不變式”。
【例3】設計算法生成魔方陣
魔方陣是我國古代發明的一種數字游戲:n階魔方是指這樣一種方陣,它的每一行、每一列以及對角線上的各數之和為一個常數,這個常數是:1/2*n*(n2+1),此常數被稱為魔方陣常數。由於偶次階魔方陣(n=偶數)求解起來比較困難,我們這里只考慮n為奇數的情況。
以下就是一個n=3的魔方陣:
6 1 8
7 5 3
2 9 4
它的各行、各列及對角線上的元素之和為15。
算法設計:有趣的是如果我們將1,2,……n2按某種規則依次填入到方陣中,得到的恰好是奇次魔方陣,這個規則可以描述如下:
(1)首先將1填在方陣第一行的中間,即(1,(n+1)/2)的位置;
(2)下一個數填在上一個數的主對角線的上方,若上一個數的位置是(i,j),下一個數應填在(i1,j1),其中i1=i-1、j1=j-1。
(3)若應填寫的位置下標出界,則出界的值用n 來替代;即若i-1=0,則取i1=n;若j-1=0,則取j1=n。
(4)若應填的位置雖然沒有出界,但是已經填有數據的話,則應填在上一個數的下面(行減1,列不變),即取i1=i-1,j1=j。
這樣循環填數,直到把n*n個數全部填入方陣中,最后得到的是一個n階魔方陣。
算法如下:
main( )
{int i,j, i1,j1,x,n,t,a[100][100];
print(“input an odd number:”);
input(n);
if (n mod 2=0) {print(“input error!”); return;}
for( i=1;i<=n;i=i+1)
for(j=1;j<=n;j=j+1)
a[i][j]=0;
i=1; j=int((n+1)/2); x=1;
while (x<=n*n)
{a[i][j]=x; x=x+1; i1=i; j1=j; i=i-1; j=j-1;
if ( i=0) i=n;
if (j=0) j=n;
if ( a[i][j]<>0 ) { i=i1+1; j=j1;} }
for( i=1;i<=n;i=i+1)
{print(“換行符”);
for(j=1;j<=n;j=j+1)
print(a[i][j]);
}
}
算法說明:若當前位置已經填有數的話,則應填在上一個數的下面,所以需要用變量記錄上一個數據填入的位置,算法中i1,j1的功能就是記錄上一個數據填入的位置。
算法分析:算法的時間復雜度為O(n2)。
