栈
栈的思想
栈和队列都属于数据结构中的线性表,那么栈和队列究竟表达出怎样的思想呢?栈的英文是stack,有干草堆的意思,农村的朋友可能会对干草堆比较了解,收庄稼时残留的麦秸秆或是豆秸秆晒干后,将其堆起来形成一个干草堆,这些干草堆就是烧锅做饭用到的柴火,用到的时候从上面取一些,栈与干草堆的堆放和使用比较类似
栈的思想是先入后出,拿干草堆来说,先放入的干草被压到最下面,后放入的在最上面,要用的时候从最上面开始拿,栈同样如此,把几个数字放到栈里,最先放入的只能最后取出来,最后放入的最先取出来
栈的实现方式
栈有两种实现方式,一种是用数组来实现,另一种是用链表来实现,这两种实现形式都可以,都有一些优缺点,不过一些缺点可以利用一些技巧来克服
数组实现:栈是尾插且尾删的形式,数组可以直接访问头尾,对于数据的插入,查看和删除非常的方便,缺点就是可能会造成过多的内存浪费
链表实现:链表是插入一个就创造一个节点,内存的利用率更高,但缺点也很明显,就是在插入,查看和删除元素时,需要遍历一遍链表,在运行速度上稍慢一些
在日常使用中,可根据需要选择实现方式,本文章就选择了用数组的方式实现
栈的实现
实现一个栈,需要写出栈的一些基本功能,我们将这些功能封装成不同函数,用的时候直接调用即可,这些基本功能分别是
对栈的初始化 StackInit( )
在栈中插入数据 StackPush( )
删除栈中的数据 StackPop( )
查看栈顶的元素 StackTop( )
查看栈中元素个数 StackLength( )
检查栈是否为空 StackEmpty( )
销毁栈 StackDestory( )
下面是对这些函数的声明
typedef int DataType; //为了方便数据类型的更换,这里就装换一下 typedef struct StackNode { DataStyle* pos; //维护数组的指针 int top; //维护栈顶的元素 int capacity; //表示数组空间大小的变量 }StackNode; void StackInit(StackNode * ps); void StackPush(StackNode* ps, DataType val); void StackPop(StackNode* ps); int StackLength(StackNode* ps); DataType StackTop(StackNode* ps); bool StackEmpty(StackNode* ps); //这里返回的是布尔变量,为空就返回真 void StackDestory(StackNode* ps);
接下来,我们依次实现这些函数
//栈的初始化 void StackInit(StackNode* ps) { assert(ps); //断言,防止传过来空指针 ps->pos = NULL; ps->top = ps->capacity = 0; } //在栈中插入元素 void StackPush(StackNode* ps, DataType val) { assert(ps); if (ps->top == ps->capacity) //插入之前,先判断空间是否不足 { int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity; DataStyle* tmp = (DataStyle*)realloc(ps->pos, sizeof(DataStyle) * newcapacity); assert(tmp); ps->pos = tmp; ps->capacity = newcapacity; } ps->pos[ps->top] = val; ps->top++; } //删除栈中的元素 void StackPop(StackNode* ps) { assert(ps); if (ps->top > 0) //删除栈中的元素,只需将栈顶下移一位就行 ps->top--; } //查看栈顶的元素 DataType StackTop(StackNode* ps) { assert(ps); if (ps->top > 0) return ps->pos[ps->top - 1]; } //查看栈中元素个数 int StackLength(StackNode* ps) { assert(ps); return ps->top; //栈顶记录着栈中元素个数 } //查看栈是否为空 bool StackEmpty(StackNode* ps) { assert(ps); if (ps->top <= 0) return true; else return false; } //销毁栈 void StackDestory(StackNode* ps) { assert(ps); free(ps->pos); free(ps); }
栈的概念区分
说起栈,大家可能还会想到内存中的栈区,内存中的栈区存储着临时变量以及函数的栈帧,内存中的栈是操作系统中的概念,而我们接下来要了解的栈是数据结构中的概念,一个是内存区域的划分,一个是数据在内存中的存储形式
不过这两个栈虽说概念不同,但思想上都含有“先入后出”,比如一个有终止条件的递归函数,不断在内存中的栈上开辟栈帧,那么第一次调用的该递归函数,是最后才结束运行的。后面我们还会提到用数据结构中的栈来模拟递归调用函数时开辟的栈帧,这是很重要的一个非递归的思想,因为内存中的栈区并不是很大,递归调用太深就会导致栈溢出,这种情况下,我们就要利用数据结构中的栈和内存中的栈的类似思想,将递归转化成非递归的形式。
队列
队列的思想
队列这种结构对数据的存储与栈是有很大不同的,在思想上栈是先入后出,而队列则是先入先出,这就类似于我们生活中的排队问题,先排队的那个人,必然是最先被服务的人,先放入队列的那个元素也是最先被取出来的元素
队列的实现方式
队列同样可以用数组和链表来实现,不同的实现方法优缺点也不同
数组实现:对于数组来说可以直接访问队列的首元素,缺点就是在进行数据的删除时,是删除首元素,这就需要将整个数组向前挪动,在数据量较大的情况下,对资源的开销很大,且对内存的利用分不够充分
链表实现:因为队列是先入先出,因此查看和删除操作都是针对首元素,这用链表的方法就很好操作,不需要遍历整个链表,且对内存的利用是比较充分的
对比数组与链表的实现方式,链表的实现更胜一筹,链表更适合队列
队列的实现
同样,实现一个队列,需要写出队列的一些基本功能,我们将这些功能封装成不同函数,用的时候直接调用即可,这些基本功能分别是
对队列的初始化 QueueInit( )
在队列中插入数据 QueuePush( )
删除队列中的数据 QueuePop( )
查看队列的首元素 QueueTop( )
查看队列中元素个数 QueueLength( )
检查队列是否为空 QueueEmpty( )
销毁队列 QueueDestory( )
下面是对这些函数的声明
typedef int DataType; typedef struct QueueNode { DataStyle val; struct QueueNode* next; }QueueNode; typedef struct Queue { QueueNode* head; QueueNode* tail; int size; }Queue; //将头结点和尾节点封装成结构体,在频繁改变头节点时,可以避免使用双指针 void QueueInit(Queue * phead); void QueuePush(Queue* phead, DataType val); void QueuePop(Queue* phead); DataType QueueTop(Queue* phead); int QueueLength(Queue* phead); bool QueueEmpty(Queue* phead); void QueueDestory(Queue* phead);
接下来,依次实现这些函数
//队列的初始化 void QueueInit(Queue* phead) { assert(phead); phead->size = 0; phead->head = phead->tail = NULL; } //在队列中插入元素 void QueuePush(Queue* phead, DataType val) { assert(phead); QueueNode* tmp = (QueueNode*)malloc(sizeof(QueueNode)); assert(tmp); tmp->next = NULL; tmp->val = val; phead->size++; if (phead->head == NULL) { phead->head = tmp; phead->tail = tmp; } else { phead->tail->next = tmp; phead->tail = tmp; } } //删除队列中的元素 void QueuePop(Queue* phead) { assert(phead); QueueNode* tmp = phead->head; if (tmp != NULL) { phead->head = tmp->next; phead->size--; free(tmp); //删除后可别忘了释放 } } //查看队列中首元素的值 DataType QueueTop(Queue* phead) { assert(phead); assert(phead->tail); return phead->tail->val; } //查看队列中元素的个数 int QueueLength(Queue* phead) { assert(phead); return phead->size; } //查看队列是否为空 bool QueueEmpty(Queue* phead) { assert(phead); if( phead->size <= 0) return true; return false; } //销毁队列 void QueueDestory(Queue* phead) { assert(phead); while (phead->head) //因为是链表,所以要循环删除 { Queue* tmp = phead->head->next; free(phead->head); phead->head = tmp; } free(phead); }