棧
基本概念
棧(Stack)是只允許在一端進行插入或刪除的線性表。
棧頂:線性表允許進行插入刪除的一端。
棧底:固定的,不允許進行插入和刪除的一端。
特點:后進先出(LIFO)
數學性質:n個不同元素進棧,出棧元素不同排列的個數為$\frac{1}{n+1} \mathrm{C}_{2n}^{n} $(卡特蘭樹)
基本操作
InitStack(&S); //初始化一個空棧S
DestoryList(&S); //銷毀棧,並釋放棧S所占用的存儲空間
Push(&S,x); //進棧,若棧S未滿,則將x加入使之成為新棧頂
Pop(&S,&x); //出棧,若棧非空,則彈出棧頂元素,並用x返回
GetTop(S,&x); //讀棧頂元素,若棧S非空,則用x返回棧頂元素
StackEmpty(S); //判斷一個棧S是否為空。若S為空,則返回true,否則返回false
順序棧
順序棧的實現
采用順序存儲的棧,它利用一組地址連續的存儲單元存放自棧底到棧頂的數據元素,同時設一個指針指示當前棧頂元素的位置。
#define MaxSize 10 //定義棧中元素的最大個數
typedef struct{
Elemtype data[MaxSize]; //存放棧中元素
int top; //棧頂指針
}SqStack;
棧頂指針:\(S.top\),初始設置為\(S.top=-1\);棧頂元素:\(S.data[S.top]\)
順序棧的基本運算
//初始化
void InitStack(SqStack& S)
{
S.top=-1;
}
//判斷空
bool InitStack(SqStack S)
{
if(S.top=-1)
return true;
return false;
}
//進棧
//棧不滿時,棧頂指針先加1,再送值到棧頂元素
bool Push(SqStack& S,Elemtype x)
{
if(S.top==MaxSize-1)
return false;
S.data[++S.top]=x;
return true;
}
// 出棧
// 棧非空時,先取棧頂元素值,再將棧頂指針減1
bool Pop(SqStack& S,Elemtype& x)
{
if(S.top==-1)
return false;
e=S.data[S.top--];
return true;
}
//取棧頂元素
bool GetTop(SqStack S,Elemtype& x)
{
if(S.top==-1)
return false;
e=S.data[S.top];
return true;
}
共享棧
將兩個順序棧共享一個一維數組空間
鏈棧
鏈棧的實現
采用鏈式存儲;
優點是便於多個棧共享存儲空間和提高其效率,且不存在棧滿上溢的情況。
通常采用單鏈表實現,並規定所有操作都是在單鏈表的表頭進行。(Lhead指向棧頂元素)
typedef struct Linknode{
Elemtype data;
struct Linknode *next;
}*LiStack;
具體操作自己實現
棧的應用
括號匹配
算法思想:
- 初始設置一個空棧,順序讀入括號
- 若是右括號,則使置於棧頂的最急迫期待的左括號進行匹配,或者不合法的i情況(不匹配,退出程序)
- 若是左括號,則壓入棧中;算法結束時,棧為空,否則括號序列不匹配
//代碼實現
bool bracketCheck(char str[],int length)
{
SqStack S;
InitStack(S);
for(int i=0;i<length;i++)
{
if(str[i]=='('||str[i]=='['||str[i]=='{')
Push(S,str[i]);
else
{
if(StackEmpty(S))
return false;
char topElem;
Pop(S,topElem);
if(str[i]==')'&&topElem!='(')
return false;
if(str[i]==']'&&topElem!='[')
return false;
if(str[i]=='}'&&topElem!='{')
return false;
}
}
return StackEmpty(S);
}
表達式求值
中,后,前綴表達式
手算
中綴轉后綴手算方法:
- 確定中綴表達式中的各個運算的運算順序
- 選擇下一個運算符,按照【左操作數,右操作數,運算符】的方式組合成一個新的操作數
- 如果還有運算符沒被處理,就繼續②
“左優先”原則:只要左邊的運算符能先計算,就優先算左邊的(保證手算和機算的結果一樣)
后綴表達式的手算方法:從左往右掃描,每遇到一個運算符,就讓運算符前面最近的兩個操作數執行對應運算,合體為一個操作數(注意:兩個操作數的左右順序)
特點:最后出現的操作數先被運算
用棧實現后綴表達式的計算
- 從左往右掃描下一個元素,直至處理完所有元素
- 若掃描到操作數則壓入棧,並回到①;否則執行③
- 若掃描到運算符,則彈出兩個棧頂元素,執行相應運算,運算結果壓回棧頂,回到①
注意:先出棧的是”右操作數“
中綴轉前綴的手算方法:
- 確定中綴表達式中各個運算符的運算順序
- 選擇下一個運算符,按照【運算符,左操作數,右操作數】的方式組合成一個新的操作數
- 如果還有運算符沒被處理,就繼續②
“右優先”原則:只要右邊的運算符能先計算,就優先算右邊的
用棧實現前綴表達式的計算
- 從右往左掃描下一個元素,直至處理完所有元素
- 若掃描到操作數則壓入棧,並回到①;否則執行③
- 若掃描到運算符,則彈出兩個棧頂元素,執行相應運算,運算結果壓回棧頂,回到①
注意:先出棧的是”左操作數“
機算
中綴轉后綴表達式
初始化一個棧,用於保存暫時還不能確定運算順序的運算符
從左到右處理各個元素,直到末尾,可能遇到三種情況:
- 遇到操作數,直接加入后綴表達式
- 遇到界限符。遇到“(”直接入棧;遇到“)”則依次彈出棧內運算符並加入后綴表達式,直至彈出“(”為止。注意:“(”不加入后綴表達式
- 遇到運算符。依次彈出棧中優先級高於或等於當前運算符的所有運算符,並加入后綴表達式,若碰到“(”或棧空則停止。之后再把當前運算符入棧。
按上述方法處理完所有字符后,將棧中剩余運算符依次彈出,並加入后綴表達式。
中綴表達式的計算
用棧實現:初始化兩個棧,操作數棧和運算符棧
- 若掃描到操作數,壓入操作數棧
- 若掃描到運算符或界限符,則按照“中綴轉后綴”相同的邏輯壓入運算符棧(期間也會彈出運算符,每當彈出一個運算符,就需要再彈出兩個操作數棧的棧頂元素並執行相應運算,運算結果再壓回操作數棧)
遞歸的應用
函數調用背后的過程
函數調用的特點:最后被調用的函數最先執行結束(LIFO)
函數調用時需要一個棧存儲:
- 調用返回地址
- 實參
- 局部變量
遞歸調用時,函數調用棧可稱為“遞歸調用棧”
每進入一層遞歸,就將遞歸調用所需信息壓入棧頂
每退出一層遞歸,就從棧頂彈出相應信息
適合用“遞歸”算法解決:可以把原始問題轉換為屬性相同,但規模較小的問題。如:斐波那契數列
隊列
基本概念
隊列是只允許在一端進行插入,在另一端刪除的線性表。
特點:先進先出(FIFO)
基本操作
InitQueue(&Q); //初始化隊列,構造一個空隊列Q
DestoryQueue(&Q); //銷毀隊列。銷毀並釋放隊列Q所占的內存空間
EnQueue(&Q,x); //入隊,若隊列未滿,將x加入,使之成為新的隊尾
DeQueue(&Q,&x); //出隊,若隊列Q非空,刪除隊頭元素,並用x返回
GetHead(Q,&x); //讀隊頭元素,若隊列Q非空,則將隊頭元素賦值給x
QueueEmpty(Q); //判隊列空,若隊列Q為空返回true,否則返回false
隊列的順序存儲結構
隊列的順序存儲
#define MaxSize 50 //定義隊列中元素的最大個數
typedef struct{
Elemetype data[Maxsize]; //存放隊列元素
int front,rear; //隊頭指針和隊尾指針
}
初始狀態(隊空條件):\(Q.front=Q.rear=0\)
進隊操作:隊不滿時,先送值到隊尾元素,再將隊尾指針加1
出隊操作:隊不空時,先取隊頭元素值,再將隊頭指針加1
“上溢出”(加溢出):\(Q.rear=MaxSize\)
具體操作代碼自己實現
循環隊列
初始時:\(Q.front=Q.rear=0\)
隊首指針進1:\(Q.front=(Q.front+1)\%Maxsize\)
隊尾指針進1:\(Q.rear=(Q.rear+1)\%Maxsize\)
隊列長度:\((Q.rear+Maxsize-Q.front)\% MaxSize\)
判斷判空判滿方法
-
犧牲一個存儲單元區分隊空和隊滿
隊滿條件:\((Q.rear+1)\%MaxSize==Q.front\)
隊空條件仍為:\(Q.rear=Q.front\)
隊列中元素個數:\((Q.rear-Q.front+MaxSize) \% MaxSize\)
-
增加一個\(Size\)變量記錄隊列長度
隊滿條件:\(Q.Size=MaxSize\)
隊空條件仍為:\(Q.Size=0\)
-
增加\(tag=0/1\)標記出隊/入隊
\(tag=0\)時,\(Q.front=Q.rear\),則為隊空(出隊操作)
\(tag=1\)時,\(Q.front=Q.rear\),則為隊滿(入隊操作)
具體操作代碼自己實現
隊列的鏈式存儲結構
隊列的鏈式存儲結構
typedef struct{ Elemtype data; struct LinkNode *next;}LinkNode;typedef struct{ //鏈式隊列 LinkNode *front,*rear; //隊列的隊頭和隊尾指針}LinkQueue;
鏈式隊列的基本操作
以下代碼都是帶頭結點,若不帶頭結點則需要進行特殊處理(自己思考)
//初始化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; return false;}
//入隊Void EnQueue(LinkQueue &Q,Elemtype x){ LinkNode *s=(LinkNode*)malloc(sizeof(LinkNode)); s->data=x;s->next=NULL; Q.rear->next=s; Q.rear=s;}
//出隊bool DeQueue(LinkQueue &Q,Elemtype &x){ if(Q.front==Q.rear) return false; LinkNode* p=Q.front->next; x=p->data; Q.front->next=p->next; if(Q.rear==p) Q.rear==Q.front; //若原隊列中只有一個結點,刪除后變空 free(p); return true; }
雙端隊列
雙端隊列時指允許兩端都可以進行入隊和出隊操作的隊列。
輸出受限的雙端隊列:允許在一端進行插入和刪除,但在另一端只允許插入的雙端隊列
輸入受限的雙端隊列:允許在一端進行插入和刪除,但在另一端只允許刪除的雙端隊列
考點:輸出序列
隊列的應用
樹的層次遍歷
- 根結點入隊
- 若隊空(所有結點都已處理完畢),則結束遍歷;否則重復③操作
- 隊列中第一個結點,並訪問之。若其有左孩子,則將左孩子入隊;若其有右孩子,則將右孩子入隊,返回②。
操作系統的應用
特殊矩陣的壓縮存儲
一維數組的存儲結構
二維數組的存儲結構
普通矩陣的存儲
對稱矩陣
若n階方陣中任意一個元素\(a_{i,j}\)都有\(a_{i,j}=a_{j,i}\),則該矩陣為對稱矩陣。
策略:只存儲主對角線+下三角區
按行優先原則將各元素存入一維數組中(長度:\((1/n)*n/2\))
按照行優先原則,元素下標之間的對應關系如下:
三角矩陣
壓縮存儲策略:按行優先原則則將橙色區元素存入一維數組,並在最后一個位置存儲常量c句號
(下三角矩陣)元素下標之間的對應關系如下:
(上三角矩陣)元素下標之間的對應關系如下:
三對角矩陣
三對角矩陣,又稱帶狀矩陣;
當\(|i-j|>1\)時,有\(a_{i,j}=0(i\le i,j \le n)\)
壓縮存儲策略:按行(列)優先原則,只存儲帶狀部分(長度為:\(3n-2\))
按行優先原則,對應關系\(k=2i+j-1\),\(\lceil(k+2)/3 \rceil\)或者\((\lfloor (k+2)/3+1 \rfloor)\)
稀疏矩陣
非零元素遠遠少於矩陣元素的個數
壓縮存儲策略:
-
順序存儲-三元組<行,列,值>
-
鏈式存儲-十字鏈表法