1. 程式人生 > 其它 >【c 語言資料結構】棧和佇列的相關操作

【c 語言資料結構】棧和佇列的相關操作

目錄

只允許在一端進行插入或刪除操作的線性表。

一,棧的基本操作介紹

  • InitStack(&S):初始化棧。構造一個空棧S,分配記憶體空間。
  • DestroyStack(&L):銷燬棧。銷燬並釋放棧S 所佔用的記憶體空間。
  • Push(&S,x):進棧,若棧S 未滿,則將x 加入使之稱為新棧頂。
  • Pop(&S,&x):出棧,若棧非空,則彈出棧頂元素,並用x 返回。
  • GetTop(S,&x):讀棧頂元素,若棧S 非空,則用x 返回棧頂元素。

二,棧的順序儲存實現

1,順序棧的定義

見如下程式碼片段,體會順序棧的定義:

#define MaxSize 10
typedef struct{
    ElemType data[MaxSize];	//靜態陣列存放棧中元素
    int top;			//棧頂指標      
} SqStack;

void testStack(){
    SqStack S;	//宣告一個順序棧(分配空間)
    //.....
}

2,初始化操作

//初始化棧
void InitStack(SqStack &S){
    S.top=-1;	//初始化棧頂指標
}

//我們也可以知道如何判斷棧空
bool StackEmpty(SqStack S){
    if(S.top==-1)	//棧空
        return true;
    else
        return false;
}

3,進棧操作

//新元素入棧
bool Push(SqStack &S,ElemType x){
    if(S.top==MaxSize-1)	//棧滿,報錯
        return false;
    S.data[++S.top]=x;	//指標先加1,新元素入棧
    return true;
}

4,出棧操作

在棧頂彈出一個元素,並用x 返回:

bool Pop(SqStack &S,ElemType &x){
    if(S.top==-1)	//棧空,報錯
        return false;
    x=S.data[S.top];	//棧頂元素先出棧(注意一下和指標變化的先後順序)
    S.top=S.top-1;	//指標再減1
    return true;
}

三,棧的鏈式儲存實現

採用鏈式儲存的棧稱為鏈棧,其優點是便於多個棧共享儲存空間和提高其效率,且不存在棧滿上溢的情況。通常採用單鏈表實現,並規定所有的操作都是在單鏈表的表頭進行的。

現在再想一想,是不是在單鏈表中,表頭後插入一個結點就相當於元素入棧的操作,在表頭後刪除一個元素就相當於元素出棧。只不過入棧和出棧的操作都是在連結串列的表頭進行的。

佇列

一,佇列的定義

前面我們知道了棧是隻允許在一端進行插入或刪除操作的線性表,那佇列呢?

佇列是隻允許在一端進行插入,在另一端刪除的線性表。這個結合日常生活中的排隊來想,是不是非常簡單~

以下是佇列的幾種基本操作:

  • InitQueue(&Q):初始化佇列,構造一個空佇列Q。
  • DestroyQueue(&Q):銷燬佇列。銷燬並釋放佇列Q 所佔用的記憶體空間
  • EnQueue(&Q,x):入隊,若佇列Q 未滿,將x 加入,使之成為新的隊尾。
  • DeQueue(&Q,&x):出隊,若佇列Q 非空,刪除隊頭元素,並用x 返回。

二,佇列的順序實現

1,初始化操作

#define MaxSize 10	//定義佇列中元素的最大個數
typedef struct {
    ElemType data[MaxSize];	//用靜態陣列存放佇列元素
    int front,rear;	//隊頭指標和隊尾指標
}SqQueue;

//初始化佇列
void InitQueue(SqQueue &Q){
    //初始時,隊頭隊尾指標指向0
    Q.rear=Q.front=0;
}
//判斷佇列是否為空,就是頭,尾指標指向同一個元素
bool QueueEmpty(SqQueue Q){
    if(Q.rear==Q.front)	//隊空的條件
        return true;
    else
        return false;
}

2,入隊操作

//入隊
bool EnQueue(SqQueue &Q,ElemType x){
    if(佇列已滿)
        return false;	//隊滿則報錯
    Q.data[Q.rear]=x;	//新元素插入隊尾
    Q.rear=(Q.rear+1)%MaxSzie	//隊尾指標加1取模
}

上述程式中的判斷佇列是否已滿的操作我們後面再說,這裡的模運算操作其實就是將無限的整數域對映到有限的整數集合{0,1,2,.....,MaxSize-1} 上,將儲存空間在邏輯上變成了“環狀”。

