通常来说,决定采用何种方式来存储数据是非常重要的,这样便于对数据检索时,数据会自动按照某种规定的顺序给出。栈和队列是检索数据的一种常用的数据结构。栈和队列是两种非常重要的数据结构,从数据结构来看,栈和队列也是线性表。是操作受限的线性表,栈只能在一端(栈顶)进行插入和删除,队列只能在一端(队尾)进行插入在另一端(队头)进行删除。但是从数据类型来说,栈和队列是和线性表不大相同两类重要的抽象数据类型。这里,首先重点介绍栈,以及栈的先关操作的c语言实现。栈是按照后进先出(LIFO)的顺序存储和检索数据的高效数据数据结构。它检索元素的顺序与存储元素的顺序相反。栈的一个最常用就是c函数调用,这是模块化编程的一个重要组成部分当在c程序中调用一个函数时,一个包含调用信息的活动记录被压入栈中,这个栈称为程序栈。当调用函数返回时,它的活动记录就会从程序栈中弹出。栈是记录函数调用过程和返回过程的完美模型,因为函数的调用过程与函数的返回过程是相反的,这刚好就是栈后进先出的特性。栈的一个显著特点就是它按照后进先出(LIFO)的方式来存储和删除元素。这就意味着最后一个入栈的元素将会第一个被删除。在计算机中,把元素存入栈中,要往栈中压入"元素,要删除栈中的元素,就要"弹出"元素。可以通过检查栈顶元素(注意这里并不是删除它,只是检索栈顶)来获取栈顶元素的信息。
栈(stack)是限定仅在表尾进行插入和删除操作的线性表。因此,对栈来说,表尾端有其特需的含义,称为栈顶(top)相应地,表头端称为栈底(bottom),不含任何元素的空表,称为空栈。
3.栈的特性(为什么要用栈?)
栈是按照后进先出的顺序存储和检索数据的数据结构。它检索元素的顺序与存储元素的顺序刚好相反。这种特性使得栈成为程序设计的一种非常有用的工具。如上文提到的c函数调用和返回,括号匹配检测等等只要数据具有按照后进先出的特点 都可以使用栈。
4.栈的定义和实现(怎样用程序设计语言(这里以c语言为例)去实现栈?)
栈是一种线性关系的数据结构。和线性表一样,栈在计算机中的物理存储结构可分为两种 :顺序存储(顺序栈)链式存储(链栈)这里以顺序栈为例。顺序栈,栈的顺序存储结构也就是利用一组地址连续的内存单元依次存放从栈底到栈顶的数据元素同时,设立一个指针top指示栈顶元素在顺序栈中的位置,通常习惯做法,是以top = 0表示空栈,但是c语言中,数组的下标约定也是从0开始,则当用c语言进行设计时,会带来很大的不方便。另一方面,由于栈在使用过程中所需的最大空间难以估计,因此,一般来说,在初始化空栈时,不应该限定栈的最大容量。一个比较合理的办法是:先为栈分配一个基本容量的内存空间,然后在使用的过程中,当栈的空间不够用的时候,再逐段扩大。为此,可以设定两个常量:STACK_INIT_SIZE(存储空间的初始分配容量),和STACKINCREMENT(存储空间分配增量),如是我们可以以下述类型来定义顺序栈。typedef struct SqStack{ DataType *base;//指向栈底元素 DataType *top;//栈顶元素指针 int stack_size ;//最大存储容量 }SqStack;
其中,stacksize指示的是当前还可使用的栈的最大内存容量,往栈中压入一个数据元素时,stacksize-1,从栈中弹出一个数据元素时 stacksize+1。栈的初始化操作为:按设定的初始分配的内存容量(STACK_INIT_SIZE)进行第一次存储分配,base 可称为栈底指针,在顺序栈中,它始终指向栈底的位置,若 base == NULL,则表明栈结构不存在,top称为栈顶指针,其初值指向栈底。即 top = base = NULL;每当插入一个新的栈顶元素时,指针top+1;当前可用空间stacksize-1;删除栈顶元素时,指针top-1,当前可用空间stacksize+1;因此,非空栈中的栈顶指针始终在栈顶元素的下一个位置上。
</pre><p></p><pre name="code" class="html">//SqStack.h #ifndef SQSTACK_H #define SQSTACK_H //--------栈的顺序存储结构的结构体————————// #define STACK_INIT_SIZE 5//顺序栈的初始分配的内存空间 #define STACKINCREMENT 2 //宏,当内存空间不足,每次增长的内存空间 typedef int DataType ;//将栈中的数据类型抽象化,使其适用于任何类型。 typedef struct SqStack{ DataType *base;//指向栈底元素 DataType *top;//栈顶元素指针 int stack_size ;//最大存储容量 }SqStack; //--------栈的基本操作的函数的数据原型——————// //初始化栈:构造一个空栈 stack ,是栈其它操作的基础 bool init_stack(SqStack* stack); //判断栈是否为空 bool stack_is_empty( const SqStack * const stack); //向栈中压入一个数据*data,使其成为新的栈顶元素 bool stack_push (SqStack *stack,const DataType * const data); //若栈不空,用data 返回stack的栈顶元素,并返回true,否则返回false bool stack_top (const SqStack * const stack,DataType *data); //返回栈stack 中元素的个数,也就是栈的长度 int stack_length (const SqStack *const stack); //从栈底到栈顶遍历栈中元素,并输出栈中的每一个元素值。 bool stack_traverse( const SqStack * const stack); //如果栈不空,从栈中删除栈顶元素,并用data 返回 bool stack_pop (SqStack *stack, DataType *data); //清除栈中元素:执行此操作后,栈stack为空栈 bool clear_stack(SqStack* stack); //销毁栈:执行此操作后,栈stack不再存在,栈的其它操作都不能进行,除非调用 init_stack(stack)重新生成一个新栈 bool destroy_stack(SqStack* stack); #endif
//-----------stack.cpp---------顺序栈基本操作的算法描述--------// #include"stack.h" #include<stdlib.h> #include<stdio.h> //初始化栈:构造一个空栈 stack ,是栈其它操作的基础 //返回值:构造成功返回 true(1) ,构造失败 返回false(0); //参数:已经声明了但还未初始化的栈 //时间复杂度O(1) bool init_stack(SqStack* stack) { //分配内存空间 //给顺序栈分配了初始分配量(STACK_INITSIZE)个数据的内存大小 //并将内存的起始地址赋值给栈底指针stack->base if( stack == NULL)//传入参数为指针时,必须判空,参数传入异常,返回给系统值-1; exit(-1); stack->base = (DataType*) malloc( STACK_INIT_SIZE * sizeof(DataType)); if( stack->base == NULL )//内存分配失败,程序异常退出 exit(-1); //给数据元素赋初值 stack->top = stack->base;//栈初始化为空栈 stack->stack_size = STACK_INIT_SIZE;//初始化可用容量为 STACK_INIT_SIZE return true;//成功初始化栈stack } //销毁栈:执行此操作后,栈stack不再存在,栈的其它操作都不能进行,除非调用 init_stack(stack)重新生成一个新栈 //返回值:销毁成功返回 true(1),销毁失败 返回 false(0) //参数说明: stack != NULL; //时间复杂度 O(1) //顺序栈的内存空间是由 malloc 在堆中分配的,那么怎么通过free()释放其所持有的内存空间呢? bool destroy_stack(SqStack* stack){ if( stack == NULL )//参数是指针时,必须判空 exit(-1); //异常,退出 free( stack ->base );//调用free()函数释放由malloc()分配的内存空间 stack->base = NULL;//将指针置空,防止野指针 stack->top = NULL;//将指针置空,防止野指针 stack->stack_size = 0;//栈都已经销毁了,可用存储空间肯定为0啦 return true; } //清除栈中元素:执行此操作后,栈stack为空栈 //返回值:true(1) 栈已置空, false(0)清空异常 //参数:stack 必须已经初始化 //时间复杂度:O(1) bool clear_stack(SqStack* stack){ if( stack == NULL)//当参数是指针时,一定要记得判空哦, exit(-1); //程序异常退出 if( stack->base == NULL )//栈未初始化,无法执行此操作 return false ; stack -> top = stack ->base;//顺序栈 栈空的条件 return true; } //判断栈是否为空 //返回值:true(1),栈为空,false(0),栈不为空 //参数:栈stack必须初始化 //时间复杂度:O(1); bool stack_is_empty( const SqStack * const stack){ if(stack == NULL)//指针作为参数必须判空 exit(-1); if( stack->base != stack->top ) return false; else return true; } //返回栈stack 中元素的个数,也就是栈的长度 //返回值:栈的长度 //参数说明:栈必须已经初始化 //时间复杂度:O(1) int stack_length (const SqStack * const stack){ if( stack == NULL)//指针作为参数必须判空 exit(-1); int length = (stack->top - stack->base);//栈的已用空间为(top - base)连续内存的同类型指针相减为两个指针之间的元素数目 return length; } //若栈不空,用data 返回stack的栈顶元素,并返回true,否则返回false //返回值:true(1)成功获取栈顶元素,false(0)获取栈顶元素失败 //参数说明: stack 为已经初始化了的栈指针,data用来存储栈顶元素 bool stack_top ( const SqStack * const stack,DataType *data){ if( stack == NULL || data == NULL )//当传入指针时,必须判空 exit(-1); if( stack->base == stack->top )//栈为空,没有栈顶元素 return false; *data = *( stack->top -1 );//栈顶元素,因为栈顶指针指top向栈顶元素的下一位置, return true; } //向栈中压入一个数据*data,使其成为新的栈顶元素 //返回值:把数据data成功压入栈,返回true(1),否则返回false(0) //参数说明: stack 指向的栈已经初始化,data为要压入的数据元素 //时间复杂度:O(1) //测试用例选取{栈空,栈不满,栈满} bool stack_push (SqStack *stack, const DataType *const data){ if( stack == NULL || data == NULL )//指针判空 exit(-1); //判栈满,如果满,在原有的内存空间后 再分配INCREMENT if( (stack->top - stack->base) >= stack->stack_size)//如果栈顶指针top和栈底指针base中的元素个数已经大于等于最大容量了 { //必须在原有的内存空间中添内存 stack->base = (DataType* )realloc( stack->base,( stack->stack_size + STACKINCREMENT ) * sizeof(DataType)); if( stack->base == NULL)//内存分配异常 exit(-1); stack->top = stack->base + stack->stack_size;//是栈顶指针重新指向原来栈顶元素的下一位置 stack->stack_size += STACKINCREMENT;//重新分配内存后,栈的最大内存容量 } *stack->top = *data;//将数据data压入栈顶 ++stack->top ; return true; } //若栈不为空,则删除栈顶元,并用data 将其返回 //返回值:删除成功返回 true(1),删除失败返回 false(0) //参数说明: stack已经初始化,data 为删除的栈顶元素 //时间复杂度:O(1); //测试用例{栈空,栈只有一个元素,栈有多个元素,栈满} bool stack_pop (SqStack *stack, DataType *data){ if( stack == NULL || data == NULL )//传入的是指针必须判空 return false; if( stack->base == NULL )//栈没初始化,不能删除栈顶元素 return false; if( stack->base == stack->top )//栈空,不能删除栈顶元素 { return false; } else //栈非空 {--stack->top ; *data = *stack->top;//取栈顶元素 } return true ;//删除成功 } //访问data所指的数据,并将其显示在屏幕上 //返回值:访问成功返回true(1),访问失败返回false(0) //参数说明:指向数据元素data的指针 //时间复杂度:O(1) bool visit(const DataType* const data){ if( data == NULL )//指针作为参数要注意判空 return false; printf("%d, ",*data);//注意这里我定义的是DataTye 是int 所以格式控制用%d return true; } //从栈底到栈顶遍历栈中元素,并输出栈中的每一个元素值。 //遍历成功返回true(1),遍历失败,返回false(0) //参数说明: stack 为已经初始化了的栈,函数指针 bool (*visit)()为显示函数指针, //从栈底到栈顶依次对栈中的每个元素调用函数visit()用于在屏幕中显示, //这是因为不要在功能函数中添加回显函数 //时间复杂度:O(n) //测试用例{栈未初始化,栈空,栈只有一个数据元素,栈有多个数据元素,栈为满栈} bool stack_traverse( const SqStack * const stack) { if( stack == NULL )//传入参数为指针时,必须判断是否为空 return false; if( stack->base == stack->top )//判栈空 return false; else { DataType* pdata = stack->base;//设置一个指向元素的临时指针变量,用来遍历整个栈 while( (pdata != stack->top) ) { visit(pdata);//显示当前数据 ++pdata;//指向下一数据元素 } return true; } //依次访问栈底指针base 到栈顶top (包括栈底指针base,不包括栈顶指针top)的数据元素 }
//------------main.cpp---------------测试文件--------------// //测试原理:使程序中的每一条语句都执行到 //测试步骤:定义栈,初始化栈,判栈空,向栈中压入数据元素{0,1,2,多,STACK_INIT_SIZE(满),STACK_INIT_SIZE+1}个,栈的遍历{未初始化,初始化,0,1,2,多}个, //判栈长{空,1,2,多} 从栈中弹出元素{多个,2,1,0,未初始化},销毁栈(空栈,其它栈) #include"stack.h" #include<stdio.h> #include<stdlib.h> int main(void){ SqStack stack;//声明一个栈,但还未初始化,此时栈还不能用 SqStack *pstack1 = &stack;//声明一个栈指针,指向stack SqStack *pstack2 = NULL;//用来进行测试 不存在的栈 //-----------------------------------------------------测试---------------------------------------------------------// //----------------初始化栈:构造一个空栈 stack ,是栈其它操作的基础------------------------------------// //bool init_stack(SqStack* stack); bool is_init = false; is_init =init_stack(pstack1); printf("is stack init: %d\n",is_init);//初始化成功 is_init == 1 //is_init =init_stack(pstack2);//当传入指针参数为NULL 系统退出程序,并在编译器的输出窗口中显示:本机”已退出,返回值为 -1 (0xffffffff)。 printf("\n");//换行,为了在屏幕中显示的更为清晰 //-------------------------------判断栈是否为空-----------------------------------------------------// //bool stack_is_empty( const SqStack * const stack); bool is_empty = false;//定义一个bool量,来判断栈是否为空,是返回true(1),栈非空为false(0) is_empty = stack_is_empty(pstack1);//因为栈只是初始化,还未向栈中压入元素,所以栈为空 printf("is stack empty: %d\n",is_empty);//返回值1 //is_empty = stack_is_empty(pstack2);//当传入指针参数为NULL 系统退出程序,并在编译器的输出窗口中显示:本机”已退出,返回值为 -1 (0xffffffff)。 //栈非空测试留到后面 向栈中压入一个元素时再测试 //---------------------------------------返回栈stack 中元素的个数,也就是栈的长度------------------------------// //int stack_length (const SqStack *const stack); int length ; length = stack_length(pstack1);//定义一个int 变量来记录 当前栈的长度也就是栈中的元素数 printf("栈当前长度为:%d\n",length); printf("\n"); //-----------------------------向栈中压入一个数据*data,使其成为新的栈顶元素------------------------// //bool stack_push (SqStack *stack,const DataType * const data); //测试用例:向栈中压入数据元素{0,1,2,多,STACK_INIT_SIZE(满),STACK_INIT_SIZE+1}个 //为了测试方便 把STACK_INIT_SIZE == 5,STACKINCREMENT =2;也就是 初始容量为5个元素,再次分配2个 //测试用例{空,1,2,3,4,5,6,7} //若栈不空,用data 返回stack的栈顶元素并显示在屏幕上 ,并返回true,否则返回false bool is_push = false ;//定义一个变量用来判断是否成功的向栈中压入一个元素,压入成功返回true(1),压入失败返回false(0) DataType data[] ={1,2,3,4,5,6,7} ;//定义一个变量来存储栈顶元素 //stack_push(pstack2,&data[0]);//当传入指针参数为NULL 系统退出程序,并在编译器的输出窗口中显示:本机”已退出,返回值为 -1 (0xffffffff)。 stack_push(pstack1,&data[0]);//向栈中压入数据元素1,并且作为栈顶元素 DataType top_data ;//声明一个Data_Type 变量 top_data,用来显示压入栈和弹出栈的数据 stack_top(pstack1,&top_data); printf("这次压入的栈中元素为:%d\n",top_data); length = stack_length(pstack1);//定义一个int 变量来记录 当前栈的长度也就是栈中的元素数 printf("栈当前长度为:%d\n",length); printf("\n"); stack_push(pstack1,&data[1]);//向栈中压入数据元素2,并且作为栈顶元素 stack_top(pstack1,&top_data); printf("这次压入的栈中元素为:%d\n",top_data); length = stack_length(pstack1);//定义一个int 变量来记录 当前栈的长度也就是栈中的元素数 printf("栈当前长度为:%d\n",length); printf("\n"); stack_push(pstack1,&data[2]);//向栈中压入数据元素3,并且作为栈顶元素 stack_top(pstack1,&top_data); printf("这次压入的栈中元素为:%d\n",top_data); length = stack_length(pstack1);//定义一个int 变量来记录 当前栈的长度也就是栈中的元素数 printf("栈当前长度为:%d\n",length); printf("\n"); stack_push(pstack1,&data[3]);//向栈中压入数据元素4,并且作为栈顶元素 stack_top(pstack1,&top_data); printf("这次压入的栈中元素为:%d\n",top_data); length = stack_length(pstack1);//定义一个int 变量来记录 当前栈的长度也就是栈中的元素数 printf("栈当前长度为:%d\n",length); printf("\n"); stack_push(pstack1,&data[4]);//向栈中压入数据元素5,并且作为栈顶元素, stack_top(pstack1,&top_data); printf("这次压入的栈中元素为:%d\n",top_data); length = stack_length(pstack1);//定义一个int 变量来记录 当前栈的长度也就是栈中的元素数 printf("栈当前长度为:%d\n",length); printf("\n"); stack_push(pstack1,&data[5]);//向栈中压入数据元素6,并且作为栈顶元素,此时栈初始分配容量STACK_INIT_SIZE ==5已经用完,是重新再分配的内存增量STACKINCREMENT ==2 stack_top(pstack1,&top_data); printf("这次压入的栈中元素为:%d\n",top_data); length = stack_length(pstack1);//定义一个int 变量来记录 当前栈的长度也就是栈中的元素数 printf("栈当前长度为:%d\n",length); printf("\n"); stack_push(pstack1,&data[6]);//向栈中压入数据元素7,并且作为栈顶元素,此时栈满 stack_top(pstack1,&top_data); printf("这次压入的栈中元素为:%d\n",top_data); length = stack_length(pstack1);//定义一个int 变量来记录 当前栈的长度也就是栈中的元素数 printf("栈当前长度为:%d\n",length); printf("\n"); stack_push(pstack1,&data[0]);// 再分配内存STACKINCREMENT 并将元素1压入栈 ,此时顺序栈的容量为 STACK_INIT_SIZE(5)+STACKINCREMENT(2)+ STACKINCREMENT(2) == 9,已用内存空间为 8,栈未满 stack_top(pstack1,&top_data); printf("这次压入的栈中元素为:%d\n",top_data); length = stack_length(pstack1);//定义一个int 变量来记录 当前栈的长度也就是栈中的元素数 printf("栈当前长度为:%d\n",length); printf("\n"); //-------------------------------判断栈是否为空-----------------------------------------------------// //bool stack_is_empty( const SqStack * const stack); is_empty = stack_is_empty(pstack1);//因为栈已经初始化,并且向栈中压入元素,所以栈非空 printf("is stack empty: %d\n",is_empty);//返回值为false ,也就是0 //---------------------------从栈底到栈顶遍历栈中元素,并输出栈中的每一个元素值。-----------------// //bool stack_traverse( const SqStack * const stack); bool is_traverse = false;//申明了一个bool量 is_traverse 用来判断是否遍历了整个栈中元素, printf("栈中元素:"); is_traverse = stack_traverse(pstack1);//返回值为true(1),遍历成功,false(0)遍历失败 printf("遍历成功吗:%d\n",is_traverse); printf("\n"); //-------------------------------如果栈不空,从栈中删除栈顶元素,并用data 返回---------------------// //bool stack_pop (SqStack *stack, DataType *data); bool is_pop = false;//定义一个bool变量 is_pop 用来判断栈顶元素是否成功弹出 DataType pop_top ;//定义一个DataType类型变量, pop_top ,用来保存弹出的栈顶元素,我们可以发现,弹出栈的数据与压入的顺序刚好相反。 is_pop = stack_pop(pstack1,&pop_top);//成功弹出 返回true(1),弹出失败返回false(0) printf("成功删除吗:%d\n",is_pop); printf("删除的栈顶元素:%d\n",pop_top); is_pop = stack_pop(pstack1,&pop_top);//成功弹出 返回true(1),弹出失败返回false(0) printf("成功删除吗:%d\n",is_pop); printf("删除的栈顶元素:%d\n",pop_top); is_pop = stack_pop(pstack1,&pop_top);//成功弹出 返回true(1),弹出失败返回false(0) printf("成功删除吗:%d\n",is_pop); printf("删除的栈顶元素:%d\n",pop_top); is_pop = stack_pop(pstack1,&pop_top);//成功弹出 返回true(1),弹出失败返回false(0) printf("成功删除吗:%d\n",is_pop); printf("删除的栈顶元素:%d\n",pop_top); printf("\n"); //---------------------------------清除栈中元素:执行此操作后,栈stack为空栈--------------------------------------// //bool clear_stack(SqStack* stack); bool is_clear = false; //定义了一个bool量 is_clear 用来判断栈是否被清空 is_clear = clear_stack(pstack1);//当返回值为true(1)栈已经被清空 printf("栈已经清空了吗?%d \n",is_clear);//当清空 返回true(1),没清空 ,返回false(0) printf("是否为空栈:%d\n",stack_is_empty(pstack1));//判断清空后是否为空 //这里测试删除空栈 is_pop = stack_pop(pstack1,&pop_top);//成功弹出 返回true(1),弹出失败返回false(0) printf("成功删除吗:%d\n",is_pop); printf("\n"); //--------------销毁栈:执行此操作后,栈stack不再存在,栈的其它操作都不能进行,除非调用 init_stack(stack)重新生成一个新栈-----------// //bool destroy_stack(SqStack* stack); bool is_destroy = false; is_destroy =destroy_stack(pstack1); if(is_destroy == 1) printf("你造吗?栈已经被销毁啦!不要对me做任何操作啦!么么哒!!!"); pstack1 = NULL;//销毁栈后,要将指向栈的指针置空,防止野指针 //stack_push(pstack1,&data[2]);//不相信,测试下:出现异常直接返回给系统:本机”已退出,返回值为 -1 (0xffffffff)。 return 0; }
6.结果与结论:
栈是一种单端受限的线性表,只能在一端进行插入和删除。数据结构的逻辑结构是一对一的关系,同时也是因为只能在一端进行插入和删除,这也就决定了栈的特性,后进先出
在学习数据结构过程中,我们必须牢记 结构决定性质,性质决定用途。所以凡是满足后进先出这种性质的都可以用一个栈结构来进行描述,例如在c语言的函数调用过程和返回
过程保存函数状态信息利用的就是栈。数据结构还有另一种非常重要的方面,就是物理结构,我们知道,在计算机中存储数据元素集合,一般有两种方法,顺序存储:利用大片
连续存储单元来进行存储,这种存储方式,逻辑相邻的物理也就是在内存中的位置也相邻。另一种方式就是利用指针,将数据和指向数据的指针封装为节点,数据域存储元素本身指针域存储数据间的关系。顺序存储结构的优势就是(1)支持随机访问:也就是说只要知道元素的位置,查找元素的时间复杂度为O(1) (2)存储相同数据集所需的存储空间比链式存储空间要少:因为链式存储结构必须借助指针来表示数据间的关系,而顺序存储结构不需要。 顺序结构的缺点是:(1)插入和删除要移动大量的数据元素,最坏时间复杂度为插入到表头时为O(n)(2)当数据集不知道规模时,无法分配合理的内存空间。当然了在这里,栈是只在表的一端(表尾)进行插入和删除的线性表,而顺序表,在表尾进行插入和删除的时间复杂度为最佳时间复杂度
为O(1),同时当栈空间不足时,可以用realloc()函数进行分配。为了节省内存空间,可以采用顺序存储结构来实现栈,也就是上述代码所定义的栈的抽象数据类型。在下篇博客中,会用链式结构来定义一个栈的抽象数据类型。可以比较这两种的优劣性,当然了,因为栈是线性表,是一种特需的线性表,如果学了面向对象编程的同学,也可以通过继承线性表的方式来定义栈的抽象数据类型。
测试结果如下:
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/tianxiaolu1175/article/details/47833593