数据结构栈和队列的基本算法实现
限定性线性表——栈
栈的定义
栈作为一种限定性的线性表,是将线性表的插入和删除操作限制为仅在表的一端进行。
基本算法演示
/*
栈的常见操作:
1.初始化栈
2.元素进栈
3.元素出栈
4.栈的遍历
5.判断栈是否为空栈
6.清空整个栈
*/
# include <stdio.h>
# include <stdlib.h>
typedef struct Node
{
int date;
struct Node * pNext;
}NODE,* PNODE;
typedef struct Stack
{
PNODE pTop;
PNODE pBottom;
}STACK, * PSTACK;
void init(PSTACK pS)
{
pS->pTop = (PNODE)malloc(sizeof(NODE));
if (NULL == pS->pTop)
{
printf("动态内存分配失败");
exit(-1);
}
else
{
pS->pBottom = pS->pTop;//如果分配成功的话,这两个节点都指向 同一个节点(头节点)
pS->pTop->pNext = NULL; //模拟最后的那个“头节点”pS->Bottom->pNext = NUll 也是一样
}
}
void push(PSTACK pS, int val)
{
PNODE pNew = (PNODE)malloc(sizeof(NODE));
pNew->date = val;
pNew->pNext = pS->pTop;//按照逻辑来的话,进栈时,新的元素会在栈顶,所以要pNext->pTop
pS->pTop = pNew; //把新的入栈的元素作为栈的top
}
void traverse(PSTACK pS)
{
PNODE p = pS->pTop;
while (p != pS->pBottom)
{
printf("%d ",p->date);
p = p->pNext;
}
printf("\\n");
return;
}
bool empty(PSTACK pS)
{
if(pS->pTop == pS->pBottom)
return true; //如果为空,返回true(证明是空的)
else
return false;
}
//把pS所指向的栈出栈一次,并把出栈的元素存入pVal形参所指向的变量中,
//如果出栈成功,返回true,否则返回false
bool pop(PSTACK pS,int * pVal)
{
if (empty(pS))//pS 本身存放的就是S的地址,直接返回给empty()函数
{
return false;
}
else
{
//首先需要一个指针r来指向 栈顶元素,但是如果是pS->pTop = pS->pNext 的话
//内存就没有释放,造成内存泄漏,所以这个方法不可取。
PNODE r = pS->pTop;
*pVal = r->date;
pS->pTop = r->pNext;//r 指向栈顶,所以把r的next域赋给栈顶
free(r);
r = NULL;
return true;
}
}
//清空
void clear(PSTACK pS)
{
if(empty(pS))
{
return;
}
else
{
PNODE p = pS->pTop;
PNODE q = NULL;
while(p!=pS->pBottom)
{
q = p->pNext;
free(p);
p = q;
}
//清空之后pTop 的值一定要改写
pS->pTop = pS->pBottom;
}
}
int main(void)
{
STACK S;
int val;
init(&S);//对栈进行初始化 ,去地址才会放入元素
push(&S,1);
push(&S,2);
push(&S,3);
push(&S,4);
push(&S,5);
push(&S,6);
traverse(&S);
clear(&S); //清空之后就会提示出栈失败
if(pop(&S,&val))//需要判断是否为空,如果空了就无法出栈,所以需要一个返回值,但是进栈不会满的。
{
printf("出栈成功,出栈的元素是%d\\n",val);
}
else
{
printf("出栈失败!\\n");
}
traverse(&S);
return 0;
}
运行演示
算法小结
所有的算法已经给出,值得注意的是在clear()
算法中 PNODE p = pS->pTop;PNODE q = NULL;
定义了两个指针,以为一个被free掉后就无法进行操作了,对于pop()
函数就没有这个问题,因为它只执行了一次 ,也就是说,只进行了一次出栈操作,然后操作完成之后才把r指针给free掉的,所以一个指针就可以完成这个操作。
限定性线性表——队列
队列是另外一种限定性的线性表,它只允许在表的一端插入元素,在另外一端删除元素。
基本算法演示(链队列)
/*
队列的常见操作:
1.初始化队列
2.元素进队列
3.元素出队列
4.队列的遍历
*/
#include <stdio.h>
#include <stdlib.h>
typedef struct Node
{
int date;
struct Node * pNext;
}NODE, * PNODE;//LInkQueueNode
typedef struct LinkQueue
{
PNODE pFront;
PNODE pRear;
}LINKQUEUE,* PLINKQUEUE;
bool InitQueue(PLINKQUEUE pQ)
{
pQ->pFront= (PNODE)malloc(sizeof(NODE));
if (NULL == pQ->pFront)
{
printf("动态内存分配失败!");
exit(-1);
}
else if(NULL != pQ->pFront)
{
pQ->pRear = pQ->pFront;
pQ->pFront->pNext = NULL;
return (true);
}
else
return (false);//溢出
}
bool EnterQueue(PLINKQUEUE pQ ,int x)
{
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if(pNew != NULL)
{
pNew->date = x;
pNew->pNext = NULL;
pQ->pRear->pNext = pNew;
pQ->pRear = pNew;
return (true);
}
else
return false;
}
void traverse(PLINKQUEUE pQ)
{
PNODE p = pQ->pFront->pNext;//注意这个地方队列和栈的不同
//PNODE p = pS->pTop; while (p != pS->pBottom) 这是栈的条件
while (p)
{
printf("%d ",p->date);
p = p->pNext;
}
printf("\\n");
return;
}
bool DeleteQueue(PLINKQUEUE pQ,int * x) //出队
{
PNODE p;
if (pQ->pRear==NULL) //队列为空
return false;
p=pQ->pFront; //p指向第一个数据节点
if (pQ->pFront==pQ->pRear) //队列中只有一个节点时
pQ->pFront=pQ->pRear=NULL;//必须要更改值,不然指针就会指向他处
else //队列中有多个节点时
pQ->pFront=pQ->pFront->pNext;
*x = p->date;
free(p);
return true;
}
int main()
{
LINKQUEUE Q;
int x;
InitQueue(&Q);
EnterQueue(&Q,10);
EnterQueue(&Q,20);
EnterQueue(&Q,30);
EnterQueue(&Q,40);
traverse(&Q);
DeleteQueue(&Q,&x);
traverse(&Q);
return 0;
}
运行演示
算法小结
队列的操作和栈的操作基本原理上是差不多的,值得注意的是再对队列进行遍历的话和栈的遍历稍微有点差别。其中需要注意的地方已经在代码块中进行了说明。
基本算法演示(循环队列)
/*
1.循环队列初始化
2.循环队列进队
3.循环队列出队
4.循环队列遍历
5.循环队列长度
*/
// 实现循环队列
#include <stdio.h>
#include <stdlib.h>
#define MaxSize 21
typedef int ElementType;
typedef struct {
int data[MaxSize];
int rear; // 队尾指针
int front; // 队头指针
}Queue,*L;
void InitQueue(Queue * Q )
{
Q->front = Q->rear = 0;
}
// 元素入队
void AddQ(Queue *PtrQ, int item)
{
if( (PtrQ->rear+1)%MaxSize == PtrQ->front )
{
printf("队列满.\\n");
return;
}
PtrQ->rear = (PtrQ->rear+1) % MaxSize;
PtrQ->data[PtrQ->rear] = item;
}
// 删除队头元素并把队头元素返回
int DeleteQ( Queue *PtrQ )
{
if( PtrQ->front == PtrQ->rear )
{
printf("队列空.\\n");
return -1;
}
else {
PtrQ->front = (PtrQ->front+1) % MaxSize;
return PtrQ->data[PtrQ->front];
}
}
// 队列元素的遍历
void print(Queue *PtrQ)
{
int i = PtrQ->front;
if( PtrQ->front == PtrQ->rear )
{
printf("队列空.");
return;
}
printf("队列存在的元素如下:");
while( i != PtrQ->rear)
{
printf("%d ", PtrQ->data[i+1]);
i++;
i = i % MaxSize;
}
return;
}
int len(Queue *PtrQ)
{
return (PtrQ->rear-PtrQ->front+MaxSize)%MaxSize;
}
int main()
{
Queue Q; //注意不是Queue * Q; 因为数组本身就是地址吧~(emmmm,应该是,求大佬解答)
int length;
length = len(&Q); //用Queue * Q 的话会报错
InitQueue(&Q);
AddQ(&Q,1);
AddQ(&Q,2);
AddQ(&Q,3);
AddQ(&Q,4);
print(&Q);
DeleteQ(&Q);//出队一次
print(&Q);
printf("\\n循环队列的长度为%d",length);
return 0;
}
运行演示
算法小结
循环队列和链队列基本是一致的,之所以引入“循环队列”是因为,对于顺序列会存在“假溢出的现象”。相关概念不多做解释,原理主要在数据结构-用C语言描述(第二版)[耿国华] 一书的p101-103。值得注意的是,在main
方法中和链队列不同的是Queue Q;
个人认为是利用数组模拟的原因,因为数组本身也是利用地址传值嘛。关于循环队列长度计算:当rear大于front时,循环队列的长度:rear-front,当rear小于front时,循环队列的长度:分为两类计算 0+rear和Quesize-front即rear-front+Quesize。总的来说,总长度是(rear-front+Quesize)%Quesize
循环链表拓展
头节点循环链表
带头结点的循环链表表示队列, 并且只设一个指针指向队尾元素结点, 试编写相应的队列初始化,入队列和出队列的算法。
/* 数据结构算法题(假设以带头结点的循环链表表示队列,
* 并且只设一个指针指向队尾元素结点(注意不设头指针)
* 试编写相应的队列初始化,入队列和出队列的算法!)
*/
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define OK 1
#define ERROR 0
typedef int QElemType;
typedef int Status;
typedef struct QNode
{
QElemType data;
struct QNode * rear;
struct QNode * next;
}QNode,*LinkQueue;
//链式队列的初始化
Status InitLinkQueue(LinkQueue * L)
{
(*L)=(LinkQueue)malloc(sizeof(QNode));
if((*L)==NULL)
{
printf("内存分配失败!\\n");
return OK;
}
(*L)->rear=(*L);
return OK;
}
//链式队列的建立
Status Create(LinkQueue * L,int n)
{
srand(time(0));
LinkQueue P;
for(int i=0;i<n;i++)
{
P=(LinkQueue)malloc(sizeof(QNode));
P->data=rand()%100+1;
(*L)->rear->next=P;
(*L)->rear=P;
}
P->next=(*L);
return OK;
}
//入队操作
Status EnQueue(LinkQueue * L,QElemType e)
{
LinkQueue P;
P=(LinkQueue)malloc(sizeof(QNode));
P->data=e;
P->next=(*L);
(*L)->rear->next=P;
(*L)->rear=P;
return OK;
}
//出队操作
Status DeQueue(LinkQueue * L,QElemType * e)
{
LinkQueue temp;
*e=(*L)->next->data;
temp=(*L)->next;
(*L)->next=(*L)->next->next;
delete(temp);
return OK;
}
//输出
void Print(LinkQueue * L)
{
LinkQueue P;
P=(*L)->next;
printf("输出元素:\\n");
while(P!=(*L))
{
printf("%d ",P->data);
P=P->next;
}
printf("\\n");
}
int main()
{
LinkQueue L;
int ElemNumber;
QElemType EnElem,DeElem;
InitLinkQueue(&L);
printf("请输入元素个数:\\n");
scanf("%d",&ElemNumber);
Create(&L,ElemNumber);
Print(&L);
printf("请输入入队元素:\\n");
scanf("%d",&EnElem);
EnQueue(&L,EnElem);
Print(&L);
printf("出队操作,并返回出队元素:\\n");
DeQueue(&L,&DeElem);
printf("出队元素为:%d\\n",DeElem);
Print(&L);
return 0;
}
参考文献
- 带头结点的循环链表表示队列, 并且只设一个指针指向队尾元素结点, 试编写相应的队列初始化,入队列和出队列的算法
- 数据结构-用C语言描述(第二版)[耿国华]
- 循环队列长度计算