目录
基本概念
- 栈:只能在一端进行插入(入栈)或删除(出栈)的操作的线性表,允许操作的一段称为栈(Top)。
- 特点:先进后出(FILO)
- 存储结构:顺序栈和链式栈
- 数学性质:当n个元素以某种顺序进栈,并且可在任意时刻出栈时(满足FILO),所获得的元素排列的数目N恰好满足
- 队列:它也是一种受限的线性表,其限制为仅允许在表的一端(队尾-Rear)进行插入(入队),在表的另一端(队头-Front)进行删除(出队)。
- 特点:先进先出(FIFO)
- 存储结构:顺序队和链队
栈
- 顺序栈:---,几个状态,两个操作,st表示一个顺序栈
- 栈空:st.top==-1(也有的书表示为st.top==0,具体情况视规定)
- 栈满:设maxSize为最大 元素个数,st.top==maxSize-1(注意是st.top==-1和st.top==0为栈空条件时候,第一个元素存储的位置也有区别)
- 非法状态:上溢(栈满时继续入栈)和下溢(栈空时继续出栈)
- 两个操作:入栈:++(st.top);st.data[st.top]=x; 出栈:x=st.data[st.top];--(st.top); 注:对于独立的自增操作,++a总比a++效率要高,原因在于前置++不会产生临时对象,有兴趣的自行去了解。
- Q:编写算法,判断表达式中的括号是否正确,表达式已经存入exp[]中,字符个数为n
#include<iostream> using namespace std; /*---------------栈-------------------*/ //用顺序栈来实现括号匹配 #define maxSize 20 void math(char exp[], int n) { //为了简便直接定义,也可以自己定义一个结构体 char st[maxSize] = {0}; int top = -1; for (int i=0; i < n; ++i) { if (exp[i] == '(') { st[++top] == '('; } if (exp[i] == ')') { if (top == -1) { cout << exp << ":括号匹配失败!\n"; return; } else { --top; //相当于遇到一个')'就将栈中的'('pop出来一个 } } } //只有当恰好栈空的时候为匹配成功 if (top == -1) { cout << exp << ":括号匹配成功!\n"; return; } else { cout << exp << ":括号匹配失败!\n"; return; } } int main() { char exp[] = "(haha))("; char exp1[] = "(haha)(("; char exp2[] = "(haha)()"; math(exp, 8); math(exp1, 8); math(exp2, 8); return 0; }
- 链栈:---,两个状态,两个操作,lst(它的next指向top)表示一个链栈
- 栈空:lst->next=NULL;
- 栈满:链式栈可以动态分配,只要在内存允许的范围内即可。
- 两个操作:入栈:p->next=lst->nxet;lst->next=p;头插法建立链表中的插入操作 。 出栈:p=lst->next;x=p->data;lst->next=p->next;free(p); 就是单链表的删除操作。
实现链栈的基本操作,初始化,判断空,进栈,出栈
#include<iostream>
using namespace std;
//链栈
//定义结点
typedef struct LNode {
int data;
struct LNode* next;
}LNode;
//初始化栈,其实就是一个指向top元素的LNode指针
void initLStack(LNode*& lst) {
lst = (LNode*)malloc(sizeof(LNode)); //和书二有些区别,因为lst书中默认认为已经分配了空间
lst->next = NULL;
}
//判断栈是否为空
int isEmpty(LNode* lst) {
if (lst->next == NULL) {
return 1;
}
else
{
return 0;
}
}
//入栈
void push(LNode*& lst,int x) {
LNode* newNode = (LNode*)malloc(sizeof(LNode));
newNode->data = x;
newNode->next = lst->next;
lst->next = newNode;
}
//出栈
int pop(LNode*& lst,int &x) {
LNode* p;
if (lst->next == NULL) {
return 0; //栈空
}
else
{
p = lst->next;
x = p->data;
lst->next = p->next;
free(p);
return 1;
}
}
//通过出栈操作打印链栈全部元素
void printLStack(LNode*& lst) {
int flg = 1;
cout << "依次pop为:";
while (flg)
{
int temp;
flg=pop(lst,temp);
if (flg == 1) {
cout << temp << " ";
}
}
cout << endl;
}
int main() {
int temp = 0;
cin >> temp;
LNode* lst;
initLStack(lst);
while (temp!=-1) //-1为结束符
{
push(lst, temp);
cin >> temp;
}
if (isEmpty(lst) == 0) {
cout << "链栈非空!"<<endl;
}
else {
cout << "链栈为空!"<<endl;
}
printLStack(lst);
free(lst); //养成好习惯,记得释放空间
return 0;
}
队列
- 顺序队
- 循环队列:为什么要有循环队列,我们首先要了解“假溢出”,定义两个指针rear,front,分别指向队尾,队首。maxSize为最大元素个数,元素进队的时候rear后移,元素出队的时候front也要后移,当经历一系列的操作后,两个指针最终都会到达数组末端maxSize-1,队中没有元素了,但是没办法让新元素进队,这就是“假溢出”。所以就产生了循环队列来解决这个问题。
- 循环队列的要素
- 两个状态:队空:qu.rear=qu.front,队满:(qu.rear+1)%maxSize=qu.front
- 两个操作:
- 进队:移动队尾指针 qu.rear=(qu.rear+1)%maxSize;qu.data[qu.rear]=x;
- 出队:移动队首指针 qu.front=(qu.front+1)%maxSize;x=qu.data[qu.front];
- 循环队列:为什么要有循环队列,我们首先要了解“假溢出”,定义两个指针rear,front,分别指向队尾,队首。maxSize为最大元素个数,元素进队的时候rear后移,元素出队的时候front也要后移,当经历一系列的操作后,两个指针最终都会到达数组末端maxSize-1,队中没有元素了,但是没办法让新元素进队,这就是“假溢出”。所以就产生了循环队列来解决这个问题。
- 链队:采用单链表,lqu表示链队
- 两个状态:链队空:lqu->rear==NULL或lqu->front==NULL; 队满:不存在队满的操作(内存允许),若想判断队的长度可以维护个length变量。
- 两个操作:
- 进队:lqu->raer->next=p;lqu->rear=p;
- 出队:p=lqu->front;lqu->front=p->next;x=p->data;free(p);
代码演示:
前面实现了栈的一些定义和操作,来实现一下链队的基本操作,例如链队的初始化,是否空,入队,出队操作,但是也注意,仅仅利用了一些思想并非真正的编程的时候(要封装)就是这样,简化代码。
#include<iostream>
using namespace std;
/*--------------队列------------------*/
//单链表结点
typedef struct LNode {
int data;
struct LNode* next;
}LNode;
//链队
typedef struct Lqu {
LNode* rear;
LNode* front;
int length; //与书中写法不一样,我们来维护一个length
}Lqu;
//初始化链队
void initLqu(Lqu*& lqu) {
lqu = (Lqu*)malloc(sizeof(Lqu));
lqu->front = lqu->rear = NULL;
lqu->length = 0;
}
//判断链队是否空
int isEmpty(Lqu* lqu) {
if (lqu->length == 0) {
return 1;
}
else
{
return 0;
}
}
//入队
void enQueue(Lqu*& lqu,int x) {
LNode* newNode = (LNode*)malloc(sizeof(LNode));
newNode->data = x;
newNode->next = NULL;
//空队和非空队要做不同的处理
if (lqu->length == 0) {
lqu->front = lqu->rear = newNode;
}
else
{
lqu->rear->next = newNode;
lqu->rear = newNode;
}
++(lqu->length);
}
//出队
int deQueue(Lqu*& lqu,int &x) {
LNode* p = lqu->front;
//队为空则没有元素出队
if (lqu->length == 0) {
return 0;
}
x = p->data; //取出该值
//只有一个结点的时候要特殊处理
if (lqu->length == 1) {
lqu->front = lqu->rear=NULL;
}
else {
lqu->front = lqu->front->next;
}
--(lqu->length);
free(p);//出队后删除释放以前的结点
return 1;
}
//顺序打印队,通过出队操作
void printLqueue(Lqu*& lqu) {
int flg = 1;
cout << "依次出队为:";
while (flg)
{
int temp;
flg = deQueue(lqu, temp);
if (flg == 1) {
cout << temp << " ";
}
}
cout << endl;
}
int main() {
int temp = 0;
cin >> temp;
Lqu *lqu;
initLqu(lqu);
while (temp!=-1) //-1为结束符
{
enQueue(lqu, temp);
cin >> temp;
}
if (isEmpty(lqu) == 0) {
cout << "链队非空!"<<endl;
}
else {
cout << "链队为空!"<<endl;
}
printLqueue(lqu);
free(lqu); //养成好习惯,记得释放空间
return 0;
}
多说几句
ADT:Abstract Data type,抽象数据类型,可以看做一些数据对象以及附加在这些数据对象上的操作集合。
- 数据对象集:存储在数据结构中的数据元素
- 数据关系及:指数据对象的组织方式,如线性表的一对一,树的一对多以及图的多对多关系。
- 操作集:例如栈的入栈出栈操作,判断是否为空等操作。
ADT注意事项:
- 它的格式,类似于C语言结构体的写法,一般关系集会出现4中关系:没关系、顺序关系、树形关系和图关系。
- 例子:例如图书馆 ADT
ADT 书 { 数据对象集: 书名; 书号; 作者; }; ADT 书架 { 数据对象集: 书架号; 书={书0,书1,···,书n}; 数据关系集: 书在书架上的排列方式={<书0,书1>,<书1,书2>,······}; }; ADT 图书馆 { 数据对象集: 图书馆名; 书架={书架0,书架1,······}; 数据关系集: 书架在图书馆的排列方式={<书架0,书架1>,<书架1,书架2>,······}; 操作集: 根据书架号查找书架; 根据书名查找书; ······· }