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. 鏈表的擴展性比數組好。因為一個數組建立后所占用的空間大小就是固定的,如果滿了就沒法擴展,只能新建一個更大空間的數組;而鏈表不是固定的,可以很方便的擴展。