頭腦風暴之面試——做個程式設計師,我容易嗎?
linux和os:
netstat tcpdump ipcs ipcrm (如果這四個命令沒聽說過或者不能熟練使用,基本上可以回家,通過的概率較小,這四個命令的熟練掌握程度基本上能體現面試者實際開發和除錯程式的經驗)
cpu 記憶體 硬碟 等等與系統效能除錯相關的命令必須熟練掌握,設定修改許可權 tcp網路狀態檢視 各程式狀態 抓包相關等相關命令 必須熟練掌握awk sed需掌握共享記憶體的使用實現原理(必考必問,然後共享記憶體段被對映進程式空間之後,存在於程式空間的什麼位置?共享記憶體段最大限制是多少?)
c 程式記憶體空間分佈(注意各部分的記憶體地址誰高誰低,注意棧從高道低分配,堆從低到高分配)
ELF是什麼?其大小與程式中全域性變數的是否初始化有什麼關係(注意.bss段)使用過哪些程式間通訊機制,並詳細說明(重點)makefile編寫,雖然比較基礎,但是會被問到gdb除錯相關的經驗,會被問到如何定位記憶體洩露?
動態連結和靜態連結的區別32位系統一個程式最多多少堆記憶體多執行緒和多程式的區別(重點 面試官最最關心的一個問題,必須從cpu排程,上下文切換,資料共享,多核cup利用率,資源佔用,等等各方面回答,然後有一個問題必須會被問到:哪些東西是一個執行緒私有的?答案中必須包含暫存器,否則悲催)
寫一個c程式辨別系統是64位 or 32位寫一個c程式辨別系統是大端or小端位元組序訊號:列出常見的訊號,訊號怎麼處理?
i 是否原子操作?並解釋為什麼?
說出你所知道的各類linux系統的各類同步機制(重點),什麼是死鎖?如何避免死鎖(每個技術面試官必問)列舉說明linux系統的各類非同步機制exit() _exit()的區別?
如何實現守護程式?
linux的記憶體管理機制是什麼?
linux的任務排程機制是什麼?
標準庫函式和系統呼叫的區別?
系統如何將一個訊號通知到程式?
c語言:
巨集定義和展開(必須精通)位操作(必須精通)指標操作和計算(必須精通)記憶體分配(必須精通)sizeof必考 各類庫函式必須非常熟練的實現 哪些庫函式屬於高危函式,為什麼?(strcpy等等)
c :
一個String類的完整實現必須很快速寫出來(注意:賦值構造,operator=是關鍵)虛擬函式的作用和實現原理(必問必考,實現原理必須很熟)sizeof一個類求大小(注意成員變數,函式,虛擬函式,繼承等等對大小的影響)指標和引用的區別(一般都會問到)多重類構造和析構的順序stl各容器的實現原理(必考)extern c 是幹啥的,(必須將編譯器的函式名修飾的機制解答的很透徹)volatile是幹啥用的,(必須將cpu的暫存器快取機制回答的很透徹)static const等等的用法,(能說出越多越好)
資料結構或者演算法:
《離散數學》範圍內的一切問題皆由可能被深入問到(這個最重要,最體現功底,最能加分,特別是各類樹結構的實現和應用)各類排序:大根堆的實現,快排(如何避免最糟糕的狀態?),bitmap的運用等等hash, 任何一個技術面試官必問(例如為什麼一般hashtable的桶數會取一個素數?如何有效避免hash結果值的碰撞)
網路程式設計:
tcp與udp的區別(必問)udp呼叫connect有什麼作用?
tcp連線中時序圖,狀態圖,必須非常非常熟練socket服務端的實現,select和epoll的區別(必問)epoll哪些觸發模式,有啥區別?(必須非常詳盡的解釋水平觸發和邊緣觸發的區別,以及邊緣觸發在程式設計中要做哪些更多的確認)大規模連線上來,併發模型怎麼設計tcp結束連線怎麼握手,time_wait狀態是什麼,為什麼會有time_wait狀態?哪一方會有time_wait狀態,如何避免time_wait狀態佔用資源(必須回答的詳細)tcp頭多少位元組?哪些欄位?(必問)什麼是滑動視窗(必問)connect會阻塞,怎麼解決?(必考必問,提示:設定非阻塞,返回之後用select檢測狀態)如果select返回可讀,結果只讀到0位元組,什麼情況?
keepalive 是什麼東東?如何使用?
列舉你所知道的tcp選項,並說明其作用。
socket什麼情況下可讀?
DB:
mysql,會考sql語言,伺服器資料庫大規模資料怎麼設計,db各種效能指標最後:補充一個最最重要,最最坑爹,最最有難度的一個題目:一個每秒百萬級訪問量的網際網路伺服器,每個訪問都有資料計算和I/O操作,如果讓你設計,你怎麼設計?
程式設計師筆試知識點整理
0、常考基礎必知必會
A. 排序:排序有幾種,各種排序的比較,哪些排序是穩定的,快排的演算法;
B. 查詢:雜湊查詢、二叉樹查詢、折半查詢的對比,雜湊對映和雜湊表的區別?
C. 連結串列和陣列的區別,在什麼情況下用連結串列什麼情況下用陣列?
D. 棧和佇列的區別?
E. 多型,舉例說明;overload和override的區別?
F. 字串有關的函式,比如讓你寫一個拷貝字串的函式啊,或者字串反轉啊什麼的。strcpy和memcpy?
G. 繼承、多繼承?
H. 物件導向有什麼好處?
I. 說說static的與眾不同之處,如果一個變數被宣告為static,它會被分配在哪裡?在什麼時候分配空間等?
J. 什麼是虛擬函式、純虛擬函式、虛的解構函式,用途?
K. 記憶體洩漏及解決方法?
網路部分:
OSI模型7層結構,TCP/IP模型結構?
B. TCP/UDP區別?
C. TCP建立連線的步驟?
D. 夏農定理?
1、二叉樹三種遍歷的非遞迴演算法(背誦版)
本貼給出二叉樹先序、中序、後序三種遍歷的非遞迴演算法,此三個演算法可視為標準演算法,直接用於考研答題。
1.先序遍歷非遞迴演算法
#define maxsize 100
typedef struct
{
Bitree Elem[maxsize];
int top;
}SqStack;
void PreOrderUnrec(Bitree t)
{
SqStack s;
StackInit(s);
p=t;
while (p!=null || !StackEmpty(s))
{
while (p!=null) //遍歷左子樹
{
visite(p->data);
push(s,p);
p=p->lchild;
}//endwhile
if (!StackEmpty(s)) //通過下一次迴圈中的內嵌while實現右子樹遍歷
{
p=pop(s);
p=p->rchild;
}//endif
}//endwhile
}//PreOrderUnrec
2.中序遍歷非遞迴演算法
#define maxsize 100
typedef struct
{
Bitree Elem[maxsize];
int top;
}SqStack;
void InOrderUnrec(Bitree t)
{
SqStack s;
StackInit(s);
p=t;
while (p!=null || !StackEmpty(s))
{
while (p!=null) //遍歷左子樹
{
push(s,p);
p=p->lchild;
}//endwhile
if (!StackEmpty(s))
{
p=pop(s);
visite(p->data); //訪問根結點
p=p->rchild; //通過下一次迴圈實現右子樹遍歷
}//endif
}//endwhile
}//InOrderUnrec
3.後序遍歷非遞迴演算法
#define maxsize 100
typedef enum{L,R} tagtype;
typedef struct
{
Bitree ptr;
tagtype tag;
}stacknode;
typedef struct
{
stacknode Elem[maxsize];
int top;
}SqStack;
//後序遍歷
void PostOrderUnrec(Bitree t)
{
SqStack s;
stacknode x;
StackInit(s);
p=t;
do
{
while (p!=null) //遍歷左子樹
{
x.ptr = p;
x.tag = L; //標記為左子樹
push(s,x);
p=p->lchild;
}
while (!StackEmpty(s) &&s.Elem[s.top].tag==R)
{
x = pop(s);
p = x.ptr;
visite(p->data); //tag為R,表示右子樹訪問完畢,故訪問根結點
}
if (!StackEmpty(s))
{
s.Elem[s.top].tag =R; //遍歷右子樹
p=s.Elem[s.top].ptr->rchild;
}
}while (!StackEmpty(s));
}//PostOrderUnrec
4.層次遍歷演算法
// 二叉樹的資料結構
structBinaryTree
{
int value; // 不寫模板了,暫時用整形代替節點的資料型別
BinaryTree *left;
BinaryTree *right;
};
BinaryTree*root; // 已知二叉樹的根節點
//層次遍歷
voidLevel( const BinaryTree *root )
{
Queue *buf = new Queue(); // 定義一個空佇列,假設此佇列的節點資料型別也是整形的
BinaryTree t; // 一個臨時變數
buf.push_back(root); //令根節點入隊
while( buf.empty == false ) // 當佇列不為空
{
p = buf.front(); // 取出佇列的第一個元素
cout<<p->value<<' ';
if( p->left != NULL ) // 若左子樹不空,則令其入隊
{
q.push( p->left );
}
if( p->right != NULL ) // 若右子樹不空,則令其入隊
{
q.push( p->right );
}
buf.pop(); // 遍歷過的節點出隊
}
cout<<endl;
}
2、線性表
(1) 性表的鏈式儲存方式及以下幾種常用連結串列的特點和運算:單連結串列、迴圈連結串列,雙向連結串列,雙向迴圈連結串列。
(2)單連結串列的歸併演算法、迴圈連結串列的歸併演算法、雙向連結串列及雙向迴圈連結串列的插入和刪除演算法等都是較為常見的考查方式。
(3)單連結串列中設定頭指標、迴圈連結串列中設定尾指標而不設定頭指標以及索引儲存結構的各自好處。
3、棧與佇列
你可以問一下自己是不是已經知道了以下幾點:
(1)棧、佇列的定義及其相關資料結構的概念,包括:順序棧,鏈棧,共享棧,迴圈佇列,鏈隊等。棧與佇列存取資料(請注意包括:存和取兩部分)的特點。
(2)遞迴演算法。棧與遞迴的關係,以及藉助棧將遞迴轉向於非遞迴的經典演算法:n!階乘問題,fib數列問題,hanoi問題,揹包問題,二叉樹的遞迴和非遞迴遍歷問題,圖的深度遍歷與棧的關係等。其中,涉及到樹與圖的問題,多半會在樹與圖的相關章節中進行考查。
(3)棧的應用:數值表示式的求解,括號的配對等的原理,只作原理性瞭解,具體要求考查此為題目的演算法設計題不多。
(4)迴圈佇列中判隊空、隊滿條件,迴圈佇列中入隊與出隊(迴圈佇列在插入時也要判斷其是否已滿,刪除時要判斷其是否已空)演算法。
【迴圈佇列的隊空隊滿條件
為了方便起見,約定:初始化建空隊時,令
front=rear=0,
當隊空時:front=rear,
當隊滿時:front=rear 亦成立,
因此只憑等式front=rear無法判斷隊空還是隊滿。
有兩種方法處理上述問題:
(1)另設一個標誌位以區別佇列是空還是滿。
(2)少用一個元素空間,約定以“佇列頭指標front在隊尾指標rear的下一個位置上”作為佇列“滿”狀態的標誌。
隊空時: front=rear,
隊滿時: (rear+1)%maxsize=front】
如果你已經對上面的幾點了如指掌,棧與佇列一章可以不看書了。注意,我說的是可以不看書,並不是可以不作題哦。
////////////////////////////////////////////////////////////////////////////////////////////////
迴圈佇列的主要操作:
(1)建立迴圈佇列
(2)初始化迴圈佇列
(3)判斷迴圈佇列是否為空
(4)判斷迴圈佇列是否為滿
(5)入隊、出隊
//空出頭尾之間的一個元素不用
#include
#include
#define MAXSIZE 100
typedef struct
{
intelem[MAXSIZE];
intfront, rear;
}Quque; //定義隊頭
int initQue(Quque **q) //初始化
{
(*q)->front=0;
(*q)->rear=0;
}
int isFull(Quque *q)
{
if(q->front==(q->rear+1)%MAXSIZE)//判滿(空出一個元素不用) 劉勉剛
return 1;
else
return 0;
}
int insertQue(Quque **q,int elem)
{
if(isFull(*q))return -1;
(*q)->elem[(*q)->rear]=elem;
(*q)->rear=((*q)->rear+1)%MAXSIZE;//插入
return0;
}
int isEmpty(Quque *q)
{
if(q->front==q->rear)//判空
return 1;
else
return 0;
}
int deleteQue(Quque ** q,int *pelem)
{
if(isEmpty(*q))
return 0;
*pelem=(*q)->elem[(*q)->front];
(*q)->front=((*q)->front +1)%MAXSIZE;
return0;
}
4、串
串一章需要攻破的主要堡壘有:
1. 串的基本概念,串與線性表的關係(串是其元素均為字元型資料的特殊線性表),空串與空格串的區別,串相等的條件;
2. 串的基本操作,以及這些基本函式的使用,包括:取子串,串連線,串替換,求串長等等。運用串的基本操作去完成特定的演算法是很多學校在基本操作上的考查重點。
3. 順序串與鏈串及塊鏈串的區別和聯絡,實現方式。
4. KMP演算法思想。KMP中next陣列以及nextval陣列的求法。明確傳統模式匹配演算法的不足,明確next陣列需要改進。可能進行的考查方式是:求next和nextval陣列值,根據求得的next或nextval陣列值給出運用KMP演算法進行匹配的匹配過程。
5、多維陣列和廣義表
矩陣包括:對稱矩陣,三角矩陣,具有某種特點的稀疏矩陣等。
熟悉稀疏矩陣的三種不同儲存方式:三元組,帶輔助行向量的二元組,十字連結串列儲存。
掌握將稀疏矩陣的三元組或二元組向十字連結串列進行轉換的演算法。
6、樹與二叉樹
樹一章的知識點包括:
二叉樹的概念、性質和儲存結構,二叉樹遍歷的三種演算法(遞迴與非遞迴),在三種基本遍歷演算法的基礎上實現二叉樹的其它演算法,線索二叉樹的概念和線索化演算法以及線索化後的查詢演算法,最優二叉樹的概念、構成和應用,樹的概念和儲存形式,樹與森林的遍歷演算法及其與二叉樹遍歷演算法的聯絡,樹與森林和二叉樹的轉換。
(1) 二叉樹的概念、性質和儲存結構
考查方法可有:直接考查二叉樹的定義,讓你說明二叉樹與普通雙分支樹(左右子樹無序)的區別;考查滿二叉樹和完全二叉樹的性質,普通二叉樹的五個性質:
A.第i層的最多結點數,
B.深度為k的二叉樹的最多結點數,
C.n0=n2+1的性質,
D.n個結點的完全二叉樹的深度,
E. 順序儲存二叉樹時孩子結點與父結點之間的換算關係(root從1開始,則左為:2*i,右為:2*i+1)。
二叉樹的順序儲存和二叉連結串列儲存的各自優缺點及適用場合,二叉樹的三叉連結串列表示方法。
(2) 二叉樹的三種遍歷演算法
這一知識點掌握的好壞,將直接關係到樹一章的演算法能否理解,進而關係到樹一章的演算法設計題能否順利完成。二叉樹的遍歷演算法有三種:先序,中序和後序。其劃分的依據是視其每個演算法中對根結點資料的訪問順序而定。不僅要熟練掌握三種遍歷的遞迴演算法,理解其執行的實際步驟,並且應該熟練掌握三種遍歷的非遞迴演算法。由於二叉樹一章的很多演算法,可以直接根據三種遞迴演算法改造而來(比如:求葉子個數),所以,掌握了三種遍歷的非遞迴演算法後,對付諸如:“利用非遞迴演算法求二叉樹葉子個數”這樣的題目就下筆如有神了。
(3) 可在三種遍歷演算法的基礎上改造完成的其它二叉樹演算法:
求葉子個數,求二叉樹結點總數,求度為1或度為2的結點總數,複製二叉樹,建立二叉樹,交換左右子樹,查詢值為n的某個指定結點,刪除值為n的某個指定結點,諸如此類等等等等。如果你可以熟練掌握二叉樹的遞迴和非遞迴遍歷演算法,那麼解決以上問題就是小菜一碟了。
(4) 線索二叉樹:
線索二叉樹的引出,是為避免如二叉樹遍歷時的遞迴求解。眾所周知,遞迴雖然形式上比較好理解,但是消耗了大量的記憶體資源,如果遞迴層次一多,勢必帶來資源耗盡的危險,為了避免此類情況,線索二叉樹便堂而皇之地出現了。對於線索二叉樹,應該掌握:線索化的實質,三種線索化的演算法,線索化後二叉樹的遍歷演算法,基本線索二叉樹的其它演算法問題(如:查詢某一類線索二叉樹中指定結點的前驅或後繼結點就是一類常考題)。
(5) 最優二叉樹(哈夫曼樹):
最優二叉樹是為了解決特定問題引出的特殊二叉樹結構,它的前提是給二叉樹的每條邊賦予了權值,這樣形成的二叉樹按權相加之和是最小的。最優二叉樹一節,直接考查演算法原始碼的很少,一般是給你一組資料,要求你建立基於這組資料的最優二叉樹,並求出其最小權值之和,此類題目不難,屬送分題。
(6) 樹與森林:
二叉樹是一種特殊的樹,這種特殊不僅僅在於其分支最多為2以及其它特徵,一個最重要的特殊之處是在於:二叉樹是有序的!即:二叉樹的左右孩子是不可交換的,如果交換了就成了另外一棵二叉樹。 樹與森林的遍歷,不像二叉樹那樣豐富,他們只有兩種遍歷演算法:先根與後根(對於森林而言稱作:先序與後序遍歷)。此二者的先根與後根遍歷與二叉樹中的遍歷演算法是有對應關係的:先根遍歷對應二叉樹的先序遍歷,而後根遍歷對應二叉樹的中序遍歷。二叉樹、樹與森林之所以能有以上的對應關係,全拜二叉連結串列所賜。二叉樹使用二叉連結串列分別存放他的左右孩子,樹利用二叉連結串列儲存孩子及兄弟(稱孩子兄弟連結串列),而森林也是利用二叉連結串列儲存孩子及兄弟。
7、圖
1. 圖的基本概念:圖的定義和特點,無向圖,有向圖,入度,出度,完全圖,生成子圖,路徑長度,迴路,(強)連通圖,(強)連通分量等概念。
2. 圖的幾種儲存形式:鄰接矩陣,(逆)鄰接表,十字連結串列及鄰接多重表。在考查時,有的學校是給出一種儲存形式,要求考生用演算法或手寫出與給定的結構相對應的該圖的另一種儲存形式。
3. 考查圖的兩種遍歷演算法:深度遍歷和廣度遍歷
深度遍歷和廣度遍歷是圖的兩種基本的遍歷演算法,這兩個演算法對圖一章的重要性等同於“先序、中序、後序遍歷”對於二叉樹一章的重要性。在考查時,圖一章的演算法設計題常常是基於這兩種基本的遍歷演算法而設計的,比如:“求最長的最短路徑問題”和“判斷兩頂點間是否存在長為K的簡單路徑問題”,就分別用到了廣度遍歷和深度遍歷演算法。
4. 生成樹、最小生成樹的概念以及最小生成樹的構造:PRIM演算法和KRUSKAL演算法。
考查時,一般不要求寫出演算法原始碼,而是要求根據這兩種最小生成樹的演算法思想寫出其構造過程及最終生成的最小生成樹。
5. 拓撲排序問題:
拓撲排序有兩種方法,一是無前趨的頂點優先演算法,二是無後繼的頂點優先演算法。換句話說,一種是“從前向後”的排序,一種是“從後向前”排。當然,後一種排序出來的結果是“逆拓撲有序”的。
6. 關鍵路徑問題:
這個問題是圖一章的難點問題。理解關鍵路徑的關鍵有三個方面:
一是何謂關鍵路徑;
二是最早時間是什麼意思、如何求;
三是最晚時間是什麼意思、如何求。
簡單地說,最早時間是通過“從前向後”的方法求的,而最晚時間是通過“從後向前”的方法求解的,並且,要想求最晚時間必須是在所有的最早時間都已經求出來之後才能進行。
在實際設計關鍵路徑的演算法時,還應該注意以下這一點:採用鄰接表的儲存結構,求最早時間和最晚時間要採用不同的處理方法,即:在演算法初始時,應該首先將所有頂點的最早時間全部置為0。關鍵路徑問題是工程進度控制的重要方法,具有很強的實用性。
7. 最短路徑問題:
與關鍵路徑問題並稱為圖一章的兩隻攔路虎。概念理解是比較容易的,關鍵是演算法的理解。最短路徑問題分為兩種:一是求從某一點出發到其餘各點的最短路徑(單源最短路徑);二是求圖中每一對頂點之間的最短路徑。這個問題也具有非常實用的背景特色,一個典型的應該就是旅遊景點及旅遊路線的選擇問題。解決第一個問題用DIJSKTRA演算法,解決第二個問題用FLOYD演算法,注意區分。
8、查詢(search)
先弄清楚以下幾個概念:關鍵字、主關鍵字、次關鍵字的含義;靜態查詢與動態查詢的含義及區別;平均查詢長度ASL的概念及在各種查詢演算法中的計算方法和計算結果,特別是一些典型結構的ASL值,應該記住。
一般將search分為三類:在順序表上的查詢;在樹表上的查詢;在雜湊表上的查詢。
(1) 線性表上的查詢:
主要分為三種線性結構:
順序表——傳統查詢方法:逐個比較;
有序順序表——二分查詢法(注意適用條件以及其遞迴實現方法);
索引順序表——對索引結構,採用索引查詢演算法。注意這三種表下的ASL值以及三種演算法的實現。
(2) 樹表上的查詢:
樹表主要分為以下幾種:二叉排序樹(即二叉查詢樹),平衡二叉查詢樹(AVL樹),B樹,鍵樹。其中,尤以前兩種結構為重,也有部分名校偏愛考B樹的。由於二叉排序樹與平衡二叉樹是一種特殊的二叉樹。
二叉排序樹,簡言之,就是“左小右大”,它的中序遍歷結果是一個遞增的有序序列。平衡二叉排序樹是二叉排序樹的優化,其本質也是一種二叉排序樹,只不過,平衡排序二叉樹對左右子樹的深度有了限定:深度之差的絕對值不得大於1。對於二叉排序樹,“判斷某棵二叉樹是否二叉排序樹”這一演算法經常被考到,可用遞迴,也可以用非遞迴。平衡二叉樹的建立也是一個常考點,但該知識點歸根結底還是關注的平衡二叉樹的四種調整演算法,調整的一個參照是:調整前後的中序遍歷結果相同。
B樹是二叉排序樹的進一步改進,也可以把B樹理解為三叉、四叉....排序樹。除B樹的查詢演算法外,應該特別注意一下B樹的插入和刪除演算法,因為這兩種演算法涉及到B樹結點的分裂和合並,是一個難點。 鍵樹(keywordtree),又稱數字搜尋樹(digitalsearch
tree)或字元樹。trie樹也可說等同於鍵樹或屬於鍵樹的一種。鍵樹特別適用於查詢英文單詞的場合。一般不要求能完整描述演算法原始碼,多是根據演算法思想建立鍵樹及描述其大致查詢過程。
(3) 基於雜湊表的查詢演算法:
雜湊譯自“hash”一詞,意為“雜湊”或“雜湊”。雜湊表查詢的基本思想是:根據當前待查詢資料的特徵,以記錄關鍵字為自變數,設計一個function,該函式對關鍵字進行轉換後,其解釋結果為待查的地址。基於雜湊表的考查點有:雜湊函式的設計,衝突解決方法的選擇及衝突處理過程的描述。
9、內部排序
考查你對書本上的各種排序演算法及其思想以及其優缺點和效能指標(時間複雜度)能否瞭如指掌。
排序方法分類有:插入、選擇、交換、歸併、計數等五種排序方法。
(1)插入排序中又可分為:直接插入、折半插入、2路插入(?)、希爾排序。這幾種插入排序演算法的最根本的不同點,說到底就是根據什麼規則尋找新元素的插入點。直接插入是依次尋找,折半插入是折半尋找,希爾排序,是通過控制每次參與排序的數的總範圍“由小到大”的增量來實現排序效率提高的目的。
(2)交換排序,又稱氣泡排序,在交換排序的基礎上改進又可以得到快速排序。快速排序的思想,一語以敝之:用中間數將待排資料組一分為二。
(3)選擇排序可以分為:簡單選擇、樹選擇、堆排序。選擇排序相對於前面幾種排序演算法來說,難度大一點。這三種方法的不同點是,根據什麼規則選取最小的數。
簡單選擇,是通過簡單的陣列遍歷方案確定最小數;
樹選擇,是通過“錦標賽”類似的思想,讓兩數相比,不斷淘汰較大(小)者,最終選出最小(大)數;
而堆排序,是利用堆這種資料結構的性質,通過堆元素的刪除、調整等一系列操作將最小數選出放在堆頂。堆排序中的堆建立、堆調整是重要考點。
(4)歸併排序,是通過“歸併”這種操作完成排序的目的,既然是歸併就必須是兩者以上的資料集合才可能實現歸併。所以,在歸併排序中,關注最多的就是2路歸併。演算法思想比較簡單,有一點,要銘記在心:歸併排序是穩定排序。
(5)基數排序,是一種很特別的排序方法,也正是由於它的特殊,所以,基數排序就比較適合於一些特別的場合,比如撲克牌排序問題等。基數排序,又分為兩種:多關鍵字的排序(撲克牌排序),鏈式排序(整數排序)。基數排序的核心思想也是利用“基數空間”這個概念將問題規模規範、變小,並且,在排序的過程中,只要按照基排的思想,是不用進行關鍵字比較的,這樣得出的最終序列就是一個有序序列。
本章各種排序演算法的思想以及虛擬碼實現,及其時間複雜度都是必須掌握的。
//////////////////////////////////////////////穩定性分析////////////////////////////////////////////////
排序演算法的穩定性,通俗地講就是能保證排序前2個相等的數其在序列的前後位置順序和排序後它們兩個的前後位置順序相同。
穩定性的好處:若排序演算法如果是穩定的,那麼從一個鍵上排序,然後再從另一個鍵上排序,第一個鍵排序的結果可以為第二個鍵排序所用。基數排序就是這樣,先按低位排序,逐次按高位排序,低位相同的元素其順序再高位也相同時是不會改變的。另外,如果排序演算法穩定,對基於比較的排序演算法而言,元素交換的次數可能會少一些(個人感覺,沒有證實)。
分析一下常見的排序演算法的穩定性,每個都給出簡單的理由。
(1) 氣泡排序
氣泡排序就是把小的元素往前調或者把大的元素往後調。比較是相鄰的兩個元素比較,交換也發生在這兩個元素之間。所以,如果兩個元素相等,我想你是不會再無聊地把他們倆交換一下的;如果兩個相等的元素沒有相鄰,那麼即使通過前面的兩兩交換把兩個相鄰起來,這時候也不會交換,所以相同元素的前後順序並沒有改變,所以氣泡排序是一種穩定排序演算法。
(2) 選擇排序
選擇排序是給每個位置選擇當前元素最小的,比如給第一個位置選擇最小的,在剩餘元素裡面給第二個元素選擇第二小的,依次類推,直到第n-1個元素,第n個元素不用選擇了,因為只剩下它一個最大的元素了。那麼,在一趟選擇,如果當前元素比一個元素小,而該小的元素又出現在一個和當前元素相等的元素後面,那麼交換後穩定性就被破壞了。比較拗口,舉個例子,序列5 8 5 2 9,我們知道第一遍選擇第1個元素5會和2交換,那麼原序列中2個5的相對前後順序就被破壞了,所以選擇排序不是一個穩定的排序演算法。
(3) 插入排序
插入排序是在一個已經有序的小序列的基礎上,一次插入一個元素。當然,剛開始這個有序的小序列只有1個元素,就是第一個元素。比較是從有序序列的末尾開始,也就是想要插入的元素和已經有序的最大者開始比起,如果比它大則直接插入在其後面,否則一直往前找直到找到它該插入的位置。如果碰見一個和插入元素相等的,那麼插入元素把想插入的元素放在相等元素的後面。所以,相等元素的前後順序沒有改變,從原無序序列出去的順序就是排好序後的順序,所以插入排序是穩定的。
(4) 快速排序
快速排序有兩個方向,左邊的i下標一直往右走,當a[i] <= a[center_index],其中center_index是中樞元素的陣列下標,一般取為陣列第0個元素。而右邊的j下標一直往左走,當a[j]> a[center_index]。如果i和j都走不動了,i <= j, 交換a[i]和a[j],重複上面的過程,直到i>j。交換a[j]和a[center_index],完成一趟快速排序。在中樞元素和a[j]交換的時候,很有可能把前面的元素的穩定性打亂,比如序列為 5 3 3 4 3 8 9 10
11,現在中樞元素5和3(第5個元素,下標從1開始計)交換就會把元素3的穩定性打亂,所以快速排序是一個不穩定的排序演算法,不穩定發生在中樞元素和 a[j] 交換的時刻。
(5) 歸併排序
歸併排序是把序列遞迴地分成短序列,遞迴出口是短序列只有1個元素(認為直接有序)或者2個序列(1次比較和交換),然後把各個有序的段序列合併成一個有序的長序列,不斷合併直到原序列全部排好序。可以發現,在1個或2個元素時,1個元素不會交換,2個元素如果大小相等也沒有人故意交換,這不會破壞穩定性。那麼,在短的有序序列合併的過程中,穩定是是否受到破壞?沒有,合併過程中我們可以保證如果兩個當前元素相等時,我們把處在前面的序列的元素儲存在結果序列的前面,這樣就保證了穩定性。所以,歸併排序也是穩定的排序演算法。
(6) 基數排序
基數排序是按照低位先排序,然後收集;再按照高位排序,然後再收集;依次類推,直到最高位。有時候有些屬性是有優先順序順序的,先按低優先順序排序,再按高優先順序排序,最後的次序就是高優先順序高的在前,高優先順序相同的低優先順序高的在前。基數排序基於分別排序,分別收集,所以其是穩定的排序演算法。
(7) 希爾排序(shell)
希爾排序是按照不同步長對元素進行插入排序,當剛開始元素很無序的時候,步長最大,所以插入排序的元素個數很少,速度很快;當元素基本有序了,步長很小,插入排序對於有序的序列效率很高。所以,希爾排序的時間複雜度會比o(n^2)好一些。由於多次插入排序,我們知道一次插入排序是穩定的,不會改變相同元素的相對順序,但在不同的插入排序過程中,相同的元素可能在各自的插入排序中移動,最後其穩定性就會被打亂,所以shell排序是不穩定的。
(8) 堆排序
我們知道堆的結構是節點i的孩子為2*i和2*i+1節點,大頂堆要求父節點大於等於其2個子節點,小頂堆要求父節點小於等於其2個子節點。在一個長為n 的序列,堆排序的過程是從第n/2開始和其子節點共3個值選擇最大(大頂堆)或者最小(小頂堆),這3個元素之間的選擇當然不會破壞穩定性。但當為n /2-1, n/2-2, ...1這些個父節點選擇元素時,就會破壞穩定性。有可能第n/2個父節點交換把後面一個元素交換過去了,而第n/2-1個父節點把後面一個相同的元素沒有交換,那麼這2個相同的元素之間的穩定性就被破壞了。所以,堆排序不是穩定的排序演算法。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
氣泡排序 插入排序 二路插入排序 希爾排序 快速排序 選擇排序 歸併排序 堆排序演算法的C/C++實現//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <iostream>
using namespace std;
//交換兩個數的值
void swap(int &a,int &b)
{
int tmp;
tmp=a;
a=b;
b=tmp;
}
//螢幕輸出陣列
void display(int array[],int len)
{
cout<<"the resultis:"<<endl;
for (int i = 0 ;i < len;i++ )
{
cout<<array[i]<<" ";
}
cout<<endl;
}
/*
氣泡排序
演算法思想:將被排序的記錄陣列R[1..n]垂直排列,每個記錄R[i]看作是重量為R[i].key的氣泡。
根據輕氣泡不能在重氣泡之下的原則,從下往上掃描陣列 R:凡掃描到違反本原則的
輕氣泡,就使其向上"飄浮"。如此反覆進行,直到最後任何兩個氣泡都是輕者在上,
重者在下為止。
時間複雜度 o(n^2)
空間複雜度 o(1)
比較次數 n(n+1)/2
*/
void bubble_sort(int array[],int len)
{
for (int i = len-1 ;i >= 0;i-- )
{
for(int j = 0;j < i;j++)
if(array[j] > array[j+1])
swap(array[j],array[j+1]);
}
}
/*
直接插入排序
演算法思想:把n個待排序的元素看成為一個有序表和一個無序表,開始時有序表中只包含一個元
素,無序表中包含有n-1個元素,排序過程中每次從無序表中取出第一個元素,將它
插入到有序表中的適當位置,使之成為新的有序表,重複n-1次可完成排序過程。
時間複雜度 o(n^2)
空間複雜度 o(1)
比較次數 n(n+1)/2
*/
void insert_sort(int array[],int len)
{
int tmp,i,j;
for(i = 1;i < len;i++)
{
if (array[i] < array[i-1])
{
tmp = array[i];
array[i] = array[i-1];
//插入到相應位置
for (j = i-2;j >= 0;j--)
{
//往後移
if (array[j] > tmp)
array[j+1] =array[j];
else
{
array[j+1] = tmp;
break;
}
}
if(j == -1)
array[j+1] = tmp;
}
}
}
/*
2-路插入排序
演算法思想:增加一個輔助空間d,把r[1]賦值給d[1],並將d[1]看成是排好序後處於中間
位置的記錄。然後從r[2]開始依次插入到d[1]之前或之後的有序序列中。
時間複雜度 o(n^2)
空間複雜度 o(1)
比較次數 n(n+1)/2
*/
void bi_insert_sort(int array[],int len)
{
int* arr_d = (int*)malloc(sizeof(int) * len);
arr_d[0] = array[0];
int head = 0,tail = 0;
for (int i = 1;i < len; i++ )
{
if (array[i] > arr_d[0])
{
int j;
for ( j= tail;j>0;j--)
{
if (array[i] <arr_d[j])
arr_d[j+1] =arr_d[j];
else
break;
}
arr_d[j+1] = array[i];
tail += 1;
}
else
{
if (head ==0)
{
arr_d[len-1] = array[i];
head =len-1;
}
else
{
int j;
for (j = head;j <=len-1;j++)
{
if (array[i] >arr_d[j])
arr_d[j-1] =arr_d[j];
else
break;
}
arr_d[j-1] = array[i];
head -= 1;
}
}
}
for (int i = 0;i < len; i++)
{
int pos = (i + head );
if(pos >= len) pos -= len;
array[i] = arr_d[pos];
}
free(arr_d);
}
/*
希爾排序
演算法思想:先將整個待排序記錄分割成若干子序列分別進行直接插入排
序,待整個序列中的記錄基本有序時,再對全體記錄進行一
次直接插入排序
時間複雜度 o(n^2)
空間複雜度 o(1)
比較次數 ?
*/
void shell_insert(int array[],int d,int len)
{
int tmp,j;
for (int i = d;i < len;i++)
{
if(array[i] < array[i-d])
{
tmp = array[i];
j = i - d;
do
{
array[j+d] = array[j];
j = j - d;
} while (j >= 0 &&tmp < array[j]);
array[j+d] = tmp;
}
}
}
void shell_sort(int array[],int len)
{
int inc = len;
do
{
inc = inc/2;
shell_insert(array,inc,len);
} while (inc > 1);
}
/*
快速排序
演算法思想:將原問題分解為若干個規模更小但結構與原問題相似的子問題。
遞迴地解這些子問題,然後將這些子問題的解組合成為原問題的解。
時間複雜度 o(nlogn)
空間複雜度 o(logn)
比較次數 ?
*/
void quick_sort(int array[],int low,int high)
{
if (low < high)
{
int pivotloc =partition(array,low,high);
quick_sort(array,low,pivotloc-1);
quick_sort(array,pivotloc+1,high);
}
}
int partition(int array[],int low,int high)
{
int pivotkey = array[low];
while (low < high)
{
while(low < high &&array[high] >= pivotkey)
--high;
swap(array[low],array[high]);
while(low < high &&array[low] <= pivotkey)
++low;
swap(array[low],array[high]);
}
array[low] = pivotkey;
return low;
}
/*
直接選擇排序
演算法思想:每一趟在n-i+1個記錄中選取關鍵字最小的記錄作為有序序列中的第i個記錄
時間複雜度 o(n^2)
空間複雜度 o(1) ?
比較次數 n(n+1)/2
*/
int SelectMinKey(int array[],int iPos,int len)
{
int ret = 0;
for (int i = iPos; i < len; i++)
{
if (array[ret] > array[i])
{
ret = i;
}
}
return ret;
}
void select_sort(int array[],int len)
{
for (int i = 0; i < len; i++)
{
int j =SelectMinKey(array,i,len);
if (i != j)
{
swap(array[i],array[j]);
}
}
}
/*
歸併排序
演算法思想:設兩個有序的子檔案(相當於輸入堆)放在同一向量中相鄰的位置上:R[low..m],R[m+1..high],先將它們合併到一個區域性的暫存向量R1(相當於輸出堆)中,待合併完成後將R1複製回R[low..high]中。
時間複雜度 o(nlogn)
空間複雜度 o(n)
比較次數 ?
*/
void merge(int array[],int i,int m, int n)
{
int j, k;
int iStart = i, iEnd = n;
int arrayDest[256];
for ( j = m + 1,k = i; i <= m&& j <= n; ++k)
{
if (array[i] < array[j])
arrayDest[k] = array[i++];
else
arrayDest[k] = array[j++];
}
if (i <= m)
for (;k <= n; k++,i++)
arrayDest[k] = array[i];
if(j <= n)
for (;k <= n; k++,j++)
arrayDest[k] = array[j];
for(j = iStart; j <= iEnd; j++)
array[j] = arrayDest[j];
}
void merge_sort(int array[],int s,int t)
{
int m;
if (s < t)
{
m = (s + t )/2;
merge_sort(array,s,m);
merge_sort(array,m+1,t);
merge(array,s,m,t);
}
}
/*
堆排序
演算法思想:堆排序(Heap Sort)是指利用堆(heaps)這種資料結構來構造的一種排序演算法。
堆是一個近似完全二叉樹結構,並同時滿足堆屬性:即子節點的鍵值或索引總是
小於(或者大於)它的父節點。
時間複雜度 o(nlogn)
空間複雜度 o(1)
比較次數:較多
*/
void heap_adjust(int array[],int i,int len)
{
int rc = array[i];
for(int j = 2 * i; j <len; j *= 2)
{
if(j < len && array[j]< array[j+1]) j++;
if(rc >= array[j]) break;
array[i] = array[j]; i = j;
}
array[i] = rc;
}
void heap_sort(int array[],int len)
{
int i;
for(i = (len-1)/2; i >= 0; i--)
heap_adjust(array,i,len);
for( i = (len-1); i > 0; i--)
{
swap(array[0],array[i]); //彈出最大值,重新對i-1個元素建堆
heap_adjust(array,0,i-1);
}
}
int main()
{
int array[] = {45, 56, 76, 234, 1, 34,23, 2, 3, 55, 88, 100};
int len = sizeof(array)/sizeof(int);
//bubble_sort(array,len); //氣泡排序
/*insert_sort(array,len);*/ //插入排序
/*bi_insert_sort(array,len);*/ //二路插入排序
/*shell_sort(array,len);*/ //希爾排序
/*quick_sort(array,0,len-1);*/ //快速排序
/*select_sort(array,len);*/ //選擇排序
/*merge_sort(array,0,len-1);*/ //歸併排序
heap_sort(array,len); //堆排序
display(array,len);
return 0;
}
<|>對排序演算法的總結
按平均時間將排序分為四類:
(1)平方階(O(n2))排序
一般稱為簡單排序,例如直接插入、直接選擇和氣泡排序;
(2)線性對數階(O(nlgn))排序
如快速、堆和歸併排序;
(3)O(n1+£)階排序
£是介於0和1之間的常數,即0<£<1,如希爾排序;
(4)線性階(O(n))排序
如桶、箱和基數排序。
各種排序方法比較
簡單排序中直接插入最好,快速排序最快,當檔案為正序時,直接插入和冒泡均最佳。
影響排序效果的因素
因為不同的排序方法適應不同的應用環境和要求,所以選擇合適的排序方法應綜合考慮下列因素:
①待排序的記錄數目n;
②記錄的大小(規模);
③關鍵字的結構及其初始狀態;
④對穩定性的要求;
⑤語言工具的條件;
⑥儲存結構;
⑦時間和輔助空間複雜度等。
不同條件下,排序方法的選擇
(1)若n較小(如n≤50),可採用直接插入或直接選擇排序。
當記錄規模較小時,直接插入排序較好;否則因為直接選擇移動的記錄數少於直接插人,應選直接選擇排序為宜。
(2)若檔案初始狀態基本有序(指正序),則應選用直接插人、冒泡或隨機的快速排序為宜;
(3)若n較大,則應採用時間複雜度為O(nlgn)的排序方法:快速排序、堆排序或歸併排序。
快速排序是目前基於比較的內部排序中被認為是最好的方法,當待排序的關鍵字是隨機分佈時,快速排序的平均時間最短;
堆排序所需的輔助空間少於快速排序,並且不會出現快速排序可能出現的最壞情況。這兩種排序都是不穩定的。
若要求排序穩定,則可選用歸併排序。但本章介紹的從單個記錄起進行兩兩歸併的 排序演算法並不值得提倡,通常可以將它和直接插入排序結合在一起使用。先利用直接插入排序求得較長的有序子檔案,然後再兩兩歸併之。因為直接插入排序是穩定的,所以改進後的歸併排序仍是穩定的。
10、OSI模型7層結構,TCP/IP模型結構?
osi參考模型
osi參考模型中的資料封裝過程
下面的圖表試圖顯示不同的TCP/IP和其他的協議在最初OSI模型中的位置:
7 |
應用層 |
例如HTTP、SMTP、SNMP、FTP、Telnet、SIP、SSH、NFS、RTSP、XMPP、Whois、ENRP |
6 |
表示層 |
|
5 |
會話層 |
例如ASAP、TLS、SSH、ISO 8327 / CCITT X.225、RPC、NetBIOS、ASP、Winsock、BSD sockets |
4 |
傳輸層 |
|
3 |
網路層 |
|
2 |
資料鏈路層 |
例如Ethernet、Token ring、HDLC、Frame relay、ISDN、ATM、802.11 WiFi、FDDI、PPP |
1 |
物理層 |
tcp/ip參考模型
tcp/ip參考模型分為四個層次:應用層、傳輸層、網路互連層和主機到網路層:
tcp/ip參考模型的層次結構
通常人們認為OSI模型的最上面三層(應用層、表示層和會話層)在TCP/IP組中是一個應用層。由於TCP/IP有一個相對較弱的會話層,由TCP和RTP下的開啟和關閉連線組成,並且在TCP和UDP下的各種應用提供不同的埠號,這些功能能夠被單個的應用程式(或者那些應用程式所使用的庫)增加。與此相似的是,IP是按照將它下面的網路當作一個黑盒子的思想設計的,這樣在討論TCP/IP的時候就可以把它當作一個獨立的層。
4 |
應用層 |
例如HTTP、FTP、DNS |
3 |
傳輸層 |
|
2 |
網路互連層 |
對於TCP/IP來說這是因特網協議(IP) |
1 |
網路介面層 |
應用層
該層包括所有和應用程式協同工作,利用基礎網路交換應用程式專用的資料的協議。應用層是大多數普通與網路相關的程式為了通過網路與其他程式通訊所使用的層。這個層的處理過程是應用特有的;資料從網路相關的程式以這種應用內部使用的格式進行傳送,然後被編碼成標準協議的格式。
一些特定的程式被認為執行在這個層上。它們提供服務直接支援使用者應用。這些程式和它們對應的協議包括HTTP(The WorldWide Web)、FTP(檔案傳輸)、SMTP(電子郵件)、SSH(安全遠端登陸)、DNS(名稱<-> IP 地址尋找)以及許多其他協議。
一旦從應用程式來的資料被編碼成一個標準的應用層協議,它將被傳送到IP棧的下一層。
在傳輸層,應用程式最常用的是TCP或者UDP,並且伺服器應用程式經常與一個公開的埠號相聯絡。伺服器應用程式的埠由InternetAssigned Numbers Authority(IANA)正式地分配,但是現今一些新協議的開發者經常選擇它們自己的埠號。由於在同一個系統上很少超過少數幾個的伺服器應用,埠衝突引起的問題很少。應用軟體通常也允許使用者強制性地指定埠號作為執行引數。
連結外部的客戶端程式通常使用系統分配的一個隨機埠號。監聽一個埠並且然後通過伺服器將那個埠傳送到應用的另外一個副本以建立對等連結(如IRC上的dcc檔案傳輸)的應用也可以使用一個隨機埠,但是應用程式通常允許定義一個特定的埠範圍的規範以允許埠能夠通過實現網路地址轉換(NAT)的路由器對映到內部。
每一個應用層(TCP/IP參考模型 的最高層)協議一般都會使用到兩個傳輸層協議之一:面向連線的TCP傳輸控制協議和無連線的包傳輸的UDP使用者資料包文協議 。
常用的應用層協議有:
執行在TCP協議上的協議:
- HTTP(HypertextTransfer Protocol,超文字傳輸協議),主要用於普通瀏覽。
- HTTPS(HypertextTransfer Protocol over Secure Socket Layer, or HTTP over SSL,安全超文字傳輸協議),HTTP協議的安全版本。
- FTP(File Transfer Protocol,檔案傳輸協議),由名知義,用於檔案傳輸。
- POP3(PostOffice Protocol, version 3,郵局協議),收郵件用。
- SMTP(SimpleMail Transfer Protocol,簡單郵件傳輸協議),用來傳送電子郵件 。
- TELNET(Teletypeover the Network,網路電傳),通過一個終端(terminal)登陸到網路。
- SSH(Secure Shell,用於替代安全性差的TELNET),用於加密安全登陸。
執行在UDP協議上的協議:
其他:
- DNS(Domain Name Service,域名服務),用於完成地址查詢,郵件轉發等工作(執行在TCP和UDP協議上)。
- ECHO(EchoProtocol,迴繞協議),用於查錯及測量應答時間(執行在TCP和UDP協議上)。
- SNMP(SimpleNetwork Management Protocol,簡單網路管理協議),用於網路資訊的收集和網路管理。
- DHCP(DynamicHost Configuration Protocol,動態主機配置協議),動態配置IP地址。
- ARP(Address Resolution Protocol,地址解析協議),用於動態解析乙太網硬體的地址。
傳輸層
傳輸層的協議,能夠解決諸如可靠性(“資料是否已經到達目的地?”)和保證資料按照正確的順序到達這樣的問題。在TCP/IP協議組中,傳輸協議也包括所給資料應該送給哪個應用程式。
在TCP/IP協議組中技術上位於這個層的動態路由協議通常被認為是網路層的一部分;一個例子就是OSPF(IP協議89)。
TCP(IP協議6)是一個“可靠的”、面向連結的傳輸機制,它提供一種可靠的位元組流保證資料完整、無損並且按順序到達。TCP儘量連續不斷地測試網路的負載並且控制傳送資料的速度以避免網路過載。另外,TCP試圖將資料按照規定的順序傳送。這是它與UDP不同之處,這在實時資料流或者路由高網路層丟失率應用的時候可能成為一個缺陷。
較新的SCTP也是一個“可靠的”、面向連結的傳輸機制。它是面向紀錄而不是面向位元組的,它在一個單獨的連結上提供了通過多路複用提供的多個子流。它也提供了多路自定址支援,其中連結終端能夠被多個IP地址表示(代表多個物理介面),這樣的話即使其中一個連線失敗了也不中斷。它最初是為電話應用開發的(在IP上傳輸SS7),但是也可以用於其他的應用。
UDP(IP協議號17)是一個無連結的資料包協議。它是一個“best effort”或者“不可靠”協議——不是因為它特別不可靠,而是因為它不檢查資料包是否已經到達目的地,並且不保證它們按順序到達。如果一個應用程式需要這些特點,它必須自己提供或者使用TCP。
UDP的典型性應用是如流媒體(音訊和視訊等)這樣按時到達比可靠性更重要的應用,或者如DNS查詢這樣的簡單查詢/響應應用,如果建立可靠的連結所作的額外工作將是不成比例地大。
DCCP目前正由IEFT開發。它提供TCP流動控制語義,但對於使用者來說保留了UDP的資料包服務模型。
TCP和UDP都用來支援一些高層的應用。任何給定網路地址的應用通過它們的TCP或者UDP埠號區分。根據慣例使一些大眾所知的埠與特定的應用相聯絡。
RTP是為如音訊和視訊流這樣的實時資料設計的資料包協議。RTP是使用UDP包格式作為基礎的會話層,然而據說它位於因特網協議棧的傳輸層。
網路互連層
正如最初所定義的,網路層解決在一個單一網路上傳輸資料包的問題。類似的協議有X.25和ARPANET的Host/IMP Protocol。
隨著因特網思想的出現,在這個層上新增了附加的功能,也就是將資料從源網路傳輸到目的網路。這就牽涉到在網路組成的網上選擇路徑將資料包傳輸,也就是因特網。
在因特網協議組中,IP完成資料從源傳送到目的基本任務。IP能夠承載多種不同的高層協議的資料;這些協議使用一個唯一的IP協議號進行標識。ICMP和IGMP分別是1和2。
一些IP承載的協議,如ICMP(用來傳送關於IP傳送的診斷資訊)和IGMP(用來管理多播資料),它們位於IP層之上但是完成網路層的功能,這表明了因特網和OSI模型之間的不相容性。所有的路由協議,如BGP、 OSPF、和RIP實際上也是網路層的一部分,儘管似乎它們應該屬於更高的協議棧。
網路介面層
網路介面層實際上並不是因特網協議組中的一部分,但是它是資料包從一個裝置的網路層傳輸到另外一個裝置的網路層的方法。這個過程能夠在網路卡的軟體驅動程式中控制,也可以在韌體或者專用晶片中控制。這將完成如新增報頭準備傳送、通過物理媒介實際傳送這樣一些資料鏈路功能。另一端,鏈路層將完成資料幀接收、去除報頭並且將接收到的包傳到網路層。
然而,鏈路層並不經常這樣簡單。它也可能是一個虛擬專有網路(VPN)或者隧道,在這裡從網路層來的包使用隧道協議和其他(或者同樣的)協議組傳送而不是傳送到物理的介面上。VPN和隧道通常預先建好,並且它們有一些直接傳送到物理介面所沒有的特殊特點(例如,它可以加密經過它的資料)。由於現在鏈路“層”是一個完整的網路,這種協議組的遞迴使用可能引起混淆。但是它是一個實現常見覆雜功能的一個優秀方法。(儘管需要注意預防一個已經封裝並且經隧道傳送下去的資料包進行再次地封裝和傳送)。
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
1、物理層(physical layer)
物理層規定了啟用、維持、關閉通訊端點之間的機械特性、電氣特性、功能特性以及過程特性。該層為上層協議提供了一個傳輸資料的物理媒體。
在這一層,資料的單位稱為位元(bit)。
屬於物理層定義的典型規範代表包括:eia/tia rs-232、eia/tia rs-449、v.35、rj-45等。
2、資料鏈路層(data link layer)
資料鏈路層在不可靠的物理介質上提供可靠的傳輸。該層的作用包括:實體地址定址、資料的成幀、流量控制、資料的檢錯、重發等。
在這一層,資料的單位稱為幀(frame)。
資料鏈路層協議的代表包括:sdlc、hdlc、ppp、stp、幀中繼等。
3、網路層(network layer)
網路層負責對子網間的資料包進行路由選擇。此外,網路層還可以實現擁塞控制、網際互連等功能。
在這一層,資料的單位稱為資料包(packet)。
網路層協議的代表包括:ip、ipx、rip、ospf等。
4、傳輸層(transport layer)
傳輸層是第一個端到端,即主機到主機的層次。傳輸層負責將上層資料分段並提供端到端的、可靠的或不可靠的傳輸。此外,傳輸層還要處理端到端的差錯控制和流量控制問題。 在這一層,資料的單位稱為資料段(segment)。
傳輸層協議的代表包括:tcp、udp、spx等。
5、會話層(session layer)
會話層管理主機之間的會話程式,即負責建立、管理、終止程式之間的會話。會話層還利用在資料中插入校驗點來實現資料的同步。
會話層協議的代表包括:netbios、zip(appletalk區域資訊協議)等。
6、表示層(presentation layer)
表示層對上層資料或資訊進行變換以保證一個主機應用層資訊可以被另一個主機的應用程式理解。表示層的資料轉換包括資料的加密、壓縮、格式轉換等。
表示層協議的代表包括:ascii、asn.1、jpeg、mpeg等。
7、應用層(application layer)
應用層為作業系統或網路應用程式提供訪問網路服務的介面。
應用層協議的代表包括:telnet、ftp、http、snmp等。
集線器hub工作在OSI參考模型的(物理)層;
網路卡工作在OSI參考模型的(物理)層;
路由器router工作在OSI參考模型的(網路)層;
交換機Switch工作在OSI參考模型的(資料鏈路)層。
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
(附)10、tcp建立連線為什麼要三次握手?
tcp是一個面向連線的協議,在傳送資料以前,必須要首先建立一條連線。連線的建立需要經過三次握手。為什麼要經過三次握手呢,每次握手雙方都做了些什麼?
1)什麼是tcp報文?
tcp報文就是通過tcp協議傳送的資料包,由tcp頭和資料段組成。
tcp頭是固定的20個位元組,它的格式為:
2)第一次握手做什麼?
請求端(客戶端)會向服務端(被請求端)傳送一個tcp報文,申請開啟某一個埠。因為沒有資料,所以這個報文僅包含一個tcp頭。其中:
SYN=1;當建立一個新的連線時, SYN標誌變1。
序號;序號用來標識從客戶端向服務端傳送的資料位元組流。
此時客戶端進入SYN_SENT狀態。
3)第二次握手做什麼?
服務端收到客戶端的SYN包,也會發一個只包含tcp頭的報文給客戶端。
ACK=1;服務端確認收到資訊
確認序號;客戶端序號+1,作為應答
SYN=1;因為tcp的連線是雙向的,服務端作為應答的同時請求建立連線。
此時服務端進入SYN_RECV狀態
4)第三次握手做什麼?
ACK=1;客戶端確認收到資訊
確認序號;服務端序號+1,作為應答
此時客戶端進入ESTABLISHED狀態,服務端收到ACK後也會進入此狀態
可見,客戶端和服務端都保留了對方的序號,這三次握手缺少任何一步都無法實現這一目標。在三次握手過程中,出現了一些中間狀態。
5)什麼是半連線佇列?
第一次握手完成後,服務端傳送ACK+SYN包到客戶端,在收到客戶端返回前的狀態為SYN_RECV,服務端為此狀態維護一個半連線佇列。當服務端收到客戶的確認包時,刪除該條目,服務端進入ESTABLISHED狀態。Listen中的backlog參數列示這兩個狀態合的最大值。若客戶端完成第一次握手後不再傳送ACK包,導致服務端未完成佇列溢位,達到Dos攻擊的目的。
6)什麼是SYN-ACK 重傳?
Dos攻擊可以達到目的的一個重要因素是服務端在傳送完SYN+ACK包後會等待客戶端的確認包,如果等待時間內未收到,服務端會進行首次重傳,等待一段時間仍未收到客戶確認包,會進行第二次重傳,直到重傳次數超過系統規定的最大值,系統將該連線資訊從半連線佇列中刪除。如果系統刪除的頻率小於半連線狀態的增長頻率,服務端就無法正常提供服務。
7)Tcp關閉連線需要四次握手,這又是為什麼呢?
這是由tcp半關閉(harf-close)造成的。既然一個TCP連線是全雙工(即資料在兩個方向上能同時傳遞),因此每個方向必須單獨地進行關閉。即一方傳送一個FIN,另一方收到後傳送一個ACK,這就是所謂的四次握手了。
8)第一次握手做什麼?
客戶端傳送一個FIN(這個客戶端是主動發起關閉的一端,與建立連線時的客戶端不一定是同一主機)
此時客戶端進入FIN_WAIT_1狀態。
9)第二次握手做什麼?
服務端收到FIN,發回客戶端一個ACK,確認序號為收到的序號加1(因為FIN和SYN一樣,會佔用一個序號);客戶端收到ACK之後會進入FIN_WAIT_2狀態,服務端會進入CLOSE_WAIT狀態。
10)第三次握手做什麼?
服務端傳送給客戶端一個FIN。服務端進入LAST_ACK狀態。
11)第四次握手做什麼?
客戶端收到FIN,發回服務端一個ACK,確認序號為收到的序號加1;客戶端會進入TIME_WAIT狀態,2MSL超時後進入CLOSE狀態。服務端收到ACK後也會進入CLOSE狀態。
其實我們通俗的說每次握手其實就是發一次資料包的過程。建立連線時雙方共傳送了3個包,關閉連線時傳送和確認的兩次握手決定了一端資料流的關閉,四次握手可以保證兩方都關閉。
12)為什麼建立連線是三次握手,而關閉連線是四次呢?
建立連線時,服務端可以把應答ACK和同步SYN放在一個報文裡進行傳送。而關閉連線時,收到FIN通知僅僅表示對方沒有資料傳送過來了,並不表示自己的資料全部傳送給了對方。所以ACK和FIN是分了兩次進行傳送。如果服務端收到FIN,恰恰自己也沒有資料要發,是不是ACK和FIN可以一起發給客戶端呢,這樣就可以少一次資料流了。世界是美好的,經典的TCP連線狀態圖中也考慮到了這種情況,tcp關閉連線確實是只有三次資料流動,服務端將ACK和FIN放在一個包裡進行傳送,但四次握手這個概念卻已經根深蒂固無法更改了。
13)Tcp的各個狀態是怎樣的?
客戶端的正常tcp狀態:
CLOSED->SYN_SENT(第1次)->ESTABLISHED(第3次)->FIN_WAIT_1(第1次)->FIN_WAIT_2(第2次)->TIME_WAIT(第4次)->CLOSED
服務端的正常tcp狀態:
CLOSED->LISTEN->SYN_RCVD(第2次)->ESTABLISHED(第3次)->CLOSE_WAIT(第2次)->LAST_ACK(第3次)->CLOSED(第4次)
tcp還有其他的非正常狀態,在此不做討論,下篇文章再說。
11、陣列和連結串列的優缺點
陣列,在記憶體上給出了連續的空間。連結串列,記憶體地址上可以是不連續的,每個連結串列的節點包括原來的記憶體和下一個節點的資訊(單向的一個,雙向連結串列的話,會有兩個)。
陣列優於連結串列的:
A. 記憶體空間佔用的少,因為連結串列節點會附加上一塊或兩塊下一個節點的資訊。
但是陣列在建立時就固定了。所以也有可能會因為建立的陣列過大或不足引起記憶體上的問題。
B. 陣列內的資料可隨機訪問,但連結串列不具備隨機訪問性。這個很容易理解,陣列在記憶體裡是連續的空間,比如如果一個陣列地址從100到200,且每個元素佔用兩個位元組,那麼100-200之間的任何一個偶數都是陣列元素的地址,可以直接訪問。
連結串列在記憶體地址可能是分散的。所以必須通過上一節點中的資訊找能找到下一個節點。
C. 查詢速度上。這個也是因為記憶體地址的連續性的問題,不羅索了。
連結串列優於陣列的:
A. 插入與刪除的操作。如果陣列的中間插入一個元素,那麼這個元素後的所有元素的記憶體地址都要往後移動。刪除的話同理。只有對資料的最後一個元素進行插入刪除操作時,才比較快。連結串列只需要更改有必要更改的節點內的節點資訊就夠了。並不需要更改節點的記憶體地址。
B. 記憶體地址的利用率方面。不管你記憶體裡還有多少空間,如果沒辦法一次性給出陣列所需的要空間,那就會提示記憶體不足,磁碟空間整理的原因之一在這裡。而連結串列可以是分散的空間地址。
C. 連結串列的擴充套件性比陣列好。因為一個陣列建立後所佔用的空間大小就是固定的,如果滿了就沒法擴充套件,只能新建一個更大空間的陣列;而連結串列不是固定的,可以很方便的擴充套件。
相關文章
- 今天開始頭腦風暴
- 程式設計師進階之路之面試題與筆試題集錦(一)程式設計師面試題筆試
- 程式設計師進階之路之面試題與筆試題集錦(三)線上程式設計題程式設計師面試題筆試
- 頭腦風暴問題而不是想法
- 什麼是商業頭腦風暴?
- 前端效能優化總結,這也是我做程式設計師的第五個年頭了前端優化程式設計師
- 風變,我的程式設計啟蒙老師程式設計
- 做個清醒的程式設計師之要不要做程式設計師程式設計師
- 設計模式之面向切面程式設計AOP設計模式程式設計
- 我招了個“水貨”程式設計師程式設計師
- 身為產品經理,你真的明白頭腦風暴怎麼運用嗎?
- 畫江湖之面試篇 [第一篇] tip:基礎是每個程式設計師必須的面試程式設計師
- 這項技術,風頭正勁,BAT要力捧!程式設計師:我徹底慌了...BAT程式設計師
- 我為我是個程式設計師而驕傲程式設計師
- 我真的要做一輩子的程式設計師嗎?程式設計師
- 面試了一個 39 歲程式設計師,我有點慌……面試程式設計師
- 做一個心理健康的程式設計師程式設計師
- 曾經有個能幫我修電腦的程式設計師,我沒有好好珍惜……程式設計師
- 小白程式設計師最容易踩的“坑”,你踩過幾個?程式設計師
- 程式設計師嘛,先做個好架構師再說程式設計師架構
- 面試了一個 39 歲程式設計師後,我被罵了……面試程式設計師
- java程式設計師最難面試之“今日頭條”Java程式設計師面試
- windows程式設計師開發linux程式的頭一個月Windows程式設計師Linux
- 【譯】我是一個平庸的程式設計師程式設計師
- 做個清醒的程式設計師之擁抱AI程式設計師AI
- 做個清醒的程式設計師之拒絕工作程式設計師
- 【程式設計師腦洞故事】宇宙盡頭的描述符(上)程式設計師
- 程式設計師: 我只要技術牛就行! 軟體工程師: 那你能做個好專案嗎?程式設計師軟體工程工程師
- 好程式設計師:Java程式設計師面試秘籍程式設計師Java面試
- 有哪些錯是Java程式設計師在面試中最容易犯的呢?Java程式設計師面試
- 我的程式設計師之路程式設計師
- 你好,我是程式設計師程式設計師
- 妹子幫個忙!嫁給程式設計師好嗎?程式設計師
- 華為大佬:做一個快樂的程式設計師程式設計師
- 你需要程式設計師鼓勵師嗎?程式設計師
- 程式設計靠天分?不!我學程式設計靠風變程式設計
- 程式設計師街頭生存手冊程式設計師
- 程式設計師,你焦慮嗎?程式設計師
- 程式設計師的路好走嗎程式設計師