結合迴圈佇列,我們可以知道,佇列已滿的條件:隊尾指標的再下一個位置是對頭,即 (Q.rear+1)%MaxSize==Q.front)

3,出隊操作

只能讓隊頭元素出隊。刪除一個隊頭元素,並用x 返回

bool DeQueue(SqQueue &Q,ElemType &x){
    if(Q.rear==Q.front)	//判斷隊空
        return false;
    x=Q.data[Q.front];
    Q.front=(Q.front+1)%MaxSize;	//隊頭指標後移
    return true;
}

那如何知道佇列元素的個數呢?這裡有一個公式,希望我們記住:(rear+MaxSize-front)%MaxSize ,自己可以驗證一下這個公式的合法性。

三,佇列的鏈式實現

1,佇列的鏈式實現

程式碼如下:

typedef struct LinkNode{	//鏈式佇列結點
    Elemtype data;
    struct LinkNode *next;
}LinkNode;

typedef struct{					//鏈式佇列
    LinkNode *front,*rear;		//佇列的隊頭和隊尾指標
}LinkQueue;

初始化佇列(帶頭結點)的實現:

void InitQueue(LinkQueue &Q){
    //初始時 front、rear都指向頭結點
    Q.front=Q.rear=(LinkNode*)malloc(sizeof(LinkNode));
    Q.front->next=NULL;
}
void testLinkQueue(){
    LinkQueue Q;	//宣告一個佇列
    InitQueue(Q);	//初始化佇列
    //... 後續操作...
}

示意圖如下:

由此可知,判斷一個佇列是否為空的程式碼實現如下:

bool IsEmpty(LinkQueue Q){
    if(Q.front==Q.rear)
        return true;
    else
        return false;
}

不帶頭結點的佇列初始化:

void InitQueue(LinkQueue &Q){
    //初始時,front、rear 都指向NULL
    Q.front=NULL;
    Q.rear=NULL;
}

//此時判斷佇列是否為空即隊頭指標為NULL
bool IsEmpty(LinkQueue Q){
    if(Q.front==NULL)
        return true;
    else
        return false;
}

2,入隊

帶頭結點 的新元素入隊操作只需要記得只能從隊尾新增元素即可,邏輯也比較簡單,直接見如下程式碼片段:

void EnQueue(LinkQueue &Q,ElemType x){
    LinkNode *s=(LinkNode *)mallloc(sizeof(LinkNode));
    s->data=x;
    s->next=NULL;
    Q.rear->next=s;	//新結點插入到rear 之後
    Q.rear=s;	//修改表尾指標
}

不帶頭結點 新元素入隊,由於開始front、rear 指標都是指向 NULL 的,所以需要對其進行修改操作:

void EnQueue(LinkQueue &Q,ElemType x){
    LinkNode *s=(LinkNode *)mallloc(sizeof(LinkNode));
    s->data=x;
    s->next=NULL;
    if(Q.front==NULL){	//在空佇列中插入第一個元素,需要特殊處理
        Q.front=s;		//修改隊頭隊尾指標
        Q.rear=s;
    }else{
        Q.rear->next=s;	//新結點插入到 rear 結點之後
        Q.rear=s;	//修改 rear 指標
    }
}

3,出隊

帶頭結點隊頭元素出隊操作,邏輯比較簡單,直接見程式碼:

bool DeQueue(LinkQueue &Q,ElemType &x){
    if(Q.front==Q.rear)
        return false;	//空隊
    LinkNode *p=Q.front->next;
    x=p->data;	//用變數x 返回隊頭元素
    Q.front->next=p->next;	//修改頭結點的next 指標
    if(Q.rear==p)	//此次是最後一個結點出隊
        Q.rear=Q.front;	//修改rear 指標
    free(p);	//釋放結點空間
    return true;
}

不帶頭結點 隊頭元素出隊,程式碼如下:

bool DeQueue(LinkQueue &Q,ElemType &x){
    if(Q.front==NULL)
        return false;	//空隊
    LinkNode *p=Q.front;	//p指向此次出隊的結點
    x=p->data;	//用變數x 返回隊頭元素
    Q.front=p->next;	//修改front 指標
    if(Q.rear==p){	//此次是最後一個結點出隊
        Q.front=NULL;	//front指向NULL
        Q.rear=NULL;	//rear指向NULL
    }
    free(p);
    return true;
}

最後,我們一定要多動手、勤思考,這樣我們才有可能攻陷資料結構!