数据结构——栈与队列的实现与相互实现

目录

一、栈和队列的概念

二、栈的相应接口实现 

1、栈的初始化(void StackInit(Stack* ps);)

2、扩容操作(void StackCapacity(Stack* ps);)

3、入栈操作(void StackPush(Stack* ps, STDataType data);)

4、出栈操作(void StackPop(Stack* ps); )

4、获取栈顶元素(STDataType StackTop(Stack* ps);)

5、获取栈的数据有效个数(int StackSize(Stack* ps);)

6、判断栈是否为空(int StackEmpty(Stack* ps);)

三、队列的相应接口实现

1、队列的初始化(void QueueInit(Queue* q);)

2、队尾入队列(void QueuePush(Queue* q, QDataType data); )

3、队头出队列(void QueuePop(Queue* q);)

4、获取队列头部元素(QDataType QueueFront(Queue* q);)

5、 获取队列尾部元素 (QDataType QueueBack(Queue* q);)

6、  获取队列中有效元素个数 (int QueueSize(Queue* q);)

7、 销毁队列(void QueueDestroy(Queue* q);)

四、两个栈实现队列

一、要求和方法

二、具体实现与代码

1、栈的接口代码

2、栈实现队列的接口代码 

 五、两个队列实现栈

一、要求与方法

二、具体实现与代码

1、队列的接口代码

六、循环队列

一、要求与方法

二、具体实现与代码

1、循环队列的接口代码

七、总结 


一、栈和队列的概念

      栈是一种特殊的线性表,它规定仅能在固定的一端进行元素的插入与删除操作 。执行插入和删除操作的这一端被称作栈顶,而另一端则是栈底。它的规定也就导致了它的特点:后进先出(Last In First Out)。它的实现可以用数组和链表的结构,但基于它的特点使用数组更优一点,可以直接在数组尾部进行插入或者删除。

图1.1:栈的简图 

     队列也是一种特殊的线性表,只允许在一端进行插入元素操作,在另一端进行删除元素。具有先进先出 FIFO(First In First Out) 特点,进行插入操作的一端称为队尾,进行删除操作的一端称为队头。它也可以用数组和链表的结构实现,但使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出元素,需要移动元素,效率会比较低。当然在循环队列时,我们选择使用定长数组的方式实现,因为循环队列的大小是固定的,所开空间重复使用,而链表入队列、出队列的时候需要申请空间和释放空间。

图1.2:队列的简图  

二、栈的相应接口实现 

              图2.1:动态增长栈

1、栈的初始化(void StackInit(Stack* ps);)

              图2.2:栈的初始化

     这里栈的初始化有很多种方式,我们定义的栈是动态栈所以需要动态申请空间,如果定义的栈是静态栈的话就可以一开始把空间给定。我们给初始容量为0,这里的top指向的是栈顶元素的下一位,也就是栈里面的元素个数,当栈顶top与容量capacity相等的时候我们就进行扩容。

2、扩容操作(void StackCapacity(Stack* ps);)

              图2.3:扩容操作

     在栈的初始化时我们给栈的容量为0,所以我们在扩容时要注意一下这种情况。容量为0的时候我们给4个栈元素容量,然后当容量不够时,我们使用二倍的方式去增容。每次增容完成时别忘了更改一下容量capacity的大小。

3、入栈操作(void StackPush(Stack* ps, STDataType data);)

              图2.4:入栈操作

     入栈操作就是和入数组操作类似,需要用assert断言一下传过来的指针是否为有效指针,再判断一下容量是否足够,然后将元素入进栈里面。

4、出栈操作(void StackPop(Stack* ps); )

              图2.5:出栈操作

     出栈操作就是让top减一个,让我们去访问栈的时候访问不到该数据即可。注意一下这里出栈的时候栈不能够为空栈,我们用assert去判断一下top。

4、获取栈顶元素(STDataType StackTop(Stack* ps);)

              图2.6:获取栈顶元素

     获取栈顶元素的时候还是得判断一下是否为空栈,这里有top指向的栈顶元素的下一个元素只需要减1就可以访问到栈顶元素。

5、获取栈的数据有效个数(int StackSize(Stack* ps);)

              图2.7:获取栈的元素有效个数

     这里直接返回top即可。

6、判断栈是否为空(int StackEmpty(Stack* ps);)

              图2.8:判断栈是否为空

     这里判断是否为空还是去判断top的值,为0则为空返回1,不为0则不为空返回0。

7、销毁栈(StackDestroy(Stack* ps);)

              图2.9:销毁栈

     销毁栈主要是释放掉我们给栈的数组申请的空间,然后将数组指针置为空,容量和栈顶置为0。

