目录
拓展知识1 C—动态内存分配之malloc与realloc的区别
拓展知识 c语言中,当局部变量的地址赋给指针时,为什么其生存周期可以延长至整个程序结束 ?
1、基础知识
栈和队列是两种应用非常广泛的数据结构,它们都来自线性表的数据结构,都是“操作受限”的线性表。
栈一般用顺序表实现,队列一般用链表实现
2、栈
2.1 基础知识
栈是线性表的一种具体形式,其特点就是先进后出,例我们的浏览器,每点击一次后退都是退回到最近的一次浏览网页。还有撤销功能
其实,所谓的栈,其实也就是一个特殊的线性表(线性表根据物理结构可分为顺序表和链表),但是它在操作上有一些特殊的要求和限制:1、栈的元素必须后进先出;2、栈的操作只能在这个线性表的表尾进行;
注意:对于栈来说,这个表尾称为栈的栈顶top,相应的表头称为栈底bottom
栈在计算机的实现有多种方式:硬堆栈、软堆栈
硬堆栈:利用CPU中的某些寄存器组或类似的硬件或使用内存的特殊区域来实现。这类堆栈容量有限,单速度很快
软堆栈:这类堆栈主要在内存中实现,堆栈容量可以达到很大,在实现方式上,又称为动态方式和静态方式两种
官方定义:栈(stack)是限制在表的一端进行插入和删除操作的线性表。又称为一个后进先出(Last in first out LIFO)的线性表
栈顶top:允许进行插入、删除操作的一端,又称为表尾,用栈顶指针top来指示栈顶元素
栈底bottom:是固定端,又称为表头
空栈:当表中没有元素时称为空栈
设栈s=(a1,a2,a3,...,an),则a1称为栈底元素,an称为栈顶元素,如下图所示
栈中元素按a1,a2,a3...an,的次序进栈,退栈的第一个元素应为栈顶元素,即栈的修改是按后进先出的原则进行的。
栈的基本操作包含:初始化、进栈、出栈、取栈顶元素(出栈的同时可以取栈顶元素)、清空一个栈、销毁一个栈、计算栈的当前容量等。
栈的插入操作push,叫做进栈,也称为压栈、入栈。类似于子弹放入弹夹的动作
栈的删除操作pop,叫做出栈,也称为弹栈,如同弹夹中中的子弹出夹
因为栈的本质是一个线性表,线性表有两种存储形式,那么栈也有分为栈的顺序存储结构和栈的链式存储结构
栈是有限制的线性表,故栈和线性表的结构体区别就是:栈比线性表多一个栈顶指针
栈一般用顺序表实现,队列一般用链表实现
2.2 栈的顺序存储结构(动态顺序存储)
栈的顺序存储结构简称为顺序栈,和线性表相类似,用一维数组来存储栈。根据数组是否可以根据需要增大,又可分为静态顺序栈和动态顺序栈和动态顺序栈
静态顺序栈实现简单,但不能根据需要增大栈的存储空间
动态顺序栈可以根据需要增大栈的存储空间,但实现较为复杂
最开始栈中不包含任何数据,叫做空栈,此时栈顶是栈底。然后数据从栈顶进入,栈顶栈底分离,整个栈的当前容量变大,数据出栈时从栈顶弹出,栈顶下移,整个栈的当前容量变小。
2.2.1 栈的顺序存储表示
采用动态一维数组来存储栈,所谓动态,指的是栈的大小可以根据需要增加。
用bottom表示栈底指针,栈底固定不变的;栈顶则随着进栈和退栈操作而变化。用栈顶指针top指示当前栈顶位置
用top=bottom作为栈空的标记,每次top指向栈顶数组的下一个存储位置
结点进栈:首先将数据元素保存到栈顶top所指的当前位置,然后top加一,使得top指向栈顶的下一个存储位置;
结点出栈:首先执行top减一,使得top指向栈顶元素的存储位置,然后将栈顶元素取出。
栈的顺序存储的结构体定义如下:
//此处定义了一个顺序存储的栈
#define Stack_init_size 100 /*栈初始空间大小*/
#define Stack_increment_size 10 /*存储空间分配增量*/
typedef int ElemType; /*栈存储空间类型*/
typedef struct sqstack
{
ElemType Stack_size; /*当前已经分配空间,以元素为单位*/
ElemType * top; /*栈顶指针*/
ElemType * bottom; /*栈底指针,栈不存在时值为NULL*/
}SqStack;
typedef struct sqstack* pSqStack; /*定义了栈指针类型*/
2.2.2 创建一个栈
typedef int state;
#define ok 1
#define error 0
//初始化动态顺序栈
state init_SqStack(pSqStack s)
{
printf("初始化动态顺序栈\n");
/*step1:首先申请一定大小的空间,该空间地址赋值给栈底指针*/
s->bottom = (ElemType *)malloc(Stack_init_size*sizeof(ElemType));
/*step2:若申请失败,则返回错误*/
if (!s->bottom) return error;
/*step3:使栈顶指针和栈底指针指向同一内存空间*/
s->top = s->bottom;//空表时,栈顶指针和栈底指针指向同一内存
/*step4:顺序栈的初始尺寸赋值*/
s->Stack_size = Stack_init_size;
printf("初始化成功\n\n");
return ok;
}
2.2.3 入栈操作
入栈操作又称为压栈操作,就是向栈中存放数据;
入栈操作要在栈顶进行,每次向栈中压入一个数据,top指针就要+1,若栈满就再次申请空间。
//入栈,入栈之前要确保空间够用
state push_SqStack(pSqStack s, ElemType value)
{
printf("入栈操作,插入的元素值为%d\n",value);
/*step1:首先判断是否已经满栈,满栈扩展空间*/
if (s->top-s->bottom==s->Stack_size)
{
/*step1.1 扩展栈的空间*/
printf("空间不足,再次申请%d个空间\n", Stack_increment_size);
s->bottom = (ElemType *)realloc(s->bottom,(s->Stack_size+ Stack_increment_size)*sizeof(ElemType));
/*step1.2:判断是否申请内存成功,失败直接返回*/
if (s->bottom == NULL)
{
printf("扩展栈空间失败,入栈操作失败\n\n");
return error;
}
/*step1.3 申请成功,改变栈结构体遍历s*/
else
{
s->top = s->bottom + s->Stack_size; //栈顶指针改变
s->Stack_size = s->Stack_size + Stack_increment_size; //栈空间大小改变
}
}
/*step2:入栈操作*/
*(s->top) = value;
(s->top)++;
printf("入栈操作成功\n\n");
return ok;
}
拓展知识1 C—动态内存分配之malloc与realloc的区别
https://www.cnblogs.com/tangshiguang/p/6735402.html
在程序的执行期间分配内存时,内存区域中的这个空间称为堆(heap)。还有另一个内存区域,称为栈(stack),其中的空间分配给函数的参数和本地变量。在执行完该函数后,存储参数和本地变量的内存空间就会释放。堆中的内存是由程序员控制的。在分配堆上的内存时,由程序员跟踪所分配的内存何时不再需要,并释放这些空间,以便于以后重用它们。
使用动态内存很明显的好处就是:不需要预先分配存储空间且分配的空间可以根据程序的需要扩大或缩小,这样可以有效的使用内存空间。
C函数库中的malloc和free分别用于执行动态内存分配和释放。这两个函数的原型如下所示,他们都在头文件stdlib.h中声明。
void *malloc ( size_t size );
void free ( void *pointer );
malloc的作用是在内存的动态存储区中分配一个长度为size的连续空间。其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针。还有一点必须注意的是,当函数未能成功分配存储空间(如内存不足)就会返回一个NULL指针。所以在调用该函数时应该检测返回值是否为NULL,确保非空之后再使用非常重要。malloc所分配的内存是一块连续的空间。同时,malloc实际分配的内存空间可能会比你请求的多一点,但是这个行为只是由编译器定义的。malloc不知道用户所请求的内存需要存储的数据类型,所以malloc返回一个void *的指针,它可以转换为其它任何类型的指针。
由于内存区域总是有限的,不能不限制地分配下去,而且一个程序要尽量节省资源,所以当所分配的内存区域不用时,就要释放它,以便其它的变量或者程序使用。这时我们就要用到free函数。free的参数必须要么是NULL,要么是从malloc、relloc、calloc返回的值。作用是释放之前返回的指针指向的内存空间,向free传递一个NULL参数不会产生任何效果。
calloc和realloc与malloc的区别 : calloc和realloc的原型如下:
void *calloc ( size_t num_elements, size_t element_size );
void *realloc (void *ptr, size_t new_size );
calloc和malloc 主要的区别在于前者在返回内存的指针之前将它初始化为0,另外它们请求数量的方式不同。calloc的参数包括所需元素的数量和每个元素的字节,根据这些值可以计算出总共需要分配的内存空间。
realloc函数用于修改一个原先已经分配的内存块的大小,可以使一块内存的扩大或缩小。当起始空间的地址为空,即*ptr = NULL,则同malloc。当*ptr非空:若nuw_size < size,即缩小*ptr所指向的内存空间,该内存块尾部的部分内存被拿掉,剩余部分内存的原先内容依然保留;若nuw_size > size,即扩大*ptr所指向的内存空间,如果原先的内存尾部有足够的扩大空间,则直接在原先的内存块尾部新增内存,如果原先的内存尾部空间不足,或原先的内存块无法改变大小,realloc将重新分配另一块nuw_size大小的内存,并把原先那块内存的内容复制到新的内存块上。因此,使用realloc后就应该改用realloc返回的新指针。
2.2.4 出栈操作
出栈操作就是在栈顶取出数据,栈顶指针随之下移的操作;
每当从栈内弹出一个数据,栈顶指针就减一
//出栈,出栈之前要确保栈不为空
state pop_SqStack(pSqStack s, ElemType * value)
{
printf("出栈操作\n");
/*step1:判断栈是否为空,为空直接返回*/
if (s->bottom==s->top)
{
printf("栈已空,无法进行出栈操作\n\n");
return 0;
}
/*step2:出栈操作*/
*value = *(--s->top);
printf("出栈成功,出栈的元素值为%d\n\n",*value);
return ok;
}
2.2.5 清空一个栈
所谓清空一个栈,就是将栈中的元素全部作废,单栈本身物理空间并不发生改变(不是销毁)
因此我们只要将s指向的头指针赋值为栈底指针的值就可以了
//清空栈
state clear_SqStack(pSqStack s)
{
printf("清空栈操作开始\n");
s->top = s->bottom;
printf("栈已经清空\n\n");
return 1;
}
2.2.6 销毁一个栈
销毁一个栈与清空一个栈不同,销毁一个栈是要释放掉该栈所占据的物理内存空间,因此不要把销毁一个栈和清空栈混淆
//销毁栈
state destroy_SqStack(pSqStack s)
{
printf("销毁栈开始\n");
/*step1:获取要释放的次数*/
int length = s->Stack_size;
/*step2:释放空间*/
for (int i=1;i<=length;i++)
{
free(s->bottom);
s->bottom++;
}
/*step3:栈结构体s的成员变量重新赋值*/
s->bottom = NULL;
s->top = NULL;
s->Stack_size = 0;
printf("销毁栈操作结束\n\n");
return 1;
}
2.2.7 计算栈的当前容量
计算栈的当前容量其实也就是计算栈中元素的个数,因此只要返回s->top减去s->bottom
注意:栈的最大容量是指该栈占据内存空间的大小,其值是s->stack_size,它与栈的当前容量不是一个概念
//计算栈当前包含的元素个数
int item_num_in_SqStack(pSqStack s)
{
printf("计算当前栈包含的元素个数\n");
int num = s->top - s->bottom;
printf("计算完毕,当前栈包含的元素个数为%d",num);
return num;
}
2.2.8 读取栈中全部值
//读取栈中全部的元素值,首先要确保不是空栈
state read_SqStack(pSqStack s)
{
printf("读取栈中全部元素值\n");
/*step1:判断是否是空栈*/
if (s->bottom==s->top)
{
printf("此栈为空栈,无需读取元素\n\n");
return error;
}
/*step2:读取全部元素值*/
ElemType * p = s->bottom;
while (p!=s->top)
{
printf("%d ",*(p));
p++;
}
printf("\n读取全部元素值完毕\n\n");
return ok;
}
2.2.9 main测试函数
void main()
{
/*初始化*/
//pSqStack s=(pSqStack)malloc(sizeof(SqStack));
SqStack s;
init_SqStack(&s);
int value;
pop_SqStack(&s, &value);
read_SqStack(&s);
/*进制转换*/
//b_to_d();
/*入栈*/
//for (int i = 1;i <= 5;i++)
//{
// push_SqStack(s, i);
//}
//read_SqStack(s);
///*出栈*/
//pop_SqStack(s, &value);
//pop_SqStack(s, &value);
//pop_SqStack(s, &value);
}
2.2.10 实例分析
题目:利用栈的数据结构特点,将二进制转换为十进制数
分析:二进制数是计算机数据的存储形式,他是由一串0和1组成的
一个二进制数要转换为相应的十进制数,就是从最低位起用每一位去乘以相应位的积,也就是说用第n位取乘以2^(n-1)
,然后全部加起来
我们学习编程经常会接触到不同进制的数,而最多的就是二进制,八进制,十进制,十六进制
二进制是计算机唯一认识的,十进制是人民通常使用的
//二进制转十进制
state b_to_d(SqStack s)
{
printf("实现二进制转化为十进制,请输入二进制的表示,即只能输入0或1\n");
printf("开始输入,注意:输入9表示输入结束\n");
/*step1:依次输入*/
int value = 0;
while (1)
{
printf("输入:");
scanf("%d", &value);
while (value != 0 && value != 1&& value != 9)
{
printf("二进制表示只能输入0或1,但是您输入了%d,请重新输入:",value);
scanf("%d", &value);
}
if (value == 9)
{
printf("检测到结束表示符9,结束输入\n\n");
break;
}
push_SqStack(&s, value);
}
/*step2"查看此时栈中元素是否符合输入*/
int length = 0;
length = item_num_in_SqStack(&s);
if (length==0)
{
printf("栈中无元素,无需进行进制转换\n\n\n");
return 0;
}
printf("此时栈中元素共有%d个\n,分别是:", length);
read_SqStack(&s);
/*step3:依次出栈*/
int sum = 0;
int out_value = 0;
for(int i=0;i<length;i++)
{
pop_SqStack(&s, &out_value);
sum = sum + out_value*pow(2,i);
}
printf("转换结束,转换结果为:%d",sum);
return ok;
}
一个字节8bit,刚好可以用两个十六进制数完整表示,也大大节省了显式空间
2.3 栈的顺序存储结构(静态顺序存储)
顺序栈根据数组是否可以根据需要增大,可分为静态顺序栈和动态顺序栈
静态顺序栈采用静态一维数组来存储栈
栈底固定不动,而栈顶随着进栈和退栈操作变化;用一个整型变量top(称为栈顶指针)来指示当前栈顶位置;
用top=0表示栈空的初始状态,每次top指向栈顶在数组中的存储位置
结点进栈:首先执行top加1,使top指向新的栈顶位置,然后将数据元素保存到栈顶(top所指的当前位置)
结点出栈:首先把top指向的栈顶元素取出,然后执行top减一,使top指向新的栈顶位置
若栈的数组有Maxsize个元素,则top=Maxsize-1时栈满
2.3.1 静态顺序栈的结构体定义
#define STACK_MAXSIZE 100
typedef int ElemType;
typedef struct stack
{
ElemType data[STACK_MAXSIZE];
int top;//top指向栈顶元素
}stack;
typedef struct stack * pstack;
2.3.2 入栈
//静态顺序栈入栈,入栈要确保地址不越界
state push(pstack s, ElemType value)
{
printf("入栈\n");
/*step1:确保现在是否还有空间存放*/
if (s->top>= STACK_MAXSIZE)
{
printf("静态顺序栈已满,故无法完成入栈操作\n\n\n");
return 0;
}
/*step2:入栈*/
s->top++;
s->data[s->top] = value;
printf("入栈成功,入栈元素值为%d\n\n\n",value);
return 1;
}
2.3.3 出栈
//静态顺序栈出栈,首先确保栈不空
state pop(pstack s, ElemType *value)
{
printf("出栈操作\n");
/*step1:首先查看栈是否为空*/
if (s->top==0)
{
printf("栈已空,故出栈失败\n\n\n");
return 0;
}
/*step2:出栈*/
*value = s->data[s->top];
s->top--;
printf("出栈成功,出栈元素值为%d\n\n\n", *value);
return 1;
}
2.3.4 读取栈中全部元素值(不是出栈)
//读取栈中全部元素,不是出栈
state read(pstack s)
{
printf("读取全部元素值\n");
/*step1:判断是否为空栈*/
if (s->top==0)
{
printf("栈空,无法读取\n\n\n");
return 0;
}
/*step2:读取*/
int i = 1;
while(i<=s->top)
{
printf("%d",s->data[i]);
i++;
}
printf("\n读取结束\n\n\n");
return 1;
}
2.3.5 main测试函数
void main()
{
/*初始化*/
stack s;
init(&s);
read(&s);
/*入栈*/
for (int i=1;i<=5;i++)
{
push(&s, i);
}
read(&s);
/*出栈*/
int value = 0;
pop(&s, &value);
pop(&s, &value);
pop(&s, &value);
pop(&s, &value);
pop(&s, &value);
}
2.4 栈的链式存储结构
栈的链式存储结构,简称栈链
通常我们用的都是栈的顺序存储结构
栈因为只是栈顶来做插入和删除操作,所以比较好的方法就是将栈顶放在单链表的头部,栈顶指针和单链表的头指针合二为一
2.4.1 结构体定义
typedef int ElemType;
typedef struct stack_node
{
ElemType data;//存放栈的数据
stack_node * next;
}stack_node,*p_stack_node;
typedef struct link_stack
{
p_stack_node top;//栈顶指针
int size;
}link_stack, *p_link_stack;
2.4.2 初始化
//初始化
state init(p_link_stack s)
{
printf("初始化栈\n");
/*step1:链栈结构体初始化*/
s->size = 0;//栈尺寸赋值为0
s->top = (p_stack_node)malloc(sizeof(stack_node));//栈顶赋值
/*step2:初始化第一个结点*/
p_stack_node pnode = s->top;
pnode->next = NULL;
pnode->data = 0;
printf("初始化结束\n\n\n");
return 1;
}
2.4.3 入栈
//入栈
state push(p_link_stack s, ElemType value)
{
printf("入栈操作\n");
/*step1:先创建结点并赋值*/
p_stack_node pnode = (p_stack_node)malloc(sizeof(stack_node));
pnode->data = value;
pnode->next = s->top;
/*step2:移动栈顶指针*/
s->top = pnode;
s->size++;
printf("入栈成功\n\n\n");
return 1;
}
2.4.3 出栈
//出栈
state pop(p_link_stack s, ElemType * value)
{
printf("出栈操作\n");
/*step1:首先判断是否栈空*/
p_stack_node pnode = s->top;
if (pnode->next == NULL)
{
printf("栈空,无法出栈\n\n\n");
return 0;
}
/*step2:获取出栈值*/
*value = pnode->data;
/*step3:更改栈顶指针*/
s->top = pnode->next;
s->size--;
/*step3:释放空间*/
free(pnode);
printf("出栈成功,出栈元素值为%d\n\n\n",*value);
return 1;
}
2.4.4 main测试
#include<iostream>
using namespace std;
typedef int ElemType;
typedef struct stack_node
{
ElemType data;//存放栈的数据
stack_node * next;
}stack_node,*p_stack_node;
typedef struct link_stack
{
p_stack_node top;//栈顶指针
int size;
}link_stack, *p_link_stack;
#define ok 1
#define error 0
typedef int state;
//初始化
state init(p_link_stack s)
{
printf("初始化栈\n");
/*step1:链栈结构体初始化*/
s->size = 0;//栈尺寸赋值为0
s->top = (p_stack_node)malloc(sizeof(stack_node));//栈顶赋值
/*step2:初始化第一个结点*/
p_stack_node pnode = s->top;
pnode->next = NULL;
pnode->data = 0;
printf("初始化结束\n\n\n");
return 1;
}
//入栈
state push(p_link_stack s, ElemType value)
{
printf("入栈操作\n");
/*step1:先创建结点并赋值*/
p_stack_node pnode = (p_stack_node)malloc(sizeof(stack_node));
pnode->data = value;
pnode->next = s->top;
/*step2:移动栈顶指针*/
s->top = pnode;
s->size++;
printf("入栈成功\n\n\n");
return 1;
}
//出栈
state pop(p_link_stack s, ElemType * value)
{
printf("出栈操作\n");
/*step1:首先判断是否栈空*/
p_stack_node pnode = s->top;
if (pnode->next == NULL)
{
printf("栈空,无法出栈\n\n\n");
return 0;
}
/*step2:获取出栈值*/
*value = pnode->data;
/*step3:更改栈顶指针*/
s->top = pnode->next;
s->size--;
/*step3:释放空间*/
free(pnode);
printf("出栈成功,出栈元素值为%d\n\n\n",*value);
return 1;
}
void main()
{
link_stack s;
init(&s);
push(&s, 1);
push(&s, 2);
push(&s, 3);
int value = 0;
pop(&s,&value);
pop(&s, &value);
pop(&s, &value);
}
3 队列
3.1 队列基本概念
队列queue:也是运算受限的线性表,是一种先进先出(First in First out,简称FIFO)的线性表,只允许在表的一端进行插入,另外一端只允许删除。
队首front:允许进行删除的一端称为队首
队尾rear:允许进行插入的一端称为队尾
先进入的队列的成员总是先离开队列
队列中没有元素时称为空队列,在空队列中依次加入元素a1,a2,a3...,an之后,a1是队首元素,an是队尾元素,显然退出队列的次序也只能是a1,a2,a3...,an,即队列的修改是依先进先出的元素进行的,如下图
队列既可以用链表实现,也可以用顺序表实现,跟栈相反的是,栈一般用动态顺序表实现,队列一般用链表实现,简称为链队列
3.2 链队列
链队的操作实际上是单链表的操作,只不过删除在表头进行,插入在表尾进行。插入、删除分别修改不同的指针。链队运算及指针变化如下:
空队列时,front和rear都指向头结点
注意:头指针基本不变,改变头结点的指向即可
3.2.1 队列结构体定义
typedef int ElemType;
//链队列的结点
typedef struct quene_node
{
ElemType value;
quene_node * next;
}quene_node,*p_quene_node;
//链队列指针
typedef struct quene
{
p_quene_node front;
p_quene_node rear;
}quene,*p_quene;
3.2.2 初始化:创建一个队列
创建一个队列主要完成两个任务:
1、在内存中创建一个头结点
2、创建一个队列,将队列的头指针和尾指针都指向头结点
//初始化
p_quene init()
{
printf("初始化操作\n");
/*step1:创建结点*/
p_quene_node pnode = (p_quene_node)malloc(sizeof(quene_node));
pnode->next = NULL;
pnode->value = 0;
/*step2:创建队列*/
p_quene q = (p_quene)malloc(sizeof(quene));
q->front = pnode;
q->rear = pnode;
printf("创建成功\n\n\n");
return q;
}
3.2.3 入队操作
入队操作图示如下:
注意:入队时头指针不变只改变队尾指针
//入队
state push(p_quene q,ElemType value)
{
printf("入队操作\n");
/*step1:创建结点*/
p_quene_node pnode= (p_quene_node)malloc(sizeof(quene_node));
if (!pnode) return 0;
pnode->value = value;
pnode->next = NULL;
/*step2:将新结点链接到队尾*/
q->rear->next = pnode;
q->rear = pnode;
printf("入队成功,入队元素值为%d\n\n\n", value);
}
3.2.4 出队操作
出队操作时,队头指针不发生改变,队头指针始终指向头结点;
出队时,只需改变队头指针指向的头结点的next即可,出队操作图示如下:
如果原队列只剩下最后一个元素,那么我们就应该处理一下队尾指针
//出队
state pop(p_quene q, ElemType *value)
{
printf("出队操作\n");
/*step1:首先确保栈不空*/
if (q->front==q->rear)
{
printf("队空,无法出队\n\n\n");
return 0;
}
/*step2:出队操作*/
p_quene_node pnode= q->front->next;
*value = pnode->value;
q->front->next = pnode->next;
/*step3:判断此次是否删除了最后一个元素*/
if (pnode->next == NULL)
{
printf("此次删除的是最后一个元素\n");
q->rear = q->front;
}
free(pnode);
printf("出队成功,出队的元素值为%d\n\n\n", *value);
return ok;
}
3.2.5 销毁队列
由于队列建立在内存的动态区,因此当一个队不再用时应当将它即时销毁,以免过多的占用内存空间
//销毁队列
state destroy(p_quene q)
{
printf("销毁队列\n");
/*step1:将头指针依次后移,遍历队列,释放空间*/
while (q->front!=q->rear)
{
p_quene_node pnode= q->front;
q->front = pnode->next;
free(pnode);
}
/*step2:q->front==q->rear时表示还有一个结点*/
free(q->front);
printf("销毁成功\n\n\n");
return 1;
}
3.2.6 cpp文件
#include<iostream>
using namespace std;
typedef int ElemType;
//链队列的结点
typedef struct quene_node
{
ElemType value;
quene_node * next;
}quene_node,*p_quene_node;
//链队列指针
typedef struct quene
{
p_quene_node front;
p_quene_node rear;
}quene,*p_quene;
typedef int state;
#define ok 1
#define error 0
//初始化
p_quene init()
{
printf("初始化操作\n");
/*step1:创建结点*/
p_quene_node pnode = (p_quene_node)malloc(sizeof(quene_node));
pnode->next = NULL;
pnode->value = 0;
/*step2:创建队列*/
p_quene q = (p_quene)malloc(sizeof(quene));
q->front = pnode;
q->rear = pnode;
printf("创建成功\n\n\n");
return q;
}
//入队
state push(p_quene q,ElemType value)
{
printf("入队操作\n");
/*step1:创建结点*/
p_quene_node pnode= (p_quene_node)malloc(sizeof(quene_node));
if (!pnode) return 0;
pnode->value = value;
pnode->next = NULL;
/*step2:将新结点链接到队尾*/
q->rear->next = pnode;
q->rear = pnode;
printf("入队成功,入队元素值为%d\n\n\n", value);
}
//出队
state pop(p_quene q, ElemType *value)
{
printf("出队操作\n");
/*step1:首先确保栈不空*/
if (q->front==q->rear)
{
printf("队空,无法出队\n\n\n");
return 0;
}
/*step2:出队操作*/
p_quene_node pnode= q->front->next;
*value = pnode->value;
q->front->next = pnode->next;
/*step3:判断此次是否删除了最后一个元素*/
if (pnode->next == NULL)
{
printf("此次删除的是最后一个元素\n");
q->rear = q->front;
}
free(pnode);
printf("出队成功,出队的元素值为%d\n\n\n", *value);
return ok;
}
//销毁队列
state destroy(p_quene q)
{
printf("销毁队列\n");
/*step1:将头指针依次后移,遍历队列,释放空间*/
while (q->front!=q->rear)
{
p_quene_node pnode= q->front;
q->front = pnode->next;
free(pnode);
}
/*step2:q->front==q->rear时表示还有一个结点*/
free(q->front);
printf("销毁成功\n\n\n");
return 1;
}
void main()
{
/*初始化*/
p_quene q = init();
/*入队*/
push(q, 1);
push(q, 2);
push(q, 3);
/*出队*/
int value = 0;
pop(q, &value);
pop(q, &value);
//pop(q, &value);
//pop(q, &value);
/*销毁队列*/
destroy(q);
pop(q, &value);
printf("");
}
3.3 队列的顺序存储结构
为什么说队列的实现更愿意用链式存储结构呢?我们先按照应有的思路来考虑一下如何构造队列的顺序存储结构,然后发觉都遇到了麻烦。
我们假设一个队列有n个元素,则顺序存储的队列需要建立一个大于n的存储单元,并把队列的所有元素存储在数组的前n个单元,数组下标为0的一端是队头。
入队操作其实就是在队尾追加一个元素,不需要任何移动,时间复杂度为O(1)
出队则不同,因为我们已经设下标为0的位置是队列的队头,因此每次出队列操作所有的元素都要向前移动
在现实中也是如此,一群人在排队买火车票,前边的人买好了离开,后面的人就要全部向前一步补上空位
可是我们研究数据结构和算法的一个根本目的就是要想方设法提高我们的程序的效率,按刚才的方式,出队列的时间复杂度是O(n),效率大打折扣
如果我们不去限制队头一定要在下标为0的位置,那么出队列的操作就不需要移动全体元素
但是这样也会出现一些问题,例如按下边的情形继续入队列,就会出现数组越界的错误
可是事实上,下标为0和1的两个位置还空着,这种情况叫做假溢出。
3.3.1 循环队列(Circular Queue)的定义
我们再想想,要解决假溢出的办法就是如果后面满了,就再从头开始,也就是头尾相接的循环
循环队列它的容量是固定的,并且他的队头和队尾指针都可以随着元素入出队列而发生改变,这样循环队列逻辑上就好像是一个环形存储空间。
要注意的是,在实际的内存当中,不可能有真正的环形存储区,我们只是用顺序表来模拟出来逻辑上的循环
循环队列的实现只需要灵活改变front和rear指针即可,也就是让front或rear的指针不断加1,即使超出了地址范围,也会自动从头开始,我们可以采取取模运算处理。
(rear+1)%queneSize
(front+1)%queneSize
取模就是取余数的意思,他取到的值永远不会大于除数
入队时尾指针向前追赶头指针,出队时头指针向前追赶尾指针,故对空和队满时头尾指针均相等,因此,无法通过front=rear来判断队列空还是满,解决此问题的方法是:约定入队前,测试尾指针在循环意义下加1是否等于头指针,若相等则认为队满
rear所指的单元始终为空(即,rear指向的单元始终是下一个入队的元素可存放的地方)
循环队列为空:front=rear
循环队列满:(rear+1)%MAX_QUEUE_SIZE
注意:其实,循环队列最少空一个,用于判断队列空还是满
拓展知识 c语言中,当局部变量的地址赋给指针时,为什么其生存周期可以延长至整个程序结束 ?
https://bbs.youkuaiyun.com/topics/391815969?page=1
变量是在内存中分配的地址保存的, 生命周期到了, 这个变量的内存释放了, 但要注意, 释放, 只是告诉系统, 这块内存我不用了, 你自己收回去, 但并不代表这个地址都不存在了, 这地址是一直都在的, 通过指针, 当然可以继续访问. 但他的值是什么东西, 就要看系统有没有把它分配给其它变量了, 如果没有分配 , 可能是万年不变, 如果被分配了, 那就不知道是什么东西了.
3.3.2 循环队列的结构体定义
//顺序队列结构体
#define QueueMaxSIZE 5
typedef int ElemType;
typedef struct Circule_queue
{
ElemType data[QueueMaxSIZE]; //存放队列数据
int front; //队头下标
int rear; //队尾下标
}Circule_queue,* pCircule_queue;
3.3.3 循环队列的初始化操作
//顺序队列初始化
typedef int state;
pCircule_queue init()
{
printf("初始化操作\n");
pCircule_queue p = (pCircule_queue)malloc(sizeof(Circule_queue));
p->front = 0;
p->rear = 0;
printf("初始化操作成功\n\n\n");
return p;
}
3.3.4 循环队列的入队
//入队,
state insert(pCircule_queue p, ElemType value)
{
printf("入队操作\n");
/*step1:判断是否队满*/
int i = 0;
i = (p->rear + 1) % QueueMaxSIZE;
if (i == p->front)
{
printf("队满,无法入队\n\n\n");
return 0;
}
/*step2:入队*/
p->data[p->rear] = value;
p->rear= i;
printf("入队元素(%d)成功\n\n\n", value);
return 1;
}
3.3.5 循环队列的出队
//出队
state deleteQueue(pCircule_queue p, ElemType * value)
{
printf("出队操作\n");
/*step1:首先判断是否对空*/
if (p->front==p->rear)
{
printf("队空,无法出队\n\n\n");
return 0;
}
/*step2:出队*/
*value = p->data[p->front];
p->front = p->front + 1;
printf("出队成功,出队元素值为%d\n\n\n", *value);
return 1;
}
3.3.6 main测试函数
void main()
{
int value = 0;
/*初始化*/
pCircule_queue p = init();
//deleteQueue(p, &value);
/*入队*/
insert(p, 1);
insert(p, 2);
//insert(p, 3);
//insert(p, 4);
//insert(p, 5);
///*出队*/
//deleteQueue(p, &value);
//deleteQueue(p, &value);
//deleteQueue(p, &value);
}
4、栈和队列的应用
4.1递归和分治思想(未完成)
4.2 汉诺塔(未完成)
4.3 八皇后问题(未完成)