一.队列
1.队列的概念
队列是一种特殊的线性结构,它只允许在队列的首部(head)进行删除操作,在称
为“出队”,而在队列的尾部(tail)进行插入操作,这称为“入队”。当队列中没有元素时
(即head==tail),称为空队列。
1)、限定在表的一端插入、另一端删除。 插入的那头就是队尾,删除的那头就是队
头。也就是说只能在线性表的表头删除元素,在表尾插入元素。形象的说就是水龙头
和水管,流水的水嘴是队头,进水的泵是队尾,管子中间不漏水不进水。这样呲呲的
流动起来,想想就是这么个过程。
2)、先进先出 (FIFO结构)。显然我们不能在表(队列)的中间操作元素,只能是在尾
部进,在头部出去,还可以类似火车进隧道的过程。(first in first out = FIFO 结构)
2、双端队列
double-ended queue:限定插入和删除在表的两端进行,也是先进先出 (FIFO)结构,
类似铁路的转轨网络。实际程序中应用不多。
这种结构又细分为三类:
1)、输入受限的双端队列:一个端点可插入和删除,另一个端点仅可删除。
2)、输出受限的双端队列:一个端点可插入和删除,另一个端点仅可插入。
3)、等价于两个栈底相连接的栈:限定双端队列从某个端点插入的元素,只能在此端
点删除。
3、链队(有链的地方,就有指针)
用链表表示的队列,限制仅在表头删除和表尾插入的单链表。一个链队列由一个头指
针和一个尾指针唯一确定。(因为仅有头指针不便于在表尾做插入操作)。为了操作
的方便,也给链队列添加一个头结点,因此,空队列的判定条件是:头指针和尾指针
都指向头结点。
#ifndef queue_Header_h
#define queue_Header_h
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//队列的结点结构
typedef struct Node{
int data;
struct Node *next;
} Node, *Queue;
//队列的结构,嵌套
typedef struct{
Queue front;
Queue rear;
} LinkQueue;
//初始化
//开始必然是空队列,队尾指针和队头指针都指向头结点
void initQueue(LinkQueue *queue)
{
//初始化头结点
queue->front = queue->rear = (Queue)malloc(sizeof(Node));
if (NULL == queue->front) {
exit(0);
}
queue->front->next = NULL;
}
//判空
bool isEmpty(LinkQueue queue)
{
return queue.rear == queue.front ? true : false;
}
//入队,只在一端入队,另一端出队,同样入队不需要判满
void insertQueue(LinkQueue *queue, int temp)
{
Queue q = (Queue)malloc(sizeof(Node));
if (NULL == q) {
exit(0);
}
//插入数据
q->data = temp;
q->next = NULL;
//rear 总是指向队尾元素
queue->rear->next = q;
queue->rear = q;
}
//出队,需要判空
void deleteQueue(LinkQueue *queue)
{
Queue q = NULL;
if (!isEmpty(*queue)) {
q = queue->front->next;
queue->front->next = q->next;
//这句很关键,不能丢
if (queue->rear == q) {
queue->rear = queue->front;
}
free(q);
}
}
//遍历
void traversal(LinkQueue queue)
{
int i = 1;
Queue q = queue.front->next;
while (q != NULL) {
printf("队列第%d个元素是:%d\n", i, q->data);
q = q->next;
i++;
}
}
//销毁
{
while (queue->front != NULL) {
queue->rear = queue->front->next;
free(queue->front);
queue->front = queue->rear;
}
puts("销毁成功!");
}
#endif
4、顺序队列
限制仅在表头删除和表尾插入的顺序表,利用一组地址连续的存储单元依次存放队列
中的数据元素。因为队头和队尾的位置是变化的,所以也要设头、尾指针。
初始化时的头尾指针,初始值均应置为 0。 入队尾指针增 1 ,出队头指针增 1 。头尾
指针相等时队列为空,在非空队列里,头指针始终指向队头元素,尾指针始终指向队
尾元素的下一位置。
初始为空队列,那么头尾指针相等。
入队,那么尾指针加1,头指针不变。先进先出,J1先进队,则 rear+1,尾指针始终指
向队尾元素的下一位!如,J2进队,rear 继续+1,J3
进队,尾指针继续加1,如图
出队,则尾指针不变,头指针加1,注意这里都是加1,先进先出原则,J1先删除,
front+1,指向了 J2,J2删除,front+1指向了 J3,如图
最后,J3删除,则头指针再次和尾指针相等,说明队列空了。如图
在顺序队列中,当尾指针已经指向了队列的最后一个位置的下一位置时,若再有元素
入队,就会发生“溢出”。如图位置,再次入队,就会溢出
4、循环队列的诞生
顺序队列的 “假溢出” 问题:队列的存储空间未满,却发生了溢出。很好理解,比如
rear 现在虽然指向了最后一个位置的下一位置,但是之前队头也删除了一些元素,那
么队头指针经历若干次的 +1 之后,遗留下了很多空位置,但是顺序队列还在傻乎乎的
以为再有元素入队,就溢出呢!肯定不合理。故循环队列诞生!
解决“假溢出”的问题有两种可行的方法:
(1)、平移元素:把元素平移到队列的首部。效率低。否决了。
(2)、将新元素插入到第一个位置上,构成循环队列,入队和出队仍按“先进先出”的原则
进行。操作效率高、空间利用率高
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191215084020291.png
虽然使用循环队列,解决了假溢出问题,但是又有新问题发生——判空的问题,因为仅凭 front = rear 不能判定循环队列是空还是满。比如如图:
解决办法:
(1)、另设一个布尔变量以区别队列的空和满;
(2)、少用一个元素的空间,约定入队前测试尾指针在循环下加 1 后是否等于头指针,若相等则认为队满;(最常用)
(3)、使用一个计数器记录队列中元素的总数。
二.栈的初步理解
1.栈的理论
栈是一个先进后出的结构,类似于堆盘子,先放到地上的盘子最后被取走(默认只能取走一
个盘子)
栈其实就是操作受限的线性表,只有一个口,每一次操作时,这个口可以当出口也可以当入
口.
例如:水桶,注入水时,水桶的头当做入口,倒水时,水桶的头当做出口
2.栈的图解
在图解之前,先举一个例子,让大家记住栈 : 栈其实就是吃了一顿饭,然后吐出来.
这是一个空栈,只有上面是入口和出口
放入一个元素a
接着依次放入B,C元素
取出一个元素,由栈只有一个口的特点可以知道取出了C
再次放入一个元素D
栈的可用操作
根据理论环节,可以轻易的看出:栈的基本操作只有两个:
入栈
出栈
而且样子长得十分像一个水桶。
但是如果栈已经放满了,就像水桶装满了水一样,不能再放水了,即不能再进行入栈操作,
所以要在每次入栈前判断栈满的情况.同理,出栈之前,栈中必须有数据,不然就出现要么
空指针,要么野指针.都不是我们想要的结果,所以出栈前要判断栈空,.
所有一个栈一共有四个功能:
入栈(英文名:push)
判(栈)满(isFull)
出栈(pop)
判(栈)空(isEmpty)
————————————————
栈的C语言定义(结构体)
开篇就说了栈是操作收到限制的线性表,而众所周知的线性表主要有:
1.顺序存储的数组,
优点: 节省空间, 操作简单,学习成本较低,易于理解.
缺点: 栈的大小一开始就声明’死’了,不利于使用.
2.非顺序存储的链表.
优缺点:与数组栈正好相反.
两种栈各有好处,争论是愚蠢的,学习是学不完的,所以赶快开始coding吧
数组栈
数组栈,顾名思义,就是基于数组的栈,也是说把一个数组的强大的下标功能阉割掉,并且只能从一头进入(数组头明显更为方便)
所以结构体为:
(为了方便学习,存储类型统一使用int,但是我们一般更习惯在头文件下面给int 起一个别名,原因很简单:这样就这样实现简单的多态,需要将int类型栈改成char类型栈时,只需要改定义的别名中的类型即可)
typedef struct
{
int Data[MaxSize]; // 存储元素的数组
int topIdx; //栈顶指针
}SeqStack;
栈的四个基本操作定义:
//return 0 为false,1为true(下同)
// 将元素推入栈中
int Push(SeqStack &L, int e)
{ // 栈已满
if(L.topIdx==MaxSize -1)
{
return 0;
}
// 加入栈中
L.Data[L.topIdx++] = e;
// 返回自身
return e;
}
// 移除栈顶元素
int Pop(SeqStack &L)
{ // 栈空
if(L.topIdx == 0)
{
//返回失败
return 0;
}
// 打印并返回栈
int val = L.Data[--L.topIdx];
printf("%d ",val);
return val;
}
//判断栈s是否为空
int isEmpty(SeqStack s)
{
// 如果下标在0,说明栈中无元素
if(s.topIdx != 0)
{
return 1;
}
return 0;
}
// 判断栈是否已栈.
Status isFull(SeqStack s)
{
// 已满返回true(1)
if(s.topIdx != MaxSize -1)//之前的定义数组的最大值的下标
{
return 1;
}
return 0;
}
————————————————
链表栈
结构体
typedef struct LNode
{
ElemType data;
struct LNode * next;
} LNode,*LinkList;
两大功能(链表无需判满,判空也简单,不再单独实现)
Status Pop(LinkList L)
{
if(L->next == NULL)
{
return 0;
}
LinkList tem = L->next;
printf("%d ",tem->data);
L->next = tem->next;
free(tem);
return 1;
}
Status Push(LinkList L, ElemType e)
{
LinkList newNode = (LinkList) malloc(sizeof(LinkList));
newNode->data = e;
newNode->next = L->next;
L->next = newNode;
return 1;
}
三.0与NULL在不同地方的区别
- C语言中0和NULL的区别
0作为一个整数,是一个数值,可以是整型int,字符型char,长整型long等等。
0作为一个指针,是一个空指针常量。(i.e. 指针内容全为0,0x00000000),常见的 0、‘\0’、0L、3 - 3、0 * 17等都是空指针常量。
’\0’字符串结束符,用在字符串的末尾,不是指针,也不是普通的数值
什么是空指针:指针通过空指针常量赋值之后就是一个空指针,不指向任何实际的对象或者函数。
什么是NULL:在C语言 stdio.h / stddef.h中有如下定义
NULL是一个标准规定的宏定义,用来表示空指针常量
#define NULL ((void *)0)
NULL和0:
在C语言中,可以说NULL就是0,两者是完全等价的。只不过NULL用在指针和对象,0多用于数值。
NULL的值(i.e. NULL指向了内存中的什么地方):
取决于系统的实现,对于大多数系统(某些系统中NULL和0的值不同)来说,一般指向0地址,即空指针的内部全用0来表示,64位机下0x00000000。
指针初始化为NULL,指向一个无意义地址,实际上是指向了0x00000000。
- 结构体初始化
(1) 结构体元素通过以下方式初始化后每一个元素的取值为多少?
[1] 结构体指针元素 - 未分配空间
现有结构体
structure node
{
int data;
struct node* next;
};
[2] 结构体指针元素 - 分配空间
初始化方式1:
struct node* newNode;
newNode = (struct node *)malloc(sizeof(struct node));
memset(newNode, 0, sizeof(struct node));
每个元素的取值为多少?
答
分配空间之后进行如上的memset()初始化,表示
newNode -> data = 0;
newNode -> next = 0 或者说 NULL;
对于大多数系统,使用memset()来得到空指针和 *pointer = NULL 方式是等价的,但有
的系统memset(p, 0, sizeof§)时会在pointer中存着“非零空指针”。所以可用如下方式
初始化
初始化方式2:
struct node* newNode;
newNode = (struct node *)malloc(sizeof(struct node));
newNode -> data = 0;
newNode -> next = NULL;
结论:
指针初始化时,采用 pointer = NULL 和 pointer = 0 都是可以的。
在C语言中,采用 memset() 和 元素分别赋值初始化 都是可以的,没有区别。
NULL和0的区别以及结构体指针初始化验证示例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct test
{
int data;
struct test *next;
int data1;
struct test *prev;
};
int main()
{
//有符号和无符号数
int a = 1;
char b = (0 - 1);
unsigned char c = (0 - 1);
printf("%d\n", b);
printf("%u\n", c);
//NULL和0的比较
int *p1 = NULL, *p2 = 0, *p3 = &a;
int *p4 = (int *)malloc(sizeof(int));
memset(p4, 0, sizeof(int));
//结构体初始化
struct test *p5 = (struct test *)malloc(sizeof(struct test));
memset(p5, 0, sizeof(struct test));
struct test *p6 = (struct test *)malloc(sizeof(struct test));
p6->data = 0;
p6->next = NULL;
printf("%d\n", sizeof(struct test));
printf("%d\n", p5->data);
printf("%d\n", p6->data);
printf("0x%p 0x%p 0x%p 0x%p 0x%p 0x%p\n", p1, p2, p3, p4, p5->next, p6->next);
return 0;
}
四.深度优先搜索(很简单的初识…)
深度优先搜索:
搜索分为两类:深度优先搜索(DFS)和广度优先搜索(BFS),来看看深度优先搜索。
深度优先搜索(DFS)可以说就是“一条路走到黑”的一种搜索方法,有序的尝试每一种可能,在进行每一种可能尝试时,都将这种可能性贯彻到底,一直到该种可能被尝试到出了确切的结果或者走到了边界。深度优先搜索就是“一条路走到黑”或者“不撞南墙不回头”的搜索算法。
例子:
输入一个数n(n是不超过9的自然数),对1~n的数进行全排列后输出,每个数不能相同。这题能直接用循环解,进行枚举遍历,若数不相同就输出,现在用深度优先搜索遍历,先上代码:
#include<stdio.h>
int a[10], book[10], n;
void dfs(int step)
{
int i;
if(step == n + 1) //输出条件
{
for(i = 1; i <= n; i++)
{
printf("%d", a[i]);
}
printf("\n");
return;
}
for(i = 1; i <= n; i++)
{
if(book[i] == 0)
{
a[step] = i;
book[i] = 1;
dfs(step + 1);
book[i] = 0;
}
}
return;
}
int main()
{
scanf("%d", &n);
dfs(1);
return 0;
}
对代码进行解释:
1.首先我们定义一个长度为10的数组a来储存数字,再定义一个标记数组book来标记哪
些数出现过。
2.深度优先搜索代码使用递归来实现,因为每一步的处理都一样。
3.首先用一个for循环来判断哪些数没有出现过,再将这个数放入数组a的对应位置中,
用book来标记此数已经出现过,递归进行下一步。
4.将刚才出现过的数重新置0,代表这个数又可以用了,再return返回上一次尝试,此
时手中多了一个数,可以开始新的尝试了。
五.c语言线性表顺序存储结构
用一段地址连续的存储单元依次存储线性表的数据元素。
//线性表的顺序存储结构//
#include<stdio.h>
#include<stdlib.h>
#define Max 80 //存储空间初始分配量
#define Increment 10 //存储空间分配增量
typedef struct
{
int *elem; // 存储空间基地址,此处为int型,视情况而定
int length; // 元素表当前长度
int size; //当亲分配的存储容量
}SqList;
顺序表的初始化操作是为顺序表分配一个预定大小的数组空间,并将顺序表的长度设为0。
<一>int InitList(SqList &L)
{
L.elem=(int *)malloc(Max*sizeof(int));
if(!L.elem)
return;//exit(0); //存储分配失败
L.length=0; //空表长度为0
L.size=Max; //初始存储容量
return Ok;
}
<二>int CreatList(SqList &L)
{
L.elem=(int *)malloc(Max*sizeof(int));
if(!L.emle)
return;//exit(0);
L.length=0;
L.size=Max;
printf("请输入表的长度:");
scanf("%d",&L.length);
printf("请输入%d个数:",L.length);
for(i=0;i<L.length;i++)
scanf("%d",&L.elem[i]);
}
获取元素操作:将线性表中的第i个位置元素值位置返回
int GetElem(SqList &L,int i,int e)
{
// 1 <= i <= L.length
if( i <1 || i > L.length)
return ERROR;
*e=L.elem[i-1];
return Ok;
}
线性表的插入操作
`
int ListDelete(SqList &L,int i,int *e)
{
int k;
if(L.length==0) //线性表为空
return ERROR;
if(i<1||i>L.length) //插入不正确
return ERROR;
*e=L.elem[i-1];
if(i<L.length) //如果插入不是最后的位置
{
for(k=i;k<L.length;k++)
L.elem[k-1]=L.elem[k];
}
L.length--
return OK;
}
六.做题中的问题和新体会
1.尝试使用流程图
题目描述
津津的零花钱一直都是自己管理。每个月的月初妈妈给津津300300元钱,津津会预算这个月的花销,并且总能做到实际花销和预算的相同。
为了让津津学习如何储蓄,妈妈提出,津津可以随时把整百的钱存在她那里,到了年末她会加上20%20%还给津津。因此津津制定了一个储蓄计划:每个月的月初,在得到妈妈给的零花钱后,如果她预计到这个月的月末手中还会有多于100100元或恰好100100元,她就会把整百的钱存在妈妈那里,剩余的钱留在自己手中。
例如1111月初津津手中还有8383元,妈妈给了津津300300元。津津预计1111月的花销是180180元,那么她就会在妈妈那里存200200元,自己留下183183元。到了1111月月末,津津手中会剩下33元钱。
津津发现这个储蓄计划的主要风险是,存在妈妈那里的钱在年末之前不能取出。有可能在某个月的月初,津津手中的钱加上这个月妈妈给的钱,不够这个月的原定预算。如果出现这种情况,津津将不得不在这个月省吃俭用,压缩预算。
现在请你根据2004年1月到12月每个月津津的预算,判断会不会出现这种情况。如果不会,计算到2004年年末,妈妈将津津平常存的钱加上20%还给津津之后,津津手中会有多少钱。
思路:一开始感觉这个题没有什么困难的,就是不同情况,但是在敲代码的时候,逻辑会有时出错,然后自己用流程图来表示了不同情况的路程,这样就不会出错,并且很清晰,可以跟着流程图,来实现代码
#include <stdio.h>
main()
{
int a[13],i,yu=0,k=0,sum=0;
for(i=1;i<=12;i++)
{
scanf("%d",&a[i]);
}
for(i=1;i<=12;i++)
{
yu=yu+300;
if(yu-a[i]>=0)
{
if(yu-a[i]>=100)
{
sum+=(yu-a[i])/100;
yu=yu-(yu-a[i])/100*100-a[i];
}
else
{
yu=yu-a[i];
}
}
else
{
printf("-%d",i);
k=1;
break;
}
}
if(k==0)
{
printf("%d",yu+sum*120);
}
}
2.桶排序的灵活运用
题目描述
现有nn个正整数,n≤10000n≤10000,要求出这nn个正整数中的第kk个最小整数(相
同大小的整数只计算一次),k≤1000k≤1000,正整数均小于3000030000。
思路:一开始,我想的使用插入排序,来先排序,再寻找k,每次找到一个最小的,那么从那个最小的开始继续寻找,但是最后发现这样的时间复杂度会非常高,而且也很浪费空间。
之后想到了桶排序,并且用桶排序,在输出的时候,也非常直观明了,很方便
#include <stdio.h>
main()
{
int a[1000000],n,k,i,j,t,m=0,flag=0,temp,p;
scanf("%d %d",&n,&k);
for(i=0;i<1000000;i++)
{
a[i]=0;
}
for(i=0;i<n;i++)
{
scanf("%d",&p);
a[p]++;
}
// 1 3 3 7 2 5 1 2 4 6
// 1 1 2 2 3 3 4 5 6 7
for(i=0;i<30005;i++)
{
if(a[i]!=0)
{
m++;
}
if(m==k)
{
flag=1;
break;
}
}
if(flag==1)
{
printf("%d",i);
}
else
{
printf("NO RESULT");
}
}