三、队列的相应接口实现

              图3.1:队列的定义

注意:这里队列的主体结构是链表,但是由于我们需要进行一端也就是队尾入元素,另一端队头出元素。如果只有一个头指针指向队头我们需要入元素的时候就需要遍历队列去找到队尾再入元素很麻烦,所以我们多定义了一个尾指针指向队尾。我们需要在后面的接口中去频繁改变指针指向的位置,所以用一个结构体去包含这两个指针,改变指针值的时候就不需要传二级指针了,多加了一个size值可以方便我们进行判空等其他操作。

1、队列的初始化(void QueueInit(Queue* q);)

              图3.2:队列初始化

     队列的初始化,我们还并没有创建一个链表,我们只需要初始化指向队头的头指针、指向队尾的尾指针和size。

2、队尾入队列(void QueuePush(Queue* q, QDataType data); )

              图3.3:队尾入队列

     入队列的时候,我们先malloc申请一个链表节点去存放data值,然后让新的链表节点next指向NULL,val值存放data值。这里要注意一点最开始初始化的时候头指针和尾指针都为空,所以在入第一个节点元素的时候,要让头指针和尾指针都指向新节点,后面入节点元素的时候则只需要动尾指针即可,别忘了记录个数的size要自加1。

3、队头出队列(void QueuePop(Queue* q);)

              图3.4:队头出队列

     首先还是需要用size的值去判断队列是否为空,不为空才能够出元素。出一个元素需要释放掉存放这个元素所申请的空间,这里还要注意分两种情况:一种队列只有一个元素的时候,出队列的时候要同时改变头指针和尾指针。因为它们指向的是同一个位置。另一种队列有多个数据的时候,就只需要将头指针指向的下一个位置保存起来,然后释放掉当前头指针指向的存放数据申请的空间,让头指针指向前面存放的下一个位置。

4、获取队列头部元素(QDataType QueueFront(Queue* q);)

     

              图3.5:获取队列头部元素

     获取头部元素的时候判断一下是否为空,访问头指针指向的元素即可。

5、 获取队列尾部元素 (QDataType QueueBack(Queue* q);)

              图3.6:获取队列尾部元素

     获取尾部元素的时候判断一下是否为空,访问尾指针指向的元素即可。

6、  获取队列中有效元素个数 (int QueueSize(Queue* q);)

              图3.7: 获取队列中有效元素个数 

     获取队列中有效元素个数的时候访问size的值即可。

7、 销毁队列(void QueueDestroy(Queue* q);)

              图3.8:销毁队列

     销毁队列的关键点是要释放掉我们用于存放元素所申请的每个空间,所以用一个循环来释放掉每个空间,当cur指针为空即为所有空间释放完成,将头指针和尾指针再次置为空,size置为0。

四、两个栈实现队列

一、要求和方法

     要求:栈实现队列的主要要求就是将栈对元素的后进先出(Last In First Out),通过两个栈来相互倒元素,实现元素的先进先出 FIFO(First In First Out)。

     方法:将其中一个栈用作存放元素的栈,另一个栈用来倒数据(暂时存放数据),我们需要出元素的时候把存放元素的栈中的元素入到倒数据的栈里面,当存放元素的栈只有一个元素的时候就直接出栈不入另一个倒数据的栈,然后将倒数据栈里面的元素重新再入回存放栈元素的栈即可。

二、具体实现与代码

     

              图4.1:栈实现队列的定义

     这里的p1栈是用来存放元素的栈,p2是用来倒元素的栈,相应接口的实现理解了方法用栈的相应函数接口即可。

1、栈的接口代码

typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top; // 栈顶
	int capacity; // 容量 
}Stack;
// 初始化栈 
void StackInit(Stack* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}
//扩容
void StackCapacity(Stack* ps)
{
	int Newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
	STDataType* a = (STDataType*)realloc(ps->a, sizeof(STDataType) * Newcapacity);
	if (a == NULL)
	{
		perror("realloc fail");
	}
	ps->a = a;
	ps->capacity = Newcapacity;
}
 //入栈 
void StackPush(Stack* ps, STDataType data)
{
	assert(ps);
	if (ps->capacity == ps->top)
	{
		StackCapacity(ps);
	}
	ps->a[ps->top] = data;
	ps->top++;
}
 //出栈 
void StackPop(Stack* ps)
{
	assert(ps);
	assert(ps->top > 0);
	ps->top--;
}
// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(ps->top > 0);
	return ps->a[ps->top - 1];
}
 //获取栈中有效元素个数 
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->top;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
int StackEmpty(Stack* ps)
{
	if (ps->top == 0)
		return 1;
	else
		return 0;
}
// 销毁栈 
void StackDestroy(Stack* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}

