0.展示PTA總分
1.本章學習總結
1.一維數組
- 1.定義和引用
- 一般形式:
類型名 數組名 [數組長度];
這里數組長度必須是個常量! - c語言規定,數組名表示該函數所分配連續內存空間中第一個單元的地址,即首地址。數組空間一經分配,在運行過程中就不會再改變,因此<span style="color:red"數組名是一個地址常量,不允許改變。
- 數組下標從0開始,合理取值范圍為[0,數組長度-1];
- 一般形式:
- 2.一維數組的初始化
- 一般形式:
類型名 數組名 [數組長度]={初值表}
,初值表的數據是按順序依次給數組元素賦初值的; - 定義靜態數組,如果沒有對該靜態數組賦初值,系統會自動賦為0;
- 數組初始化時,如果對全部元素都賦了值,就可以省略數組長度;
- 初始化也可以只針對部分數組,但是這里長度不可省!否則系統會自動認為該數組長度為初值表中的數據個數;
- 一般形式:
2.二維數組
- 1.定義和引用
- 一般形式:
類型名 數組名 [行長度][列長度];
- 行下標的合理取值范圍為[0,行下標-1],列下標的合理取值范圍是[0,列下標-1];
- 一般形式:
- 2.二維數組的初始化
- 分行賦值一般形式:
類型名 數組名[行長度][列長度]={{初值表0},···,{初值表k},···}; //把初值表k中的元素依次賦給第k行的元素;
- 順序賦值一般形式:```類型名 數組名 行長度][列長度]={初值表};//按數組元素在內存中的存放順序,把初值表中的數據依次賦給元素;
- 如果對全部元素賦了初值,或分行賦值時,列出了全部行,就可以省略行長度,但是列長度是不可以省略的!
- 分行賦值一般形式:
3.一維字符數組和字符串
- 1.字符串
- 定義:字符串是由有效字符和一個結束標志'\0'組成。
- "a"和'a'是不一樣的!,前者是字符串常量,包括'a'和'\0'兩個字符,后者是字符常量,只有一個字符;
- 由於字符串結束符'\0'代表空操作,無法輸入,所以,輸入字符串時,需要事先設定一個輸入結束符,一旦輸入它,就表示字符串輸入結束,並把輸入的結束字符轉換為字符串結束符'\0'.
- 2.初始化
- 設置靜態字符數組,如果沒有對該字符數組賦初值,系統自動賦為'\0'
- 字符數組的初始化還可以用字符串常量,例:```static char s[6]={"hello"},但要注意的是,字符串有一個結束標志'\0',將字符串存入字符數組中,數組長度至少是字符串的有效長度+1;
- 3.字符串的輸入和輸出
- 輸入
輸入函數 | 形式 | 使用范圍 |
---|---|---|
fgets 函數 | fgets(字符數組名/字符指針, 可讀入的字符個數size, stdin) | 可以吸收換行符和空格,但是字符串長度不可超過size-1,因為最后一定會留一個儲存位置來儲存'\0',如果 size 大於字符串的長度,則多余的部分系統會自動用 '\0' 填充。 |
gets函數 | gets(數組名) | 遇到換行符結束,並自動把換行符改成'\0'加在末尾。gets() 有一個非常大的缺陷,即它不檢查預留存儲區是否能夠容納實際輸入的數據,換句話說,如果輸入的字符數目大於數組的長度,gets 無法檢測到這個問題,就會發生內存越界 |
scanf函數 | scanf("%s",字符數組名) | 遇到換行符和空格就結束,末尾自動加上'\0',不吸收換行符合空 |
- 輸出
輸出函數 | 形式 | 使用范圍 |
---|---|---|
printf函數 | printf("%s",字符數組名) | 遇到'\0'就輸出結束 |
puts函數 | puts(數組名) | 遇到'\0'就輸出結束,但是和printf不一樣的一點是,puts輸出結束后,會自動換行 |
4.數組中如何查找數據**
- 1.遍歷法
這是最簡單的方法,從數組中的第一個數一直檢索到數組的最后一個數,如果找到用戶想要的那個數據就提前退出。
/*在數組num[]中,找到用戶輸入的那個數據,如果找到請輸出該數據在數組中的下標,如果沒有找到,就輸出"not find"*/
int num[10]={1,4,7,9,12,45,67,33,21,8}; //數組num;
int x;//用戶想要找的數據;
輸入用戶想要找的數據x
for i=0 to 9
if x的值和num[i]的值相等
輸出該數字在數組中的下標i的值
break
end if
end for
if i>=10 //說明已經檢索到超出數組的范圍,沒有找到用戶需要的數字;
輸出"not find"
end if
運行結果:
這種方法的好處是,思路簡單,容易操作,但是,如果數組長度較大時,使用這種方法對計算機來說,計算量較大,效率很低,很容易出現運行超時的現象!所以我們還需要利用題目的某些關鍵條件來優化它,減少計算機的計算量。
- 2.二分查找法(只適用於數組中的元素本就有序的情況)
設n個元素的數組a已經有序,用low和high兩個變量來表示查找的區間,即在a[low]~a[high]中去查找用戶輸入的值x,和區間中間位置的元素a[mid] (mid=(low+high)/2)作比較,如果相等則找到,算法結束;如果是大於則修改區間(數組為升序數列:low=mid+1;數組為降序數列:high=mid-1);如果是小於也修改區間(數組為升序的改為:high=mid-1,;數組為降序數列:low=mid+1);一直到x=a[mid] (找到該值在數組中的位置)或者是low>high(在數組中找不到x的值)時算法終止;
/*在有序數組num中,尋找用戶輸入的值x*/
自定義二分查找函數int Bsearch(int a[],int n,int x)
主函數main中
int a[10]={10,9,8,7,6,5,4,3,2,1};//有序數組;
int x ;//用戶輸入的數;
int m ;找到的數所在的下標;
輸入用戶想要尋找的數;
進入函數Bsearch尋找x的值,把返回的值賦給m
if m大於等於0
說明找到x的位置,輸出該值在數組中的下標;
else
說明沒有找到x的位置,即,該數組中沒有x,輸出“not find”;
end if
自定義函數Bsearch
int low = 0//查找范圍的最小下標,先從數組第一位開始;
int high = n-1//查找范圍的最大下標,先從數組的最后一位開始;
int mid; //查找范圍的中間位置;
while(low<=high)
mid = (low+high)/2;
if x==a[mid]
說明找到x的位置,break跳出循環;
else if x小於a[mid]
改變查找范圍:low=mid+1;
else x大於a[mid]
改變查找范圍:high=mid-1;
end if
end while
if low<=high //表示找到x的位置;
返回mid的值;
else //表示沒有找到x的位置
返回-1;
end if
運行結果:
使用二分查找法可以大大縮短計算機運行的時間,但唯一不足的就是其只局限於在有序數列中查找數據。
5.數組中如何插入數據
先在數組中找到需要插入的位置,將處於該位置及后面的數據全部都往后移一位,然后把該數據插入。例將一個給定的整數插到原本有序的整數序列中,使結果序列仍然有序(具體題目參加pta題集:插入排序)。大概做法如下:
#define N 1000//保證數組長度足夠大;
int num[N] ;//有序數組;
int n;//用戶想要輸入數據的個數;
int data;//插入的數據
輸入數據個數n;
for i=1 to n
輸入數據,賦給num[i];
end for
輸入插入的數據data;
for i=0 to n
if data<=num[i]
for j=n to i //將后面的數據全都往后移一位;
num[j]=num[j-1]
end for
break;//退出循環;
end if
end for
插入數據data,num[i]=data;
輸出數組
運行結果:
6.數組中如何刪除數據
- 1.建立新函數
顧名思義,就是建立一個新的函數,把刪除后的數據按順序保存到新函數中去。
#define N 1000//保證輸入的數據不超過1000個;
int num[N];//用戶輸入的數據;
int n;//用戶想要輸入的數據個數;
int str;//保存刪除后的數組;
int data;//用戶想要刪除的數據;
輸入數據個數n
for i=0 to n
輸入數據,賦給num[i]
end for
輸入想要刪除的數據
for i=0 to n
if num[i]!=data
str[j++]=num[i]
end if
end for
if j!=i
說明找到刪除數據,輸出刪除數據后的數組str[]
else
說明沒有找到刪除數據。
end if
運行結果:
這樣的好處是可以保存原來的數組,而且思路簡單,容易想到。
- 2.重構數組
這種方法是把原來的數組重構,即直接在原數組上做改動。基本步驟就是
/*刪除字符*/
#define N 1000//保證輸入的數據不超過1000個;
char str[N];//用戶輸入的數據;
char letter;//用戶想要刪除的字母;
輸入字符串str[]
輸入想要刪除的字母letter;
for i=0 to str[i]!='\0'||str[i]!='\n'
if str[i]!= letter
str[j++]=str[i]
end if
end for
給str數組最后加上結束標志'\0'
輸入字符串str
運行結果:
重構數組方法在處理大數據時可以減少占用計算機過多的儲存空間,但其缺點就是無法保存原來的數據。
7.排序方法
- 選擇排序法
第一趟先選擇數組中的第一個數,和后面的數做比較,找到最大/最小的那個數,和第一位數交換,;第二趟然后取第二個數,和第二位后面數作比較,找到除第一個數外的最大/最小的數,和第二位交換位置,第三趟取第三位數···直到第n趟,只剩一個數,排序結束;
/*從大到小排列*/
for i=0 to n //外循環
index=i;//index用於保存最最大的數據的下標;
for j=i to n //內循環
if num[index]<num[j] //尋找從第i個數據開始往后的最大數據;
index=j;
end if
end for
交換num[index]和num[i]的數據;
end for
運算結果:
{{uploading-image-358528.png(uploading...)}}
思路簡單,運用的范圍較廣;
- 冒泡排序法
這種方法從第一個數開始重復地走訪過要排序的元素列,依次比較兩個相鄰的元素,如果他們的順序錯誤就把他們交換過來。一直重復地進行直到沒有相鄰元素需要交換,也就是說該元素已經排序完成。
/*數列從大到小排序*/
for i=1 to n //外循環
for j=0 to n-i //內循環
if num[j]<num[j+1]; //交換相鄰的兩個數;
temp=num[j];
num[j]=num[j+1];
num[j+1]=temp;
end if
end for
end for
運行結果:
8.數組做枚舉用法
- 1.尋找重復數據
有時候我們需要在某個數組a[]中找到重復的某個數據,或者出現次數最多的某個數,這個時候我們可以用下標法,數組的下標做枚舉用法,列舉出所有可能出現的數字,借助輔助數組hash[]來查找數據,用數列a[]的每個元素作為數組hash[]的下標,以hash[a[i]]的值來查找所需的數據a[i]。
/*查找數組中重復的數據*/
定義查找重復數據的函數FindRepeat(int n)
主函數中
int n;//定義變量n保存數據個數;
輸入n的值
if FindRepeat(n)的值小於0
說明沒有找到重復的數據,輸出"NO";
else
說明找到重復的數據,輸出該數據;
end if
FindRepeat函數中
初始化hash數組為0;//這里一般定義靜態數組,來給hash[]中每個元素初始化為0;這里要注意hash的長度,如果data的值為0-n,那么數組hash的長度范圍為n+1;
for i=1 to n //n為用戶要輸入的數的個數;
用戶輸入一個數data
end for
if hash[data]==1
有重復數據
return data
else
數據data暫時還不是重復數據,hash[data]=1;
end if
end for
沒有重復數據
return -1
運行結果:
在這里合理的設置靜態輔助數組————hash(哈希)組,可以簡化我們的代碼,提高運行效率,這種方法在大數據處理中非常好用!但是它也有個局限性,就是只適用於正整數,不適用於負數和小數
- 2.閱覽室
1.2 本章學習體會
- 數組讓我們進一步的了解到c語言的
可怕魅力,在字符數組這方面,對如何運用合適的輸入語句來讀取字符串,我還不太熟練,這體現在我的pta題集中,經常出現最長字符串的測試點錯誤!這點需要注意。學習了數組意味着,我們要學習了更多的題型,如果沒有及時復習方法的話,很容易就忘記,應該課后多總結總結,配合老師給的視頻和pta題集,多復習鞏固。 - 這兩周的代碼量為:1494
2.PTA實驗作業
2.1求矩陣中的最大小值
2.1.1 數據處理
#define N 10
定義數組FindMaxMin(int num[][N],int n,int m)
在主函數main中
定義整型的二維數組num[N][N]
定義變量矩陣的行數n;
定義變量矩陣的列數m;
while(scanf("%d %d",&n,&m)==2) //表示讀入兩個有效的數字,配合while循環可以重復測試數據;
for i=0 to n
for j=0 to m
輸入矩陣
end for
end for
進入FindMaxMin函數尋找最大值和最小值;
end while
在FindMaxMin函數中
定義變量max;
定義變量min;
定義變量最大值max所在的行標maxRow;
定義變量最大值max所在的列表maxCol;
定義變量最小值min所在的行標minRow;
定義變量最小值min所在的列表minCol;
給max和min賦初值為矩陣的第一個數num[0][0];
最大最小值的行標列標都為0;
for i=0 to n
for j=0 to m
if 最大值max小於num[i][j]
max=num[i][j]
maxRow=i;
maxCol=j;
end if
if 最小值大於num[i][j]
min=num[i][j];
minRow=i;
minCol=j;
end if
end for
end for
輸出最大值和最小值以及他們所在的位置;
2.1.2 代碼截圖
2.1.3 造測試數據
輸入數據 | 輸出數據 | 說明 |
---|---|---|
3 5 57 30 66 41 98 11 93 54 62 31 49 87 71 70 37 | max=98 row=0 col=4 min=11 row=1 col=0 | 題目給的樣例 |
10 10 1 ~100 | max=100 row=9 col=9 min=1 row=0 col=0 | 矩陣數據最多 |
4 3 1 2 5 6 1 5 8 9 9 5 8 3 | max=9 row=2 col=1 min=1 row=0 col=0 | 有兩個最大的數,輸出行最小的那個行標和列標 |
1 1 9 | max=0 row=0 col=0 min=0 row=0 col=0 | 只有一個數據 |
2.1.4 PTA提交列表及說明
答案錯誤:錯誤原因是不可以重復測試數據;
編譯錯誤:我以為是測試點兩個最大最小輸出前面那個的行標和列標錯誤,於是在判斷大小的前面加上了等號,然后我把等號刪掉了;
答案錯誤:都是無法重復測試數據的原因;
格式錯誤:在輸出最小值及其行標列標的語句中沒有加上換行符;
2.2 閱覽室
2.2.1 數據處理
#define Max 2000 //1000本書借和還兩個操作
定義函數FrequencyTime(int record[Max][3],int end)
在主函數main中
定義二維數組record[Max][3]
定義變量book來保存書號
定義變量hour來保存小時數
定義變量minute來保存分鍾數
定義變量days來保存天數
定義變量state來保存書的狀態
定義變量k來表示數組的行標,k的值表示一天記錄了多少次書的借入和借出;
輸入天數
for i=0 to days
k=0
while(1)
輸入書號,書的狀態,小時數,分鍾數;
if book==0//說明一天結束,要計算其平均閱讀時間和借書次數
進入FrequencyTime函數
break;
end if
record[k][0]=book;
record[k][1]=state;
record[k][2]=hour*60+minute;//把時間和分鍾合成一個數據存放起來,方便計算
k++
end while
end for
在FrequencyTime函數中
定義變量times保存總的借書時間,並初始化為0
定義變量count保存借書次數,並初始化為0
for i=0 to k
if 這本書是借的狀態
for j=i+1 to k
if 同一本書,還沒有換就又借出去,說明該記錄無效
break退出循環
end if
if 同一本書,還回來了
總時間再加上借出的時間;並且借出次數加上1
break;
end if
end for
end if
end for
if count為0
輸出"0 0",表示一天都沒有借書
else
輸出次數和平均每次借書時間
2.2.2 代碼截圖
2.2.3 造測試數據
輸入數據 | 輸出數據 | 說明 |
---|---|---|
3 1 S 08:10 2 S 08:35 1 E 10:00 2 E 13:16 0 S 17:00 0 S 17:00 3 E 08:10 1 S 08:20 2 S 09:00 1 E 09:20 0 E 17:00 | 2 196 0 0 1 60 | sample等價,結果有舍入,有不匹配,有直接結束 |
1 1 S 09:10 1 S 10:10 1 E 13:07 1 E 15:48 0 S 17:00 | 1 177 | 同一本書被借多次,有不匹配 |
1 1000 S 00:00 1000 E 24:00 0 S 24:00 | 1 1440 | 書號、時間取到邊界值 |
1 1000 E 09:56 1000 E 10:11 1000 S 10:30 1000 S 11:56 1000 E 14:37 1000 E 16:27 | 1 161 | 最大數據,有連續S和連續E,有S和E全反 |
2.2.4 PTA提交列表及說明
部分正確:只考慮到如果書號相同,兩次書的狀態不同就可以算記錄有效,沒有考慮到有重復的S和E,還有同書號E在S前的無效記錄;
部分正確:忽略了S和E匹配的問題,以及如果有同書號重復S的問題;
答案錯誤:for循環如果沒有加大括號的話,后面只能跟一個語句,我沒有意識到這一點,導致計算次數和平均時間的語句不在for循環內,因而數據無法滿足條件,進入if語句進行計算;
部分正確:還是忽略了S重復且和E的不匹配問題;
答案錯誤:我以為我前面的答案錯誤的代碼是接近答案的然后復制了之前的代碼,按照部分正確中的錯誤進行修正,其實就是計算借書次數和平均時間不在循環體內....
2.3 A-B
2.3.1 數據處理
#define M 10002
定義函數DeleteLetter來完成A-B
在主函數main中
定義字符數組a[M]
定義字符數組b[M]
輸入字符串a
輸入字符串b
進入函數DeleteLetter
在函數DeleteLetter中
for i=0 to a[i]!='\n'
for j=0 to b[j]!='\n'
if a[i]==b[j]
說明是需要刪除的字符,退出循環
end if
if b[j]=='\n'
說明不是需要刪除的字符,輸出該字符
end if
end for
end for
2.3.2 代碼截圖
- 在聽了老師的建議后,這里補充一個比較簡便的做法:
2.3.1 數據處理
定義輔助數組hash[256];
定義字符ch;
定義字符串str[10002];
輸入字符串a賦給str
while((ch=getchar())!='\n')//輸入字符串b中的每個字符;
hash[ch]=1;//整型和字符型本就可以相互轉換,這里把ch對應的ASCII的值作為hash的下標,表示需要刪除的字符;
end while
while (str[i]!='\n') //str[i]是以'\n'為結束標志;
if (hash[str[i]]==0) //說明是可以輸出的字符
輸出字符str[i];
end if
i++;
end while
2.3.2 代碼截圖
- 這種方法的代碼量比我之前的少太多了,而且計算機運行的時間也比之前的要快,效率提高了許多!!
2.3.3 造測試數據
輸入數據 | 輸出數據 | 說明 |
---|---|---|
I love GPLT! It's a fun game! aeiou | I lv GPLT! It's fn gm! | sample 等價 |
ab ···· bb····ab(總共9996個a,4個b) | a···a(9996個a) | 最長字符串,要刪除的都在中間和兩端 |
how old are you how old are you | 全刪除 | |
a···a(10000個) b | a···a(10000個) | 最長字符無刪除 |
2.3.4 PTA提交列表及說明
部分正確:字符數組的范圍沒有考慮好,導致最長字符串的最后一個字符被吞,所以關於長字符串的測試點都錯了;還有全刪除,如果全刪除的話,會輸出換行符;
部分正確:在對字符串b的遍歷中,for語句的括號中多了不小心被打上去的“j<”,刪去;
部分正確:即使把字符串最大的長度修改到10001,也是不行的,因為可能輸入了10000個字符,用fgets讀取時,第10001個字符自動被賦為'\0'導致錯誤;所以字符長度最大值應該是10002;
編譯錯誤:沒有定義變量flag就使用它,定義一下flag;
部分錯誤:還是字符串的長度沒有設置好,最大長度應該是10002。
3.閱讀代碼
代碼功能
輸入測試樣例數和表達式,判斷兩個表達式的是否等價,如果等價就輸出YES,如果不是則輸出NO;
代碼的優點
- 1.合理定義全局變量,解決了函數只能返回一個變量的缺點,有利於數據之間的傳遞;
- 2.分裝函數合理清晰,每個函數都有自己的功能,易於修改調試,且函數間的關聯性強;
- 3.合理設置二維數組h[][]配合record函數來判斷計算機應該要做哪種運算,而不是運用過多的if語句,這樣不僅可以減少代碼量,配合switch語句還提高了計算機的運行效率,並且,這里也解決了算術表達式中的優先級的問題;
- 4.邏輯思維能力強,代碼的構思很巧妙,環環相扣,大佬求抱大腿!!