1. 程式人生 > >再談資料結構(一):棧和佇列

再談資料結構(一):棧和佇列

1 - 前言

棧和佇列是兩種非常常用的兩種資料結構,它們的邏輯結構是線性的,儲存結構有順序儲存和鏈式儲存。在平時的學習中,感覺雖然棧和佇列的概念十分容易理解,但是對於這兩種資料結構的靈活運用及程式碼實現還是比較生疏。需要結合實際問題來熟練佇列和棧的操作。

2 - 例題分析

2.1 - 鐵軌(Rails, ACM/ICPC CERC 1997, UVa 514)

某城市有一個火車站,鐵軌鋪設如圖6-1所示。有n節車廂從A方向駛入車站,按進站順
序編號為1~n。你的任務是判斷是否能讓它們按照某種特定的順序進入B方向的鐵軌並駛出
車站。例如,出棧順序(5 4 1 2 3)是不可能的,但(5 4 3 2 1)是可能的。
Sample Input
5
1 2 3 4 5
5 4 1 2 3
0
6
6 5 4 3 2 1
0
0
Sample Output
Yes
No

Yes
在這裡插入圖片描述

為了重組車廂,你可以藉助中轉站C。這是一個可以停放任意多節車廂的車站,但由於
末端封頂,駛入C的車廂必須按照相反的順序駛出C。對於每個車廂,一旦從A移入C,就不
能再回到A了;一旦從C移入B,就不能回到C了。換句話說,在任意時刻,只有兩種選擇:
A→C和C→B。

涉及知識及演算法:

把station看做是一個棧,按1~N的順序,先入棧一個車廂,如果棧頂的車廂編號和所給出的編號一樣。那麼火車就出棧,迴圈直到棧裡邊所有滿足出站順序的火車都出站或棧空,否則就入棧。最後判斷所有火車是否都出站了。若都出站,輸出Yes,否則輸出No。

#include<iostream>
#include<stack> //使用棧所需要引用的標頭檔案
#include<cstdio>    //讓c++可以使用c的輸入輸出的操作。
#include<algorithm>
#include<string>
using namespace std;

const int MAXN = 1010;

int n,target[MAXN];

int main()
{
    while(scanf("%d",&n)==1){
        stack<int> s;   //定義一個棧為s
        int A = 1,B = 1;
        for(int i=1;i<=n;i++)
            scanf("%d",&target[i]);
        int ok = 1;
        while(B<=n){
            if(A == target[B]){A++;B++;}    //如果入棧元素A與target元素一致則直接輸出,A+1,B+1
            else if(!s.empty()&&s.top()==target[B]){s.pop();B++;}   //如果棧s不為空且棧頂元素與target元素一致,則輸出。B++
            else if(A<=n) s.push(A++);  //如果入棧元素A與target元素不一直且棧頂元素與target元素不一致,入棧,A++
            else {ok = 0;break;}    //迴圈到最後還有元素剩餘則ok=0;
        }
        printf("%s\n",ok? "yes":"no");
    }
    return 0;
}


在C++中,可以使用關於棧的標頭檔案:

#include<stack>
並且支援相應的棧的基本操作

定義stack 物件的示例程式碼如下:

stack<int> s1;
stack<int> s2;
stack 的基本操作有:

  • 入棧,如例:s.push(x);
  • 出棧,如例:s.pop();注意,出棧操作只是刪除棧頂元素,並不返回該元素。
  • 訪問棧頂,如例:s.top()
  • 判斷棧空,如例:s.empty(),當棧空時,返回true。
  • 訪問棧中的元素個數,如例:s.size()。

具體基本操作實現程式碼:

讓我們用更加底層的程式碼來實現棧的這些基本操作(要在C++的編譯環境中實現此程式碼,因為Status InitStack(SqStack &S);使用了c++的引用語法)

  • 順序棧:
#include <stdio.h>
#include <stdlib.h>
//--------------------棧的順序儲存結構--------------------
#define STACK_INIT_SIZE 100
#define STACKINCREACE 10
typedef char Elemtype;//在標頭檔案中說明
typedef int Status;
typedef struct{
    Elemtype *base;
    Elemtype *top;
    int stacksize;
}SqStack;
//----------------------函式宣告部分----------------------
Status InitStack(SqStack &S);
Status Push(SqStack &S,Elemtype e);
Status Pop(SqStack &S,Elemtype &e);
Status GetTop(SqStack S,Elemtype &e);
Status StackEmpty(SqStack S);
Status ClearStack(SqStack &S);
Status DestroyStack(SqStack &S);