2、栈实现队列的接口代码 

typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top; // 栈顶
	int capacity; // 容量 
}Stack;
typedef struct
{
	Stack p1;
	Stack p2;
} MyQueue;
MyQueue* myQueueCreate()//初始化
{
	MyQueue* tmp = (MyQueue*)malloc(sizeof(MyQueue));
	StackInit(&(tmp->p1));
	StackInit(&(tmp->p2));
	return tmp;
}
void myQueuePush(MyQueue* obj, int x)//入元素
{
    StackPush(&(obj->p1), x);
}
int myQueuePop(MyQueue* obj)//出元素
{
    while (StackSize(&obj->p1) > 1)
    {
        StackPush(&obj->p2, StackTop(&obj->p1));
        StackPop(&obj->p1);
    }
    int tmp = StackTop(&obj->p1);
	StackPop(&obj->p1);
    while (StackSize(&obj->p2))
    {
        StackPush(&obj->p1, StackTop(&obj->p2));
        StackPop(&obj->p2);
    }
    return tmp;
}

int myQueuePeek(MyQueue* obj)//访问头元素
{
    if (StackEmpty(&obj->p1))
    {
        return 0;
    }
    else
    {
        return obj->p1.a[0];
    }
}

bool myQueueEmpty(MyQueue* obj)//判空
{
    if (StackEmpty(&obj->p1))
    {
        return true;
    }
    else
    {
        return false;
    }
}

void myQueueFree(MyQueue* obj)//销毁
{
    StackDestroy(&obj->p1);
    StackDestroy(&obj->p2);
    free(obj);
}

 五、两个队列实现栈

一、要求与方法

     要求:队列实现栈的主要要求就是将队列对元素的先进先出 FIFO(First In First Out),通过两个队列来倒元素,实现元素的后进先出(Last In First Out)。

     方法:在需要出元素的时候,将存放元素的队列的元素入到另一个队列里面,然后存放元素的队列只有一个元素的时候就只出队列不入到另一个队列即可。这里可以选择出完元素之后,再将元素倒回去,即一个栈总是保持为空队列,另一个队列为入元素的队列。也可以选择不倒回去,即轮流作为空队列。我这里为了更简单一点选择了不倒回去,轮流为空队列。

二、具体实现与代码

              图5.1:队列实现栈的定义

     这里的p1队列是用来存放元素的队列,p2是用来倒元素的队列,相应接口的实现理解了方法用队列的相应函数接口即可。

1、队列的接口代码

typedef int QDataType;
typedef struct QListNode
{
	struct QListNode* next;
	QDataType val;
}QNode;
typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;
void QueueInit(Queue* q)
{
	assert(q);
	q->phead = NULL;
	q->ptail = NULL;
	q->size = 0;
}
// 队尾入队列 
void QueuePush(Queue* q, QDataType data)
{
	assert(q);
	QNode* tmp = (QNode*)malloc(sizeof(QNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	tmp->next = NULL;
	tmp->val = data;
	if (q->phead ==NULL)
	{
		q->ptail = q->phead = tmp;
	}
	else
	{
		q->ptail->next = tmp;
		q->ptail = tmp;
	}
	q->size++;
}
// 队头出队列 
void QueuePop(Queue* q)
{
	assert(q);
	assert(q->size != 0);
	if (q->phead->next == NULL)
	{
		free(q->ptail);
		q->phead = q->ptail = NULL;
	}
	else
	{
		QNode* hmp = q->phead->next;
		free(q->phead);
		q->phead = hmp;
	}
	q->size--;
}
// 获取队列头部元素 
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(q->size != 0);
	return q->phead->val;
}
// 获取队列尾部元素 
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(q->size != 0);
	return q->ptail->val;
}
// 获取队列中有效元素个数 
int QueueSize(Queue* q)
{
	assert(q);
	return q->size;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q)
{
	if (q->size == 0)
		return 1;
	else
		return 0;
}
// 销毁队列 
void QueueDestroy(Queue* q)
{
	assert(q);
	QNode* cur = q->phead;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	q->phead = q->ptail = NULL;
	q->size = 0;
}

2、队列实现栈的接口代码

