【計劃執行報告】Day5 04-04 第10章C++典型面試題(續)&PCA降維原理推導&圖像處理初探
Day5 04-04 C++第10章典型面試題(續)&PCA降維原理推導&圖像處理初探
這是我:計劃執行的第5天
今天是灰色的一天,默哀之餘仍需努力奮鬥!
1.今日感想
- 週末相對工作日來說時間可以集中一些,劃分相對簡單,但是目前的問題是:佈置的任務總是不能按時完成,有些任務(比如較複雜的數學推導,機器學習報告)根本不知道要花多久(總不能到時間沒做完就不做了吧,畢竟是要展示的);
- 飯後休息半個小時真的很有必要,的確會精神很多,提高效率;
- 計劃一定要在寫blog前完成,畢竟後者耗時更長,很可能寫完blog後很可能一時半會兒搞不清明天要做啥了;
- 線性代數、概率統計真的得好好回顧一下,只可惜書在學校,網上找不到對應版本的電子書(主要是不方便翻閱);
- 像競賽訓練這個板塊,我只留了1個小時,但是對於陌生且較爲複雜的問題(最近今天都是),我1小時內頂多理解原理,沒時間編寫;爲此,我的想法是:今日理解,明日實現(或許這麼做比剛看完原理就實現更好);
- 關於計劃執行報告的寫法:①我收回昨天的觀點,目前還是決定不把內容單獨成篇,一天中學了什麼,就把總結寫在當天的執行報告中,儘管這會使知識較爲零碎,但暫不考慮,也沒精力考慮;等到某一個知識體系學習得較爲全面時,可以單獨寫一篇文章(時間充足的前提下),這時候就可以大面積地copy執行報告中的相關內容,從而大量節省時間;②標題突出一天中各個主要事件(不超過三個);③關於筆記:大多數情況下我會邊學邊記(Win10自帶sticky Notes挺好用的),部分也會在寫blog時記錄下來
2.計劃執行報告
2.1近期計劃(03-31-04-12)
1.準備4月10日的機器學習最終報告——《暢想無監督學習》;查找文獻與知識補充:《機器學習——算法視角》、數學知識
2.完成專業課的作業(流體機械能轉化、生物質能,新能源熱利用可以往後稍稍);
3.備戰藍橋杯,爲此:①每天1h左右的刷題或者典型題攻克;②知識補充:《程序員的面試筆記:C/C++、算法、數據結構篇》
2.2今日計劃表
2.3實際時間分配
今天洗漱時間超了些時(上廁所+多記了10min),明天注意!
3.C++程序設計基礎知識梳理&典型面試題(續)
還剩下幾個典型面試題(位運算與內存分配相關),不多了(如果在明早能完成的話就補到)
3.1 程序的編譯和執行
【知識梳理】
- 過程:源程序–>預處理–>編譯–>彙編–>鏈接–>可執行文件
- 預處理器除了處理#開頭的代碼行外,還做了:①處理預定義的宏:例如__DATE__、__FILE__;②處理預註釋:用空格替代註釋;③處理三元符:如??=替換成"#",??/替換成""(歷史遺留問題,老鍵盤上沒有”#“或"^")
- 編譯器對預處理過的代碼進行詞法分析、語法分析和語義分析,將符合規則的程序轉換成等價的彙編代碼
- 彙編器將彙編代碼翻譯成可識別的機器指令
- 鏈接器把標準庫文件、目標代碼和其他目標文件鏈接到一起
【典型面試題】
- 簡述#include<>和#include""的區別
- 簡述#與##在define中的作用
- 簡述assert斷言的概念
3.2 變量
【典型面試題】
-
寫出下面代碼執行後i、j、m、n的值
int i=10,j=10; int m=(i++)+(i++)+(i++); int n=(++j)+(++j)+(++j);
考察了i++與++j的區別,答案:
VS2005下:i=13;j=13;m=30;n=39
GCC下:i=13;j=13;m=30;n=37這說明編譯器不同,對同一個表達式的處理順序可能會有差異。
-
簡述C++的類型轉換操作符
答:
static_cast:①基本類型轉換②在對象指針之間的類型轉換時,可以將父類指針轉變爲子類指針(當父類指針指向父類對象時轉換不安全),也可以將子類指針轉換爲父類指針;③不相關的類的指針無法相互轉換dynamic_cast:①只能用於對象指針之間的類型轉換;②父類指針–>子類指針過程中會對其背後的對象類型進行檢查以確保類型完全匹配,static_cast則不會;③當父類指針指向子類對象,且父類中包含虛函數時,轉換才能成功,否則返回NULL(對引用則是拋出異常)
const_cast:一般情況下,無法將常量指針直接賦值給普通指針,因此通過它可以移除常量指針的const屬性,實現const指針–>非const指針轉換
reinterpret_cast:①將一個類型的指針轉換爲另一個類型的指針,不論兩者是否有繼承關係;②可以把一個指針轉換爲一個整數,或把一個整數轉換爲一個指針;③常用於不同函數指針之間的轉換
-
簡述靜態全局變量的概念
答:
靜態全局變量通過在全局變量前加個static形成。與普通全局變量的區別:
①靜態全局變量通常在源文件中聲明和定義,不能使用extern關鍵字將其導出,作用域僅限定於定義靜態全局變量所在的文件內部;普通全局變量的作用域爲整個工程,在頭文件中用extern聲明,並在源文件中定義;
②頭文件中聲明靜態全局變量的同時會初始化(無論是否顯式初始化),相當於在頭文件中同時完成聲明和定義;普通全局變量不能在頭文件中定義;
③若多個源文件都是用#include包含了定義靜態全局變量的頭文件,那麼靜態全局變量在各個源文件中都有一份單獨的copy,且初始值相同,copy之間相互獨立
3.3 條件語句與循環語句
【典型面試題】 巧打乘法口訣表
描述:編寫一個函數,接收一個整形參數n表示輸出規模,要求只用一重循環輸出乘法口訣表的全部內容,並且程序中不能使用任何條件語句。
思路 除法與取餘的妙用
#include<iostream>
using namespace std;
//一重循環、禁用條件語句
void print(int n){
int row=1,col=1;
char flag[3]=" \n"; //注意轉義字符只佔1個字節
while(row<=n){
cout<<row<<"*"<<col<<"="<<row*col<<flag[col/row];
int tmp=col%row+1;
row+=col/row;
col=tmp;
}
}
int main(){
print(9);
return 0;
}
3.4 宏定義與內聯
【知識梳理】
-
對於宏定義:
1) 所謂的宏函數實際上不是函數,只是形式與用起來像函數而已
2)宏函數的機理是在預處理階段將宏定義原原本本地按照字符串進行替換,因此,與普通函數相比,宏函數省去了函數調用過程,節省了函數調用的開銷
3)在宏定義中,最好將參數加上括號,這樣在替換時保證括號內的表達式優先運算
4)使用宏定義時儘量不要將++x這類表達式作爲參數 -
對於內聯函數:
1)inline不能出現在函數的聲明前
2) 確保內聯函數的函數體十分簡單
3)不建議將構造函數定義爲內聯函數,因爲構造函數會隱式調用基類的構造函數,並初始化成員變量,使得代碼展開比想象中大得多
【典型面試題】 簡述內聯函數與宏定義的區別
答:
相同處:
①目的相同:都能夠節省頻繁的函數調用過程中的時空開銷,提高程序執行效率;
②實現類似:都是通過將函數調用替換成完整的函數體;
區別:
①宏定義僅僅是字符串的替換,而內聯函數是個函數,具有函數的基本性質,因此內聯函數可以像普通函數一樣調試,而宏定義不能;
②內聯函數和宏定義的代碼展開發生在不同階段(分別是編譯階段和預處理階段),因此許多編譯階段的工作只對內聯函數有效,例如類型安全檢查和自動類型轉換;
③內聯函數作爲類的成員函數時,可以訪問類的所有成員,this指針也會被隱式地正確使用,而宏定義實現不了;
3.5 sizeof的使用
【知識梳理】
- sizeof不是一個函數,而是一個單目運算符
- sizeof的操作數可以是類型名(如sizeof(int)),也可以是表達式
- 將數組名作爲sizeof的操作數可以獲得整個數組所佔的空間,但如果將數組名作爲實參傳遞給子函數,那麼子函數的形參對應的參數已變爲指針,此時sizeof獲取的只是指針所佔的字節數
- 不要用sizeof計算字符串的長度!用strlen()來算!
int main(){ char s[20]; cin>>s; cout<<strlen(s)<<endl;//字符串長度(除去'\0') cout<<sizeof(s)<<endl; //永遠爲20 return 0; }
- 結構體sizeof的計算結果必須是結構體中佔用空間最多的成員所佔空間的整數倍
- 存在結構體嵌套時的數據對齊,要以結構體中最深層的基本數據類型爲準
【典型面試題】不能使用sizeof計算的表達式
答:
①位域聲明結構體中的成員;②函數名;③無返回值或返回值爲void的函數
struct baby{ //位域
unsigned int gender:1;
unsigned int weight:5;
unsigned int week:7;
unsigned int blood:3;
unsigned int height:8;
};
int triple(int num){
return 3*num;
}
void show(){
cout<<"hello"<<endl;
}
int main(){
sizeof(baby);//4
//sizeof(baby.gender); //CE
sizeof(triple); //CE
sizeof(triple(3)); //4
sizeof(show()); //warning,結果爲1
return 0;
}
3.6 內存分配
還存在點理解問題
3.7 位運算
【典型面試題】
-
不使用臨時變量交換兩個數(兩種解法)
方法1 加減法實現
void swap_1(int& a,int& b){ a=a-b; b=a+b; a=b-a; }
方法2 異或操作實現
void swap_2(int& a,int& b){ a=a^b; b=a^b; //b=a^b^b=a a=a^b; //a=a^b^a=b }
-
計算二進數中1的個數
知識點:與操作和移位操作
方法1//通過num&1取二進制末位,不斷右移 int binCount_1(int num){ int ans=0; while(num){ if(num&1) ans++; num=num>>1; } return ans; }
方法2
//從後往前數1的個數: //利用num-1&num除去最後一位1 //直到num爲0 int binCount_2(int num){ int count=0; while(num){ count++; num=num&(num-1);//除去最後一個1 } return count; }
一個具體實例流程幫助理解:
-
將二進制數倒數第M位的前N位取反
思路:準備一個變量x=1–>將x左移N位–>x=x-1–>將x左移M位–>將二進制數和x進行異或運算int getNum(int num,int m,int n){ int x=1; x=x<<n; x-=1; x=x<<m; return num^x; }
一個具體實例流程幫助理解:
-
找出人羣中唯一的“單身狗”
描述:一個數組中除了一個數字只出現一次外,其餘數字均出現偶數次,要求只掃描數組一次,找出這個只出現一次的數字。
思路:異或操作去重int getSingleDog(int* a,int n){ int ans=0; for(int i=0;i<n;i++) ans^=a[i]; return ans; }
-
找出人羣中三個“單身狗”中的任意一個
描述:一個數組中除了三個數字只出現一次外,其餘數字均出現偶數次,要求用最小的時間複雜度找出這三個數字中的任意一個。
思路:將三個數分到不同的兩個組中,通過異或操作去重,難點在於如何將三個數分開。這時候我們可以先舉個簡單的例子來幫助分析問題:
比如數組{2,3,8,7,3,2,5},一共七個元素,這就是說分成兩組後必有一個組的元素個數爲奇數個,且另一個爲偶數個,事實上,對於含有奇數個“單身狗”的數組,其元素總數一定爲奇,其兩個子分組的元素個數一定是一奇一偶。那麼怎麼分成兩組呢?對於二分類,可以藉助0-1的特性,根據其二進制數中的某一位來劃分,比如我們就根據最末位來劃分:
參照位 0001 {2,3,8,7,3,2,5} --> 0:{2,2,8} 1:{3,3,5,7}可見8被單獨分開了,由此我們便可以得到另一個規律:目標“單身狗”所在的組類元素個數一定爲奇。這樣我們只需對含奇數個元素的組進行異或運算去重即可。
這樣就OK了嗎?其實,我們的這種取特定位進行分類對某些數據是不能一次分開的,比如數組{2,3,9,13,3,2,5},三個”單身狗”均爲奇數,無法通過末位分離,甚至都不能通過倒數第2位分離。因此,對於更復雜的情況,爲了確保能把三個“單身狗”分開,我們可以一次用數據的每一位(如int類型就是32位)作爲分類標準進行二分類,這樣的話總能找到某一位能把它們仨分開,因此在外面嵌套一層循環就可以了。
到此還沒結束,如何判斷它們仨是否被分開呢?考慮其中沒有被分開的情況,比如按照倒數第2位劃分:
參照位 0010 {2,3,9,13,3,2,5} --> 0:{2,2} 1:{3,3,5,9,13}
我們發現,沒被分開時它們仨所在的數組元素個數一定奇數,而另一組的異或運算結果一定爲0。從而,分類失敗時偶數個數的類組的元素異或值一定爲0,而分類成功時偶數個數的類組的元素異或值一定不爲0。因此,每輪循環我們對偶數個數的類組進行個異或值判斷就好了。
代碼如下:
關於pair的使用#define BIT 32 int getAnySingleDog(int* a,int n){ int cluster=1; int m=0; int ans1=0,ans2=0; pair<int,int> p1,p2; //(異或值,元素個數) for(int m=0;m<BIT;cluster=1<<++m){ p1.first=0,p1.second=0; p2.first=0,p2.second=0; for(int i=0;i<n;i++){ if(a[i]&cluster){ p1.first^=a[i]; p1.second++; } else{ p2.first^=a[i]; p2.second++; } } if((p1.second&1)&&p2.first==0|| (p2.second&1)&&p1.first==0) continue; //未分開 return p1.second&1?p1.first:p2.first; } } int main(){ int a[]={2,3,9,13,3,2,5}; int n=sizeof(a)/sizeof(int); cout<<"singleDog is "<<getAnySingleDog(a,n); //9 return 0; }
3.8 main函數
【知識梳理】
- main函數不是整個程序的入口,實際上在main函數執行前已經做了一些初始化工作,main函數執行之後也有一些掃尾工作
- 按照C99標準,main函數有兩種形式,且返回值都是int類型,儘管void main()在一些編譯器上也行,但這是不標準的
int main()//形式1 int main(int argc,char* argv[])//形式2
- 關於形式2的形參說明:第一個參數argc是argument count的縮寫,整數類型,表示通過命令行輸入的參數個數;第二個參數argv是argument value的縮寫,字符串指針數組類型,其中argv[0]是程序的名字,其餘元素爲通過命令行輸入的參數
【典型面試題】
- 簡述main函數執行前後都發生了什麼?
答:main函數第一行代碼執行之前會調用全局對象和靜態對象的構造函數,初始化全局變量和靜態變量;main函數最後一行代碼執行之後會調用在atexit中註冊的函數,並且調用順序與註冊順序相反(函數註冊中使用了棧)。
4.藍橋杯競賽模擬賽:競賽經驗收穫
- 當數據量來到以上時,O()的算法就不可行了,複雜度最多爲O();簡單的判斷方法:題目沒有提示時,看最內層循環代碼執行的次數是否超過
- 貌似難題的複雜度大多爲O(),而且與動態規劃沾點邊
5.機器學習PCA原理推導
本來想寫完這個類似算法流程圖後開始編程的,然後發現自己好多數學概念不清楚(怪我沒打好數學基礎),導致花了很多時間理解概念,不過基本原理已經推出來了
參考的一些數學知識
6. Python3圖像處理初探
我把這部分時間歸爲了“創造性開發”中,實際上是爲機器學習報告打點技術基礎,第一次把圖像視作矩陣來進行操作,通過查閱一系列命令用法,做了幾個簡單的圖像處理操作。
先列出我的收穫:通過幾次調試的失敗我認識到了PNG格式的圖片顏色標準爲RGBA,而JPEG格式的圖片的爲RGB,因此對於不同類型的圖片,每個像素的維度會有所區別(PNG爲4,JPEG爲3)。
- 反色
我把上面PCA推導的筆記進行了簡單的反色處理 O(NM)
- 圖像等比例放大
放大圖像n倍(n爲整數),我的思路就是把每個像素擴展成n*n的陣列,每個小陣列中的像素顏色與原始的像素顏色一致,比如我把壁紙像素放大兩倍後的結果:
看到結果時,我開心極了,以爲自己找到了讓圖像更清晰的一種方式(按照我的設想應該不會改變清晰度,也就是說放大之後與放大之前看着一樣清晰),但是後面的實驗結果讓我意識到自己在想peach。。。
我把一個小圖標放大了100倍,結果如下:
臥槽,反而不清晰了!!但實際不是,仔細想想,我做的事無非是把原本的一個像素擴大了100倍,那麼並沒考慮像素與像素之間的顏色過渡,因此才感覺這麼的不自然(馬賽克畫質)。
下一步的優化或許可以考慮像素之間的漸變?
不管怎樣,也算有所收穫吧~
最後貼上實驗所用代碼:
from skimage import io,data,data_dir
# from PIL import Image
import numpy as np
def load_image(path):
return io.imread(path)
def inv_color(rgb):
return np.array([255, 255, 255]) - rgb
def img_zoom(data, times):
height = data.shape[0]
width = data.shape[1]
new_img = np.zeros((height*times, width*times, 4), np.uint8)
for i in range(height):
for j in range(width):
rgb = data[i][j]
ii = times * i
jj = times * j
for k in range(times):
new_img[ii+k][jj:jj+times] = rgb
# print(new_img.size)
io.imshow(new_img)
return new_img
desktop = r'C:\Users\****\Desktop' # 反正就是桌面路徑啦
if __name__ == '__main__':
img_dir = desktop + r"\timo.ico"
# img_dir = data_dir + "\Chelsea.jpg"
img = load_image(img_dir)
# print(img_dir)
# print(np.shape(img))
# io.imshow(img)
img = img_zoom(img, 100)
io.imsave(desktop + "\out1.png", img)
# io.show()
明天也要加油~