栈和队列详解

简介: 栈和队列详解

一、什么是栈和队列

(1)栈:

栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素————百度百科


   

   


由此可见栈是一种先进后出(First In Last Out)即FILO结构。

(2)队列

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头————百度百科


       队列是一种先进先出(First In First Out)的结构即FIFO结构。

二、线性栈的操作实现

栈可分为线性栈和链式栈,不同的实现方法匹配于不同的场景。

(1)栈的结构定义:

typedef int STDataType;
typedef struct Stack {
  STDataType* a;//线性栈需要数组
  int top;//栈顶指针记录元素个数
  int capacity;//栈的容量
}ST;

(2)栈的初始化:

ST* InitNewStack(STDataType n)
{
  ST* p = (ST*)malloc(sizeof(ST));//初始化生成栈
  p->a = (STDataType*)malloc(sizeof(STDataType) * n);//对栈的数组进行开辟
  if (p->a == NULL)//开辟检查
  {
    perror("malloc");
    exit(-1);
  }
  p->top = -1;//将指针置为-1,也可以为0,只不过后续实现有稍微不同
  p->capacity = n;//将容量设置好
  return p;//返回新的栈
}

(3)入栈操作:

void StackPush(ST* st, STDataType x)
{
  assert(st);
  if (st->top == st->capacity)//检查栈是否满了
  {
    STDataType* tmp = (STDataType*)realloc(st->a, sizeof(st->capacity) * 2 * sizeof(STDataType));//栈满了进行扩容操作
    if (tmp == NULL)//扩容检查
    {
      perror("realloc");
      exit(-1);
    }
    st->a = tmp;
    st->capacity *= 2;//将容量记录扩大
  }
  st->a[++st->top] = x;//对元素进行入栈,栈顶元素+1
}

(4)出栈操作:

void StackPop(ST* st)
{
  assert(st);//断言是否为空栈
  if (st->top >= 0)
    st->top -= 1;//只要不为空栈,仅仅将栈顶指针-1即可
  return;
}

(5)栈判空:

bool StackEmpty(ST* st)
{
  assert(st);
  return st->top == -1;//当top为-1表示空栈
}

(6)取栈顶元素:

STDataType StackFront(ST* st)
{
  assert(st);
  return st->a[st->top];//直接返回栈顶元素即可
}

(7)打印栈:

void Print(ST* st)//进行打印
{
  assert(st);
  int len = sizeof(st->a) / sizeof(STDataType);
  int i = 0;
  for (i = 0; i <= st->top; i++)
  {
    printf("%3d", i);
  }
  printf("\n");
  for (i = 0; i <= st->top; i++)
  {
    printf("---");
  }
  printf("\n");
  for (i = 0; i <= st->top; i++)
  {
    printf("%3d", st->a[i]);
  }
  printf("\n\n\n");
  return;
}

(8)销毁栈:

void StackClear(ST* st)//由内而外进行销毁,防止内存泄漏
{
  assert(st);
  free(st->a);
  st->a = NULL;
  free(st);
  st = NULL;
  return;
}

(9)测试栈:

void Test()
{
  ST* st = InitNewStack(MAX_OP);
  int i = 0;
  StackPush(st, 1);
  if (!StackEmpty(st))
  {
    StackPush(st, 2);
    StackPush(st, 3);
    StackPush(st, 4);
    StackPush(st, 5);
    StackPush(st, 6);
    StackPush(st, 7);
    StackPush(st, 8);
    Print(st);
  }
  if (!StackEmpty(st))
  {
    StackPop(st);
    StackPop(st);
    StackPop(st);
    Print(st);
  }
  printf("StackFront: %d\n", StackFront(st));
  StackClear(st);
  return;
}

结果打印为:


怎么样,栈的操作实现是不是挺简单的,也没有想象的那么难,下面我们看看链式栈的实现方式吧。

三、链式栈的操作实现

(1)栈的结构定义:

typedef int STDataType;
typedef struct STNode {//栈的元素
  struct STNode* next;//指针域
  STDataType data;//数据域
}Node;
typedef struct Stack {//栈顶指针
  Node* top;
}ST;

(2)栈的初始化:

Node* InitNewNode(STDataType x)//栈节点的初始化
{
  Node* p = (Node*)malloc(sizeof(Node));//开辟新节点
  if (p == NULL)//节点检查
  {
    perror("malloc");
    exit(-1);
  }
  p->next = NULL;//指针域置空
  p->data = x;//数据域赋值
  return p;//返回新节点
}
void InitNewST(ST* st)
{
  assert(st);
  st->top = NULL;//将栈顶指针置空
  return;
}

(3)入栈操作:

void StackPush(ST* st, STDataType x)
{
  assert(st);
  if (st->top == NULL)//栈顶指针为空时
  {
    Node* node = InitNewNode(x);
    st->top = node;
    return;
  }
  Node* node = InitNewNode(x);
  node->next = st->top;//头插法入栈
  st->top = node;//栈顶指针指向头节点
  return;
}

(4)出栈操作:

void StackPop(ST* st)
{
  assert(st);
  if (st->top == NULL)//空栈直接返回
    return;
  if (st->top->next)//栈的元素大于1个
  {
    Node* tmp = st->top;//直接进行头删
    st->top = tmp->next;//指针指向下一个节点
    free(tmp);
    return;
  }
  free(st->top);//只有一个节点直接删除,并将top置空
  st->top = NULL;
  return;
}

