[TOC]html
該系列博客的目的是爲了學習一遍數據結構中經常使用的概念以及經常使用的算法,爲筆試準備;主要學習過程參考王道的《2018年-數據結構-考研複習指導》;前端
已總結章節:node
上篇博客《數據結構與算法》-2-線性表中介紹了線性結構中的線性表的定義、基本操做以及線性表的兩種存儲方式:順序存儲與鏈式存儲;這一篇博客將主要介紹線性結構中的受限線性表:棧、隊列, 線性表推廣:數組。算法
主要包含的內容有:後端
其知識框架以下圖所示: 數組
棧(Stack): 只能在一端執行插入或刪除操做的線性表;數據結構
棧頂(Top):框架
線性表容許執行插入或刪除操做的一端;學習
棧底(Botton):spa
線性表不容許執行插入或刪除操做的一端;
**注意:**根據棧的定義能夠獲得,棧是先進後出的線性表;
InitStack(&S)
:初始化一個空棧S;
StackEmpty(S)
:判斷S是否爲空棧;
Push(&S, x)
:進棧操做;若未滿,則將x進棧,做爲棧頂;
Pop(&S, &x)
:出棧操做;若非空,則將棧S的棧頂元素彈出,並用x返回;
GetTop(S, &x)
:讀取棧頂元素;若非空,用x返回棧頂元素;
ClearStack(&S)
:銷燬棧,並釋放其存儲空間;
棧的順序存儲稱爲順序棧,它是利用一組地址連續的存儲單元存放從棧底到棧頂的全部數據元素,同時附設一個指針top,指向當前棧頂位置。
棧的順序存儲類型描述:
# define MaxSize 50 typedef struct{ ElemType data[MaxSize]; int top; }SqStack;
S.top
,初始時S.top=-1
;S.data[S.top]
;S.top==-1
;S.top == MaxSize -1
;初始化:
void InitStack(&S){ S.top = -1; }
判棧空:
bool StackEmpty(S){ if(S.top == -1) return true; else return false; }
進棧:
bool Push(&S, x){ if(S.top == MaxSize-1) // 判滿 return false; else s.top++; S.data[S.top] = x; // 或 S.data[++S.top] = x; return true; }
出棧:
bool Pop(&S, &x){ if(S.top == -1) // 判空 return false; x = S.data[S.top]; S.top--; // 或 x = S.data[S.top--] return true; }
讀棧頂元素:
bool GetTop(S, &x){ if(S.top == -1) // 判空 return false; x = S.data[S.top]; return true; }
利用棧底位置不變的特性,可讓兩個順序棧共享一個一維數組,以下圖所示:
s0.top = -1
表示0號棧棧空;s1.top = MaxSize -1
表示1號棧棧空;s1.top - s0.top = 1
表示棧滿;棧的順序存儲稱爲順序棧,那麼採用鏈式存儲的棧則稱爲鏈棧;
**優勢:**便於多個棧共享存儲空間和提升效率,不存在棧滿溢出的狀況;
採用單鏈表來實現,並規定全部操做再單鏈表表頭進行,沒有頭結點;Lhead指向棧頂元素;如圖所示:
棧的鏈式存儲的類型描述:
typedef struct Linknode{ ElemType data; struct Linknode *next; }*LiStack;
隊列(Queue):
只容許在表的一端插入,另外一端刪除;
隊頭(Front):
容許刪除的一端;
隊尾(Rear):
容許插入的一端;
空隊列:
不含任何元素的空表;
**注意:**根據隊列的定義能夠獲得,隊列是先進先出的線性表;
InitQueue(&Q)
:初始化隊列,構造一個空隊列Q;
QueueEmpty(Q)
:判空;
EnQueue(&Q, x)
:入隊操做;先判滿,再入隊;
DeQueue(&Q, &x)
:出隊操做;先判空,再出隊,並用x返回;
GetHead(Q, &x)
:讀隊頭元素,並用x返回;
隊列的順序實現是指:分配一塊連續的存儲單元存放隊列中的元素;
隊列的順序存儲類型描述:
#define MaxSize 50 typedef struct{ ElemType data[MaxSize]; int front, rear; }SqQueue;
Q.front == Q.rear == 0
; 上面講述了隊列順序存儲時的判空條件,即Q.front == Q.rear == 0
;那麼判滿條件呢?是Q.rear == MaxSize -1
嗎?顯然不是,假如隊頭已經有出隊的元素,這時候是一種「假溢出」;
爲解決上述隊列順序存儲的缺點,這裏有了一種循環隊列,即當隊頭指針Q.front == MaxSize -1
,或隊尾指針Q.rear == MaxSize -1
時,再前進一個位置時,會自動到0;
Q.front = Q.rear = 0
;Q.front = (Q.front + 1) % MaxSize
;Q.rear = (Q.rear + 1) % MaxSize
;Q.rear + MaxSize - Q.front) % MaxSize
; 從上圖能夠看出,初始化時Q.front = Q.rear
;當入隊操做多於出隊操做時,隊尾指針很快就能遇上隊頭指針,當Q.front = Q.rear
時(圖d1),也表明隊滿;
那麼Q.front = Q.rear
便可以表示隊空,也能夠表示隊滿?那怎麼來區分呢?這裏有三種處理方式:
(Q.rear + 1) % MaxSize = Q.front
;Q.front == Q.rear
;Q.rear + MaxSize - Q.front) % MaxSize
;Q.size
;
Q.size = MaxSize -1
;Q.size = 0
;tag=0
,因刪除致使Q.front = Q.rear
,則表示隊空;tag=1
,因插入致使Q.front = Q.rear
,則表示隊滿;初始化:
void InitQueue(&Q){ Q.rear = Q.front = 0; }
判隊空:
bool IsEmpty(Q){ if(Q.rear == Q.front) return true; else return false; }
入隊:
bool EnQueue(SqQueue &Q, ElemType x){ if((Q.rear + 1) % MaxSize == Q.front) // 判隊滿 return false; Q.data[Q.rear] = x; // 隊尾元素賦值 Q.rear = (Q.rear + 1) % MaxSize; // 隊尾元素加1 return true; }
出隊:
bool DeQueue(SqQueue &Q, ElemType &x){ if (Q.rear == Q.front) // 判隊空 return false; x = Q.data[Q.front]; // 取出隊頭元素 Q.front = (Q.front + 1) % MaxSize; // 隊頭指針加1 return true; }
隊列的鏈式存儲稱爲鏈隊列;它其實是一個同時帶有隊頭指針和隊尾指針的單鏈表;
隊列的鏈式存儲類型描述:
typedef struct{ ElemType data; struct LinkNode *next; }LinkNode; typedef struct{ LinkNode *front, *rear; }LinkQueue;
不帶頭結點的鏈式隊列:
Q.front == NULL, Q.rear == NULL
時,鏈式隊列爲空;帶頭結點的鏈式隊列:
初始化:
void InitQueue(LinkQueue &Q){ Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode)); // 創建頭結點 Q.front -> next = NULL; // 初始爲空 }
判隊空:
bool isEmpty(LinkQueue Q){ if(Q.front == Q.rear) return true; else return false; }
入隊:
void EnQueue(LinkQueue &Q, ElemType x){ s = (LinkNode *)malloc(sizeof(LinkNode)); // 新建結點 s -> data = x; // 對新結點數據域賦值 s -> next = NULL; // 將新結點指針域設空 Q.rear -> next = s; // 鏈表尾插入 Q.rear = s; // 對尾指針指向隊尾元素 }
出隊:
void DeQueue(LinkQueue &Q, ElemType &x){ if(Q.front == Q.rear) // 判空 return false; p = Q.front -> next; // 須要出隊的結點 x = p -> data; // 賦值 Q.front -> next = p -> next; // 斷開p結點 if(Q.rear == p) // 判斷原隊列是否只有一個元素 Q.rear = Q.front; free(p); return true; }
雙端隊列指的是兩端均可以執行入隊、出隊操做;
入隊:
出隊:
即有一端只容許入隊;
即有一端只容許出隊;
算法流程:
**Step 1:**初始化一個空棧,順序讀入括號;
**Step 2:**如果左括號,則進棧;
**Step 3:**如果右括號,則取出棧頂元素,斷定是否匹配;
在後綴表達式中,已經考慮了運算的優先級,沒有括號;可使用棧來計算;
**Step 1:**依次讀取後綴表達式; **Step 2:**若遇到操做數,則將操做進棧; **Step 3:**若遇到運算符,則前後彈出兩個棧內操做數,進行計算,計算結果,從新進棧; **Step 4:**重複上述步驟
將中綴表達式轉換稱後綴表達式,可使用棧來轉換,須要根據運算符在棧內外的優先級來判斷出/入棧操做;
操做符 | # | ( | *, / | +, - | ) |
---|---|---|---|---|---|
isp(棧內優先級) | 0 | 1 | 5 | 3 | 6 |
icp(棧外優先級) | 0 | 6 | 4 | 2 | 1 |
**Step 1:**首先,依次讀取中綴表達式; **Step 2:**若遇到操做數,則直接輸出; **Step 3:**若遇到操做符;
在遞歸的調用過程當中,系統爲每一層的返回點、局部變量、傳入實參等開闢了遞歸工做棧進行數據存儲,遞歸次數過多,容易形成棧溢出。其效率不高的緣由是遞歸調用的過程當中,包含了須要重複的計算;
舉例以下圖:
**Step 1:**根節點入隊; Step 2:若隊空,則結束遍歷;不然執行Step 3;
**Step 3:**隊列中第一個結點出隊,並訪問。
數據結構中考慮矩陣是:如何用最小的內存空間來存儲一樣的一組數據;
定義:數組是由n個相同類型的數據元素構成的有序序列,其中每個數據元素稱爲一個數組元素;
數組與線性表的關係:
注意:數組一旦定義,維數再也不改變,只有初始化、銷燬、存取元素、修改元素的操做;
一個數組的全部元素在內存中佔用一段連續的存儲空間;
壓縮存儲: 壓縮存儲是指多個值相同的元素只分配一個存儲空間,對零元素不分配存儲空間;
特殊矩陣: 指具備許多相同元素或零元素,而且分佈有必定規律的矩陣;對稱矩陣、上(下)三角矩陣、對角矩陣;
特殊矩陣的壓縮存儲:
找出特殊矩陣中相同值的分佈規律,而後存入到一個存儲空間內;
注意:下面幾種特殊矩陣的壓縮存儲,均採用按行優先存儲;
將對稱矩陣$A[1\cdots n][1\cdots n]$存儲到一維數組$B[n(n+1)/2]$中;
上三角矩陣與主對角線上元素$a_{i,j}(i \geq j)$對應數組的下標位置爲: $$ k = 1+2+\cdots+(i-1)+(j-1)=\dfrac{i(i-1)}{2}+j-1 $$ 下三角矩陣與上三角矩陣相反,所以有: $$ k= \begin{cases} \dfrac{i(i-1)}{2}+j-1 & i \geq j (下三角區與主對角線元素)\ \dfrac{j(j-1)}{2}+i-1 & i < j (上三角區元素) \end{cases} $$ 注意:數組下標從0開始;
將上/下三角矩陣$A[1\cdots n][1\cdots n]$存儲到一維數組$B[n(n+1)/2+1]$中;
下三角矩陣:
$$ k= \begin{cases} \dfrac{i(i-1)}{2}+j-1 & i \geq j (下三角區與主對角線元素)\ \dfrac{n(n+1)}{2} & i < j (上三角區元素) \end{cases} $$
上三角矩陣:
$$ k = n + (n-1) + (n-2)+\cdots +(n-j+2)+(j-1+1)=\dfrac{(i-1)(2n-i+2)}{2}+j-i $$
$$ k= \begin{cases} \dfrac{(i-1)(2n-i+2)}{2}+j-i & i \leq j (上三角區與主對角線元素)\ \dfrac{n(n+1)}{2} & i > j (下三角區元素) \end{cases} $$
注意:數組下標從0開始;
矩陣A中3條對角線元素$a_{i,j}(1 \leq i, j \leq n, |i-j|\leq 1)$對應數組中的下標爲: $$ k = 2i + j -3 $$
存儲方式,以下圖所示,僅僅只記錄其非零元素的位置與值;
總結:
該篇博客主要棧、隊列、數組矩陣三部份內容;
有關棧,主要介紹了棧的基本概念、棧的基本操做;並根據其兩種存儲方式,一種是順序存儲,包括順序棧、順序棧的基本操做、共享棧;另外一種是鏈式存儲,包括鏈式棧;
有關隊列,主要介紹了隊列的基本概念、隊列的基本操做;並根據其兩種存儲方式,一種是順序存儲,包括隊列的順序存儲、循環隊列;另外一種是鏈式存儲,包括鏈隊列、鏈隊列的基本操做、雙端隊列;
第三部分介紹了棧和隊列的一些應用,包括:括號匹配、表達式求值、遞歸、層次遍歷、計算機系統;
最後介紹了幾種特殊矩陣,以及對應的壓縮存儲方式;