3.1 栈
3.1.1 抽象数据类型栈的定义
栈(stack):是限定仅在表尾进行插入或删除操作的线性表。
因此,对栈来说,表尾端有其特殊含义,称为栈顶(top),相应地,表头端称为栈底(bottom)。不含元素的空表称为空栈。
3.1.2 栈的表示和实现
和线性表类似,栈也有两种存储表示方式。
栈可以用数组和链表实现,但两者确各有千秋。
数组栈:
顾名思义,由数组开辟空间所得到的顺序结构,开辟的空间具有连续性的特点。因为这个特点,所以数组栈可以随机访问,读取的效率高。但是,我们需要思考一个问题,数组栈就没有缺点吗?如果没有,那么为什么又会存在链栈这一概念?其实这两者最大的区别在于读取效率和空间利用。数组栈的读取效率高,但是当我存放一个不知道空间大小的东西在栈里面,那么我的数组空间应该开辟多大?显然,数组栈的空间开辟是具有不确定性的,如果空间不够,可能会造成数组越界等情况。而如果数组空间开辟过大就会造成空间的利用率低,浪费内存的情况。
链栈:
运用链表的方式为栈开辟空间,这样可以极大的节省空间,我存入一个数据,那么我就开辟一个数组的空间。这种创建栈的方式非常有利于那种对于存储空间要求高的编程性工作上,例如嵌入式等对于空间的要求就很高。但是,这种链表方式创建的栈相较于数组栈来说,它获取栈元素会更麻烦,每次获取对栈中的某一个操作我都需要去遍历,依次来找到我所要操作的元素,这也是链栈的不利之处。
栈是一种特殊的顺序结构,它的特点在于后进先出,这个特点在以后的工作或者学习中能够起到很大的作用。
对于栈的创建,以下代码选择了链栈方式
1、栈的创建(栈的初始化)
struct Node
{
int data;
struct Node* next;
};
struct stack
{
struct Node* stackTop;//栈顶标记
int size;//栈中的元素个数
};
//创建结点
struct Node* createNode(int data)
{
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
//创建栈,就是创建一个struct stack的变量
struct stack* createStack(){
//创建过程就是初始化过程
struct stack* myStack = (struct stack*)malloc(sizeof(struct stack));
myStack->stackTop = NULL;
myStack->size = 0;
return myStack;
}
2、入栈
//入栈操作
void push(struct stack* myStack,int data)
{
//插入的这个结点创建出来
struct Node* newNode = createNode(data);
//入栈操作就是链表表头的插入
newNode->next = myStack->stackTop;
myStack->stackTop = newNode;
myStack->size++;
}
3、获取栈顶元素
//获取栈顶元素
int top(struct stack* myStack)
{
if(myStack->size == 0)
{
//防御性编程
printf("栈为NULL,无法获取栈顶元素!\n");
system("pause");//防止闪屏
return myStack->size;
}
return myStack->stackTop->data;
}
4、出栈(删除结点)
//出栈(即删除结点)
void pop(struct stack* myStack)
{
if(myStack->size == 0)
{
printf("栈为NULL,无法出栈!\n");
system("pause");
}
else
{
struct Node* nextNode = myStack->stackTop->next;
free(myStack->stacTop);
myStack->stackTop = nextNode;
myStack->size--;
}
}
5、判空
//判空
int empty(struct stack* myStack)
{
if(myStack->size == 0)
{
return 0;
}
return 1;
}
链栈总代码
#include<stdio.h>
#include<stdlib.h>
struct Node
{
int data;
struct Node* next;
};
//创建结点
struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
struct stack
{
struct Node* stackTop;//栈顶标记
int size;//栈中的元素个数
};
//创建栈,就是创建一个struct stack的变量
struct stack* createStack() {
//创建过程就是初始化过程
struct stack* myStack = (struct stack*)malloc(sizeof(struct stack));
myStack->stackTop = NULL;
myStack->size = 0;
return myStack;
}
//入栈操作
void push(struct stack* myStack, int data)
{
//插入的这个结点创建出来
struct Node* newNode = createNode(data);
//入栈操作就是链表表头插入
newNode->next = myStack->stackTop;
myStack->stackTop = newNode;
myStack->size++;
}
//获取栈顶元素
int top(struct stack* myStack)
{
//防御性编程
if (myStack->size == 0)
{
printf("栈为NULL,无法获取栈顶元素!\n");
system("pause");
return myStack->size;
}
return myStack->stackTop->data;
}
//出栈(即删除结点)
void pop(struct stack* myStack)
{
if (myStack->size == 0)//防御性编程
{
printf("栈为NULL,无法出栈!\n");
system("pause");
}
else
{
struct Node* nextNode = myStack->stackTop->next;
free(myStack->stackTop);
myStack->stackTop = nextNode;
myStack->size--;
}
}
//(判空)
int empty(struct stack* myStack)
{
if (myStack->size == 0) {
return 0;
}
return 1;
}
int main()
{
struct stack* myStack = createStack();
push(myStack, 1);
push(myStack, 2);
push(myStack, 3);
while (empty(myStack))//如果栈为空,则empty函数返回0,循环结束
{
printf("%d\t", top(myStack));
pop(myStack);
}
printf("\n");
system("pause");//防止闪屏
return 0;
}
3.2 队列
3.2.1 抽象数据类型队列的定义
和栈相反,队列(queue)是一种先进先出(first in first out ,缩写为FIFO)的线性表。它只允许在表的一端进行插入,而在另一端删除元素。
在队列中,允许插入的一端叫做队尾(rear),允许删除的一端则称为队头(front)。
3.2.2 链队列——队列的链式表示和实现
类比于栈,队列同样可以使用数组或者链表实现队列,这两者最大区别在于栈是先进后出,队列是先进先出。那么我在此提出两个值得思考的问题,问题一:链栈和数组栈各有优缺点,链队列和数组队列中有何突出的区别,哪种方式实现数组的方式更好?问题二:队列的特点是先进先出,即头出尾进。那么数组队列是否可以尾进头出?链队列呢?如果可以,是为什么,如果不可以,那又是为什么?
(大家可以先结合以下的代码重点思考第二个问题,问题一的答案是链队列更好,至于为什么,其实跟问题二的答案是一样的,我会将答案在最后公布)
1、队列创建前的准备
struct Node
{
int data;
struct Node* next;
};
/*创建队列结点*/
struct Node* createNode(int data)
{
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
struct Queue
{
struct Node* frontNode;//指向队头的指针
struct Node* tailNode;//指向队尾的指针
int size;//队列的长度
};
2、创建队列
struct Queue* createQueue()
{
struct Queue* myQueue = (struct Queue*)malloc(sizeof(struct Queue));
myQueue->frontNode = myQueue->tailNode = NULL;
myQueue->size = 0;
return myQueue;
}
/*在一开始,队列是空的,所以我们需要将队头指针与队尾指针放在一起*/
3、入列
void push(struct Queue* myQueue, int data)
{
struct Node* newNode = createNode(data);
if (myQueue->size == 0)
{
myQueue->frontNode = myQueue->tailNode = newNode;
}
else
{
myQueue->tailNode->next = newNode;
myQueue->tailNode = newNode;
}
myQueue->size++;
}
3、获取栈顶元素
int front(struct Queue* myQueue)
{
if (myQueue->size == 0)
{
printf("队为NULL,无法获取队头元素!\n");
system("pause");//暂停窗口
return myQueue->size;
}
return myQueue->frontNode->data;
}
4、出队
void pop(struct Queue* myQueue)
{
if(myQueue->size == 0{
printf("队为NULL,无法出队!\n);
system("pause");
return;
}
else
{
struct Node* nextNode = myQueue->frontNode->next;
free(myQueue->frontNode);
myQueue->frontNode = nextNode;
myQueue->size--;
}
}
5、判空
int empty(struct Queue* myQueue)
{
if (myQueue->size == 0)
{
return 0;
}
else
{
return 1;
}
}
链队列总代码
#include<stdio.h>
#include<stdlib.h>
struct Node
{
int data;
struct Node* next;
};
//创建结点
struct Node* createNode(int data)
{
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
struct Queue
{
struct Node* frontNode;//队头指针
struct Node* tailNode;//队尾指针
int size;//队列的长度
};
//创建队列
struct Queue* createQueue()
{
struct Queue* myQueue = (struct Queue*)malloc(sizeof(struct Queue));
myQueue->frontNode = myQueue->tailNode = NULL;
myQueue->size = 0;
return myQueue;
}
//STL 封装很多的容器 stack 和 queue
//入队
void push(struct Queue* myQueue, int data)
{
struct Node* newNode = createNode(data);
if (myQueue->size == 0)
{
myQueue->frontNode = myQueue->tailNode = newNode;
//当队列为空时,队头指针与队尾指针在同一个结点上
}
else
{
myQueue->tailNode->next = newNode;
myQueue->tailNode = newNode;
}
myQueue->size++;
}
//出队
void pop(struct Queue* myQueue)
{
if (myQueue->size == 0) {
printf("队为NULL,无法出队!\n");
system("pause");
return;
}
else
{
struct Node* nextNode = myQueue->frontNode->next;
free(myQueue->frontNode);
myQueue->frontNode = nextNode;
myQueue->size--;
}
}
//获取队头元素
int front(struct Queue* myQueue)
{
if (myQueue->size == 0)
{
printf("队为NULL,无法获取队头元素!\n");
system("pause");
return myQueue->size;
}
return myQueue->frontNode->data;
}
//判空
int empty(struct Queue* myQueue)
{
if (myQueue->size == 0)
{
return 0;
}
else
{
return 1;
}
}
int main() {
//创建队列
struct Queue* myQueue = createQueue();
//入队
push(myQueue, 1);
push(myQueue, 2);
push(myQueue, 3);
//获取队列元素
while (empty(myQueue))
{
printf("%d\t", front(myQueue));
pop(myQueue);
}
printf("\n");
system("pause");
return 0;
}
好的,那我们现在回到问题二,不知道大家的想法是否跟我一样。我的想法是:一般情况下,链队列会比数组队列常用一点,数组队列不可以实现头进尾出,而链队列可以。因为我们需要注意一个很重要的点,数组分配的空间是连续的,而链表分配的空间是不连续。想要实现头进尾出,无非就是将队头指针当作队尾指针来用,队尾指针当作队头指针用,那么链表可以在头结点处加结点,新加的结点成为头结点,但是数组比如a[0]前面可以加a[-1]、a[-2]吗?。很显然这是不行的。
3.2.3 循环队列——队列的顺序表示和实现
循环队列的特点:
1、元素在队尾插入,在队头删除,遵循先进先出原则
2、在C语言中不能用动态分配的一堆数组来实现循环队列。如果用户的应用程序中设有循环队列,则必须为它设定一个最大队列长度;若用户无法预估所用队列最大长度,则宜采用链队列。由于循环队列基于数组实现,所以它的访问速度很快,特别是在移动元素时。
3、相比普通的队列,循环队列在元素出队时无需移动大量元素,只需移动头指针。
4、循环队列的优点在于可以更有效地利用存储空间,并解决因数组越界而产生的假溢出问题。
1、定义队列结构
#define MAX_SIZE 5 //宏定义队列能存放的元素的最大个数,数组的长度
typedef struct {
int data[MAX_SIZE];//保存数据的数组
int front;//保存队头元素下标的队头指针
int rear;//保存队尾元素下标的队尾指针
}Queue;
2、初始化,只要用初始化队头指针和队尾指针
void initQueue(Queue* q) {
q->front = 0;//初始化队头指针
q->rear = 0;//初始化队尾指针
}
3、判断队空
int isEmpty(Queue* q) {
return q->front == q->rear;
}
4、判断队满
int isFull(Queue* q) {
return (q->rear + 1) % MAX_SIZE == q->front;
}
5、元素入队
void push(Queue* q, int val) {
//如果队满,不能入队
if (isFull(q)) {
printf("队满,无法入队!\n");
return;
}
q->rear = (q->rear + 1) % MAX_SIZE;
q->data[q->rear] = val;
}
6、元素出队
int pop(Queue* q) {
//如果队空,不能出队
if (isEmpty(q)) {
printf("队空,不能出队");
return -1;
}
q->front = (q->front + 1) % MAX_SIZE;
int temp = q->data[q->front];
return temp;
}
7、获取队头元素
int getFront(Queue* q) {
if (isEmpty(q)) {
printf("队空,无元素\n");
}
int temp = q->front + 1;
return q->data[temp];
}
8、获取队尾元素
int getRear(Queue* q) {
if (isEmpty(q)) {
printf("队空,无元素\n");
return -1;
}
return q->data[q->rear];
}
总代码
#include<stdio.h>
#define MAX_SIZE 5 //队列能存放的元素的最大个数,数组的长度
//定义队列结构
typedef struct {
int data[MAX_SIZE];//保存数据的数组
int front;//保存队头元素下标的队头指针
int rear;//保存队尾元素下标的队尾指针
}Queue;
//初始化,只用初始化队头指针和队尾指针
void initQueue(Queue* q) {
q->front = 0;
q->rear = 0;
}
//判断队空
int isEmpty(Queue* q) {
return q->front == q->rear;
}
//判断队满
int isFull(Queue* q) {
return (q->rear + 1) % MAX_SIZE == q->front;
}
//元素入队
void push(Queue* q, int val) {
//如果队满,不能入队
if (isFull(q)) {
printf("队满,无法入队!\n");
return;
}
q->rear = (q->rear + 1) % MAX_SIZE;
q->data[q->rear] = val;
}
//元素出队
int pop(Queue* q) {
//如果队空,不能出队
if (isEmpty(q)) {
printf("队空,不能出队");
return -1;
}
q->front = (q->front + 1) % MAX_SIZE;
int temp = q->data[q->front];
return temp;
}
//获取队头元素
int getFront(Queue* q) {
if (isEmpty(q)) {
printf("队空,无元素\n");
}
int temp = q->front + 1;
return q->data[temp];
}
//获取队尾元素
int getRear(Queue* q) {
if (isEmpty(q)) {
printf("队空,无元素\n");
return -1;
}
return q->data[q->rear];
}
int main() {
//定义一个队列
Queue q;
//初始化队列
initQueue(&q);
//依次入队4个元素
push(&q, 10);
push(&q, 20);
push(&q, 30);
push(&q, 40);
//弹出两个元素 10,20
printf("%d ", pop(&q));
printf("%d \n", pop(&q));
//再插入两个
push(&q, 50);
push(&q, 60);
//将所有元素出队
// 30 40 50
printf("%d ", pop(&q));
printf("%d ", pop(&q));
printf("%d ", pop(&q));
printf("%d \n", pop(&q));
//-1 提示队空
printf("%d ", pop(&q));
return 0;
}