本文旨在讲解基本的数据结构知识(主要是栈与队列),使用C/C++语言从零实现各种数据结构,适合于代码能力薄弱的初学者以及想要复习数据结构的读者们。笔者能力有限,所讲述的内容也无法面面俱到,如有表述错误,欢迎指正!
一、栈
1、栈的基本概念
栈是一种特殊的线性表,其只能在表的一端进行插入和删除,这一端通常被称为栈顶,而另一端称为栈底,即只能在栈顶进行插入(入栈)和删除(出栈)。故栈又称为先进后出(FIFO)的线性表(先进栈的元素靠近栈底,后进栈的元素靠近栈顶)。
栈有两种实现方式:顺序栈(顺序存储)和链栈(链式存储)。
2、顺序栈
(1)结构定义
顺序栈是使用顺序存储结构实现的栈,底层使用数组实现,需要定义两个指针:base指针(栈底指针)永远指向栈底,top指针(栈顶指针)永远指向栈顶的上一个位置。
由于入栈与出栈均在栈顶进行,所以top指针需要在入栈和出栈后移动,而base指针不变,故设定base指针为底层数组的首地址指针。结构定义代码如下:
const int MAXSIZE = 100;
typedef int SElemType;
/* 顺序栈 */
typedef struct
{
SElemType* base; //栈底指针
SElemType* top; //栈顶指针
int length; //栈的长度
}SqStack;
(2)初始化
使用C++的new关键字初始化base指针,并令top指针等于base指针(栈为空时base==top)
void InitStack(SqStack& S)
{
S.base = new SElemType[MAXSIZE];
if (!S.base)
return;
S.top = S.base;
S.length = 0;
}
(3)入栈
入栈前需要判断栈是否为满,若满则无法入栈(top-base=MAXSIZE则栈满)。若允许入栈,则在top指针指向的位置(top指针永远指向栈顶的上一个位置)插入对应数据,并修改top指针,即top自增。
void Push(SqStack& S, SElemType e)
{
if (Full(S)) //S.top - S.base == MAXSIZE
return;
*S.top = e;
S.top++;
//可写为:*S.top++=e;
++S.length;
}
(4)出栈
出栈前需要判断栈是否为空,若空则无法出栈(top==base则栈空)。若允许出栈,则删除top指针指向的数据,并修改top指针,即top自减。
int Pop(SqStack& S)
{
if (Empty(S)) //S.top == S.base
return -1;
S.top--;
int e= *S.top;
//可写为:e=*--S.top;
--S.length;
return e;
}
(5)取栈顶元素
栈顶元素是(top-1)所指向的元素,在取栈顶元素前同样需要判断是否栈为空,代码如下:
int GetTop(const SqStack& S)
{
if (Empty(S))
return -1;
return *(S.top - 1);
}
3、链栈
(1)结构定义
链栈是使用链式存储结构的栈,底层使用链表实现。链栈的核心结构是链栈结点,与链表结点类似,包括数据域data与指针域next,结构定义如下:
/* 链栈 */
typedef struct SNode
{
SElemType data;
struct SNode* next;
}SNode, * LinkStack;
(2)初始化
链栈不需要像链表那样的头结点(因为栈的入栈出栈操作均在栈顶,插入采用头插法即可,不需要额外的头结点),故初始化只需要令链栈S(指向链栈结点的指针)为空即可。
void InitStack(LinkStack& S)
{
S = NULL;
}
(3)入栈
链栈的入栈采用头插法,代码如下:
void Push(LinkStack& S, SElemType e)
{
SNode* p = new SNode;
p->data = e;
p->next = S;
S = p;
}
(4)出栈
链栈的出栈操作只需要修改S,令其指向栈顶结点的下一个结点,即S=S->next,并将原本的栈顶结点释放,代码如下:
int Pop(LinkStack& S)
{
if (!S)
return -1;
int e = S->data;
SNode* p = S;
S = S->next;
delete p;
return e;
}
(5)取栈顶元素
栈顶结点即为链栈S指针指向的结点,代码如下:
int GetTop(const LinkStack& S)
{
if (S != NULL)
return S->data;
return -1;
}
二、队列
1、队列的基本概念
队列是一种先进先出(FIFO)的线性表,只允许在一端删除(出队),在另一端插入(入队),出队的那一端称为队头,入队的那一端称为队尾。
队列有两种实现方式:顺序结构(循环队列)和链式结构(链队列)。
2、循环队列
(1)结构定义
循环队列是采用顺序结构的队列,采用循环队列的好处是最大限度的使用存储空间。在结构定义中,需要一个存储数据的数组的首地址指针base,以及两个指针(使用数组的下标表示)front和rear,前者始终指向队头,后者始终指向对头的后一个位置。
const int MAXSIZE = 100;
typedef int QElemType;
/* 顺序队 */
typedef struct
{
QElemType* base;
int front;
int rear;
}SqQueue;
(2)初始化
使用C++的new关键字初始化base,并指定front=rear=0(当队空时front==rear)。
void InitQueue(SqQueue& Q)
{
Q.base = new QElemType[MAXSIZE];
if (!Q.base)
return;
Q.front = Q.rear = 0;
}
(3)求队列长度
int Length(const SqQueue& Q)
{
return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}
(4)判断队空
bool Empty(const SqQueue& Q)
{
return Q.front == Q.rear;
}
(5)判断队满
bool Full(const SqQueue& Q)
{
return (Q.rear + 1) % MAXSIZE == Q.front;
}
(6)入队
入队前需判断是否队满,若队满则无法入队。若允许入队,由于rear指向队尾的后一个位置,故在rear指向位置插入数据,并更新rear,即rear=(rear+1)%MAXSIZE。
void EnQueue(SqQueue& Q, QElemType e)
{
if (Full(Q))
return;
Q.base[Q.rear] = e;
Q.rear = (Q.rear + 1) % MAXSIZE;
}
(7)出队
出队前需判断是否队空,若队空则无法出队。若允许出队,则更新队头front,即front=(front+1)%MAXSIZE。
int DeQueue(SqQueue& Q)
{
if (Empty(Q))
return -1;
int e = Q.base[Q.front];
Q.front = (Q.front + 1) % MAXSIZE;
return e;
}
(8)取队头元素
队头元素即为front指向的元素,代码如下:
int GetFront(SqQueue Q)
{
if (Empty(Q))
return -1;
return Q.base[Q.front];
}
3、链队列
(1)结构定义
链队列是采用链式存储结构实现的队列,一个链队列需要两个分别指向队头与队尾的指针(分别称为头指针和尾指针),对于链队列,采用头结点,并让头指针始终指向头结点。
/* 链队 */
typedef struct QNode //链队结点
{
QElemType data;
struct QNode* next;
}QNode;
typedef struct
{
QNode* front;
QNode* rear;
int length;
}LinkQueue; //链队
(2)初始化
链队的front指针指向头结点(头结点不计入链队的长度),而rear指针指向最后一个结点,当链队为空时,只有一个头结点,此时front==rear。代码如下:
void InitQueue(LinkQueue& Q)
{
Q.front = Q.rear = new QNode;
Q.front->next = NULL;
Q.length = 0;
}
(3)入队
链队入队采用尾插法,所以需要设置尾指针rear(这与链栈不同,链栈的入栈操作采用头插法),插入的方法与链表类似,以下是代码实现:
void EnQueue(LinkQueue& Q, QElemType e)
{
QNode* p = new QNode;
p->data = e;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
++(Q.length);
}
(4)出队
链队出队采用删除第一个结点的方式,即让头结点的下一个结点为第二个结点,并释放第一个结点的空间,具体代码如下:
int DeQueue(LinkQueue& Q)
{
if (Empty(Q)) //Q.front == Q.rear为空
return -1;
QNode* p = new QNode;
p = Q.front->next;
int e = p->data;
Q.front->next = p->next;
if (Q.rear == p)
Q.rear = Q.front;
delete p;
--(Q.length);
return e;
}
(5)取队头元素
链队队头是链队头结点的下一个结点元素,实现代码如下:
int GetFront(LinkQueue Q)
{
if (Q.front == Q.rear)
return -1;
return Q.front->next->data;
}