版权声明:本文为博主原创文章,转载请注明来源 https://blog.csdn.net/C1664510416/article/details/82948380
16.复习
数据结构:
狭义:
数据结构是专门研究数据存储的问题
数据的存储包括:个体存储 + 个体之间关系的存储
广义:
数据结构既包括数据的存储也包括数据的操作
对存储数据的操作就是算法。
算法:
狭义:
算法是和数据存储密切相关的
广义:
算法和数据的存储方式无关
这就是泛型思想
数据的存储结构:
线性存储
连续存储【数组】
优点:存取速度很快
缺点:插入删除元素很慢,空间通常有限制
非连续存储【链表】
优点:空间没有限制
插入元素很快
缺点:
存取速度很慢
线性结构的应用:
栈
队列
非线性存储
树
图
17.栈
定义:
一种可以实现“先进后出”的存储结构。
分类:
静态栈
动态栈
算法:
出栈
压栈
栈程序的演示:
应用:
函数调用
中断
表达式求值
内存分配
缓冲处理
迷宫
18.队列
定义:一种可以实现“先进先出”的存储结构。
分类:
链式队列:
用链表实现
静态队列:
用数组实现
静态队列,通常都是循环队列
循环队列:
1.静态队列为什么要是循环队列
因为不论是从头部( front )删除,还是从尾部(rear)增加,地址都在向“后”移动,若不循环,终究会将申请的内存耗尽。
2.循环队列需要几个参数来确定
需要2个参数来确定,2个参数不同的场合有不同的意义。(front rear)
1). 队列初始化(front 和 rear 的值都是零),
2). 队列非空(front 代表的是队列的第一个元素, rear 代表的是队列的最后一个有效元素的,后一个元素),
3). 队列空(front 和 rear的值相等,但不一定是零)
3.循环队列需要几个参数来确定
2个
4.循环队列入队伪算法
两步搞定:
1.将值存入r所代表的位置
2.r=(r+1)%数组的长度
5.循环队列出队伪算法
一步搞定:
1.f=(f+1)%数组的长度
6.如何判断循环队列是否为空
看 front 与 rear 的值是否相等,若相等,则一定为空。
7.如何判断队列是否已满
两种方式:多增加一个标志来存放数量。
少用一个元素来判断。
如果紧挨着就是满了,
伪算法:
if( (r + 1)%数组的长度== f )
满了
else
不满
18.求链表的长度
while(p->pNext!=NULL)
{
cnt++;
p=p->pNext;
}
19.复习—队列
队列:
定义:一种可以实现先进先出的存储结构。
分类: 静态队列:数组实现,数组实现的队列都是循环队列。
链式队列:
循环队列:
1.静态队列为什么要是循环队列
因为不论是从头部( front )删除,还是从尾部(rear)增加,地址都在向“后”移动,若不循环,终究会将申请的内存耗尽。
2.循环队列需要几个参数来确定
需要2个参数来确定,2个参数不同的场合有不同的意义。(front rear)
1). 队列初始化(front 和 rear 的值都是零),
2). 队列非空(front 代表的是队列的第一个元素, rear 代表的是队列的最后一个有效元素的,后一个元素),
3). 队列空(front 和 rear的值相等,但不一定是零)
3.循环队列需要几个参数来确定
2个
4.循环队列入队伪算法
两步搞定:
1.将值存入r所代表的位置
2.r=(r+1)%数组的长度
5.循环队列出队伪算法
一步搞定:
1.f=(f+1)%数组的长度
6.如何判断循环队列是否为空
看 front 与 rear 的值是否相等,若相等,则一定为空。
7.如何判断队列是否已满
两种方式:多增加一个标志来存放数量。
少用一个元素来判断。
如果紧挨着就是满了,
伪算法:
if( (r + 1)%数组的长度== f )
满了
else
不满
20.循环队列的程序演示
#include <stdio.h>
#include <malloc.h>
typedef struct Queue
{
int *pBase;
int front;
int rear;
}QUEUE;
void init(QUEUE*); //创建队列
bool en_queue(QUEUE *,int); //入队
void travel(QUEUE *); //遍历
bool full_q(QUEUE *); //队列是否已满
bool out_q(QUEUE *,int*); //出队
bool empty_q(QUEUE *); //队列是否为空
int main(void)
{
QUEUE Q;
init(&Q);
int val=0;
en_queue(&Q,1);
en_queue(&Q,2);
en_queue(&Q,3);
en_queue(&Q,4);
en_queue(&Q,5);
en_queue(&Q,6);
en_queue(&Q,7);
en_queue(&Q,8);
travel(&Q);
if(out_q(&Q,&val))
{
printf("出队元素是%d\n",val);
}else{
printf("出队失败!\n");
}
if(out_q(&Q,&val))
{
printf("出队元素是%d\n",val);
}else{
printf("出队失败!\n");
}
travel(&Q);
return 0;
}
bool empty_q(QUEUE *pQ)
{
if(pQ->front ==pQ->rear)
{
return true;
}else{
return false;
}
}
bool out_q(QUEUE *pQ,int *pVal)
{
if(empty_q(pQ))
{
return false;
}else{
*pVal=pQ->pBase[pQ->front];
pQ->front=(pQ->front +1)%6;
return true;
}
}
void init(QUEUE *pQ) //创建队列
{
pQ->pBase=(int *)malloc(sizeof(int)*6);
pQ->front=0;
pQ->rear=0;
}
void travel(QUEUE* pQ)
{
int i=pQ->front;
while(i!=pQ->rear)
{
printf("%d",pQ->pBase[i]);
i=(i+1)%6;
}
}
bool full_q(QUEUE *pQ)
{
if((pQ->rear +1)%6==pQ->front )
{
return true;
}else{
return false;
}
}
bool en_queue(QUEUE *pQ,int val)
{
if(full_q(pQ))
{
return false;
}else{
pQ->pBase[pQ->rear]=val;
pQ->rear =(pQ->rear + 1)%6;
return true;
}
}
21.队列的具体应用
所有和时间有关的操作都与队列有关。
22.递归
函数互相调用的流程。
递归定义:一个函数自己直接或间接调用自己。
(判断流程为否时,代码可以当作没有写)
程序举例:
#include <stdio.h>
void f(int n)
{
if(n==1)
{
printf("哈哈\n");
}else{
printf("嘿嘿\n");
f(n-1);
}
return;
}
int main()
{
f(5);
return 0;
}
23.递归的几个例子
实现递归的条件:函数调用其本身,设置正确的结束条件。
1.求阶乘:
循环实现:
#include <stdio.h>
int main()
{
int val;
int i,sum=1;
printf("请输入一个数字: Val=");
scanf("%d",&val);
for(i=1;i<=val;i++)
{
sum=sum*i;
}
printf("sum=%d\n",sum);
return 0;
}
阶乘的递归实现:
#include <stdio.h>
int f(int n)
{
if(1==n)
{
return 1;
}else{
return f(n-1)*n;
}
}
int main()
{
int i=0,n=0;
printf("请输入一个数:n=");
scanf("%d",&n);
i=f(n);
printf("%d的阶乘是%d\n",n,i);
return 0;
}
24.递归实现汉诺塔
#include <stdio.h>
void hanoi(int n,char x,char y,char z);
int main()
{
int n;
printf("请输入汉诺塔的层数:");
scanf("%d",&n);
hanoi(n,'x','y','z');
return 0;
}
void hanoi(int n,char x,char y,char z)
{
if(n==1)
{
printf("%c->%c\n",x,z);
}else{
hanoi(n-1,x,z,y);
printf("%c->%c\n",x,z);
hanoi(n-1,y,x,z);
}
}
25.一个函数为什么可以自己调用自己(递归)
递归:一个函数直接或间接调用自己。
直接调用自己:在自己的函数里直接调用自己。
void sum(int n)
{
return sum(n);
}
间接调用自己:通过一个函数的传递调用自己。
void f(int n)
{
g(n);
}
void g(int n)
{
f(m);
}
一个函数为什么可以自己调用自己:
代码块只有一块存储空间。存储区,代码区的执行是互相不影响的。
26.递归必须满足三个条件
递归必须满足的条件:
1.递归必须有一个明确的终止条件。
2.该函数处理的数据规模必须在递减。
3.这个转化必须是可解的。
学习:搞懂递归的经典算法
27.递归和循环的比较
循环和递归:
递归:易于理解(树),速度慢,存储空间大
循环:浪费空间少,存储空间少
28.汉诺塔
伪算法:
如果是一个盘子
直接将A柱子上的盘子从A移到C
否则
先将A上的n-1借助C移到B上
直接将A柱子上的第n个盘子移到C上
再将B柱子上的n-1个盘子借助A移到C上
#include <stdio.h>
void hanoi(int n,char x,char y,char z); //X 代表放盘子的柱子,Y代表借助的柱 子,Z代表移到的柱子
int main()
{
int n;
printf("请输入汉诺塔的层数:");
scanf("%d",&n);
hanoi(n,'x','y','z');
return 0;
}
void hanoi(int n,char x,char y,char z)
{
if(n==1)
{
printf("%c->%c\n",x,z);
}else{
hanoi(n-1,x,z,y);
printf("%c->%c\n",x,z);
hanoi(n-1,y,x,z);
}
}
29.递归的应用
树和森林就是以递归的方式定义的;
数和图的很多算法都是以递归来实现的;
很多数学公式就是以递归的方式定义的;
斐波那契数列:1 2 3 5 8 13 21