//--------------------主函式入口--------------------
int  main(){
    SqStack s1;
    char getElem=NULL;
    char popElem=NULL;
    int empty_Flag=NULL;
    InitStack(s1);
    Push(s1,'a');
    Push(s1,'b');   //壓棧2個元素,當前棧中的元素為a,b
    GetTop(s1,getElem);
    printf("棧頂元素是:%c\n",getElem);
    Pop(s1,popElem);
    printf("出棧的元素是:%c\n",popElem);
    GetTop(s1,getElem);
    printf("棧頂元素是:%c\n",getElem);

    if(StackEmpty(s1))
    {
        printf("空棧");

    }
    else printf("不為空棧\n");
    Pop(s1,popElem);
    printf("出棧的元素是:%c\n",popElem);
    if(StackEmpty(s1))
    {
        printf("空棧");

    }
    else printf("不為空棧\n");
    ClearStack(s1);
    DestroyStack(s1);
    return 0;
//    printf("棧的長度:%d\n",StackLength(stack));


}
//------------------棧的初始化函式------------------
Status InitStack(SqStack &S){
    S.base = (Elemtype *)malloc(STACK_INIT_SIZE*sizeof(Elemtype));
    //2016.4.17編譯時報錯:missing “)”before ;
    //錯誤原因:由於標頭檔案中定義常量時後面加了;導致編譯的時候將STACK_INIT_SIZE用100;代替,
    //使得該函式提前結束語100處,malloc後的“(”找不到匹配的“)”而報錯
    //解決方法:去掉常量定義中的;
    if(!S.base){
        return false;
    }
    S.stacksize=STACK_INIT_SIZE;
    S.top=S.base;
    return true;
}
//---------------------入棧函式---------------------
Status Push(SqStack &S,Elemtype e){
//判斷是否溢位
    if(S.top-S.base>=S.stacksize){
        S.base=(Elemtype *)realloc(S.base,(S.stacksize+STACKINCREACE)*sizeof(Elemtype));
        if(!S.base){
            return false;
        }
        S.top=S.base+S.stacksize;//注意因為這裡的棧底指標的改變,導致棧頂指標隨之改變
        S.stacksize+=STACKINCREACE;
    }
//壓棧部分
    *S.top=e;
    S.top++;
    return true;
}
//---------------------出棧函式---------------------
Status Pop(SqStack &S,Elemtype &e){
//非法判斷
    if(S.base==S.top){
        return false;
    }
    S.top--;    //注意這裡因為top指向棧中當前元素的上一個空間,所以要先將其位置減一
    e=*S.top;
    return true;
    }
//-------------------檢視棧頂元素-------------------
Status GetTop(SqStack S,Elemtype &e){
    if(S.base==S.top ){
        return false;
    }
    e=*(S.top-1);
    return true;
}
//------------------判斷棧是否為空------------------
Status StackEmpty(SqStack S){
    if(S.base==S.top){
        return true;
    }
    return false;
}


//--------------------清空棧------------------------
Status ClearStack(SqStack &S){//清空棧的時候不用將stacksize重新賦值
    S.top=S.base;             //因為經過realloc函式重新分配空間後(stacksize大小改變),
    return true;            //S.base指向的是一段stacksize大小的連續儲存空間
                            //即使將他重置,剩餘的空間也是閒置的(順序表裡也只是經當前長度置為0)
    }
//--------------------銷燬棧------------------------
Status DestroyStack(SqStack &S){
    free(S.base);
    free(S.top);
    S.base=NULL;
    return true;
}

  • 鏈式棧的基本操作
#include<stdio.h>
#include<stdlib.h>
#define OK 1
#define ERROR 0
typedef int SElemType ;

typedef struct  SNode{

   SElemType data;
   struct SNode *next;

}StackNode,*LinkStack;

//初始化

void Init_Stack_L(LinkStack L){

	L=(StackNode*)malloc(sizeof(StackNode));
	L->next=NULL;
}

//Push:入棧
int Push(LinkStack &L,SElemType e){

	LinkStack p;
	p=(StackNode*)malloc(sizeof(StackNode));
	if(p==NULL)  return ERROR;
	p->data=e;
	p->next=L;
	L=p;
    return OK;
}
int Pop(LinkStack &L,SElemType &e)
{
	LinkStack p=L;
	L=p->next;
	e=p->data;
	free(p);
	return OK;

}
/*void Print(LinkStack &L) {
	int e;
	LinkStack p=L;
    while(p)
	{
		printf("%3d",p->data);
		p=p->next;
	}
	printf("\n");
}*/
int main()
{
	LinkStack L;
	int e;
	Init_Stack_L(L);
	Push(L,1);
	Push(L,2);
	Push(L,3);
	Pop(L,e);
	printf("%d\n",e);
	Pop(L,e);
	printf("%d\n",e);
	return 0;


}

