第四次C博客作業
Q0.展示PTA總分
-
一維數組
-
二維數組
-
字符數組
Q1.本章學習總結
1.1 學習內容總結
- 數組中數據的查詢
- 遍歷查找法:顧名思義,將數組中的數據都遍歷一遍,如果數組都遍歷完了則說明沒有找到數據,反之即找到了目標數據
- 二分查找法:注意使用的前提是數組中的元素有序排列!如果不是有序排列則需要在查找前將數組排序。二分查找具體操作為:將數組正中間的元素與查找的數據比較,兩者相等即為找到了,否則將數組分為左右兩個部分,如果中間的元素大於查找的數據,則進一步查找左半部分,否則進一步查找右半部分,重復以上過程。
/*遍歷查找和二分查找的簡單樣例:輸出目標數據在數組中的位置*/
int a[5] = { 1,2,3,4,5 };
輸入要查找的數據
for i=0 to 4 do //遍歷查找法
if 查找的數據等於數組中當前下標的元素 then
輸出i並結束循環
end if
end for
int left = 0, right = 4; //二分查找法。左部開始為0,右部開始為數組長度減一
int mid;
while left的值小於right //循環條件,如果left>right則說明沒有找到目標數據
mid = (left + right) / 2; //這句是關鍵
if 查找的數據等於數組中當前下標的元素 then
輸出i並結束循環
else if 查找的數據大於中間元素 then //如果沒有找到則將左部或右部折半
left = mid + 1;
else
right = mid - 1;
end if
end while
- 數組中數據的插入
- 一個思路是將插入的數據和數組中已有的數據做比較,記錄下比插入的數據大的數組元素的下標,將數據插入到該位置,再將后面所有的元素后移動一位
- 也可以將數據直接插入到數組的末尾,再與前面的元素逐個進行比較、交換的操作
/*插入新數據到數組中的簡單樣例*/
int a[6] = { 1,2,3,4,5 };
輸入要插入的新數據insert
for i=0 to 4 do //尋找第幾個元素比插入的數據大
if 當前的元素大於插入的數據 then
儲存當前i為index並結束循環
end if
end for
for i=5 to index do //將index后的元素全部右移1位
a[i] = a[i - 1];
end for
a[index] = insert; //再將要插入的數據傳給記錄的位置
a[5] = insert; //先插末尾再排序的方法
for i=5 to 0 do
if a[i]的值小於a[i-1]的值 then
交換a[i]和a[i-1]
end if
end for
- 數組中數據的刪除
- 可以遍歷數組,當數組當前元素等於要刪除的數據時直接將后面的元素全部左移1位,將該元素覆蓋掉,從而實現刪除
- 或是再定義一個數組,用來儲存原數組中要留下的元素
/*刪除數組中的元素的簡單樣例*/
int a[5] = { 1,2,3,4,5 };
輸入要刪除的數據del
for i=0 to 4 do
if 當前的元素等於要刪除的數據 then
儲存當前i為index並結束循環
end if
end for
for i=index to 4 do //將index后的元素全部左移1位
a[i] = a[i + 1];
end for
int b[5]; //再定義一個數組的方法
定義數組b的下標j,長度k,初值均為0
for i=0 to n do
if a[i]的值不等於要刪除的值 then
b[j++]=a[i];
k++;
end if
end for
- 數組的排序
- 選擇法:對數組中固定的一個位置和后面的所有元素逐個進行比較,它每輪會將最小的數放到最前
- 冒泡法:數組按順序對兩個相鄰的元素進行比較,它每輪會將最大的數放到最后
- 用一張圖表示一下它們的區別(畫的都是第一輪比較)
/*冒泡排序和選擇排序的簡單樣例*/
輸入數組長度n;
輸入n個數組元素;
for i=0 to n do //選擇法排序
for j=i+1 to n do
if a[i]的值比a[j]的值大
交換a[i]和a[j]
for i=1 to n do //冒泡法排序,這個循環控制冒泡的次數
for j=0 to n-i do
if a[j]的值大於a[j+1]
交換a[j]和a[j+1]
- 數組做枚舉用法
- 說實話我並沒有看懂這個是什么意思,百度到的也都是enum的枚舉類型,只能按我個人的理解寫了
- 個人感覺是將數組的元素都列舉出來,之后直接使用的意思,這里我直接用2840題庫中的查找星期題目來做例子吧
#include <stdio.h>
#include <string.h>
#define MAXS 80
int getindex(char* s);
int main()
{
int n;
char s[MAXS];
scanf("%s", s);
n = getindex(s);
if (n == -1) printf("wrong input!\n");
else printf("%d\n", n);
return 0;
}
int getindex(char* s)
{
const char week[7][10] = { "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday" }; //將所有元素都枚舉出來,賦予week[0]到week[6],例如在后面使用week[5]即代表字符串Friday
int i;
for (i = 0; i < 7; i++)
{
if (strcmp(week[i], s) == 0) //比較輸入的字符串是否與week數組中的元素相同
return i;
}
return -1;
}
- 哈希數組
-
散列表(Hash table,也叫哈希表),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。
-
引用自哈希表的百度百科
- 根據上面的資料我們可以知道哈希數組是按照一個key值直接訪問該位置儲存的元素(我的推測)
- 目前我們做過的跟哈希數組有關的題目例子:有重復的數據,就是直接將數據元素作為下標,直接查找里面的內容
- 一般的方法我們選擇按順序儲存輸入的內容,再通過遍歷的方式來判斷有無重復,但在這題中如果這樣寫,又遇到輸入100000個不同數的極端情況,那么我們的循環要走100000x100000遍,導致運行超時,所以我們選擇直接將輸入的數據作為下標,元素內容為該數字出現的次數,當輸入的數字出現次數超過1時即有重復數據,結束循環。這樣子寫即使遇到最極端的情況,循環也只需要走100000遍,運行的效率大大提高了
#include<stdio.h>
int main()
{
int a[100001] = { 0 }; //初始化數字1-100000的出現次數均為0
int n;
int i;
int num;
scanf("%d", &n);
for (i = 0; i < n; i++)
{
scanf("%d", &num);
a[num]++;
if (a[num] > 1) //如果這個數字出現超過1次即有重復數據,直接結束循環
{
printf("YES");
break;
}
}
if (i >= n) //如果上面這個循環能走完就說明沒有重復數據
printf("NO");
return 0;
}
1.2 本章學習體會
- 數組對前面部分的內容的綜合性較強,要求對循環、分支等結構內容要有一定的熟練度
- 題目的難度又又又提升了,但還是不能ctrl c+v,也最好不要說什么“我看看答案,看懂了就可以直接復制過來了”之類的話,如果不動手寫的話真的很容易就忘了
(大腦:我懂了 手:不你不懂) - 個人認為數組的概念和使用部分並不難理解,只要稍加練習和思考都能做出來
- 遇到小問題試着問一下同學/老師,有時候就是一點小錯誤反而找不出來,別人一下就能發現,但也不能啥問題都一股腦的都丟給別人,別人看着講的累,自己聽的也累
- 代碼量統計(
題目太難了刷不上去了)
Q2.PTA實驗作業
2.1 閱覽室
2.1.1 偽代碼
定義day記錄要輸入幾天的數據
定義count記錄當前為第幾天
定義hour、minute、number、ch記錄借書時間、書號、借出/歸還
定義數組book行長1001,列長6,其中每列代表如下:0-S,1-E,2、4-借出去的時間,3、5-歸還的時間
定義數組borrow、totaltime、avgtime記錄每天借出去的書本數量、總閱讀時間、平均閱讀時間
輸入總天數
for count=1 to day do
讓book數組里的全部內容均為0
輸入書號、借出/歸還、小時、分鍾
while 書號不為0 do
if 輸入了S then
記錄該書號S、借出的小時和分鍾
end if
else if 輸入了E且該書號之前有及記錄過S then
記錄該書號E、歸還的小時和分鍾
end if
if 該書號有同時記錄S和E then
當天借書量+1
計算閱讀時間加到當天總閱讀時間內
清空該書號的S、E記錄
end if
輸入書號、借出/歸還、小時、分鍾
end while
if 該天的借書量不為0 then
計算平均閱讀時間
end for
輸出每天的借書量和平均閱讀時間
return 0;
2.1.2 代碼截圖
2.1.3 造數據測試
輸入數據 | 輸出數據 | 備注 |
---|---|---|
![]() |
![]() |
題目例子 |
![]() |
0 0 | 有連續S和連續E,有S和E全反 |
![]() |
2 90 | 同一本書被借走多次 |
2.1.4 PTA提交列表及說明
- 第一個部分正確:連續S E和同一本書被借多次測試點未過,嘗試在寫入E的條件判斷中加入前面要有讀入過S
- 第二個部分正確:這兩個測試點還是沒有過,經測試后發現問題沒有解決,在S E都讀入計算時間后將該書號的S E都清空
- 答案正確:經過上面兩次修正后成功通過
2.2 螺旋方陣
2.2.1 偽代碼
定義number為要螺旋輸出的數,初值為1
定於circle記錄要轉幾圈才能輸出完畢
定義數組a行長列長均為10
輸入行數n
for circle=1 to (n + 1) / 2 do
for j=circle-1 to n-circle do
a[circle - 1][j] = number;
number加1
end for
for i=circle to n-circle do
a[i][n - circle] = number;
number加1
end for
for j=n-circle-1 to circle-1 do
a[n - circle][j] = number;
number加1
end for
for i=n-circle-1 to circle do
a[i][circle - 1] = number;
number加1
end for
end for
輸出數組a
return 0;
2.2.2 代碼截圖
2.2.3 造數據測試
輸入數據 | 輸出數據 | 備注 |
---|---|---|
5 | ![]() |
題目例子 |
1 | 1 | 最小n |
8 | ![]() |
最大偶數n |
9 | ![]() |
最大n |
2.2.4 PTA提交列表及說明
- 雖然這題提交列表是一遍過,但在2840題庫中做這道題的時候做了很久,但在vs中一直有奇怪的錯誤,而我也不可能連測試樣例都過不了就提交上PTA吧?改到正確且各種情況都測試了以后我才提交了PTA於是一遍過
- 一開始做的時候我完全沒有想到利用轉的圈數去做,直接右下左上的順序一步步寫循環,寫的漏洞百出,只完成了最外圈往右和往下的輸出,其他部分全部爆炸
- 然而我並沒有死心,還是按照這個思路再次優化代碼,優化到了第一圈完美輸出,但第二圈開始又會將第一圈的內容覆蓋掉
- 這時候我發現這個方法的問題,越到內圈越難寫,變量太少,對i、j的循環的起始終止值很難控制,於是把所有代碼刪除,重新構思
- 又經過幾次嘗試后引入了變量circle,由轉的圈數控制起始、終止值,思路開闊了許多,寫的也很順利,但同時也遇到了新問題,什么時候循環條件加等號,循環初值要等於多少,n、circle和i、j之間又有什么關系等等
- 寫完后因為測試數據較少就將1到9都測試了一遍,很幸運偶數部分沒有遇到問題,於是就提交PTA通過了
2.3 刪除重復字符
2.3.1 偽代碼
定義字符型數組ch,長度為96,全部賦0
定義字符變量chh
while 讀入字符chh不為回車時 do
ch[chh - ' '] = chh;
for i=0 to 95 do
if ch[i]的內容不為0 then
輸出ch[i]
return 0;
2.3.2 代碼截圖
2.3.3 造數據測試
輸入數據 | 輸出數據 | 備注 |
---|---|---|
ad2f3adjfeainzzzv | 23adefijnvz | 題目例子 |
(全部空格) | (一個空格) | 最長字符,全空格 |
$!@REewt@@jdzoi23412 | !$1234@ERdeijotwz | |
a | a | 最短字符串 |
2.3.4 PTA提交列表及說明
- 部分正確:一開始寫的不是這個方法,用的是先讀入數據再遍歷刪除排序的方法,但是這樣寫有空格的測試點又過不了,又突然靈光一現又有了新的方法
- 答案正確:因為我的代碼不太一樣,就說一下思路吧。
- 首先是ch的長度96,查閱課本后的ASCII碼表,發現是從32到127,這樣字符減去空格字符就是0~95了,而且這樣讀入已經是按ASCII碼排好序的狀態
- 然后是刪除重復部分,並沒有特意去寫,這個字符減去32對應的下標的數組直接就賦值這個字符,再次遇到這個元素也只是再次賦值,不會有兩個該元素
- 最后控制只輸出不為\0的數組元素就完成了題目的要求
Q3.代碼閱讀
-
題目說明:
-
參考代碼:
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string.h>
#include <limits.h>
using namespace std;
int main(void)
{
int n,begin = 0,end = INT_MAX;
int x;
char str[10];
while( cin >> n && n )
{
getchar();
begin = 0,end = INT_MAX;
while ( gets(str) && strcmp(str,"right on") )
{
if( strcmp(str,"too high") == 0 && n < end )
end = n;
else
if( strcmp(str,"too low") == 0 && n > begin )
begin = n;
cin >> n;
getchar();
}
if( n > begin && n < end )
printf("Stan may be honest\n");
else
printf("Stan is dishonest\n");
}
return 0;
}
- 代碼閱讀
- 這題比較簡單,說一下題意,Stan和Ollie玩猜數游戲,Ollie猜一個數,Stan告訴她答案太大/太小/正確,但玩了幾輪后Ollie發現不對勁,
覺得Stan肯定在耍老娘,他肯定是改過答案!我們要根據他們游戲的過程來判斷Stan有沒有作弊在耍Ollie - 每次輸入right on就輸出一次Stan是否誠實,之后輸入0可以退出循環結束程序
- 雖然是C++的代碼,但他似乎除了cin >> n以外也沒有C++的代碼了
- 代碼很注意細節,每次讀取完數字n后都會getchar()吸收換行符
- 外部循環條件是輸入數字n,如果n不為0就進入循環;內部循環條件是輸入字符串str,如果str不為right on就繼續循環
- 內部循環將字符串str和too high(too low)作對比,如果輸入的數字小於原來的最大數(大於原來的最小數)就更新尾部(頭部)為n
- 輸入right on后將最后讀到的數和頭部、尾部做比較,如果大於頭部小於尾部,那么Stan可能是個誠實的孩子沒有說謊,否則他就是不誠實的壞孩子
個人覺得這題完全可以改成用C編寫,而且他引用了這么多頭文件似乎也沒怎么用到,還有個不知道干啥用的變量x
- 這題比較簡單,說一下題意,Stan和Ollie玩猜數游戲,Ollie猜一個數,Stan告訴她答案太大/太小/正確,但玩了幾輪后Ollie發現不對勁,