小肥柴慢慢手写数据结构(C篇)(3-1 Stack栈简介)

目录

3-1 栈的概念

一句话概括:压弹夹。看过动作片的朋友很容易就理解了,在这个场景中:
(1)压入子弹(Push
(2)开枪消耗子弹/退子弹(Pop
(3)子弹就是元素(Element
(4)弹夹就是栈(Stack
上图:
在这里插入图片描述
栈顶元素称为Top,栈底元素称为Button,其实也有很多别的称谓,意思大致都是一样的;新元素只能添加在现有栈顶的上方,取走的元素也只能是当前栈顶元素(那么下一个元素会自动变成新的栈顶)。

正规一点的概括栈,描述如下(参考 liuyubobo的ppt):
(1)栈是数组/链表的子集数据结构,也是线性的;
(2)常规的讲,栈只能从一端添加元素,也只能从相同的这端取出元素。

3-2 栈的动态数组实现

使用之前实现的动态数组,要点如下:
(1)仅在数组尾部进行push和pop操作;
(2)动态数组的标记topIndex标记top元素;
(3)pop操作并没有将元素真正剔除出数组,只是使用topIndex屏蔽了后续元素;
(4)push操作时,若现有数组满员,扩容;
(5)pop操作时现有数组使用元素低于容量上限的1/4,且当前容量高于默认容量,缩减数组容量。

  1. ArrayStack.h
#ifndef _ARRAY_STACK_H
#define _ARRAY_STACK_H

#define EMPTY_TOS (-1)
#define DEFAULT_CAPACITY (20)

typedef int ElementType;

struct StackRecord {
	int capacity;
	int topIndex;
	ElementType *array;
};
typedef struct StackRecord *Stack;

int isEmpty(Stack S);
int isFull(Stack S);
Stack createStack();
void disposeStack(Stack S); //销毁栈
void makeEmpty(Stack S); //清空当前栈
void push(ElementType X, Stack S);
ElementType top(Stack S); //单纯获取头部元素
void pop(Stack S);
ElementType topAndPop(Stack S); //弹出元素并返回弹出元素内容
void printStack(Stack S);
#endif
  1. ArrayStack.c,代码简单配合要点说明很容易看明白
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "ArrayStack.h"

Stack createStack(){
	Stack S = (Stack )malloc(sizeof(struct StackRecord));
	if(!S)
		return NULL;
	S->array = malloc(sizeof(ElementType) * DEFAULT_CAPACITY);
	if(!S->array){
		free(S);
		return NULL;
	}
	
	memset(S->array, 0, DEFAULT_CAPACITY);
	S->capacity = DEFAULT_CAPACITY;
	S->topIndex = EMPTY_TOS;

	return S;
}

int isEmpty(Stack S){
	return S->topIndex == EMPTY_TOS;
}

int isFull(Stack S){
	return (S->topIndex + 1) == S->capacity;
}

void makeEmpty(Stack S){
	S->topIndex = EMPTY_TOS;
}

void disposeStack(Stack S){
	if(!S){
		free(S->array);
		free(S);
	}
}

void reSize(Stack S, int size){
	ElementType *arr = malloc(sizeof(ElementType) * size);
	memset(arr, 0, size);
	int i;
	for(i = 0; i <= S->topIndex; i++)
		arr[i] = S->array[i];
	
	ElementType *oldArr = S->array;
	S->array = arr;
	S->capacity = size;
	free(oldArr);
}

void push(ElementType X, Stack S){
	if(isFull(S)) //满员,扩容
		reSize(S, S->capacity*2);
	//这里我比较喜欢,灵活应用前++和后++
	S->array[++S->topIndex] = X;
}

void pop(Stack S){
	if(!isEmpty(S)){
		S->topIndex--;
		int len = S->topIndex + 1;
		if(len == S->capacity/4 && S->capacity > DEFAULT_CAPACITY)
			reSize(S, S->capacity/2); //参考liuyubobo大佬的防抖缩编
	}
}

ElementType top(Stack S){
	return isEmpty(S) ? INT_MIN : S->array[S->topIndex];
}

ElementType topAndPop(Stack S){
	return isEmpty(S) ? INT_MIN : S->array[S->topIndex--];
}

void printStack(Stack S){
	if(isEmpty(S))
		printf("\nEmpty!");
	else{
		printf("\n%d(top)", top(S));
		int i = S->topIndex - 1;
		while(i >= 0)
			printf("\n%d", S->array[i--]);
	}
}
  1. 测试
#include <stdio.h>
#include <stdlib.h>
#include "ArrayStack.h"

int main(int argc, char *argv[]) {
	Stack stack = createStack();
	
	int i;
	printf("add 0~8:");
	for(i=0; i<10; i+=2)
		push(i, stack);
	printStack(stack);	
	
	printf("\n\nadd 1~9:");
	for(i=1; i<10; i+=2)
		push(i, stack);
	printStack(stack);
	
	printf("\n\npop: ");
	pop(stack);
	printStack(stack);
	
	printf("\n\npop 3times ");
	pop(stack);
	pop(stack);
	pop(stack);
	printStack(stack);
	
	printf("\n\ntop=>%d", top(stack));
	printStack(stack);
	
	printf("\n\ntopAndPop=>%d", topAndPop(stack));
	printStack(stack);
	
	printf("\n\n cap before add overflow is %d", stack->capacity);
	for(i=10; i<30; i++)
		push(i, stack);
	printf("\n\n cap after add overflow is %d", stack->capacity);
	printStack(stack);
	
	return 0;
}

3-3 栈的链表实现

要点如下:
(1)使用头插法和删除头结点方法对应实现push和pop操作;
(2)为了方便操作,使用虚拟头结点;
(3)为了减少遍历可能,维护一个节点计数器,方便查询节点个数和判断空栈;
(4)销毁栈要注意释放顺序。
注:实际上也可以使用双链表,或者添加尾部标记来实现,还是那句话,根据实际需要来选择。

  1. ListStack.h
#ifndef _LIST_STACK_H
#define _LIST_STACK_H

typedef int ElementType;
typedef struct Node{
	ElementType Element;
    struct Node *Next;
} Node;

typedef struct StackRecord{
	Node *header;
	int len;
} *Stack;

int isEmpty(Stack S);
int getSize(Stack S);
Stack createStack();
void disposeStack(Stack S);
void makeEmpty(Stack S);
void push(ElementType X, Stack S);
ElementType top(Stack S);
void pop(Stack S);
ElementType topAndPop(Stack S);
void printStack(Stack S);
#endif
  1. ListStack.c
#include <stdlib.h>
#include <stdio.h>
#include "ListStack.h"

Stack createStack(){
	Stack stack = (Stack)malloc(sizeof(struct StackRecord));
	if(!stack)
		return NULL;
	
	stack->header = (Node *)malloc(sizeof(struct Node));
	if(!stack->header){
		free(stack);
		return NULL;
	}
	stack->header->Next = NULL;
	stack->len = 0;
	return stack;
}

int isEmpty(Stack S){
	return S->len == 0;
}

int getSize(Stack S){
	return S->len;
}

void push(ElementType X, Stack S){
	Node *node = (Node *)malloc(sizeof(struct Node));
	if(S&&node){
		node->Element = X;
		node->Next = S->header->Next;
		S->header->Next = node;
		S->len++;
	}
}

void pop(Stack S){
	if(!isEmpty(S)){
		Node *node = S->header->Next;
		S->header->Next = node->Next;
		S->len--;
		node->Next = NULL;
		free(node);
	}
}

ElementType top(Stack S){
	return isEmpty(S) ? INT_MIN : S->header->Next->Element;
}

ElementType topAndPop(Stack S){
	if(isEmpty(S))
		return INT_MIN;
		
	Node *node = S->header->Next;
	S->header->Next = node->Next;
	S->len--;
	int res = node->Element;
	free(node);
	return res;
}

void makeEmpty(Stack S){
	while(!isEmpty(S))
		pop(S);
}

void disposeStack(Stack S){
	makeEmpty(S);
	free(S->header);
	free(S);
}

void printStack(Stack S){
	if(isEmpty(S))
		printf("\Empty!");
	else{
		printf("\n%d(top)", S->header->Next->Element);
		Node *node = S->header->Next->Next;
		while(node){
			printf("\n%d", node->Element);
			node = node->Next;
		}		
	}
}
  1. 测试
#include <stdio.h>
#include <stdlib.h>
#include "ListStack.h"

int main(int argc, char *argv[]) {
	Stack s = createStack();
	int i;
	for(i = 0; i<10; i++)
		push(i, s);
	printStack(s);
	
	printf("\n\n(%d)top , len=%d", top(s), getSize(s));
	printf("\n(%d)topAndPop", topAndPop(s));
	printf("\n(%d)top , len=%d\n", top(s), getSize(s));
	printStack(s);
	
	printf("\npop==>");
	pop(s);
	printf("\n(%d)top , len=%d\n", top(s), getSize(s));
	printStack(s);
	
	makeEmpty(s);
	printf("\n\n(%d)top , len=%d", top(s), getSize(s));
	
	return 0;
}

3-4 时间复杂度

栈的操作其实很简单,只能一头进出,所以相关的操作时间复杂度都是 O ( 1 ) O(1) O(1),且在我们的设计中,已经维护了一个元素个数计数器,那么getSzie()操作时间复杂度也是 O ( 1 ) O(1) O(1),这或许是很多教材上没有讨论Stack时间复杂度的原因,实在是太简单了。

3-5 Linux内核源码栈的寻迹

栈有很多经典的应用,如:linux内核的进程调度和虚拟地址空间,Android的ActivityStack和微信小程序的界面机制(界面的跳转,启动和恢复其实都是围绕着栈开展的)等等,再实际使用时并不一定使用了我们介绍的数据结构形式,但在核心思想上还是一致的:FILO。

单看C相关的实现:Linux进程描述符task_struct结构体和虚拟地址知识点篇幅都很大,不易展开讲解(建议闲的无聊的朋友自己去翻翻《Linux内核场景分析》以及相关的资料,参考链接[1]/[2]/[3])。可以先挑选了一个简单的分析对象Media-device和Media-entity(参考链接[4]/[5]/[6])。

3-5-1 Media模块

  1. Media framework简介(转至参考贴[4])
    简单的说就是Linux在运行时状态下发现媒体设备(media device)拓扑并对其进行配置。为了达到这个目的,media framework将硬件设备抽象为一个个的entity,它们之间通过links连接。
    (1)entity:硬件设备模块抽象(类比电路板上面的各个元器件、芯片)
    (2)pad:硬件设备端口抽象(类比元器件、芯片上面的管脚)
    (3)link:硬件设备的连线抽象,link的两端是pad(类比元器件管脚之间的连线)
  2. Media-entity代码中stack的应用和体现
    (1)这里的设备拓扑采用了图的形式去实现遍历(Media-entity.h)
struct media_entity_graph {
	struct {
		struct media_entity *entity;
		int link;
	} stack[MEDIA_ENTITY_ENUM_MAX_DEPTH];
	int top;
};

(2)栈对应的操作和相关宏(Media-entity.c),push/pop/peek/top都有

/* push an entity to traversal stack */
static void stack_push(struct media_entity_graph *graph,
		       struct media_entity *entity)
{
	if (graph->top == MEDIA_ENTITY_ENUM_MAX_DEPTH - 1) {
		WARN_ON(1);
		return;
	}
	graph->top++;
	graph->stack[graph->top].link = 0;
	graph->stack[graph->top].entity = entity;
}

static struct media_entity *stack_pop(struct media_entity_graph *graph)
{
	struct media_entity *entity;

	entity = graph->stack[graph->top].entity;
	graph->top--;

	return entity;
}

#define stack_peek(en)	((en)->stack[(en)->top - 1].entity)
#define link_top(en)	((en)->stack[(en)->top].link)
#define stack_top(en)	((en)->stack[(en)->top].entity)

(3)对应的调用举例
EXPORT_SYMBOL_GPL(media_entity_graph_walk_start)是对外保留的模块接口

/**
 * media_entity_graph_walk_start - Start walking the media graph at a given entity
 * @graph: Media graph structure that will be used to walk the graph
 * @entity: Starting entity
 *
 * This function initializes the graph traversal structure to walk the entities
 * graph starting at the given entity. The traversal structure must not be
 * modified by the caller during graph traversal. When done the structure can
 * safely be freed.
 */
void media_entity_graph_walk_start(struct media_entity_graph *graph,
				   struct media_entity *entity)
{
	graph->top = 0;
	graph->stack[graph->top].entity = NULL;
	stack_push(graph, entity);
}
EXPORT_SYMBOL_GPL(media_entity_graph_walk_start);

3-5-2 Linux内核源码中的一段注释

实际上之前我们查看链表的linux源码中有这样一段描述,挺有意思的:

/*
 * list_add - add a new entry
 * @new: new entry to be added
 * @head: list head to add it after
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 */
Static  inline  void  list_add(struct list_head *new, struct list_head *head)
{
	__list_add(new, head, head->next);
}

“This is good for implementing stacks” 哈哈,其实在作者看来stack就是一个顺手用链表做出来的数据结构,并没有那么复杂,这同之前我们介绍的Media模块代码中使用数组简单实现stack功能的思想是一致的,够用就好。

3-5-3 Linux内核栈提要

此外,Linux内核在.s文件中有很多栈操作体现(push和pop),而在c代码中并没有那么直接,例如内核栈共用体 thread_union (include\linux\Sched.h)

union thread_union {
	struct thread_info thread_info;
	unsigned long stack[THREAD_SIZE/sizeof(long)];
};

里面就包含了一个内核栈结构,可以看到这里的栈是非常简单的,就一个long型数组,在具体应用中直接操作索引,所以栈的本质其实还是看栈标记物(SP-StackPointer,堆栈指针)+偏移,仅此而已:

extern union thread_union init_thread_union;
#define init_stack	(init_thread_union.stack)

在 arch\tile\kernel\Setup.c 中:

/*
 * per-CPU stack and boot info.
 */
DEFINE_PER_CPU(unsigned long, boot_sp) =
	(unsigned long)init_stack + THREAD_SIZE;

在Process.h中就有SP相关代码:

#define INIT_SP	(sizeof(init_stack) + (unsigned long) &init_stack)

#define INIT_THREAD	{	\
	.sp = INIT_SP,		\
}

[1] Linux进程描述符task_struct结构体详解–Linux进程的管理与调度(一) (这是一个系列,蛮好的)
[2] Linux虚拟地址空间布局
[3] linux 内核源代码情景分析——用户堆栈的扩展
[4] V4L2框架-media device(帮你了解Media模块)
[5] Media Controller devices (对应的英文文档)
[6] Linux的EXPORT_SYMBOL和EXPORT_SYMBOL_GPL的使用和区别 (帮助理解代码)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值