2-2 矩陣鏈乘(Matrix Chain Multiplication, UVa 442)

輸入n個矩陣的維度和一些矩陣鏈乘表示式,輸出乘法的次數。如果乘法無法進行,輸
出error。假定A是mn矩陣,B是np矩陣,那麼AB是mp矩陣,乘法次數為mnp。如果A的
列數不等於B的行數,則乘法無法進行。
例如,A是50
10的,B是1020的,C是205的,則(A(BC))的乘法次數為10205(BC的
乘法次數)+ 50105((A(BC))的乘法次數)= 3500。

Sample Input

9
A 50 10
B 10 20
C 20 5
D 30 35
E 35 15
F 15 5
G 5 10
H 10 20
I 20 25
A
B
C
(AA)
(AB)
(AC)
(A(BC))
((AB)C)
(((((DE)F)G)H)I)
(D(E(F(G(HI)))))
((D(EF))((GH)I))

Sample Output

0
0
0
error
10000
error
3500
15000
40500
47500
15125

涉及知識及演算法:

棧對於表示式求值有著特殊的作用。
本題的關鍵是解析表示式。本題的表示式比較簡單,可以用一個棧來完成:遇到字母時
入棧,遇到右括號時出棧並計算,然後結果入棧。因為輸入保證合法,括號無須入棧。

#include<iostream>
#include<stack> //使用棧所需要引用的標頭檔案
#include<cstdio>    //讓c++可以使用c的輸入輸出的操作。
#include<algorithm>
#include<string>
using namespace std;

struct Matrix{
    int a,b;
    Matrix(int a=0, int b=0):a(a),b(b){}    //賦預設值a=0,b=0;

}m[26]; //構造一個矩陣的結構體
stack<Matrix>s; //初始化一個結構體的棧
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++){
        string name;
        cin>> name;
        int k = name[0]-'A';
        cin >> m[k].a>>m[k].b;
    }
    string expr;
    while(cin>>expr)
    {
        int len = expr.length();
        bool error = false;
        int ans = 0;
        for(int i = 0; i<len;i++){
            if(isalpha(expr[i])) s.push(m[expr[i]-'A']);
            else if(expr[i]==')'){
                Matrix m2 = s.top();s.pop();
                Matrix m1 = s.top();s.pop();
                if(m1.b!=m2.a){
                    error = true;
                    break;
                }
                ans +=m1.a*m1.b*m2.b;
                s.push(Matrix(m1.a,m2.b));

            }
        }
        if(error) printf("error\n");
        else printf("%d\n",ans);
    }
    return 0;
}

2.3 -  團體佇列(Team Queue,UVa540)

有t個團隊的人正在排一個長隊。每次新來一個人時,如果他有隊友在排隊,那麼這個
新人會插隊到最後一個隊友的身後。如果沒有任何一個隊友排隊,則他會排到長隊的隊尾。
輸入每個團隊中所有隊員的編號,要求支援如下3種指令(前兩種指令可以穿插進
行)。

  • ENQUEUEx:編號為x的人進入長隊。
  • DEQUEUE:長隊的隊首出隊。
  • STOP:停止模擬。
    對於每個DEQUEUE指令,輸出出隊的人的編號。

Sample Input

2
3 101 102 103
3 201 202 203
ENQUEUE 101
ENQUEUE 201
ENQUEUE
102
ENQUEUE 202
ENQUEUE 103
ENQUEUE
203
DEQUEUE
DEQUEUE
DEQUEUE
DEQUEUE
DEQUEUE
DEQUEUE
STOP
2
5
259001 259002 259003 259004 259005
6 260001 260002 260003 260004 260005
260006
ENQUEUE 259001
ENQUEUE 260001
ENQUEUE 259002
ENQUEUE
259003
ENQUEUE 259004
ENQUEUE 259005
DEQUEUE
DEQUEUE
ENQUEUE
260002
ENQUEUE
260003
DEQUEUE
DEQUEUE
DEQUEUE
DEQUEUE
STOP
0

Sample Output

Scenario
#1
101
102
103
201
202
203

Scenario
#2
259001
259002
259003
259004
259005
260001

【分析】
本題有兩個佇列:每個團隊有一個佇列,而團隊整體又形成一個佇列。例如,有3個團
隊1,2,3,隊員集合分別為{101,102,103,104}、{201,202}和{301,302,303},當前
長隊為{301,303,103,101,102,201},則3個團隊的佇列分別為{103,101,102}、
{201}和{301,303},團隊整體的佇列為{3,1,2}。程式碼如下:

