数据结构-栈和队列
栈
栈是一种特殊的线性表。它的结构非常类似于日常生活中的瓶子,只有一端开口,而插入和删除的操作只能在瓶子的瓶口处进行,这种一端开口是它的特殊性。这种特殊性也决定了栈中的数据操作顺序是先入后出的,也就是说先进入栈中的数据,最后才能出去。
1.1 栈的定义
栈是一种特殊的线性表。其特殊性在于限定插入和删除数据元素的操作只能在线性表的一端进行。
结论:后进先出(Last In First Out),简称为LIFO线性表。
栈的基本运算:
栈的基本运算有六种:
构造空栈:InitStack(S)、
判栈空: StackEmpty(S)、
判栈满: StackFull(S)、
进栈: Push(S,x)、可形象地理解为压入,这时栈中会多一个元素
退栈: Pop(S) 、 可形象地理解为弹出,弹出后栈中就无此元素了。
取栈顶元素:StackTop(S),不同与弹出,只是使用栈顶元素的值,该元素仍在栈顶不会改变。
Note:栈是一种先进后出的线性表,但是并不意味着不会出现先进先出的情况。如:当1、2、3、4顺次入栈时,出栈的顺序也可能是1、2、3、4,这种情况发生条件是每个数入栈后出栈,然后下一个数入栈。即:1入栈后出栈,然后是2入栈后出栈,其次是3入栈后出栈,最后是4入栈后出栈,这样顺序就变成了1、2、3、4。
由于栈也是线性表,因此线性表的存储结构对栈也适用,通常栈有顺序栈和链栈两种存储结构,这两种存储结构的不同,则使得实现栈的基本运算的算法也有所不同。
栈的溢出问题解析:
在顺序栈中有”上溢”和”下溢”的概念。顺序栈好比一个盒子,我们在里头放了一叠书,当我们要用书的话只能从第一本开始拿,那么当我们把书本放到这个栈中超过盒子的顶部时就放不下了,这时就是”上溢”,”上溢”也就是栈顶指针指出栈的外面,显然是出错了。反之,当栈中已没有书时,我们再去拿,看看没书,把盒子拎起来看看盒底,还是没有,这就是”下溢”。”下溢”本身可以表示栈为空栈,因此可以用它来作为控制转移的条件。而对于链栈则没有上溢的限制,它就象是一条一头固定的链子,可以在活动的一头自由地增加链环(结点)而不会溢出,链栈不需要在头部附加头结点,因为栈都是在头部进行操作的,如果加了头结点,等于要在头结点之后的结点进行操作,反而使算法更复杂,所以只要有链表的头指针就可以了。
1.2 顺序栈
在顺序栈中数据是按照顺序进行存储的。需要我们了解的是顺序栈中有上溢和下溢的概念。
溢出简单定义:
上溢:栈顶指针指出栈的外面。我们把顺序栈看做一个盒子,那么当我们把数据放到这个栈中超过盒子的顶部时就放不下了,这时指针指向了栈的外面,这种现象我们称为上溢。
下溢:从空栈中取数据。当栈中没有数据时,我们再去取数据,看看没数据,把盒子拎起来看看盒底,还是没有,这就是下溢。
顺序栈的类型定义为:
typedef struct
{
int data[maxsize];
int top;
}SqStack;
1.2.1 顺序栈的基本运算
(1) 初始化栈
void initStack(SqStack &st)
{
st.top = -1;
}
(2)判栈空
int isEmpty(SqStack st)
{
if(st.top==-1)
{
return 1;
}
else
return 0;
}
(3)判栈满
int isFull(SqStack st) {
return st.top==maxsize-1;
}
(4)进栈
int Push(SqStack &st,int x)
{
if(st.top==maxsize-1) //这里要注意,栈满不能进栈
return 0;
++(st.top); //先移动指针,再进栈
st.data[st.top]=x;
return 1;
}
(5)出栈
int Pop(SqStack &st,int &x)
{
if(st.top==-1) //这里要注意,栈空不能出栈
return 0;
x=st.data[st.top];//先出栈栈,先移动指针
--(st.top);
return 1;
}
(6)取栈顶元素
int stacktop(SqStack &st)
{
if(st.top==-1)
return 0;
return st.data[st.top];
}
代码实现如下:
#include <stdio.h>
#define maxsize 5
typedef struct
{
int data[maxsize];
int top;
}SqStack;
void initStack(SqStack &st)
{
st.top = -1;
}
int isEmpty(SqStack st)
{
return st.top==-1;
}
int isFull(SqStack st) {
return st.top==maxsize-1;
}
int Push(SqStack &st,int x)
{
if(st.top==maxsize-1) //这里要注意,栈满不能进栈
return 0;
++(st.top); //先移动指针,再进栈
st.data[st.top]=x;
return 1;
}
int Pop(SqStack &st,int &x)
{
if(st.top==-1) //这里要注意,栈空不能出栈
return 0;
x=st.data[st.top];//先出栈栈,先移动指针
--(st.top);
return 1;
}
int stacktop(SqStack &st)
{
if(st.top==-1)
return 0;
return st.data[st.top];
}
int StackLength(SqStack st)
{ /* 返回S的元素个数,即栈的长度 */
return st.top+1;
}
void show(SqStack st)
{
int i;
printf("输出栈中元素:\n ");
for(i=0;i<=st.top;i++)
printf("%d ",st.data[st.top]);
printf("\n ");
}
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char** argv) {
int a,b,c;
SqStack st;
initStack(st);
a= isEmpty(st);
printf("是否为空 %d (1:为空 0:不为空)\n",a);
Push(st,2);
a= isEmpty(st);
c=StackLength(st);
show(st);
printf("是否为空 %d 栈长度为%d (1:为空 0:不为空)\n",a,c);
Pop(st,b);
a= isEmpty(st);
printf("是否为空 %d 出栈元素为%d (1:为空 0:不为空)\n",a,b);
return 0;
}
1.3 链栈
若是栈中元素的数目变化范围较大或不清楚栈元素的数目,就应该考虑使用链式存储结构。人们将用链式存储结构表示的栈称作”链栈”。链栈通常用一个无头结点的单链表表示。如图所示:
和顺序栈不同,它没有固定的结构,也不会出现数据的上溢,它就像是一条一头固定的链子,可以在活动的一头自由地增加链环(结点)而不会溢出。
1.3.1 基本运算
(1) 建栈
void initstack(linkstack *s)
{
s->top=NULL;
}
(2)判栈空
int stackempty (linkstack *s)
{
return s->top==NULL;
}
(3) 进栈
void push(linkstack *s,datatype x)
{
stacknode *p=(stacknode *)malloc(sizeof(stacknode));
p->data=x;
p->next=s->top;
s->top=p;
}
(4) 出栈
int pop(linksatck *s)
{
int x;
stacknode *p=s->top;
if(stackempty(s))
error(“stack underflow”);
x=p->data;
s->top=p->next;
free(p);
return x;
}
(5) 取栈顶元素
int stacktop(linkstack *s)
{
if(stackempty(s))
error(“stack is empty”);
return s->top->data;
}
代码实现如下:
#include <stdio.h.>
#include <malloc.h>
typedef struct LNode
{
int data;
struct LNode *next;
}LNode;
void InitStackL(LNode *&lst){
lst=(LNode *)malloc(sizeof(LNode));//制造一个头结点
lst->next=NULL;
}
int isEmpty(LNode *lst){
if(lst->next==NULL)
return 1;
else
return 0;
}
void push(LNode *lst,int x)
{
LNode *p;
p=(LNode *)malloc(sizeof(LNode));
p->next=NULL;
p->data=x;
p->next=lst->next;
lst->next=p;
}
int pop(LNode *lst,int &x)
{
LNode *p;
if(lst->next==NULL)
return 0;
//以下就是单链表的删除操作
p=lst->next;
x=p->data;
lst->next=p->next;
free(p);
return 0;
}
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char** argv) {
LNode *lst;
int a,b=3,c;
InitStackL(lst);
a=isEmpty(lst);
printf("栈是否为空 :%d (1:空 0:不为空) \n",a);
push(lst , b);
a=isEmpty(lst);
printf("入栈 :%d \n",b);
printf("栈是否为空 :%d (1:空 0:不为空) \n",a);
pop(lst,c);
a=isEmpty(lst);
printf("出栈 :%d \n",c);
printf("栈是否为空 :%d (1:空 0:不为空) \n",a);
return 0;
}
1.4 栈的应用
1) 数制转换
2)语法词法分析
3)表达式求值等
1.5 栈的递归和实现
汉诺塔的问题:
解决:
1)如果有一个盘子,直接从X移到Z即可。
2)如果有n个盘子要从X移到Z,Y作为辅助。问题可以转化为,先将上面n-1个从X移动到Y,Z作为辅助,然后将第n个从X移动到Z,最后将剩余的n-1个从Y移动到Z,X作为辅助。
完整实现代码,包括栈的实现:
#include<iostream>
#include<stack>
using namespace std;
//把塔x顶部的n个碟子移到塔y
//用塔Z做中转地
void towerOfHanoi(int n, int x, int y, int z)
{
if (n > 0)
{
towerOfHanoi(n - 1, x, z, y);
cout << "Move top disk for tower " << x << " to top of tower " << y << endl;
towerOfHanoi(n - 1, z, y, x);
}
return;
}
//用栈实现汉诺塔的问题
template<class T>
void changeLength1D(T*&a, int oldLength, int newLength)
{//更改长度
if (newLength < 0)
{
throw("wrong ");
}
T* temp = new T[newLength];
int number = min(oldLength, newLength);
copy(a, a + number, temp);
delete[] a;
a = temp;
}
template<class T>
class arrayStack :public stack<T>
{
public:
arrayStack(int initialCapacity = 10);
~arrayStack() { delete[] stack; };
bool empty()const { return stackTop == -1; }
int size()const { return stackTop + 1; }
T& top()
{
if (stackTop == -1)
{
throw ("wrong");
}
return stack[stackTop];
}
void pop()
{
if (stackTop == -1)
{
throw("empty");
}
stack[stackTop--].~T();
}
void push(const T& theElement);
private:
int stackTop;
int arrayLength;
T* stack;
};
template<class T>
arrayStack<T>::arrayStack(int initialCapacity)
{
//structure funs
if (initialCapacity < 1)
{
// ostringstream s;
// s << "Initial capacity = " << initialCapacity << " Must be > 0 ";
// throw(s.str());
}
arrayLength = initialCapacity;
stack = new T[arrayLength];
stackTop = -1;
}
template<class T>
void arrayStack<T>::push(const T& theElement)
{
if (stackTop == arrayLength - 1)
{
changeLength1D(stack, arrayLength, 2 * arrayLength);
arrayLength *= 2;
}
stack[++stackTop] = theElement;
}
arrayStack<int>tower[4];
void moveAndShow(int, int, int, int);
void showState()
{
int d = tower[3].size();
for (int i = 0; i < d; i++)
cout << tower[3].top() << endl;
}
void towerOfHanoi(int n)
{
for (int d = n; d > 0; d--)
{
tower[1].push(d);
}
moveAndShow(n, 1, 2, 3);
}
void moveAndShow(int n, int x, int y, int z)
{//把塔x顶部的n个碟子移到塔y,显示移动后的布局
//用塔Z做中转站
if (n > 0)
{
moveAndShow(n - 1, x, z, y);
int d = tower[x].top();
tower[x].pop();
tower[y].push(d);
showState();
moveAndShow(n - 1, z, y, x);
}
}
int main()
{
int a = 4;
towerOfHanoi(4);//栈实现
cout << endl;
towerOfHanoi(4, 1, 2, 3);//递归函数实现
return 0;
}
2、队列
2.1 队列定义
队列(Queue)也是一种运算受限的线性表,它的运算限制与栈不同,是两头都有限制,插入只能在表的一端进行(只进不出),而删除只能在表的另一端进行(只出不进),允许删除的一端称为队尾(rear),允许插入的一端称为队头 (Front),队列的操作原则是先进先出的,所以队列又称作FIFO表(First In First Out)
队列的基本运算也有六种:
置空队 :InitQueue(Q)
判队空: QueueEmpty(Q)
判队满: QueueFull(Q)
入队 : EnQueue(Q,x)
出队 : DeQueue(Q)
取队头元素: QueueFront(Q),不同与出队,队头元素仍然保留。
队列也有顺序存储和链式存储两种存储结构,前者称顺序队列,后者为链队
队列也是一种线性结构,所以队列也有顺序存储和链式存储两种存储结构,前者称顺序队列,后者为链队。
2.2 顺序队列
与顺序栈类似,顺序队列也有上溢和下溢的情况,它们产生的原因和顺序栈类似,这里就不在细说。由于队列操作的特殊性(指针移动,元素不同)又出现了假上溢的情况。
Note:在现实生活中我们随处可见排队的情况,当队列中的人离开队列后,后面的人会上来补上,当新来人排队时是在队列中的尾部进行排列的。我们现在说的队列和生活中的排队最大的区别在于前者是指针在移动,后者是元素在移动。
Note:在队列中每插入一个元素,队列的队尾指针会向后移动一个位置;类似的在队列中每删除一个元素,队头的指针都会向接近队尾的方向移动一个位置。
2.2.1 何为假上溢
上图中的(c)操作,如果我们继续向队列中插入数据,尾指针就要跑到向量空间外面去了,尽管这时整个向量空间是空的,队列也是空的,却产生了“上溢”现象,这就是假上溢。
顺序队列的类型定义为:
typedef struct
{
int data[maxsize];
int front;
int rear;
}SqQueue;
2.1.2 队列的基本运算
(1) 构造空队列
void InitQueue(SqQueue &qu)
{
qu.front =qu.rear=0;//队首队尾指针重合,并且指向0
}
(2) 判队空
int QueueEmpty(SqQueue qu){
if(qu.front==qu.rear) //不论队首队尾指针指向数组中的那个位置
return 1; //只要两者重合,即为对空
else
return 0;
}
(3) 判队满
int QueueFull(SqQueue qu){
if((qu.rear+1)%maxsize==qu.front) //不队满的判断条件,队满侧不能入队
return 1;
else
return 0;
}
(4) 入队
int enQueue(SqQueue &qu,int x)
{
if((qu.rear+1)%maxsize==qu.front)//队满的判断条件,队满侧不能入队
return 0;
qu.rear = (qu.rear+1)%maxsize; //若队不空,则先移动指针
qu.data[qu.rear]=x; //再存入元素
return 1;
}
(5) 出队
int deQueue(SqQueue &qu,int &x)
{
if(qu.front==qu.rear) //若队空,则不能出队
return 0;
qu.front = (qu.front+1)%maxsize;//若队不空,则先移动指针
x=qu.data[qu.front]; //再取出元素
return 1;
}
(6) 取队头元素
int Queuefront(SqQueue &qu)
{
if(qu.front==qu.rear)
return 0;
return qu.data[qu.front];
}
代码实现如下:
#include <stdio.h>
#define maxsize 10
typedef struct
{
int data[maxsize];
int front;
int rear;
}SqQueue;
void InitQueue(SqQueue &qu)
{
qu.front =qu.rear=0;//队首队尾指针重合,并且指向0
}
int QueueEmpty(SqQueue qu){
if(qu.front==qu.rear) //不论队首队尾指针指向数组中的那个位置
return 1; //只要两者重合,即为对空
else
return 0;
}
int QueueFull(SqQueue qu){
if((qu.rear+1)%maxsize==qu.front) //不队满的判断条件,队满侧不能入队
return 1;
else
return 0;
}
int enQueue(SqQueue &qu,int x)
{
if((qu.rear+1)%maxsize==qu.front)//队满的判断条件,队满侧不能入队
return 0;
qu.rear = (qu.rear+1)%maxsize; //若队不空,则先移动指针
qu.data[qu.rear]=x; //再存入元素
return 1;
}
int deQueue(SqQueue &qu,int &x)
{
if(qu.front==qu.rear) //若队空,则不能出队
return 0;
qu.front = (qu.front+1)%maxsize;//若队不空,则先移动指针
x=qu.data[qu.front]; //再取出元素
return 1;
}
int Queuefront(SqQueue &qu)
{
if(qu.front==qu.rear)
return 0;
return qu.data[qu.front];
}
void show (SqQueue qu)
{
int i;
printf("队列元素:front to rear:\n");
for(i=qu.front+1;i<=qu.rear;i++)
printf("%d ",qu.data[i]);
printf("\n");
}
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char** argv) {
SqQueue qu;
int a,b=1,c=2,k;
InitQueue(qu);
a=QueueEmpty(qu);
printf("队是否为空:%d (1:空 0:不空)\n",a);
enQueue(qu,b);
printf("%d 入队\n",b);
enQueue(qu,c);
printf("%d 入队\n",c);
show (qu);
a=QueueEmpty(qu);
printf("队是否为空:%d (1:空 0:不空)\n",a);
deQueue(qu,k);
printf("%d 出队\n",k);
show (qu);
return 0;
}
2.3 循环队列
为了克服线性队列造成的空间浪费,我们引入循环向量的概念,它就好像是把向量空间弯起来,形成一个头尾相接的环形,这样当队列的头尾指针移动到队列的尾部时,再进行入队的操作,就使指针指向队列的队头,也就是从头开始。这时的队列就称循环队列。
通常我们所使用的大都是循环队列。但由于循环的原因,我们就不能当头尾指针重叠在一起来判断队列为空还是满的情况,为了避免这种情况,我们又引入了边界条件的概念。
解决方法有:
① 另设一个布尔变量以区别队列的空和满;
② 少用一个元素,当入队时,先测试入队后尾指针是不是会等于头指针,如果相等就算队已满,不许入队;
③ 使用一个记数器记录元素总数。
顺序队列的类型定义为:
typedef struct QNode
{
int data;
struct QNode *next;
}QNode;
typedef struct
{
QNode *front;
QNode *rear;
}LiQueue;
2.3.1 循环队列的基本运算
(1) 建空队
void InitQueue(LiQueue *&lqu)///初始化队列
{
lqu=(LiQueue *)malloc(sizeof(LiQueue));
lqu->front=lqu->rear=NULL;
}
(2) 判队空
int QueueEmpty(LiQueue *lqu)//判断队列为空
{
if(lqu->front==NULL||lqu->rear==NULL)
return 1;
else
return 0;
}
(3) 入队
void enQueue(LiQueue *lqu,int x)
{///入队
QNode *p;
p=(QNode *)malloc(sizeof(QNode));
p->data=x;
p->next=NULL;
if(lqu->rear==NULL)
lqu->front=lqu->rear=p;
else
{
lqu->rear->next=p;
lqu->rear=p;
}
}
(4) 出队
int deQueue(LiQueue *lqu,int &x)
{///出队
QNode *p;
if(lqu->rear==NULL)
return 0; //对空不能出队
else
p=lqu->front;
if(lqu->front==lqu->rear)
lqu->front=lqu->rear=NULL;
else
lqu->front=lqu->front->next;
x=p->data;
free(p);
return 1;
}
(5) 取队头元素
int Queuefront(LiQueue *lqu)
{
if(lqu->front==NULL||lqu->rear==NULL)
return 0;
return lqu->front->data;
}
代码实现如下:
#include <stdio.h>
#include <malloc.h>
typedef struct QNode
{
int data;
struct QNode *next;
}QNode;
typedef struct
{
QNode *front;
QNode *rear;
}LiQueue;
void InitQueue(LiQueue *&lqu)///初始化队列
{
lqu=(LiQueue *)malloc(sizeof(LiQueue));
lqu->front=lqu->rear=NULL;
}
int QueueEmpty(LiQueue *lqu)//判断队列为空
{
if(lqu->front==NULL||lqu->rear==NULL)
return 1;
else
return 0;
}
void enQueue(LiQueue *lqu,int x)
{///入队
QNode *p;
p=(QNode *)malloc(sizeof(QNode));
p->data=x;
p->next=NULL;
if(lqu->rear==NULL)
lqu->front=lqu->rear=p;
else
{
lqu->rear->next=p;
lqu->rear=p;
}
}
int deQueue(LiQueue *lqu,int &x)
{///出队
QNode *p;
if(lqu->rear==NULL)
return 0; //对空不能出队
else
p=lqu->front;
if(lqu->front==lqu->rear)
lqu->front=lqu->rear=NULL;
else
lqu->front=lqu->front->next;
x=p->data;
free(p);
return 1;
}
int Queuefront(LiQueue *lqu)
{
if(lqu->front==NULL||lqu->rear==NULL)
return 0;
return lqu->front->data;
}
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char** argv) {
LiQueue *qu;
int a,b=1,c=2,k;
InitQueue(qu);
a=QueueEmpty(qu);
printf("队是否为空:%d (1:空 0:不空)\n",a);
enQueue(qu,b);
printf("%d 入队\n",b);
enQueue(qu,c);
printf("%d 入队\n",c);
a=QueueEmpty(qu);
printf("队是否为空:%d (1:空 0:不空)\n",a);
deQueue(qu,k);
printf("%d 出队\n",k);
return 0;
}
3、总结:
最近又重新学习数据结构的东西,好长时间没看差不多都忘了。花了好几个小时,查阅资料,把数据结构栈和队列的知识从头到尾的把数据结构的线性结构复习了一遍,相信经过上面的复习,对线性结构又有了更深入的了解。你后会经常写一些总结发出来,复习起来会好很多。笔者学疏才浅,如果文章有误,敬请各位高手斧正。
参考博文 http://blog.csdn.net/zhang_xinxiu/article/details/11962605