栈和队列

1、栈和队列的定义和特点

  • 栈和队列是两种常用的、重要的数据结构
  • 栈和队列是限定插入和删除只能在表的“端点”线性表

1. 特点

  • 栈(stack):先入后出、后入先出(Last In First Out)(LIFO结构
  • 队列(queue):先入先出,后入后出(First In First Out)(FIFO结构

2. 相关概念

  • 栈:仅在表尾进行插入和删除的线性表

栈顶(Top):栈的表尾

栈底(Base):栈的表头

入()栈:插入元素到栈顶(push)

出()栈:删除栈顶的元素(pop)

  • 队列:仅在表尾插入,在表头删除的线性表

入队:在表尾插入元素

出队:在表头删除元素

2、栈的表示和操作的实现

1. 抽象数据类型定义

数据对象:

D = {ai | ai ∈ ElemSet, i = 1, 2,..., n, n >= 0}

数据关系:

R1 = {<ai-1, ai>|ai - 1, ai ∈ D, i = 2,..., n}
# 约定an端为栈顶,a1为栈底

基本操作:

# 初始化、压栈、弹栈、取栈顶元素等

2. 栈的表示

  • 顺序栈:栈的顺序存储

利用数组依次存放自栈底到栈顶的数据元素,一般栈底存放低地址端

附设top指针指向栈顶,base指针指向栈底,stacksize表示栈可用的最大容量

注:为操作方便,通常top指向栈顶元素之上的下标地址

空栈:base == top

栈满:top - base == stacksize

上溢(overflow):top - base > stacksize

下溢(underflow):top - base < 0

注:一般认为上溢是一种错误,下溢是一种结束条件

#define MAX_SIZE 5
typedef struct Element
{
	int num;
	char string[10];
}Element;

typedef struct SqStack
{
	Element* base; // 栈底指针
	Element* top;  // 栈顶指针
	int stackSize; // 栈的最大容量
}SqStack;
  • 链栈:栈的链式存储

    利用单链表存放数据元素,只能在链表头部进行操作

    栈的元素通过链表结点存储,通过栈顶指针指向栈顶下一结点指向上一次存储的元素

    注:该链表无需再设头结点

typedef struct StackNode
{
	Element data;
	struct StackNode* next;
}StackNode, * LinkStack;

3. 栈的操作

1. 顺序栈
  • InitStack(SqStack* s)
  1. 申请一个容量为MAXSIZE的数组空间,base指向栈底
  2. top指向栈底,表示空栈
  3. stacksize设为MAXSIZE
Status InitSqStack(SqStack* s)
{
	Element* p = (Element*)malloc(sizeof(Element) * MAX_SIZE);
	if (p)
	{
		s->base = p;
		s->top = s->base;
		s->stackSize = MAX_SIZE;
		return OK;
	}
	else
	{
		return ERROR;
	}
}
  • IsEmpty(SqStack* s)

    当top == base时,栈为空栈

    Status IsEmpty(const SqStack* s)
    {
    	return s->base == s->top ? TRUE : FALSE;
    }
    
    
  • GetLength(SqStack* s)

    指针间的差值(top - base)为两指针中间的元素个数

    int GetLength(const SqStack* s)
    {
    	return s->top - s->base;
    }
    
  • CleanStack(SqStack* s)

    将top重新置于base的位置,即令top = base

    void CleanSqStack(SqStack* s)
    {
    	s->top = s->base;
    }
    
  • DestroyStack(SqStack** s)

    释放内存空间

    void DestroySqStack(SqStack** sp)
    {
    	if ((*sp)->base)
    	{
    		free((*sp)->base);
    		*sp = NULL;
    	}
    }
    
  • Push(SqStack* s, Element* e)

    1. 判断是否满栈,若满则报错,或再开辟空间
    2. 将新元素压栈,栈顶指针+1
    #define APPEND_SIZE 5
    Status SqPush(SqStack* s, Element* e)
    {
    	// 判断
    	if (GetLength(s) == s->stackSize)
    	{
    		// 扩容
    		Element* p = realloc(s->base, sizeof(Element) * (s->stackSize + APPEND_SIZE));
    		if (p)
    		{
    			s->base = p;
    			s->top = s->base + s->stackSize;
    			p = NULL;
    			s->stackSize += APPEND_SIZE;
    		}
    		else
    		{
    			return ERROR;
    		}
    	}
    	// 压栈
    	memcpy((s->top)++, e, sizeof(Element));
    	return OK;
    
    }
    
  • Pop(SqStack* s, Element* e)

    1. 判断是否空栈,若空则报错
    2. 将元素弹栈,栈顶指针-1
    Status SqPop(SqStack* s, Element* e)
    {
    	// 判断
    	if (IsEmpty(s))
    	{
    		return ERROR;
    	}
    	else
    	{
    		// 弹栈
    		memcpy(e, --(s->top), sizeof(Element));
    		return OK;
    	}
    }
    
2. 链栈
  • InitStack(LinkStack* s)

    构造一个空栈并将栈顶指针置为空

    void InitLinkStack(LinkStack* s)
    {
    	*s = NULL;
    }
    
  • IsLinkStackEmpty(LiinkStack s)

    栈顶元素为空则为空表

    Status IsLinkStackEmpty(const LinkStack s)
    {
    	return s ? FALSE : TRUE;
    }
    
  • LinkStackPush(LinkStack* s, Element* data)

    1. 创建node结点,并将其next标记为s的指向
    2. 修改s的指向,使其指向栈顶结点
    Status LinkStackPush(LinkStack* s, Element* data)
    {
    	StackNode* p = (StackNode*)malloc(sizeof(StackNode));
    	if (p)
    	{
    		memcpy(&(p->data), data, sizeof(Element));
    		p->next = *s;
    		*s = p;
    		return OK;
    	}
    	else
    	{
    		return ERROR;
    	}
    }
    
  • LinkStackPop(LinkStack* s, Element* data)

    1. 判断是否下溢
    2. 释放栈顶指针
    3. 修改s的指向
    void LinkStackPop(LinkStack* s, Element* data)
    {
    	if (s)
    	{
    		StackNode* p = *s;
    		memcpy(data, &((*s)->data), sizeof(Element));
    		*s = (*s)->next;
    		free(p);
    		return OK;
    	}
    	// 下溢
    	else
    	{
    		return ERROR;
    	}
    }
    
  • GetTop(LinkStack s, Element* top)

    1. 判断栈顶是否为空
    2. 取出栈顶数据域
    void GetTop(const LinkStack s, Element* top)
    {
    	if (s)
    	{
    		memcpy(top, &(s->data), sizeof(Element));
    	}
    	else
    	{
    		return ERROR;
    	}
    }
    

4. 栈与递归

  • 递归:若一个对象部分地包含它自己,或用它自己给自己定义,则成这个对象是递归的结构

  • 递归的使用场景

  1. 函数递归
// 阶乘
long Fact(long n)
{
	assert(n >= 0);
	if (n > 1)
	{
		return Fact(n - 1) * n;
	}
	else
	{
		return 1;
	}
}

// 斐波那契数列
long Fib(long n)
{
	assert(n >= 1);
	if (n > 2)
	{
		return Fib(n - 2) + Fib(n - 1);
	}
	else
	{
		return 1;
	}
}
  1. 具有递归特性的数据结构

二叉树、广义表......

  1. 可递归求解的问题

迷宫问题、汉诺塔问题......

  • 递归思想:分而治之

  • 递归必备条件:

    1. 能将一个问题转化为新问题,而新问题与原问题的解法相同或类同,即有一定规律
    2. 通过转化问题以实现简化
    3. 必须存在一个明确的递归出口
  • 递归的过程:

    调用(压栈) -> 运算 -> 返回(弹栈)

  • 递归->非递归:

    由于递归相对时间开销大,故可以将递归问题转化为非递归问题解决

    1. 尾递归、单项递归 ==> 循环
    2. 自用栈模拟系统运行时栈

3、队列的表示和操作的实现

队列(Queue)特点:在队尾(Rear)插入元素,在队头(Front)删除元素

1. 抽象数据类型定义

数据对象:

D = {ai | ai ∈ ElemSet, i = 1, 2,..., n, n >= 0}

数据关系:

R1 = {<ai-1, ai>|ai - 1, ai ∈ D, i = 2,..., n}
# 约定a1端为队头,an为栈队尾

基本操作:

# 初始化、入队、出队、遍历等

2. 队列的表示

  • 顺序队列
typedef struct SqQueue
{
	Data* base;	// 初始化的动态内存空间
	int front;	// 队头下标
	int rear;	// 队尾下标
}SqQueue;
  • 链队列
// 链表结点
typedef struct QNode
{
	Data data;
	struct QNode* next;
}QNode, * QueuePtr;

// 链队
typedef struct LinkQueue
{
	QueuePtr front;
	QueuePtr rear;
}LinkQueue;

3. 队列的操作

1. 顺序队列

a. 空队列:rear = front = 0

b. 入队:rear对应的元素赋值,rear++

c. 出队:front++

d. 溢出分析:

  • 真溢出:front = 0 && rear == MAXQSIZE
  • 假溢出:front != 0 && rear == MAXQSIZE

e. 假溢出解决方案:

  • 出队时所有元素向前移动(时间效率差)
  • 循环队列:当假溢出,令rear = 0,即重新利用之前的空间

f. 队空和队满

  • 设置标识符
  • 少用一个元素空间,即队空时队尾与队头重合,队满时队尾+1与队头重合
  • InitSqQueue(SqQueue* q)
  1. 分配base空间,base指向数组首地址
  2. 头指针和尾指针置零,表示队空
Status InitSqQueue(SqQueue* q)
{
	q->base = (Data*)malloc(sizeof(Data) * MAXQSIZE);
	if (q->base)
	{
		q->front = 0;
		q->rear = 0;
		return OK;
	}
	else
	{
		return ERROR;
	}
}
  • GetSqQueueLength(SqQueue* q)

    int GetSqQueueLength(const SqQueue* q)
    {
    	return (q->rear - q->front + MAXQSIZE) % MAXQSIZE;
    }
    

    注:由于采用循环队列,故不可之间通过指针相减计算长度

  • IsSqQueueFull(SqQueue* q)

    若尾指针+1等于头指针则判断其为队满

    Status IsSqQueueFull(const SqQueue* q)
    {
    	return (q->rear + 1) % MAXQSIZE == q->front ? TRUE : FALSE;
    }
    
  • IsSqEmpty(SqQueue* q)

    若尾指针等于头指针则判断其为队空

    Status IsSqQueueEmpty(const SqQueue* q)
    {
    	return q->rear == q->front ? TRUE : FALSE;
    }
    
  • EnSqQueue(SqQueue* q, Data* data)

    1. 判读是否队满
    2. 新元素加入队尾,队尾指针+1
    Status EnSqQueue(SqQueue* q, Data* data)
    {
    	if (IsSqQueueFull(q))
    		return ERROR;
    	memcpy(q->base + q->rear, data, sizeof(Data));
    	q->rear = (q->rear + 1) % MAXQSIZE;
    	return OK;
    }
    
  • DeSqQueue(SqQueue* q, Data* data)

    1. 判断是否队空
    2. 拷贝队头指针所指数据,队头指针+1
    Status DeSqQueue(SqQueue* q, Data* data)
    {
    	if (IsSqQueueEmpty(q))
    		return ERROR;
    	memcpy(data, q->base + q->front, sizeof(Data));
    	q->front = (q->front + 1) % MAXQSIZE;
        return OK;
    }
    
  • GetFront(SqQueue* q, Data* data)

    Status GetFront(const SqQueue* q, Data* data)
    {
    	if (IsSqQueueEmpty(q))
    		return ERROR;
    	memcpy(data, q->base + q->front, sizeof(Data));
    	return OK;
    }
    
2. 链队

a. 空队列:rear == front

b. 入队:rear->next = x

c. 出队:free(front)

  • InitLinkQueue(LinkQueue* q)

    1. 创建头结点
    2. 首尾指针均指向该头结点
    Status InitLinkQueue(LinkQueue* q)
    {
    	QueuePtr p = (QueuePtr)malloc(sizeof(QNode));
    	if (p)
    	{
    		p->next = NULL;
    		q->front = p;
    		q->rear = p;
    		return OK;
    	}
    	else
    	{
    		return ERROR;
    	}
    }
    
  • DestroyLinkQueue(LinkQueue* q)

    从头依次释放所有结点

    void DestroyLinkQueue(LinkQueue* q)
    {
    	QNode* p = q->front;
    	QNode* tmp;
    	while (p)
    	{
    		tmp = p;
    		p = p->next;
    		free(tmp);
    	}
    	q->front = NULL;
    	q->rear = NULL;
    }
    
  • EnLinkQueue(LinkQueue* q, Data* data)

    1. 创建结点
    2. 将结点添加至队尾,队尾指针指向该结点
    Status EnLinkQueue(LinkQueue* q, Data* data)
    {
    	QNode* node = (QNode*)malloc(sizeof(QNode));
    	if (node)
    	{
    		memcpy(&(node->data), data, sizeof(Data));
    		node->next = NULL;
    		q->rear->next = node;
    		q->rear = q->rear->next;
    
    		return OK;
    	}
    	else
    	{
    		return ERROR;
    	}
    }
    
  • DeLinkQueue(LinkQueue* q, Data* data)

    1. 判断是否为空
    2. 拷贝结点
    3. 将队头指针指向next结点,原结点释放
    Status DeLinkQueue(LinkQueue* q, Data* data)
    {
    	if (IsLinkQueueEmpty(q))
    	{
    		return ERROR;
    	}
    	else
    	{
    		QNode* p = q->front->next;
    		memcpy(data, &(p->data), sizeof(Data));
    		q->front->next = p->next;
    		if (q->rear == p)
    		{
    			q->rear = q->front;
    		}
    		free(p);
    		return OK;
    	}
    }
    

    注:当出队至队列清空(即删除元素为队尾结点),则需使队头和队尾指针相等,以视为空表

  • GetFront(LinkQueue* q, Data* data)

Status GetFront_link(LinkQueue* q, Data* data)
{
	if (IsLinkQueueEmpty(q))
	{
		return ERROR;
	}
	else
	{
		memcpy(data, &(q->front->next->data), sizeof(Data));
		return OK;
	}
}