4、链表的优缺点:
线性结构的两种常见应用之一 栈(静态内存的分配、动态内存在堆里面分配的)
int i = 10;//局部变量在栈的区域分配的
int * p = (int *)malloc(100);//p是在栈去中分配的,(100)在堆区分配的
定义:一种可以实现“先进后出”的存储结构,也是一种操作受限的线性表
分类:
(1)静态栈 (例子01234 删除2 要把43都删了)
(2)动态栈(内部是链表,只能从栈顶插入,不能从栈底插入)
算法:
(1)出栈
void pushStack(PSTACK pStack, int val)
{
PNODE pNew = (PNODE)malloc(sizeof(NODE));//造出一个新节点
pNew->data = val;//把新节点的数据域放给val
pNew->pNext = pStack->pTop;//新的结点的指针域要指向下一个节点
pStack->pTop= pNew;
}
(2)压栈
// 把pStack所指的元素出栈一次,并把出栈元素存入pVal形参所指的变量中,如果出栈失败,返回false,否则返回true
bool popStack(PSTACK pstack, int * pVal)
{
PNODE p = (PNODE)malloc(sizeof(Node));
if (is_emptyStack(pstack))//所有的平Stack都存放的是主函数中s的地址
{
printf("该栈为空栈");
return false;
}
else
{
PNODE r = pstack->pTop;
*pVal = r->data;
//pstack->pTop = pstack->pTop->pNext;
pstack->pTop = r->pNext;
free(r);
r = NULL;
return true;
}
}
代码
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
typedef struct Node
{
int data;
struct Node * pNext;//指针域
}NODE,*PNODE;
typedef struct Stack
{
PNODE pTop;//永远指向栈顶元素
PNODE pBottom;//永远指向栈底元素
}STACK,*PSTACK;
void initStack(PSTACK pStack);
void pushStack(PSTACK pStack, int val);
int traverseStack(PSTACK);
bool popStack(PSTACK pstack,int * val);
bool is_emptyStack(PSTACK pStack);
void clear(PSTACK pStack);
int main()
{
STACK s;//等价于 struct Stack
int val;
initStack(&s);//目的是造出一个空战
pushStack(&s,1);//压栈
pushStack(&s, 2);
pushStack(&s, 3);
pushStack(&s, 4);
pushStack(&s, 5);
pushStack(&s, 6);
//popStack();
traverseStack(&s);//遍历输出
if (popStack(&s, &val))
{
printf("出栈成功,该出栈元素是%d\n",val);
}
else
{
printf("出栈失败\n");
}
traverseStack(&s);
clear(&s);
printf("清空完毕");
traverseStack(&s);
return 0;
}
//初始化
void initStack(PSTACK pStack)
{
pStack->pTop = (PNODE)malloc(sizeof(NODE));//造出一个节点,把地址给pTop
if (NULL == pStack->pTop)
{
printf("内存分配失败");
exit(-1);
}
else
{
pStack->pBottom = pStack->pTop;
pStack->pTop->pNext = NULL;
}
}
void pushStack(PSTACK pStack, int val)
{
PNODE pNew = (PNODE)malloc(sizeof(NODE));//造出一个新节点
pNew->data = val;//把新节点的数据域放给val
pNew->pNext = pStack->pTop;//新的结点的指针域要指向下一个节点
pStack->pTop= pNew;
}
//遍历
int traverseStack(PSTACK pStack)
{
PNODE p = pStack->pTop;
while(p!=pStack->pBottom)
{
printf("%d \t",p->data);
p = p->pNext;
}
printf("\n");
return 0;
}
bool is_emptyStack(PSTACK pStack)
{
/*
PNODE p = (PNODE)malloc(sizeof(NODE));
if (NULL == p->pNext)
return true;
else
return false;
*/
if (pStack->pTop == pStack->pBottom)
return true;
else
return false;
}
// 把pStack所指的元素出栈一次,并把出栈元素存入pVal形参所指的变量中,如果出栈失败,返回false,否则返回true
bool popStack(PSTACK pstack, int * pVal)
{
PNODE p = (PNODE)malloc(sizeof(Node));
if (is_emptyStack(pstack))//所有的平Stack都存放的是主函数中s的地址
{
printf("该栈为空栈");
return false;
}
else
{
PNODE r = pstack->pTop;
*pVal = r->data;
//pstack->pTop = pstack->pTop->pNext;
pstack->pTop = r->pNext;
free(r);
r = NULL;
return true;
}
}
//清空
void clear(PSTACK pStack)
{
if (is_emptyStack(pStack))
{
return ;
}
else
{
PNODE p = pStack->pTop;
//PNODE q = p->pNext;
PNODE q = NULL;
while (p != pStack->pBottom)
{
q = p->pNext;
free(p);
p = q;
}
pStack->pTop = pStack->pBottom;
}
}
应用:
函数调用
中断
表达式求值
内存分配
缓冲处理
迷宫
线性结构的两种常见应用之二 队列(先进先出)
定义:一种可以实现“先进先出”的存储结构
分类:
链式队列:用链表实现的
静态队列:用数组实现的
静态队列通常都必须是循环队列
(1)静态队列为什么必须是循环队列、
避免空间浪费
(2)循环队列需要几个参数来确定
两个参数:front和rear.
(3)循环队列各个参数的含义
①.队列初始化,front和rear的值都是零
②.队列非空。
front代表的是队列的第一个元素
rear代表的是队列的最后一个有效元素的下一个元素
③.队列空
front和rear的值相等,但不一定是零。
(4)循环队列入队伪算法的讲解
入队两步完成:
①.讲值存入r所代表的的位置
②.错误的写法是r=r+1;
正确的写法是:r=(r+1)%数组的长度
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ih7otm7P-1626770786985)(D:\数据结构截图\入队为算法.PNG)]
(5)循环队列出队伪算法讲解
front=(front+1)%数组的长度
(6)如何判断循环队列是否伪空
如果front与rear是否相等
(7)如何判断循环队列是否已满
预备知识:front可能比rear大,front可能比rear小,front可 能和rear相等
①:少用一个元素
如果rear和front的值挨着,则队列已满
if((rear+1)%数组长度==front)
队满
else
不满
②:多增加一个标识参数,数组长度
#include<stdio.h>
#include<malloc.h>
typedef struct Queue
{
int * pBase;
int rear;
int front;
}QUEUE,*pQUEUE;
void init(QUEUE * pQu);
bool en_queue(QUEUE * pQu, int val);
void traverse_queue(QUEUE * pQu);
bool full_queue(QUEUE * pQu);
bool out_queue(QUEUE * pQu,int * pVal);
bool empty_queue(QUEUE * pQu);
int main()
{
QUEUE Q;
int val;
init(&Q);
en_queue(&Q, 2);
en_queue(&Q, 3);
en_queue(&Q, 4);
en_queue(&Q, 5);
en_queue(&Q, 6);
en_queue(&Q, 7);
traverse_queue(&Q);
if (out_queue(&Q, &val))
{
printf("出队列的元素是%d\n",val);
}
else
{
printf("出队失败\n");
}
traverse_queue(&Q);
return 0;
}
void init(QUEUE *pQu)
{
pQu->pBase = (int *)malloc(sizeof(int) * 6);
pQu->front = 0;
pQu->rear = 0;
}
bool full_queue(QUEUE *pQu)
{
if ((pQu->rear + 1) % 6 == pQu->front)
return true;
else
return false;
}
bool en_queue(QUEUE *pQu, int val)
{
if (full_queue(pQu))
{
printf("队列已满");
return false;
}
else
{
pQu->pBase[pQu->rear] = val;
pQu->rear = (pQu->rear + 1) % 6;
return true;
}
}
void traverse_queue(QUEUE *pQu)
{
int i = pQu->front;
while (i != pQu->rear)
{
printf("%d \t", pQu->pBase[i]);
i = (i + 1) % 6;
}
printf("\n");
return;
}
bool empty_queue(QUEUE * pQu)
{
if (pQu->front == pQu->rear)
return true;
else
{
return false;
}
}
bool out_queue(QUEUE * pQu, int *pVal)
{
if (empty_queue(pQu))
{
printf("当前为空");
return false;
}
else
{
*pVal =pQu->pBase[ pQu->front];
pQu->front = (pQu->front + 1) % 6;
return true;
}
}
队列的应用
入队
bool en_queue(QUEUE *pQu, int val)
{
if (full_queue(pQu))
{
printf("队列已满");
return false;
}
else
{
pQu->pBase[pQu->rear] = val;
pQu->rear = (pQu->rear + 1) % 6;
return true;
}
}
出队
bool out_queue(QUEUE * pQu, int *pVal)
{
if (empty_queue(pQu))
{
printf("当前为空");
return false;
}
else
{
*pVal =pQu->pBase[ pQu->front];
pQu->front = (pQu->front + 1) % 6;
return true;
}
}
队列的具体应用:
所有和时间有段的操作,都有队列的影子。
专题:递归
定义:一个函数直接或者简介调用自己
函数的调用:
当一个函数的运行期间调用另一个函数时,在运行被调函数之前,系统需要完成三件事
(1)将所有的实际参数,返回地址等信息传递给被调函数保存
(2)为被调函数的局部变量(包括形参)分配存储空间
(3)将控制转移到被调函数的入口
从被调函数返回主函数时,系统也需要完成三件事
(1)保存被调函数的返回结果
(2)释放被调函数所占的存储空间
(3)依照被调函数保存的返回地址将控制转移到调用函数
递归满足的几个条件:
(1)递归必须得有一个明确的中止条件
(2)该函数所处理的数据规模必须在递减
(3)这个转换必须是可以理解的
循环和递归的关系
所有的循环都可以用递归实现,所有的递归不一定用循环实现
递归的循环的优缺点
优点:易于理解、速度慢、存储空间大
循环的优缺点:不易理解、速度快、存储空间小
递归的例子:
1.1+2+3+4+5+……100
int sum_num(int n)
{
if (1 == n)
return 1;
else
return sum_num(n - 1) + n;
}
2.求阶乘
long factorial(long n)
{
int result;
if (n <= 1)
return 1;
else
return factorial(n - 1)*n;
}
3.汉诺塔
//汉诺塔问题
/*
如果是一个盘子:
直接将A柱子上的盘子从A移动到C
否则:
先把柱子A上的n-1个盘子从A借助C移动到B
将柱子上的第n个盘子直接移到C
再将B柱子上的n-1个盘子借助A移动到C
*/
void HanuoTower(int n, char A, char B, char C)
{
if (1 == n)
{
printf("将编号为%d的盘子直接从%c柱子移动到%c\n",n,A,C);
}
else
{
//在某一次是:再将B柱子上的n-1个盘子借助A移动到C,此时形参中的charA ,应该是B
HanuoTower(n-1,A,C,B);//把n-1个盘子从A借助C移动到B
printf("将编号为%d的盘子直接从%c柱子移动到%c\n", n, A, C);
HanuoTower(n-1,B,A,C);//B柱子上的n-1个盘子借助A移动到C
}
}
4.走迷宫
递归的应用:
树和森林就是以递归的方式定义的
树和图很多算法都是以帝归来实现的
很多数学公式就是以递归的方式来定义的
斐波拉契序列
3.汉诺塔
//汉诺塔问题
/*
如果是一个盘子:
直接将A柱子上的盘子从A移动到C
否则:
先把柱子A上的n-1个盘子从A借助C移动到B
将柱子上的第n个盘子直接移到C
再将B柱子上的n-1个盘子借助A移动到C
*/
void HanuoTower(int n, char A, char B, char C)
{
if (1 == n)
{
printf("将编号为%d的盘子直接从%c柱子移动到%c\n",n,A,C);
}
else
{
//在某一次是:再将B柱子上的n-1个盘子借助A移动到C,此时形参中的charA ,应该是B
HanuoTower(n-1,A,C,B);//把n-1个盘子从A借助C移动到B
printf("将编号为%d的盘子直接从%c柱子移动到%c\n", n, A, C);
HanuoTower(n-1,B,A,C);//B柱子上的n-1个盘子借助A移动到C
}
}