《數據結構與算法》-3-棧和隊列

[TOC]html


  該系列博客的目的是爲了學習一遍數據結構中經常使用的概念以及經常使用的算法,爲筆試準備;主要學習過程參考王道的《2018年-數據結構-考研複習指導》;前端

已總結章節:node


  上篇博客《數據結構與算法》-2-線性表中介紹了線性結構中的線性表的定義、基本操做以及線性表的兩種存儲方式:順序存儲與鏈式存儲;這一篇博客將主要介紹線性結構中的受限線性表:棧、隊列, 線性表推廣:數組算法

  主要包含的內容有:後端

  • 棧的基本概念與操做、順序存儲與鏈式存儲
  • 隊列的基本概念與操做、順序存儲與鏈式存儲
  • 棧、隊列的實際應用
  • 特殊矩陣的壓縮存儲

  其知識框架以下圖所示: 數組


1. 棧

1.1 棧的基本概念

1.1.1 棧的定義

棧(Stack):只能在一端執行插入或刪除操做的線性表數據結構

棧頂(Top):框架

  線性表容許執行插入或刪除操做的一端;學習

棧底(Botton):spa

  線性表不容許執行插入或刪除操做的一端;

**注意:**根據棧的定義能夠獲得,棧是先進後出的線性表

1.1.2 棧的基本操做

InitStack(&S):初始化一個空棧S;

StackEmpty(S):判斷S是否爲空棧;

Push(&S, x):進棧操做;若未滿,則將x進棧,做爲棧頂;

Pop(&S, &x):出棧操做;若非空,則將棧S的棧頂元素彈出,並用x返回;

GetTop(S, &x):讀取棧頂元素;若非空,用x返回棧頂元素;

ClearStack(&S):銷燬棧,並釋放其存儲空間;

1.2 棧的順序存儲結構

1.2.1 順序棧

  棧的順序存儲稱爲順序棧,它是利用一組地址連續的存儲單元存放從棧底到棧頂的全部數據元素,同時附設一個指針top,指向當前棧頂位置。

  棧的順序存儲類型描述:

# define MaxSize 50
typedef struct{
	ElemType data[MaxSize];
    int top;
}SqStack;
  • 棧頂指針:S.top,初始時S.top=-1
  • 棧頂元素:S.data[S.top]
  • **進棧操做:**棧不滿時,棧頂指針加1,再賦值到棧頂元素;
  • **出棧操做:**棧非空時,先從棧頂元素取值,再將棧頂指針減1;
  • 棧空條件:S.top==-1
  • 棧滿條件:S.top == MaxSize -1

1.2.2 順序棧的基本運算

初始化:

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;
}

1.2.3 共享棧

  利用棧底位置不變的特性,可讓兩個順序棧共享一個一維數組,以下圖所示:

  • s0.top = -1表示0號棧棧空;s1.top = MaxSize -1表示1號棧棧空;
  • s1.top - s0.top = 1表示棧滿;
  • 0號棧,進棧時,指針先加1,再賦值;出棧時,先賦值,再減1;
  • 1號棧,進棧時,指針先減1,再賦值;出棧時,先賦值,再加1;
  • 共享棧的目的是爲了更有效地利用存儲空間;

1.3 棧的鏈式存儲結構

  棧的順序存儲稱爲順序棧,那麼採用鏈式存儲的棧則稱爲鏈棧

  **優勢:**便於多個棧共享存儲空間和提升效率,不存在棧滿溢出的狀況;

  採用單鏈表來實現,並規定全部操做再單鏈表表頭進行,沒有頭結點;Lhead指向棧頂元素;如圖所示:

  棧的鏈式存儲的類型描述:

typedef struct Linknode{
    ElemType data;
    struct Linknode *next;
}*LiStack;

2. 隊列

2.1 隊列的基本概念

2.1.1 隊列的定義

隊列(Queue):

只容許在表的一端插入,另外一端刪除;

隊頭(Front):

  容許刪除的一端;

隊尾(Rear):

  容許插入的一端;

空隊列:

  不含任何元素的空表;

**注意:**根據隊列的定義能夠獲得,隊列是先進先出的線性表

2.1.2 隊列的基本操做

InitQueue(&Q):初始化隊列,構造一個空隊列Q;

QueueEmpty(Q):判空;