(5)栈判空:

bool StackEmpty(ST* st)
{
  assert(st);
  return st->top == NULL;//top不为空指针
}

(6)取栈顶元素:

Node* StackTop(ST* st)
{
  assert(st);
  return st->top;//直接返回栈顶节点
}

(7)打印栈:

void output(ST* st)
{
  assert(st);
  Node* p = st->top;
  int len = 0;
  while (p)
  {
    p = p->next;
    len += 1;
  }
  int i = 0;
  for (i = 0; i < len; i++)
  {
    printf("%3d", i);
    printf("  ");
  }
  printf("\n");
  for (i = 0; i < len; i++)
  {
    printf("-----");
  }
  printf("\n");
  p = st->top;
  while (p)
  {
    printf("%3d", p->data);
    printf("->");
    p = p->next;
  }
  printf("\n\n\n");
  return;
}

(8)销毁栈:

void StackDestroy(ST* st)
{
  assert(st);
  while (st->top)
  {
    Node* tmp = st->top->next;//记录栈顶指针的下一个元素
    free(st->top);//释放掉当前元素
    st->top = tmp;//再把栈顶指针指向下一个元素
  }
  return;
}

(9)测试栈:

void Test()
{
  ST st;
  InitNewST(&st);
  StackPush(&st, 5);
  StackPush(&st, 4);
  StackPush(&st, 3);
  StackPush(&st, 2);
  StackPush(&st, 1);
  output(&st);
  if (!StackEmpty(&st))
  {
    StackPop(&st);
    StackPop(&st);
    output(&st);
  }
  Node* p = StackTop(&st);
  printf("The Stack Top Element is %d\n", p->data);
  StackDestroy(&st);
  return;
}

测试结果:


四、队列的操作实现

这里队列就不实现线性队列了,线性队列可以参考上面的线性栈来写,这里主要实现一下链式队列

(1)队列的结构定义:

typedef int QDataType;
typedef struct QueueNode {//链式队列节点
  struct QueueNode* next;
  QDataType data;
}QNode;
typedef struct Queue {//队列
  QNode* head;//队列头指针
  QNode* tail;//队列尾指针
  int size;//大小
}Que;

(2)队列的初始化:

void InitNewQueue(Que* q)
{
  assert(q);
  q->head = q->tail = NULL;//头尾节点置空
  q->size = 0;//初始化长度为0
  return;
}

(3)入队操作:

void QueuePush(Que* q, QDataType x)
{
  assert(q);
  QNode* newnode = (QNode*)malloc(sizeof(QNode));//开辟新的链式队列节点
  if (newnode == NULL)//检测是否开辟失败
  {
    perror("malloc");
    exit(-1);
  }
  newnode->data = x;
  newnode->next = NULL;
  if (q->tail == NULL)//尾指针为空即队列为空
  {
    q->head = q->tail = newnode;//头尾指针指向同一个元素
  }
  else//队列不为空
  {
    q->tail->next = newnode;//入队
    q->tail = newnode;
  }
  q->size++;//记录大小
  return;
}

(4)出队操作:

void QueuePop(Que* q)
{
  assert(q);
  if (q->head->next == NULL)//只有一个节点
  {
    free(q->head);
    q->head = q->tail = NULL;//将头尾节点置空
  }
  else//大于一个节点数量
  {
    QNode* next = q->head->next;//记录头指针下一个节点
    free(q->head);//释放当前节点
    q->head = next;//头指针移动
  }
  q->size -= 1;//记录大小
  return;
}

(5)队列判空:

bool QueueEmpty(Que* q)
{
  assert(q);
  return q->head == NULL;
}

(6)取队首队尾元素:

QDataType QueueFront(Que* q)
{
  assert(q);
  assert(!QueueEmpty(q));//不为空队列直接返回头指针数据域
  return q->head->data;
}
QDataType QueueBack(Que* q)
{
  assert(q);
  assert(!QueueEmpty(q));//不为空队列直接返回尾指针数据域
  return q->tail->data;
}

(7)队列大小:

QDataType QueueSize(Que* q)
{
  assert(q);
  return q->size;
}

(8)销毁队列:

void QueueDestroy(Que* q)
{
  assert(q);
  while (q->head)//头指针不为空
  {
    QNode* tmp = q->head->next;//记录下头指针下一个节点
    free(q->head);//释放当前节点
    q->head = tmp;
  }
  q->head = q->tail = NULL;//头尾指针置空
  q->size = 0;//数量清零
  return;
}

(9)测试队列:

void Test()
{
  Que q;
  InitNewQueue(&q);
  QueuePush(&q, 1);
  QueuePush(&q, 2);
  QueuePush(&q, 3);
  QueuePush(&q, 4);
  QueuePush(&q, 5);
  QueuePush(&q, 6);
  printf("QueueSize : %d\n", QueueSize(&q));
  while (!QueueEmpty(&q))
  {
    printf("%d ", QueueFront(&q));
    QueuePop(&q);
  }
  printf("\n");
  QueueDestroy(&q);
  return;
}

输出结果为:



如果你看到这里,觉得这篇文章对你有帮助的话,还望能三连支持一下~~

 

 

相关文章
|
7月前
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
219 1
|
5月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
99 0
栈区的非法访问导致的死循环(x64)
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
10月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
456 77
|
9月前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
219 11
|
9月前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
10月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
382 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
10月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
225 7
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
993 9
|
12月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
264 59

热门文章

最新文章