typedef int QDataType;
typedef struct QListNode
{
	struct QListNode* next;
	QDataType val;
}QNode;
typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;
typedef struct {
	Queue q1;
	Queue q2;
} MyStack;
//队列实现栈
MyStack* myStackCreate()
{
	MyStack* gmp = (MyStack*)malloc(sizeof(MyStack));
	QueueInit(&(gmp->q1));
	QueueInit(&(gmp->q2));
	return gmp;
}
//入元素
void myStackPush(MyStack* obj, int x)
{
	assert(obj);
	if (QueueEmpty(&(obj->q1)))
	{
		QueuePush(&(obj->q2), x);
	}
	else
	{
		QueuePush(&(obj->q1), x);
	}
}
//出元素
int myStackPop(MyStack* obj)
{
	Queue* Kong = &(obj->q1);
	Queue* FeiKong = &(obj->q2);
	if (!QueueEmpty(&obj->q1))
	{
		Kong = &(obj->q2);
		FeiKong = &(obj->q1);
	}
	while (QueueSize(FeiKong) > 1)
	{
		QueuePush(Kong, QueueFront(FeiKong));
		QueuePop(FeiKong);
	}
	int op = QueueFront(FeiKong);
	QueuePop(FeiKong);
	return op;
}
//头元素
int myStackTop(MyStack* obj)
{
	if (!QueueEmpty(&(obj->q1))) 
	{
		return QueueBack(&(obj->q1));
	}
	else 
	{
		return QueueBack(&(obj->q2));
	}
}
//判空
bool myStackEmpty(MyStack* obj)
{
	return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}
//销毁
void myStackFree(MyStack* obj)
{
	QueueDestroy(&(obj->q1));
	QueueDestroy(&(obj->q2));
	free(obj);
}

六、循环队列

一、要求与方法

     要求:循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

     方法:使用定长数组来实现队列,头下标指向队头元素,尾下标指向队尾元素的下一个位置。这里的关键点在于如何区分队列是空还是满,当空和满都是尾下标和头下标相等。有两种方法:一种我们开数组空间的时候,多开一个空间,这个时候队列是满就是尾下标+1和头下标相等,不过要考虑尾下标指向数组0的位置,访问的尾元素应该在数组开辟的空间-1的位置。还有一种方法是通过在队列结构体中多加一个size值去记录元素个数,当size为0即为空,size为开辟的空间个数即为满。

图6.1:循环队列

二、具体实现与代码

     这里实现整体比较简单,但需要注意的是当尾下标和头下标超过数组大小时需要返回到0,然后多开辟一个空间不放数据。

              图6.2:循环队列的定义

     k为有效数据开辟空间大小,head为头下标,tail为尾下标。

1、循环队列的接口代码

typedef struct
{
	int* a;
	int k;
	int head;
	int tail;
}MyCircularQueue;
//循环队列
MyCircularQueue* myCircularQueueCreate(int k)//初始化
{
	MyCircularQueue* tmp = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
	tmp->a = (int*)malloc(sizeof(int) * (k + 1));//重点多开辟一个空间,或者用size去记录个数来判空和判满
	tmp->k = k;
	tmp->head = tmp->tail = 0;
	return tmp;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj)//判空
{
	return obj->head == obj->tail;
}
bool myCircularQueueIsFull(MyCircularQueue* obj)//判满
{
	return (obj->tail + 1) % (obj->k + 1) == obj->head;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)//入元素
{
	assert(obj);
	if (myCircularQueueIsFull(obj))
		return false;
	else
	{
		obj->a[obj->tail] = value;
		obj->tail++;
		obj->tail %= (obj->k + 1);
		return true;
	}
}
bool myCircularQueueDeQueue(MyCircularQueue* obj)//出元素
{
	assert(obj);
	if (myCircularQueueIsEmpty(obj))
	{
		return false;
	}
	else
	{
		obj->head++;
		obj->head %= (obj->k + 1);
		return true;
	}
}
int myCircularQueueFront(MyCircularQueue* obj)//头元素
{
	assert(obj);
	if (myCircularQueueIsEmpty(obj))
	{
		return -1;
	}
	else
	{
		return obj->a[obj->head];
	}
}
int myCircularQueueRear(MyCircularQueue* obj)//尾元素
{
	assert(obj);
	if (myCircularQueueIsEmpty(obj))
	{
		return -1;
	}
	else
	{
		return obj->a[(obj->tail + obj->k) % (obj->k + 1)]; //重点
	}
}
void myCircularQueueFree(MyCircularQueue* obj)//释放
{
	free(obj->a);
	free(obj);
	obj = NULL;
}

七、总结 

     栈和队列都是广泛使用的数据结构,希望我的内容能够帮助你更轻松的理解栈与队列,栈实现队列,队列实现栈和循环队列都有其他实现方式,如果我有错误的地方也希望能够温柔的指出来,我们共同进步!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值