解读栈(Stack)和队列(Queue)! |
文章目录
►今天介绍另外两个容器队列和栈;上一节介绍的容器是链表:『数据结构与算法』解读链表!
一. 栈
1.1. 栈的定义
►栈(stack)是后进先出LIFO(last in first out)的线性表。因此,对栈来说表尾有其特殊含义,称为栈顶(top),相应地,表头称为栈底(bottom),不含元素的空表称为空栈。栈是只允许在一端进行插入或删除操作的线性表。首先栈是一种线性表,但限定这种线性表只能在某一端址行插入和l删除操作。
►栈的数学性质: n n n 个不同元素进栈,出栈元素不同排列的个数为(下述公式称为卡特兰数,可采用数学归纳法证明,有兴趣的读者可以参考组合数学教材):
1 n + 1 C 2 n n (1) \frac{1}{n+1} C_{2 n}^{n}\tag{1} n+11C2nn(1)
►栈是一种操作受限的线性表,类似于线性表,它也有对应的两种存储方式。
1.1.1. 栈的顺序存储
►采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶元素的位置。
►栈的顺序存储类型:
typedef int Elemtype;
#define MaxSize 50 //定义栈中元素的最大个数;
typedef struct //栈的顺序存储类型
{
Elemtype data[MaxSize]; //存放栈中元素;
int top; //栈顶指针;
} sqstack;
- 栈顶指针:
s.top
,初始时设置s.top= - 1
;栈顶元素:s.data[s.top]
。- 进栈操作:栈不满时,栈顶指针先加1 ,再送值到栈顶元素。
- 出栈操作:栈非空时,先取栈顶元素值,再将栈顶指针减l1。
- 栈空条件:
s.top==-1
;- 栈满条件:
s.top==MaxSize-1
;- 栈长:
s.top+1
;
►由于顺序栈的入栈操作受数组上界的约束,当对栈的最大使用空间估不足时, 有可能发生栈上溢,此时应及时向用户报告消息,以便及时处理,避免出销。
注意: 栈和队列的判空、判满条件,会因实际给的条件不同而变化, 上面提到的方法以及下面的代码实现只是在找顶指针设定的条件下的相应方法,而其他情况则需具体问题具体分析。
1.1.2. 顺序栈的基本运算(C++)
#include <iostream>
using namespace std;
typedef int Elemtype;
#define MaxSize 50 //定义栈中元素的最大个数;
typedef struct //栈的顺序存储类型
{
Elemtype data[MaxSize]; //存放栈中元素;
int top; //栈顶指针;
} sqstack;
//==================== 1、初始化
void initStack(sqstack &s) //声明mystack是x的引用(别名)
{
s.top = -1; //初始化校顶指针
}
//==================== 2、判断栈空
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.top++;
s.data[s.top] = x;
// s.data[++s.top] = x; //指针先加1 ,再入栈
return true;
}
//==================== 4、出栈
bool pop(sqstack &s, Elemtype &x)
{
if (s.top == -1) //栈空,报错
return false;
// x = s.data[s.top];
// s.top--;
x = s.data[s.top--]; //先出栈,指针再减l
return true;
}
/*==================== 4、读取栈顶元素,
仅为读取栈顶元素,并没有出栈操作,因此原栈顶元素依然保留在栈中。*/
bool getTop(sqstack &s, Elemtype &x)
{
if (s.top == -1) //栈空,报错
return false;
x = s.data[s.top]; //x记录栈顶元素
return true;
}
/*==================== 为了测试方便,自己定义的依次打印栈中元素*/
bool printData(sqstack s)
{
if (s.top == -1) //栈空,报错
return false;
while (s.top >= 0)
{
cout << s.data[s.top] << " ";
s.top--;
}
cout << endl;
return true;
}
int main()
{
sqstack mystack;
initStack(mystack); //1、初始化空栈;
if (stackEmpty(mystack)) //2、栈是否空
cout << "stack is empty!" << endl;
push(mystack, 1); //3、进栈;
push(mystack, 2);
push(mystack, 3);
push(mystack, 4);
push(mystack, 5);
printData(mystack); //打印栈中元素
cout << "mystack.top is: " << mystack.top << endl;
Elemtype x; //4、出栈;
bool pop1 = pop(mystack, x);
printData(mystack); //打印栈中元素
getTop(mystack, x);
cout << "the data of stack top is: " << x << endl;
system("pause");
return 0;
}
stack is empty!
5 4 3 2 1
mystack.top is: 4
4 3 2 1
the data of stack top is: 4
请按任意键继续. . .
1.1.3. 栈的链式存储
►采用链式存储的战称为链栈,链栈的优点是便于多个找共享存储空间和提高其效率,且不存在栈满上溢的情况。通常采用单链表实现,并规定所有操作都是在单链表的表头进行的。这里规定链栈没有头结点,Lhead 指向栈顶元素。
typedef struct Linknode{
ElemType data; //数据域
struct Linknode *next; //指针域
} *LiStack; //栈类型定义
►采用链式存储,便于结点的插入与删除。链栈的操作与链表类似,入栈和出栈的操作都在链表的表头进行。需要注意的是,对于带头结点和不带头结点的链枝,具体的实现会有所不同。
1.2. STL中的栈类成员函数(C++)
►栈定义在 < s t a c k > <stack> <stack> 头文件中(C++如果使用栈,必须加入这个头文件)。
//----容量capacity----
bool empty(); //测试是否为空栈
size_type size(); //返回栈长度
//----元素存取element access----
top(); //返回栈顶元素
//----栈运算operations----
void push(const T& x); //进栈
void pop(); //出栈
►栈应用举例:如下
#include <iostream>
#include <stack> //使用栈
using namespace std;
int main()
{
stack<int> s;
for (int i = 1; i <= 6; i++)
s.push(i); //进栈 s: 1 2 3 4 5 6
while (!s.empty())
{
cout << s.top() << " ";
s.pop(); //出栈
}
cout << endl;
system("pause");
return 0;
}
6 5 4 3 2 1
请按任意键继续. . .
1.3. STL栈应用例子(C++)
►用栈解决“符号平衡问题”。<>、{}、[]、()这些符号必须是成对出现的,输入一个字符串,判断以上符号是否匹配。
►解题思路:
- ① 如果字符是一个开放符号,则将其推入栈中。
- ② 如果字符是一个封闭符号,则当栈为空时报错。否则,将栈元素弹出。如果弹出的符号不是对应的开放符号,则报错。
- ③ 在文件结尾,如果栈非空则报错。
#include <iostream>
#include <stack>
#include <string>
using namespace std;
bool isBalanced(const string &str)
{
int len = str.size();
stack<char> mystack;
for (int i = 0; i < len; i++) //遍历字符串中的各个字符
{
if (str[i] == '[' || str[i] == '{' || str[i] == '(' || str[i] == '<')
{
mystack.push(str[i]);
}
if (str[i] == ']' || str[i] == '}' || str[i] == ')' || str[i] == '>')
{
if (mystack.empty()) //如果栈为空,则说明不平衡
return false;
switch (str[i])
{
case ']':
{
if (mystack.top() != '[')
return false;
mystack.pop();
break;
}
case '}':
{
if (mystack.top() != '{')
return false;
mystack.pop();
break;
}
case ')':
{
if (mystack.top() != '(')
return false;
mystack.pop();
break;
}
case '>':
{
if (mystack.top() != '<')
return false;
mystack.pop();
break;
}
}
}
}
if (mystack.empty()) //栈为空表示字符串是平衡的
return true;
else //没有正确的匹配
{
mystack.pop();
return false;
}
}
int main()
{
string str;
cin >> str;
bool bal = isBalanced(str);
if (bal == true)
cout << "string is balanced!" << endl;
else
cout << "string is unbalanced!" << endl;
system("pause");
return 0;
}
<abcd>
string is balanced!
请按任意键继续. . .
二. 队列
2.1. 队列的定义
►队列(queue)是一种先进先出FIFO(first in first out)的线性表。它只允许在表的一端进行插人元素,而在另一端删除元素,最早进入队列的元素最早离开。在队列中,插入的一端称为队尾(back),删除的一端称为队头(front)。队列也是一种操作受限的线性表,只允许在表的一端进行插入, 而在表的另一端进行删除。向队列中插入元素称为入队或进队; 删除元素称为出队或离队。
2.1.1. 队列的顺序存储
►队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针:队头指针front指向队头元素,队尾指针rear指向队尾元素的下一个位置(不同教材对front 和rear的定义可能不同,例如,可以让rear指向队尾元素、front指向队头元素。对于不同的定义,出队入队的操作是不同的)。
typedef int Elemtype;
#define MaxSize 50 //定义队列中元素的最大个数;
typedef struct
{
Elemtype data[MaxSize]; //存放队列元素;
int front,rear; //队头指针和队尾指针;
} sqstack;
2.1.2. 队列的链式存储
typedef int Elemtype;
typedef struct node //链式队列数据节点类型定义;
{
Elemtype data;
struct node *next; //或者 node *next;
} linkNode;
typedef struct //链式队列
{
linkNode *front, *rear; //队列的队头和l 队尾指针
} linkQueue;
2.1.3. 链式队列的基本运算(C++)
#include <iostream>
using namespace std;
typedef int Elemtype;
typedef struct node //链式队列数据节点类型定义;
{
Elemtype data;
struct node *next; //或者 node *next;
} linkNode;
typedef struct //链式队列
{
linkNode *front, *rear; //队列的队头和l 队尾指针
} linkQueue;
//==================== 1、初始化队列
void initQueue(linkQueue &q)
{
q.front = q.rear = new linkNode; //建立头结点
q.front->next = NULL; //初始为空
}
//==================== 2、判断队空
bool isEmpty(linkQueue q)
{
if (q.front == q.rear)
return true;
else
return false;
}
//==================== 3、进队
void enQueue(linkQueue &q, Elemtype x)
{
linkNode *s = new linkNode; //创建一个新的数据节点
s->data = x; //插入到链尾
s->next = NULL;
q.rear->next = s; //移动队尾指针;
q.rear = s;
}
//==================== 4、出队
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;
delete p;
return true;
}
/*遍历打印队列*/
void printQueue(linkQueue q)
{
if (!isEmpty(q))
{
q.front = q.front->next; //排除头结点;
while (q.front != q.rear)
{
cout << q.front->data << " ";
q.front = q.front->next;
}
cout << q.front->data << endl;
}
}
int main()
{
linkQueue queue;
initQueue(queue); //初始化队列
if (isEmpty(queue)) //判断队列是否为空
cout << "Queue is empty!" << endl;
enQueue(queue, 1); //进队
enQueue(queue, 2);
enQueue(queue, 3);
enQueue(queue, 4);
enQueue(queue, 5);
printQueue(queue); //打印队列元素
Elemtype x; //出队
deQueue(queue, x);
deQueue(queue, x);
printQueue(queue); //打印队列元素
system("pause");
return 0;
}
Queue is empty!
1 2 3 4 5
3 4 5
请按任意键继续. . .
2.2. STL中的队列类成员函数(C++)
►队列定义在 < q u e u e > <queue> <queue> 头文件中(C++如果使用队列,必须加入这个头文件)。
►队列类成员函数原型如下:
//----容量capacity----
bool empty(); //测试是否为空队列
size_type size(); //返回队列长度
//----元素存取element access----
front(); //返回队头元素
back(); //返回队尾元素
//----队列运算operations---
void push(const T& x); //插入一个元素到队尾
void pop(); //删除队列下一个元素
►队列应用举例:如下
#include <iostream>
#include <queue> //使用队列
using namespace std; //队列定义在std命名空间
int main()
{
//队列示例
queue<int> q;
for (int i = 1; i <= 6; i++) //进队 q: 1 2 3 4 5 6
q.push(i);
q.front() -= q.back(); //Q: -5 2 3 4 5 6
while (!q.empty())
{
cout << q.front() << " ";
q.pop();
}
cout << endl;
system("pause");
return 0;
}
-5 2 3 4 5 6
请按任意键继续. . .
2.3. STL队列应用例子(C++)
►桌上有一叠牌,从第一张牌开始从上往下依次编号1~n。当至少还剩两张牌时进行如下操作:把第一张牌扔掉,然后把新的第一张牌放到整叠牌的最后。请输入总牌数n,输出每次扔掉的牌,以及最后剩下的两张牌。
#include <iostream>
#include <queue> //使用队列
using namespace std;
int main()
{
queue<int> q; //声明队列;
int n;
cin >> n; //输出总牌数
while (n)
{
for (int i = 1; i <= n; i++)
q.push(i);
while (q.size() > 2)
{
cout << q.front() << " "; //输出队列的第一个元素
q.pop(); //队列第一个元素出队
q.push(q.front()); //将现队列第一个元素放到队尾
q.pop(); //现队列第一个元素出队
}
cout << endl;
while (!q.empty())
{
cout << q.front() << " ";
q.pop();
}
cout << endl;
cin >> n;
}
system("pause");
return 0;
}
7
1 3 5 7 4
2 6
8
1 3 5 7 2 6
4 8
9
1 3 5 7 9 4 8
6 2
10
1 3 5 7 9 2 6 10
8 4
11
1 3 5 7 9 11 4 8 2
10 6
12
1 3 5 7 9 11 2 6 10 4
12 8