EnQueue(&Q, x):入隊操做;先判滿,再入隊;

DeQueue(&Q, &x):出隊操做;先判空,再出隊,並用x返回;

GetHead(Q, &x):讀隊頭元素,並用x返回;

2.2 隊列的順序存儲結構

2.2.1 隊列的順序存儲

  隊列的順序實現是指:分配一塊連續的存儲單元存放隊列中的元素;

  • 隊頭指針(front)指向隊頭元素,隊尾指針(rear)指向隊尾元素的下一個位置;

  隊列的順序存儲類型描述:

#define MaxSize 50
typedef struct{
    ElemType data[MaxSize];
    int front, rear;
}SqQueue;
  • 隊空條件:Q.front == Q.rear == 0
  • **進隊操做:**隊不滿時,先賦值給隊尾元素,再將隊尾指針加1;
  • **出隊操做:**隊不空時,先取隊頭元素,再將隊頭指針加1;

2.2.2 循環隊列

  上面講述了隊列順序存儲時的判空條件,即Q.front == Q.rear == 0;那麼判滿條件呢?是Q.rear == MaxSize -1嗎?顯然不是,假如隊頭已經有出隊的元素,這時候是一種「假溢出」;

  爲解決上述隊列順序存儲的缺點,這裏有了一種循環隊列,即當隊頭指針Q.front == MaxSize -1,或隊尾指針Q.rear == MaxSize -1時,再前進一個位置時,會自動到0;

  • 初始時: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.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數據成員;
    • tag=0,因刪除致使Q.front = Q.rear,則表示隊空;
    • tag=1,因插入致使Q.front = Q.rear,則表示隊滿;

2.2.3 循環隊列的操做

初始化:

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;
}

2.3 隊列的鏈式存儲結構

2.3.1 隊列的鏈式存儲

  隊列的鏈式存儲稱爲鏈隊列;它其實是一個同時帶有隊頭指針和隊尾指針的單鏈表

  • 頭指針指向隊頭元素;
  • 尾指針指向隊尾元素(即隊列的最後一個結點,注意:和順序隊指向的不同);

  隊列的鏈式存儲類型描述:

typedef struct{
    ElemType data;
    struct LinkNode *next;
}LinkNode;

typedef struct{
	LinkNode *front, *rear;
}LinkQueue;

不帶頭結點的鏈式隊列:

  • Q.front == NULL, Q.rear == NULL時,鏈式隊列爲空;
  • 入隊,在鏈表尾部插入結點;
  • 出隊,取出隊頭元素,將其從鏈表中刪除;

帶頭結點的鏈式隊列:

2.3.2 鏈式隊的基本操做

初始化:

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;
}

2.4 雙端隊列

  雙端隊列指的是兩端均可以執行入隊、出隊操做

入隊:

  • 前端進的元素排列在後端進的元素的前面;
  • 後端進的元素排列在前端進的元素的後面;

出隊:

  • 先出的元素排在後出的元素前面;

2.4.1 輸出受限的雙端隊列

  即有一端只容許入隊

2.4.2 輸入受限的雙端隊列

  即有一端只容許出隊

3. 棧和隊列的應用

3.1 棧在括號匹配中的應用

算法流程:

**Step 1:**初始化一個空棧,順序讀入括號;

**Step 2:**如果左括號,則進棧;

**Step 3:**如果右括號,則取出棧頂元素,斷定是否匹配;

  • 若匹配,則彈出棧頂元素,繼續讀取;
  • 若不匹配,則退出程序,括號不匹配;

3.2 棧在表達式求值中的應用

  在後綴表達式中,已經考慮了運算的優先級,沒有括號;可使用棧來計算;

**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:**若遇到操做符;

  • 若該操做符的棧外優先級大於此時棧頂元素的棧內優先級,則將該操做符進棧;
  • 若該操做符的棧外優先級小於此時棧頂元素的棧內優先級,則將棧頂元素彈出棧;再繼續比較該操做符的棧外優先級與棧頂元素的棧內優先級;

3.3 棧對遞歸中的應用

  在遞歸的調用過程當中,系統爲每一層的返回點、局部變量、傳入實參等開闢了遞歸工做棧進行數據存儲,遞歸次數過多,容易形成棧溢出。其效率不高的緣由是遞歸調用的過程當中,包含了須要重複的計算;