#include<cstdio>
#include<queue>
#include<map>
using namespace std;
const int maxt = 1000 + 10;
int main()
{
    int t, kase = 0;
    while(scanf("%d", &t) == 1 && t)
    {
        printf("Scenario #%d\n", ++kase);
//記錄所有人的團隊編號
        map<int, int> team; //team[x]表示編號為x的人所在的團隊編號
        for(int i = 0; i < t; i++)
        {
            int n, x;
            scanf("%d", &n);
            while(n--)
            {
                scanf("%d", &x);
                team[x] = i;
            }
        }
//模擬
        queue<int> q, q2[maxt]; //q是團隊的佇列,而q2[i]是團隊i成員的佇列
        for(;;)
        {
            int x;
            char cmd[10];
            scanf("%s", cmd);
            if(cmd[0] == 'S') break;
            else if(cmd[0] == 'D')
            {
                int t = q.front();
                printf("%d\n", q2[t].front());
                q2[t].pop();
                if(q2[t].empty()) q.pop(); //團體t全體出佇列
            }
            else if(cmd[0] == 'E')
            {
                scanf("%d", &x);
                int t = team[x];
                if(q2[t].empty()) q.push(t); //團隊t進入佇列
                q2[t].push(x);
            }
        }
        printf("\n");
    }
    return 0;
}

在c++中使用佇列的標頭檔案

#include<queue>

相關佇列的基本操作:

  • push()會將一個元素置入queue中。

  • front()會返回queue內的第一個元素(也就是第一個被置入的元素)。

  • back()會返回queue中最後一個元素(也就是最後被插入的元素)。

  • top()取隊首元素(但不刪除)。

  • pop()會從queue中移除一個元素。
    注意:pop()雖然會移除下一個元素,但是並不返回它,front()和back()返回下一個元素但並不移除該元素。

佇列的基本操作底層具體實現

  • 順序佇列
#include "stdio.h"
#include "stdlib.h"
#include "io.h"
#include "math.h"
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 11 //初始容量

typedef int Status;
typedef int QElemType;//定義資料型別

//迴圈佇列的順序儲存結構
typedef struct{
	QElemType data[MAXSIZE];
	int front; //頭指標
	int rear;//尾指標,佇列非空時,指向隊尾元素的下一個位置
}SqQueue;

Status visit(QElemType item){
	printf("%d",item);
	return OK;
}

//初始化空佇列
Status InitQueue(SqQueue *sQ){
	sQ->front =0;
	sQ->rear =0;
	return OK;
}

//將佇列清空
Status ClearQueue(SqQueue *Q){
	Q->front = Q->rear =0;
	return OK;
}

//判斷佇列是否為null
Status QueueEmpty(SqQueue Q){
	if(Q.front == Q.rear)
		return TRUE;
	else
		return FALSE;
}

//返回佇列中的元素個數
int QueueLength(SqQueue Q){
	return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}

//返回隊頭元素
Status GetHead(SqQueue Q, QElemType *e){
	if(Q.front == Q.rear)//是否為空佇列
		return ERROR;
	*e = Q.data[Q.front];
	return OK;
}

//在隊尾插入元素
Status EnQueue(SqQueue *Q, QElemType e){
	if((Q->rear+1)%MAXSIZE == Q->front)//佇列已滿
		return ERROR;

	Q->data[Q->rear] =e;//插入隊尾
	Q->rear = (Q->rear +1)%MAXSIZE;//尾部指標後移,如果到最後則轉到頭部
	return OK;
}

//元素出隊
Status DeQueue(SqQueue *Q, QElemType *e){
	if(Q->front == Q->rear)//佇列空
		return ERROR;
	*e = Q->data[Q->front];//返回隊頭元素
	Q->front = (Q->front+1)%MAXSIZE;//隊頭指標後移,如到最後轉到頭部
	return OK;
}

//遍歷佇列元素
Status QueueTraverse(SqQueue Q){
	int i = Q.front;
	while((i+Q.front) != Q.rear){
		visit(Q.data[i]);
		i=(i+1)%MAXSIZE;
	}
	printf("\n");
	return OK;
}

int main(){

	Status j;
	int i=0,l;
	QElemType d;
	SqQueue Q;
	InitQueue(&Q);

	//入隊10個元素
	for(int i =0;i< MAXSIZE-1; i++){
		EnQueue(&Q,i);
	}
	QueueTraverse(Q);

	printf("依次出隊:");
	for(l=1;l<=MAXSIZE;l++)
	{
		DeQueue(&Q,&d);
		printf("d= %d,",d);
	}

	return 0;
}