最近由于实验室事情缠身,每天只能挤出两个小时来学数据结构。想赶快学完,所以学的比较粗糙。但是感觉还是应该停下来总结一下,欲速则不达啊。如果只是粗略的看完,估计对自己的提高不大。由于在平时的学习中用的并不多,所以对数据结构的理解不够深入,只能算是入门的介绍性,帮助自己梳理一下。这一篇主要将讲栈和队列。
栈和队列本质上也是线性表,只是它们是操作受限的线性表。个人的感觉是栈和队列相对链表还说还简单一些,可能自己的理解还不深,在应用上用的不多,初学者,大家见谅。
栈是限定在表尾进行插入或删除操作的线性表。表尾称为栈顶,表头称为栈底。栈是一种后进先出的线性表。
和线性表一样,栈也可以分为顺序栈和链栈。但是因为栈规定只能从栈顶进行插入和删除操作,所以顺序栈完全可以满足需求。在栈中需要注意的是栈顶指针指向的地址是没有内容的,但是分配了空间,如下图所示。
不管是哪种数据结构,最重要的是结构的定义。顺序栈的定义如下:
typedef char SElemType;
typedef struct stack
{
SElemType *base;
SElemType *top;
int stacksize;
}SqStack;
其中,stacksize指示栈的当前可使用的最大容量。base和top分别指示栈底指针和栈顶指针。
栈的基本操作函数实现:
//初始化栈
Status InitialStack(SqStack *stack)
{
(*stack).base = (*stack).top = (SElemType*)malloc(sizeof(SElemType)*MAXSIZE);
if(!(*stack).base)
{
exit(OVERFLOW);
}
(*stack).stacksize = MAXSIZE;
return OK;
}
入栈
Status InsertElem(SqStack *stack,SElemType e)
{
char *q = NULL;
//需要判断是否为栈满
if(((*stack).top-(*stack).base)>=(*stack).stacksize)
{
q = (SElemType*)realloc((*stack).base,sizeof(SElemType)*((*stack).stacksize+INCREMENTSIZE));
if(!(*stack).base)
{
exit(OVERFLOW);
}
(*stack).base = q;
(*stack).top = ((*stack).base) + (*stack).stacksize;//注意修改栈顶指针
(*stack).stacksize += INCREMENTSIZE; //注意修改栈的大小
}
*(*stack).top = e;
(*stack).top++;
return OK;
}
在这里我犯了低级错误。sizeof()这个函数的参数应该是变量的类型,如int,char等。我一开始直接填入了常量,即我写的是sizeof((*stack).stacksize+INCREMENTSIZE),导致最后运行结果出现了问题,因为这个时候默认的是int类型,如果你使用的是char型,那么就出现问题了。
//出栈
Status DeleteElem(SqStack *stack, SElemType *e)
{
if((*stack).base == (*stack).top)
{
return ERROR;
}
(*stack).top--;//栈顶指针首先要向下移动一位,才能指示有效的填充数据
*e = *(*stack).top;
return OK;
}
栈的应用就先不讲了,现在的理解深度还达不到。队列:
队列是一种先进先出的线性表。它只允许在队尾进行插入操作,在队头进行删除操作。当然,队列肯定也有两种结构,这里主要以链式队列进行介绍。
队列的结构如下所示:
typedef struct QNode
{
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
//只需要头指针和尾指针
typedef struct
{
QueuePtr front;
QueuePtr rear;
}LinkQueue;
队列的主要基本操作:/*
初始化链队,注意队头指针的下一个指向为空。
*/
Status InitQueue(LinkQueue &Q)
{
Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode));
if(!Q.front)
{
exit(OVERFLOW);
}
Q.front->next = NULL;
return OK;
}
//获得队头元素,我这里认为是front指针的下一个元素
Status GetHead(LinkQueue &Q, string &e)
{
if(Q.front == Q.rear)
{
return ERROR;
}
e = *(Q.front->next->data);
return OK;
}
//插入元素到队列中
Status EnQueue(LinkQueue &Q,string e)
{
QueuePtr p;
p = (QueuePtr)malloc(sizeof(QNode));
if(!p)
{
exit(OVERFLOW);
}
string *element = new string;//给每一个新的字符串都需要分配一个空间,让队列中的data指针指向它
*element = e;
p -> data = element;
p -> next = NULL;
Q.rear->next = p;
Q.rear = p;
return OK;
}
/*
删除元素,只能从队头进行删除。注意要考虑删除的是最后一个元素的情况。要修改队尾指针
*/
Status DeQueue(LinkQueue &Q, string &e)
{
QueuePtr p = Q.front->next;
if(Q.front == Q.rear)
{
return ERROR;
}
e = *(p->data);
Q.front->next = p->next;
//consider p points to the last element
if(p == Q.rear)
{
Q.front = Q.rear;
}
delete p->data;
free(p);
return OK;
}
/*
对队列中的元素进行遍历操作
*/
Status QueueTraverse(LinkQueue Q, Status (*visit)(QElemType))
{
QueuePtr p = Q.front->next;
int result = 0;
while(p != NULL)
{
result = visit(p->data);
if(!result)
{
break;
}
p = p->next;
}
return OK;
}
在队列中,还有一种常用的类型是循环队列。这种队列的主要作用是提高存储空间的利用率。另外,这种队列在分层机制中可用于缓冲区,通过读写指针控制有效数据的处理。
首先来看不用循环队列可能的问题,如下所示:
在队列中,存在队头和队尾两个指针,假设分配了一个4个自己的数组空间。在正常情况下,应该如左图所示。当随着进行插入操作,就会出现右图的情况,这个时候,如果我们想再次插入数据,由于队尾指针已指向分配的最大地址处,就会发生数组越界的错误。但是这个时候存储空间却没有用完,就会造成空间浪费。
这个时候,如果将这段存储空间想像成一个环形队列,可以有效的避免这个问题。
我们规定,当头指针和尾指针相等的时候,队列为空。而当队列满的时候,为了和队列为空的时候进行区别,有两种方法:
1. 设定标志位来指示空还是满,这个时候队列满也是头尾指针相等。
2. 以队头指针在队尾指针的下一位上认为是满,注意谁在前谁在后。
一般情况下,循环队列一般用于顺序存储结构中,队头队尾分别指示的是数组的下标。链队列主要用于无法预估所用大小的情况,但是感觉这个和循环链表相似。
循环队列的数据结构定义如下
typedef struct queue
{
ElemType front;
ElemType rear;
ElemType *base;
int queuesize;
}Queue, *SqQueue;
循环队列的基本操作如下://初始化队列
Status InitialQueue(Queue *queue)
{
(*queue).base = (ElemType*)malloc(MAXSIZE*sizeof(ElemType));
if(!(*queue).base)
{
exit(OVERFLOW);
}
(*queue).front = (*queue).rear = 0;
(*queue).queuesize = MAXSIZE;
return OK;
}
//插入元素到队列中
Status InsertElem(Queue *queue, ElemType e)
{
int location;
location = ((*queue).front+1)%(*queue).queuesize;
if(location == 0)
{
(*queue).base = (ElemType*)realloc((*queue).base,((*queue).queuesize + INCREMENTSIZE)*sizeof(ElemType));
if(!(*queue).base)
{
exit(OVERFLOW);
}
}
(*queue).queuesize += INCREMENTSIZE;
location = (*queue).front % (*queue).queuesize;
(*queue).base[location] = e;
(*queue).front = ((*queue).front + 1)%(*queue).queuesize;
return OK;
}
//删除队列中的元素
Status DeleteElem(Queue *queue, ElemType *e)
{
//如果队列是空
if((*queue).front == (*queue).rear)
{
return ERROR;
}
*e = (*queue).base[(*queue).rear];
(*queue).rear = ((*queue).rear + 1)%(*queue).queuesize;
return OK;
}
可见,在循环队列中,最重要的就是要搞好数组的下标操作,不是很复杂,可能是我用的不够深入,后续再进行修改。