3.4 隊列在層次遍歷中的應用

舉例以下圖:

**Step 1:**根節點入隊; Step 2:若隊空,則結束遍歷;不然執行Step 3

**Step 3:**隊列中第一個結點出隊,並訪問。

  • 若其有左孩子,則將左孩子入隊;
  • 若其有右孩子,則將右孩子入隊;
  • 返回執行Step 2

3.5 隊列在計算機系統中的應用

  • 解決主機與外部設備之間速度不匹配的問題;
  • 解決由多用戶引發的資源競爭問題;

4. 特殊矩陣的壓縮存儲

  數據結構中考慮矩陣是:如何用最小的內存空間來存儲一樣的一組數據

4.1 數組的定義

  定義:數組是由n個相同類型的數據元素構成的有序序列,其中每個數據元素稱爲一個數組元素;

數組與線性表的關係:

  • 數組是線性表的推廣;
  • 一維數組能夠看做是一個線性表;
  • 二維數組能夠看做是數據元素是線性表的線性表;

注意:數組一旦定義,維數再也不改變,只有初始化、銷燬、存取元素、修改元素的操做;

4.2 數組的存儲結構

  一個數組的全部元素在內存中佔用一段連續的存儲空間;

  • 一維數組:順序存儲;
  • 多維數組:兩種方式:按行優先、按列優先

4.3 矩陣的壓縮存儲

壓縮存儲:   壓縮存儲是指多個值相同的元素只分配一個存儲空間,對零元素不分配存儲空間;

特殊矩陣:   指具備許多相同元素或零元素,而且分佈有必定規律的矩陣;對稱矩陣、上(下)三角矩陣、對角矩陣;

特殊矩陣的壓縮存儲:

  找出特殊矩陣中相同值的分佈規律,而後存入到一個存儲空間內;

注意:下面幾種特殊矩陣的壓縮存儲,均採用按行優先存儲;

4.3.1 對稱矩陣

  將對稱矩陣$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開始;

4.3.2 三角矩陣

  將上/下三角矩陣$A[1\cdots n][1\cdots n]$存儲到一維數組$B[n(n+1)/2+1]$中;

下三角矩陣:

  • 下三角與主對角線元素$a_{i,j}(i \geq j)$與對稱矩陣的下三角與主對角線元素的下標一致;
  • 上三角元素用一個位置,數組下標$\dfrac{n(n+1)}{2}$;

$$ k= \begin{cases} \dfrac{i(i-1)}{2}+j-1 & i \geq j (下三角區與主對角線元素)\ \dfrac{n(n+1)}{2} & i < j (上三角區元素) \end{cases} $$

上三角矩陣:

  • 上三角矩陣與主對角線上元素$a_{i,j}(i \leq j)$對應數組的下標:

$$ k = n + (n-1) + (n-2)+\cdots +(n-j+2)+(j-1+1)=\dfrac{(i-1)(2n-i+2)}{2}+j-i $$

  • 下三角元素佔用一個位置,數組下標$\dfrac{n(n+1)}{2}$;

$$ 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開始;

4.3.3 三對角矩陣

  矩陣A中3條對角線元素$a_{i,j}(1 \leq i, j \leq n, |i-j|\leq 1)$對應數組中的下標爲: $$ k = 2i + j -3 $$

4.4 稀疏矩陣

  存儲方式,以下圖所示,僅僅只記錄其非零元素的位置與值;


總結:

  該篇博客主要棧、隊列、數組矩陣三部份內容;

  有關棧,主要介紹了棧的基本概念、棧的基本操做;並根據其兩種存儲方式,一種是順序存儲,包括順序棧、順序棧的基本操做、共享棧;另外一種是鏈式存儲,包括鏈式棧;

  有關隊列,主要介紹了隊列的基本概念、隊列的基本操做;並根據其兩種存儲方式,一種是順序存儲,包括隊列的順序存儲、循環隊列;另外一種是鏈式存儲,包括鏈隊列、鏈隊列的基本操做、雙端隊列;

  第三部分介紹了棧和隊列的一些應用,包括:括號匹配、表達式求值、遞歸、層次遍歷、計算機系統;

  最後介紹了幾種特殊矩陣,以及對應的壓縮存儲方式;

相關文章
相關標籤/搜索