栈的介绍与实现(详解)以及链表和顺序表的区别

链表和顺序表的区别

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问(用下标随机访问)支持O(1)不支持:O(N)
任意位置插入或者删除可能需要搬移元素,效率低O(N)只需修改指针指向
插入动态顺序表,空间不够时需要扩容(扩容本身有消耗、空间浪费)没有容量的概念(按需申请释放)
应用场景元素高效存储+频繁访问任意位置插入或删除频繁
缓存利用率(cpu高速缓存)
  • 总结:顺序表在尾插尾删,随机访问时适合使用。
  • 链表在中间插入、不需要随机访问时适合使用。    

 缓存利用率:

存储器层次结构图

简单解释一下:cpu访问数据嫌内存太慢了,会把数据加载到缓存在访问,不在的话先加载,在的话直接访问,其次cpu读这块数据的时候不会按照需要的长度去读,而是按照cpu的字长去读,比如说它是一辆大巴,拉4个字节也是拉,拉100个字节也是拉,所以它干脆拉100个,因为它觉得你访问当前位置大概率也访问后面的位置,实践当中确实也是这样的,但是这个作用在链表上面就出现了反作用,因为链表是靠地址链接连续的,地址之间可能间距小也可能间距很大,所以不仅每次不命中,还会把没用的东西也弄过去,所以链表的命中率低。

想了解更多的话,详情见: https://www.cnblogs.com/yungyu16/p/13054874.html

 栈的介绍

栈的概念及结构

  • 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
  • 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶
  • 出栈:栈的删除操作叫做出栈。出数据也在栈顶

实现栈的三种形式,双链,单链,数组,哪种更好呢?

  • 首先排除双链,因为双向链表里有俩个指针,浪费空间,保存地址的指针只有两种情况,32为环境下占4字节,64位环境下占8字节。
  • 单链和数组的区别不大,但因为CPU高速缓存对数组命中率高,栈的插入删除都是在栈顶进行操作的,只在一段,用尾端做栈顶的话,它正好都是完全符合数组的优点,所以选数组,如果没有高速缓存的概念,链表比数组有节省空间的优势。

静态栈和动态栈

  • 栈和顺序表一样都有两种实现方式,但因为静态使用起来空间固定不推荐使用,使用动态更灵活。

栈的实现

Stack.h(头文件声明)

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int STDataType;		    //将变量类型定义成DataTy 方便更改类变量类型
typedef struct Stack            //结构体创建一个栈 需要包括栈的基本内容(栈,栈顶,栈的容量)。                                                  
{
	STDataType* a;              //栈
	int top;				    //标识栈顶数据
	int capacity;				//用计数的方式来计算结构体内的容量大小
}ST;							//typedef将结构体类型重命名位ST,方便使用
void STInit(ST* pst);           //初始化
void STDesTroy(ST* pst);        //销毁
void Push(ST* pst,STDataType x);//插入
void STPop(ST* pst);		    //删除数据、出栈
STDataType STTop(ST* pst);		//获取栈顶数据
bool STEmpty(ST* pst);			//判断栈里为不为空
int STSize(ST* pst);			//获取数据个数

栈顶在哪呢? 

  •  栈顶在哪是根据底层结构定义的:
  • 数组的栈顶是尾部。

链表的栈顶是头部,因为链表是通过next指针找到下一个节点位置的,从后找前一个节点会很麻烦,栈是后进先出,把要存储的值逆置存到节点中,才能实现先出去的是最后一个值,所以要从链表尾节点开始插入,自然头部就是栈顶了 。

Stack.c

初始化栈

首先,我们需要用结构体创建一个栈,这个结构体需要包括栈的基本内容(栈,栈顶,栈的容量),在头文件里已经创建好了这里再写一下。

typedef int STDataType;//栈中存储的元素类型(这里用整型举例)

typedef struct Stack
{
	STDataType* a;//栈
	int top;//栈顶
	int capacity;//容量,方便增容
}ST;

然后,我们需要一个初始化函数,对刚创建的栈进行初始化。

//初始化栈
void STInit(ST* pst)
{
	assert(pst);
	pst->a = NULL;					//初始化栈可存储0个元素(可以先开 也可以不开)
	pst->top = pst->capacity = 0;   //初始时栈中无元素,栈顶为0;容量为0;
}

 top初始化为-1和初始化为0的区别:

这里要先思考一个问题,top指向哪里呢?是尾还是尾的下一个?(尾指的是栈顶)

  •  top初始化为-1,随着存入数据指针++,指向的是栈顶数据,即栈顶数据的下标。
  • top初始化为0,和size没什么区别,指向的是栈顶数据的下一个位置。
  • 注:数组元素下标是从0开始的。

销毁栈

因为栈的内存空间是动态开辟出来的,当我们使用完后必须释放其内存空间,避免内存泄漏。

//销毁栈
void StackDestroy(Stack* pst)
{
	assert(pst);

	free(pst->a);//释放栈
	pst->a = NULL;//及时置空
	pst->top = 0;//栈顶置0
	pst->capacity = 0;//容量置0
}

 入栈

//入栈
void StackPush(ST* pst, STDataType x)
{
	assert(pst);

	if (pst->top == pst->capacity)//栈已满,需扩容
	{
		STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType)*pst->capacity * 2);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		pst->a = tmp;
		pst->capacity *= 2;//栈容量扩大为原来的两倍
	}
	pst->a[pst->top] = x;//栈顶位置存放元素x
	pst->top++;//栈顶上移
}
  • 插入之前要先判断空间够不够,当top和capacity相等的时候就需要扩容,注意 :这里如果top指向的是栈顶数据就需要+1才跟容量相等,因为数组下标从0开始。
  • 当空间满了时,先定义一个新capacity保存pst->capacity的值也就是定义一个新容量保存原有栈的容量,防止开辟失败原容量被改变,但因为初始化容量为0的话无法二倍扩容,就需要用三目操作符判断容量是否为0为0的话给个4字节大小的空间,不为0扩容2倍,再定义个临时接收开辟空间地址的指针,防止原有空间因为开辟失败而被改变,再判断开辟是否成功,成功后将临时容量迭给原有的容量和栈指针。
  • pst->a[pst->top] = x;//栈顶位置存放元素x
    	pst->top++;//栈顶上移
  • 这里分两种情况:top初始化为-1时需要先++再赋值,因为top指向的是栈顶。
  •                              top初始化为0是先赋值再++,因为top指向的是栈顶的下一个位置。

出栈

出栈操作比较简单,即让栈顶的位置向下移动一位即可。但需检测栈是否为空,若为空,则不能进行出栈操作。

//出栈
void StackPop(ST* pst)
{
	assert(pst);
	assert(!StackEmpty(pst));//检测栈是否为空

	pst->top--;//栈顶下移
}

获取栈顶元素

获取栈顶元素,即获取栈的最上方的元素。若栈为空,则不能获取。

//获取栈顶元素
STDataType StackTop(ST* pst)
{
	assert(pst);
	assert(!StackEmpty(pst));//检测栈是否为空

	return pst->a[pst->top - 1];//返回栈顶元素
}

检测栈是否为空

检测栈是否为空,即判断栈顶的位置是否是0即可。若栈顶是0,则栈为空。

//检测栈是否为空
bool StackEmpty(ST* pst)
{
	assert(pst);
	return pst->top == 0;
}

 获取栈中有效元素个数

因为top记录的是栈顶,使用top的值便代表栈中有效元素的个数。

//获取栈中有效元素个数
int StackSize(ST* pst)
{
	assert(pst);
	return pst->top;//top的值便是栈中有效元素的个数
}

  • 46
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值