c++ 一維陣列
一維陣列
C++語言
一、為什麼要使用陣列
通過前面幾章的學習,我們已經可以編寫程式來解決各種相當複雜的問題了,但是當需要處理的資料比較多時,僅依靠前面的知識是不夠的,即使簡單的問題也可能需要比較複雜的程式來處理。請看下面的例子:
例題:輸入50個學生的某門課程的成績,打印出低於平均分的學生序號與成績。
【分析】在解決這個問題時,雖然可以通過一個變數來累加讀入的50個成績求出學生的總分,進而求出平均分。但因為只有讀入最後一個學生的分數後才能求得平均分,並且要求打印出低於平均分的學生序號和成績,故必須把50個學生的成績都保留起來,然後逐個和平均分比較,把低於平均分的成績打印出來。如果,用簡單變數a1,a2,…,a50儲存這些資料,要用50個變數儲存輸入的資料,程式片斷如下:
cin>>a1>>a2>>…>>a10;
…
cin>>a41>>a42>>…>>a50;
注意,如果真正要像上面這樣編寫程式,則上面的所有省略號必須用完整的語句寫出來。可以看出,這樣的程式是多麼繁瑣。如果說處理的資料規模達到成千上萬,上面的例子單單讀入就會異常複雜,電腦的優勢沒有得到體現。
從以上的討論可以看出,如果只使用簡單變數處理大量資料,就必須使用大量只能單獨處理的變數,即使是簡單問題也需要編寫冗長的程式。
選手們可能已經看出,我們需要把一大批具有相同性質的資料組合成一個新型別的變數,可以用簡單的程式(比如迴圈50次)對這個新變數的各個分量進行相同的處理,每個分量仍然保留單個變數的所有性質(在上面的例子中,各分量是整型變數或實型變數的性質)。
如果能像數學中使用下標變數ai形式表示這50個數,則問題就容易實現。在C++語言中,具有下標性質的資料型別是陣列。如果使用陣列,上面的問題就變得十分簡單、清晰。例如,讀入50個學生的成績,只需寫如下語句即可:
for (int i=1;i<=50;++i)
cin>>a[i];
在這裡引用了帶下標的變數(分量變數稱為陣列元素)a[i]來代替a1,a2…,a50,方括號中的i稱為下標,當迴圈變數i=1時a[i]就是a[1];當i=2時a[i]就是a[2]……;當i=50時a[i]就是a[50]。輸入的時候,讓i從1變化到50,迴圈體內輸入語句中的a[i]也就分別代表了a1,a2…,a50這50個帶下標的變數。這樣上述問題的程式可寫為:
tot = 0; // tot儲存50個學生的總分
for (int i=1;i<=50;++i) // 迴圈讀入每一個學生的成績,並把它累加到總分中
{
cin>>a[i];
tot+=a[i];
}
ave= tot/50; //計算平均分
for (int i=1;i<=50;++i)
if (a[i]<ave) cout<<“No. ”<<i<<“ ”<<a[i];
//如果第i個同學成績小於平均分,則將輸出這個學生的序號和成績。
要在程式中使用下標變數,必須先說明這些下標變數的整體為陣列,即陣列是若干個同名(如上面的下標變數的名字都為a)下標變數的集合,這些變數的型別全部一致。
二、一維陣列的定義
當陣列中每個元素只帶有一個下標時,我們稱這樣的陣列為一維陣列。
陣列的定義格式如下:
型別識別符號 陣列名[常量表達式]
說明:
①陣列名的命名規則與變數名的命名規則一致。
②常量表達式表示陣列元素的個數。可以是常量和符號常量,但不能是變數。
例如:
int a[10]; //陣列a定義是合法的
int b[n]; //陣列b定義是非法的
三、一組陣列的引用
通過給出的陣列名稱和這個元素在陣列中的位置編號(即下標),程式可以引用這個陣列中的任何一個元素。
一維陣列元素的引用格式:
陣列名[下標]
例如:
int a[10];
其中,a是一維陣列的陣列名,該陣列有10個元素,依次表示為:
a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9]。
需要注意的是:a[10]不屬於該陣列的空間範圍。
當在說明部分定義了一個數組變數之後,C++編譯程式為所定義的陣列在記憶體空間開闢一串連續的儲存單元。例如:
上例中的a陣列在記憶體的儲存如表所示:
a陣列共有10個元素組成,在記憶體中10個數組元素共佔10個連續的儲存單元。a陣列最小下標為0,最大下標9。按定義a陣列所有元素都是整型變數。
再次提醒注意:型別和變數是兩個不同概念,不能混淆。就陣列而言,程式的執行部分使用的不是陣列型別而是陣列變數。
說明:
(1)下標可以是整型常量或整型表示式。如果使用表示式作為下標,就要計算表示式的值以確定下標。
(2)C++語言中,每個陣列第一個元素的下標都是0,因此第一個元素為第0個數組元素。
(3)C++語言只能逐個引用陣列元素,而不能一次引用整個陣列。
例如:int a[100],b[100]; a=b;這樣的寫法是非法的。
(4)陣列元素可以像同類型的普通變數那樣使用,對其進行賦值和運算的操作,和普通變數完全相同。
例如: c[10]=34;實現了給c[10]賦值為34。
四、一維陣列的初始化
陣列的初始化可以在定義時一併完成。格式:
型別識別符號 陣列名[常量表達式]={值1,值2,…}
例如:
int a[5]={1,2,3,4,5}
說明:
(1)在初值列表中可以寫出全部陣列元素的值,也可以寫出部分。例如,以下方式可以對陣列進行初始化:
int x[10]={0,1,2,3,4};
該方法一次僅對陣列的前5個元素依次進行初始化。
(2)對陣列元素全部初始化為0,可以簡寫為:{0}。
例如:
int a[5]={0}; 將陣列a的5個元素都初始化為0。
五、陣列元素的插入與刪除操作
正是因為陣列的在記憶體空間的地址是連續的,所以我們在刪除或者增添元素的時候,就難免要移動其他元素的地址
例如刪除下表為3的元素,需要對下表為3的元素後面的所有元素都要做移動操作,
如圖所示:
例5.1 輸入n個數,要求程式按輸入時的逆序把這n個數打印出來,已知整數不超過100個。也就是說,按輸入相反順序列印這n個數。
【分析】我們可定義一個數組a用以存放輸入的n個數, 然後將陣列a中的內容逆序輸出。
#include<cstdio> int a[100]; int main() { int x,n=0; while(scanf("%d",&x)==1) a[n++]=x; //相當{a[n]=x;n++;} for (int i=n-1;i>=1;--i) printf("%d ",a[i]); //注意%d後面有一個空格,保證行首行尾均無空格 printf("%d\n",a[0]); return 0; }
【說明】:
語句int a[100]聲明瞭一個包含100個整型變數的陣列,它們是:a[0],a[1],a[2],…,a[99]。注意,沒有a[100]。在上述程式中,陣列a被宣告在main函式的外面。只有放在外面時,陣列a才可以開得很大;放在main函式內時,陣列稍大就會異常退出。它的道理將在後面討論,只需要記住規則即可。
陣列不能夠進行賦值操作:如果宣告的是int a[MAXN],b[MAXN],是不能賦值b=a的(Pascal語言可以的)。如果要從陣列a複製k個元素到陣列b,可以這樣做:memcpy(b,a,sizeof(int)*k)。當然了,如果陣列a和b都是浮點型的,複製時要寫成memcpy(b,a,sizeof(double)*k)。如果需要把陣列a全部複製到陣列b中,可以寫得簡單一些:memcpy(b,a,sizeof(a))。使用memcpy函式要包含標頭檔案cstring。
例5.2 將a陣列中第一個元素移到陣列末尾,其餘資料依次往前平移一個位置。
【分析】為完成題目所要求的操作,其演算法應該包括以下幾個主要步驟:
①把第一個元素的值取出放在一個臨時單元 temp中;
②通過 a[2]→a[1], a[3]→a[2], a[4]→a[3],……, a[n]→a[n-1],實現其餘元素前移
③將 temp值送入a[n].
#include<iostream> #include<iomanip> //呼叫setw函式需註明使用該庫 const int n=10; using namespace std; int a[n],temp; int main() { cout<<"read "<<n<<" datas"<<endl; for (int i=0; i<n; ++i) cin>>a[i]; temp=a[0]; for (int i=0; i<n-1; ++i) a[i]=a[i+1]; a[n-1]=temp; cout<<"Result:"<<endl; for (int i=0; i<n; ++i) cout<<setw(3)<<a[i]; //setw函式控制輸出場寬 return 0; }
執行結果 :
read 10 datas:
• 1 2 3 4 5 6 7 8 9 10
Result:
• 2 3 4 5 6 7 8 9 10 1
例5.3 賓館裡有一百個房間,從1-100編了號。第一個服務員把所有的房間門都打開了,第二個服務員把所有編號是2的倍數的房間“相反處理”,第三個服務員把所有編號是3的倍數的房間作“相反處理”…,以後每個服務員都是如此。當第100個服務員來過後,哪幾扇門是開啟的。(所謂“相反處理”是:原來開著的門關上,原來關上的門開啟。)
【分析】此題較簡單,用a[1],a[2],…,a[n]表示編號為1,2,3,…,n的門是否開著。模擬這些操作即可,參考程式如下:
#include<cstdio> #include<cstring> #define MAXN 100+10 int a[MAXN]; int main() { int n,k,first=1; memset(a,0,sizeof(a)); for (int i=1;i<=100;++i) for (int j=1;j<=100;++j) if (j%i==0) a[j]=!a[j]; for (int i=1;i<=100;++i) if (a[i]) { if(first) first=0; else printf(" "); printf("%d",i); } printf("\n"); return 0; }
執行結果:
1 4 9 16 25 36 49 64 81 100
【說明】:
memset(a,0,sizeof(a))的作用是把陣列a清零,它在cstring中定義。雖然也能用for迴圈完成相同的任務,但是用memset又方便又快捷。另一個技巧在輸出:為了避免輸出多餘空格,設定了一個標誌變數first,可以表示當前要輸出是否為第一個。第一個變數前不應該有空格,但其他都有。
例5.4 約瑟夫問題:N個人圍成一圈,從第一個人開始報數,數到M的人出圈;再由下一個人開始報數,數到M的人出圈;…輸出依次出圈的人的編號。N,M由鍵盤輸入。
【分析】 (1)由於對於每個人只有出圈和沒有圈兩種狀態,因此可以用布林型標誌陣列儲存遊戲
過程中每個人的狀態。不妨用true表示出圈,false 表示沒有出圈。
(2)開始的時候,給標誌陣列賦初值為false,即全部在圈內。
(3)模擬報數遊戲的過程,直到所有的人出圈為止。
程式如下:
#include<iostream> using namespace std; int n,m,s,f,t; bool a[101]; //根據題意開出陣列大小 int main() { cin>>n>>m; //共n人,報到m出圈 cout<<endl; for (t=1;t<=n;++t) a[t]=false; //等同於memset(a,0,sizeof(a)),要呼叫cstring庫 f=0; t=0; s=0; //剛開始所有變數預設值也是0,或者用f=t=s=0; do { ++t; //逐個列舉圈中的所有位置 if (t==n+1) t=1; //陣列模擬環狀,最後一個與第一個相連 if (a[t]==false) ++s; //第t個位置上有人則報數 if (s==m) //當前報的數是m { s=0; //計數器清零 cout<<t<<" "; //輸出出圈人的編號 a[t]=true; //此處的人已出圈,設定為空 f++; //出圈的人數增加一個 } } while(f!=n); //直到所有的人都出圈為止 return 0; }
執行結果:
輸入: 8 5
輸出: 5 2 8 7 1 4 6 3
這是一個在演算法設計上很有名氣的經典約瑟夫(Josephu)問題,它有很多變例。如猴子選大王、持密碼報數、狐狸追兔子等(見上機練習)。
例5.5 輸入十個正整數,把這十個數按由大到小的順序排列。(選擇排序 )
將資料按一定順序排列稱為排序,排序的演算法有很多,其中選擇排序是一種較簡單的方法。
【問題分析】
要把十個數按從大到小順序排列,則排完後,第一個數最大,第二個數次大,……。因此,我們第一步可將第一個數與其後的各個數依次比較,若發現,比它大的,則與之交換,比較結束後,則第一個數已是最大的數。同理,第二步,將第二個數與其後各個數再依次比較,又可得出次大的數。如此方法進行比較,最後一次,將第九個數與第十個數比較,以決定次小的數。於是十個數的順序排列結束。
如對5個進行排序,這個五個數分別為8 2 9 10 5。按選擇排序方法,過程如下:
初始資料 :8 2 9 10 5
第一次排序:8 2 9 10 5
9 2 8 10 5
10 2 8 9 5
10 2 8 9 5
第二次排序:10 8 2 9 5
10 9 2 8 5
10 9 2 8 5
第三次排序:10 9 8 2 5
10 9 8 2 5
第四次排序:10 9 8 5 2
對於十個數,則排序要進行9次。
程式如下:
#include<iostream> #include<iomanip> using namespace std; int t,a[11]; int main() { cout<<"Input 10 intergers:"<<endl; //讀入10個初始資料 for (int i=1; i<=10; ++i) cin>>a[i]; cout<<endl; for (int i=1; i<=9; ++i) //進行9次排序 for (int j=i+1; j<=10; ++j) //將第i個數與其後所有數比較 if (a[i]<a[j]) { t=a[i]; a[i]=a[j]; a[j]=t;} //若有比a[i]大,則與之交換 for (int i=1;i<=10;++i) cout<<setw(5)<<a[i]; return 0; }
執行結果:
輸入: 8 67 52 189 74 5 58 9 23 41
輸出: 189 74 67 58 52 41 23 9 8 5
例5.6 程式設計輸入十個正整數,然後自動按從大到小的順序輸出。(氣泡排序)
【問題分析】
①用迴圈把十個數輸入到A陣列中;
②從A[1]到A[10],相鄰的兩個數兩兩相比較,即:
A[1]與A[2]比,A[2]與A[3]比,……A[9]與A[10]比。
只需知道兩個數中的前面那元素的標號,就能進行與後一個序號元素(相鄰數)比較,可寫成通用形式A[i]與A[i+1]比較,那麼,比較的次數又可用1~( n-i )迴圈進行控制(即迴圈次數與兩兩相比較時前面那個元素序號有關) ;
③在每次的比較中,若較大的數在後面,就把前後兩個對換,把較大的數調到前面,否則不需調換位置。
下面例舉5個數來說明兩兩相比較和交換位置的具體情形:
5 6 4 3 7
5和6比較,交換位置,排成下行的順序;
6 5 4 3 7
5和4比較,不交換,維持同樣的順序;
6 5 4 3 7
4和3比較,不交換,順序不變
6 5 4 3 7
3和7比較,交換位置,排成下行的順序;
6 5 4 7 3
經過(1~(n-1))次比較後,將3調到了末尾
經過第一輪的1~ (N-1)次比較,就能把十個數中的最小數調到最末尾位置,第二輪比較1~ (N-2)次進行同樣處理,又把這一輪所比較的“最小數”調到所比較範圍的“最末尾”位置;……;每進行一輪兩兩比較後,其下一輪的比較範圍就減少一個。最後一輪僅有一次比較。在比較過程中,每次都有一個“最小數”往下“掉”,用這種方法排列順序,常被稱之為“冒泡法”排序。
程式如下:
#include<iostream> #include<iomanip> using namespace std; const int n=10; int t,a[n+1]; //定義陣列 int main() { for (int i=1; i<=n; ++i) cin>>a[i]; //輸入十個數 for (int j=1; j<=n-1; ++j) //冒泡法排序 for (int i=1; i<=n-j; ++i) //兩兩相比較 if (a[i]<a[i+1]) //比較與交換 {t=a[i]; a[i]=a[i+1]; a[i+1]=t;} for (int i=1; i<=n; ++i) cout<<setw(5)<<a[i]; //輸出排序後的十個數 cout<<endl; return 0; }
執行結果:
輸入: 2 5 8 6 12 34 65 22 16 55
輸出: 65 55 34 22 16 12 8 6 5 2
例5.7 用篩法求出100以內的全部素數,並按每行五個數顯示。
【問題分析】
⑴ 把2到100的自然數放入a[2]到a[100]中(所放入的數與下標號相同);
⑵ 在陣列元素中,以下標為序,按順序找到未曾找過的最小素數minp,和它的位置p(即下標號);
⑶ 從p+1開始,把凡是能被minp整除的各元素值從a陣列中劃去(篩掉),也就是給該元素值置 0;
⑷ 讓p=p+1,重複執行第②、③步驟,直到minp>floor(sqrt(N)) 為止;
⑸ 列印輸出a陣列中留下來、未被篩掉的各元素值,並按每行五個數顯示。
用篩法求素數的過程示意如下(圖中用下劃線作刪去標誌):
① 2 3 4 5 6 7 8 9 10 11 12 13 14 15…98 99 100 //置數
② 2 3 4 5 6 7 8 9 10 11 12 13 14 15…98 99 100 //篩去被2整除的數
③ 2 3 4 5 6 7 8 9 10 11 12 13 14 15…98 99 100 //篩去被3整除的數
……
2 3 4 5 6 7 8 9 10 11 12 13 14 15…98 99 100
//篩去被整除的數
程式如下:
#include<iostream> #include<math.h> //在Dev C++中可呼叫數學函式庫cmath #include<iomanip> using namespace std; const int n=100; int t; bool a[n+1]; int main() { for (int i=0; i<=n; ++i) a[i]=true; //等同於memset(a,1,sizeof(a)) , //要呼叫cstrin庫 a[1]=false; for (int i=2; i<=sqrt(n); ++i) if (a[i]) for (int j=2; j<=n/i; ++j) a[i*j]=false; t=0; for (int i=2; i<=n; ++i) if (a[i]) { cout<<setw(5)<<i; t++; if (t%5==0) cout<<endl; } return 0; }