2019.12.13学习记录

一.队列

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在不同地方的区别

  1. 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) 结构体元素通过以下方式初始化后每一个元素的取值为多少?

[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");
    }
}

发布了5 篇原创文章 · 获赞 0 · 访问量 147

猜你喜欢

转载自blog.csdn.net/qq_45336632/article/details/103545668
今日推荐