数据结构与算法(小甲鱼视频笔记,持续更新ing)

文章目录

数据结构与算法

1. 逻辑结构

  • 集合结构:集合结构中的数据元素除了同属于一个集合外,它们之间没有其他关系。
  • 线性结构:线性结构中的数据元素存在一对一的关系
  • 树形结构:树形结构中的数据元素之间存在一种一对多的关系
  • 图形结构:数据元素是多对多的关系

2. 物理结构

  • 顺序存储:是把数据元素放在地址连续的存储单元里,其数据间的逻辑关系核物理关系是一致的
  • 链式存储结构:是把数据元素放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的。

算法特性

  1. 输入:算法具有零个或多个输入
  2. 输出:算法至少有一个或多个输出
  3. 有穷性:算法执行完后,自动结束而不会无限循环
  4. 确定性:每一步都具有确定的含义,不会出现二义性;算法在一定条件下,只有一条执行路径,相同的输入只能有唯一的输出结果;算法的每个步骤都应该被精确定义而无歧义
  5. 可行性:每一步都必须是可行的,也就是每一步都能通过执行有限次数完成。

算法设计要求

  1. 正确性
    • 算法的正确性至少是具有输入、输出和加工处理无歧义性、能反映问题的要求、能得到问题的正确答案。
    • 大体分为以下四个层次:
      • 算法程序没有语法错误
      • 算法程序对于合法的输入能够产生满足要求的输出
      • 算法程序对于非法输入能够产生满足规格的说明
      • 算法程序对于故意刁难的测试输入都有满足要求的输出结果
  2. 可读性
    • 算法设计零一目的是为了便于阅读、理解和交流。
  3. 健壮性
    • 当输入数据不合法时,算法也能做出相关处理,而不是产生异常、崩溃或莫名其妙的结果
  4. 时间效率高和存储量低

算法效率的度量方法

影响因素

  1. 算法采用的策略,方案
  2. 编译产生的代码质量
  3. 问题的输入规模
  4. 机器执行指令的速度

定义

在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度,记作:T(n)=O(f(n))。他比奥斯随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。

说明

  • O()来体现算法的时间复杂度的记法,称之为大O记法
  • 一般情况下,随着输入规模n的增大,T(n)增长最慢的算法为最优算法

推导O阶方法

  • 用常数1取代运行时间中的所有加法常数。
  • 在修改后的运行次数函数中,只保留最高项。
  • 如果最高阶项存在且不是1,则出去与这个项相乘的数。
  • 得到的最后结果就是大O阶。

常见阶数

  1. 常数阶:所有加法常数都是O(1)
  2. 线性阶:一般含有非嵌套循环,线性阶就是随着问题规模n的扩大,对应计算次数呈直线增长。
  3. 平方阶:循环时间复杂度等于循环体的复杂度乘以该循环运行的次数
  4. 对数阶: 由于每次i*2之后,就距离n更近一步,假设有x个2相乘后大于或等于n,则会退出循环

算法的空间复杂度

定义:算法的空间复杂度通过计算算法所需的存储空间实现,算法的空间复杂度的计算公式记作:S(n)=O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数

  • 通常,我们都是用“时间复杂度”来指运行时间的需求,使用“空间复杂度”指空间需求
  • 当直接让我们求“复杂度”时,通常指的是时间复杂度。

线性表

定义

由零个或多个数据元素组成的有限序列

说明

  • 首先是一个序列,也就是元素之间要求先来后到。
  • 若元素存在多个,则第一个元素无前驱,而最后一个元素无后继,其他元素都有且只有一个前驱和后继。
  • 另外,线性表强调是有限的。

数据类型

定义

是一组性质相同的值的集合及定义在此集合上的一些操作的总称

分类

  • 原子类型:不可以再分解的基本类型,如整型、浮点型、字符型等
  • 结构类型:由若干个类型组合而成,是可以再分解的,例如整型数组是由若干整形数据组成的

抽象数据类型

  • 定义:是指一个数学模型及定义在该模型上的一组操作

  • 数据类型抽象:是指抽取出事物具有的普遍性的本质。他要求抽出问题的特征而忽略非本质的细节,是对具体事物的一个概括。抽象是一种思考问题的方式,隐藏了繁杂的细节。

  • 说明

    • 抽象数据类型的定义仅取决于它的一组逻辑特性,而与其在计算机内部如何表示和实现无关。
    • 抽象的意义在于数据类型的数学抽象类型
  • 格式:

    ADT //抽象数据类型名
    Data
    	//数据元素之间逻辑关系的定义
    Operation
    	//操作
    endADT
    

线性表的抽象数据类型

  • 抽象数据类型定义

    ADT 线性表
    Data
        数据对象(每个元素的类型均为DataType)
    Operation
        Initlist(*L):初始化操作,建立一个空的线性表L
        ListEmpty(L):判断线性表是否为空表,若线性表为空,返回true,否则返回false
        ClearList(*L):将线性表清空
        GetElem(L,i,*e):将线性表中的第i个未知元素值返回给e
        LOcateElem(L,e):在线性表L中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号表示成功;否则返回0表示失败
        ListInsert(*L,i,e):在线性表L中第i个位置插入新元素e
        ListDelete(*L,i,*e):删除线性表中第i个位置元素,并用e返回其值
        ListLength(L):返回线性表L的元素个数
    endADT
    
  • 对于不同的应用,线性表的基本操作是不同的,上述操作是最基本的,对于实际问题中涉及的关于线性表的更复杂操作,完全可以用这些基本操作的组合实现。

  • 线性表实现集合的并集

    void unionL(List *La, list Lb)
      int La_len,Lb_len,i ;
    	
    	ElemType e;
    	La_len = ListLength(*La);
    	Lb_len = ListLength(lb);
    	
    	for(i=1; i<=Lb_len; i++){
        GetElem(Lb,i,&e);
        if( !LocateElem(La,++La_len,e);
        }
      }
    

线性表的顺序存储结构

顺序存储结构

  • 物理上的存储方式实际上就是在内存中找个初始地址,然后通过占位的形式,把一定的内存空间给占了,然后把相同的数据类型的数据元素依次放在这块空地中

  • 结构代码

    #define MAXSIZE 20
    typedef int ElemType;
    typedef struct
    {
      ElenmType data[MAXSIZE];
      int length;  //线性表长度
    }SqList;
    

    ElemType是通用数据类型这里是整型

  • 顺序存储结构封装的三个属性

    • 存储空间的起始位置,数组data,他的存储位置就是线性表存储空间的存储位置
    • 线性表的最大存储容量:数组长度MaxSize
    • 线性表的当前长度

    注意:数组的长度与线性表的当前长度需要区分:数组长度是存放线性表的存储空间的总长度,一般初始化后不变。而线性表的当前长度是线性表中元素的个数,是会变化的。

  • 地址计算方法:LOC(ai)=LOC(a1)+(i-1)*c

    备注:可以随时计算出线性表中任意位置的地址,所以时间相同,存储时间性能都为O(1),我们通常称为随机存储结构

GetElem实现

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

typedef int Status;

//Status是函数的类型,其值是函数结果状态代码,如OK等
//初始条件:顺序线性表L已存在,1 <=ListLength(L)
//操作结果:用e返回L中第i个数据元素的值

Status GetElem(SqList L,int i, ElemType *e){
  if(L.length==0 || i<1 || i>L.length){
    return ERROR;
  }
  *e = L.data(i-1);
  
  return OK;
}

注意:返回值类型Status是一个整型,约定返回1代表OK,返回0代表ERROR

插入操作

1. 插入算法的思路
  • 如果插入位置不合理,抛出异常
  • 如果线性表长度大于数组长度,则抛出异常或动态增加数组容量
  • 从最后一个元素开始向前遍历到第i个位置,分别将他们都向后移动一个位置
  • 将要插入元素填入位置i处
  • 线性表长+1
2. 代码
Status ListInsert(SqList *L,int i, ElemType e)
{
  int k;
  if(L->length == MAXSIZE)//顺序线性表已经满了
  {
    return ERROR;
  }
  if(i<1 || i>L->lengh+1)//当i不在范围内时
  {
    return ERROR;
  }
  if(i <= L->length)//若插入数据位置不在表尾
  {
    //将要插入位置后的数据元素向后移动一位
    for( k=L->length-1; k>=i-1;k--)
    {
      L->data[k+1] = L->data[k];
    }
  }
  
  L->data[i-1] =e;//将新元素插入
  L->length++;
  
  return OK;
}

删除操作

1.算法思想
  • 如果删除位置不合理,抛出异常
  • 取出删除元素
  • 从删除位置开始便利到最后一个元素位置,分别将它们都向前移动一个位置
  • 表长-1
2. 实现代码
Status ListDelete(Sqlist *L,int i,ElemType *e)
{
  	int k;

  	if(L->length ==0){
      return ERROR;
    }
  	if( i<1 || i>L->length+1)//如果删除元素位置超出数组范围,返回ERROR
    {
      return ERROR;
    }
  
  	*e = L->data[i-1];
  
    if(i < L->length){
      for(k=i ; k<L->length ; k++){
        L->data[k-1]=L->data[k];
      }
    }
  L->length--;
  return OK;
}

线性表特点总结

  • 读取时间复杂度为O(1);插入或删除时,时间复杂度为O(n)
  • 适合元素个数比较稳定,不经常插入和删除元素存取较多的应用
优点
  • 无需为表示元素之间的逻辑关系而增加额外的存储空间
  • 可以快速地存取表中任意位置的元素
缺点
  • 插入和删除操作需要移动大量元素
  • 当线性表长度变化较大时,难以确定存储空间的容量
  • 容易造成存储空间的“碎片”

链式存储结构

定义

  • 把存储数据元素信息的域称为数据域
  • 存储直接后继位置的域称为指针域
  • 指针域中存储的信息称为指针或链
  • 两部分信息组成的数据元素称为存储映像,或称为结点(Node)
  • n个节点链接成一个链表,即为线性表的链式存储结构
  • 因为此链表的每个结点中只包含一个指针域,所以叫做单链表
  • 链表中的第一个结点的存储位置叫做头指针,最后一个指针为空

头指针与头结点的异同

头指针
  • 是指链表只想的第一个结点的指针,若链表有头结点,则是指向头结点的指针
  • 头指针具有标识作用,所以常用头指针冠以链表的名字(指针变量的名字)
  • 无论链表是否为空,头指针均不为空
  • 头指针是链表的必要元素
头结点
  • 头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(但也可以存放链表的长度)
  • 有了头结点,对在第一元素的结点前插入结点和删除第一结点其操作与其他结点的操作就统一了
  • 头结点不一定是链表的必须要素

线性表的单链表存储结构

C语言实现

typedef struct Node
{
  ElemType data; //数据域
  struct Node* Next; //指针域
}Node;
typedef struct Node* LinkList;

单链表的读取

  1. 思路

    1. 声明一个结点p指向链表第一个结点,初始化j从1开始
    2. 当j>i时,就遍历链表,让p的指针向后移动,不断只想下一个结点,j+1;
    3. 若到链表末尾p为空,则说明第i个元素不存在
    4. 否则查找成功,返回结点p的数据
  2. 代码实现

    Status GetElem (LinkList L,int i,ElemType *e)
    {
      int j;
      LinkList p;
      
      p = L->next;
      j = 1;
      
      while(p && j<i)
      {
        return ERROR;
      }
      
      *e = p->data;
      
      return OK;
    }
    

单链表的插入

1.思路
  • 声明一结点p指向链表头结点,初始化j从1开始
  • 当j<1时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1
  • 若到链表末尾p为空,则说明第i个元素不存在
  • 若则查找成功,在系统中生成一个空结点s
  • 将数据元素e赋值给s->data
  • 单链表的插入刚才两个标准语句
  • 返回成功
2.代码实现
Status ListInsert(LinkList *L,int i,ElemType e)
{
  int j;
  LinkList p,s;
  
  p = *L;
  j = 1;
  
  while( p && j<i)//用于寻找i结点
  {
    p = p->next;
    j++;
  }
  
  if( !p || j>i)
  {
    return ERROR;
  }
  
  s = (LinkList)malloc(sizeof(Node));
  s->data = e;
  
  s->next = p->next;//不能调换顺序
  p->next = s;
  
  return OK;
}

单链表的删除

1. 算法思路
  • 声明结点p指向链表第一个结点,初始化j=1
  • 当 j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1
  • 若到链表末尾p为空,则说明第i个元素不存在
  • 若查找成功,将欲删除结点p->next赋值给q
  • 单链表的删除标准语句p->next = q->next
  • 将q结点中的数据赋值给e,作为返回
  • 释放q结点
2.代码实现
Status ListDelete (LinkList *L,int i, ElemType *e)
{
  int j;
  LinkList p,q;
  
  p=*L;
  j=1;
  
  while(p->next && j<i)
  {
    p=p->next;
    ++j;
  }
  
  if(!(p->next) || j>i)
  {
    return ERROR;
  }
  
  q = p->next;
  p->next = q->next;
  
  *e = q->data;
  free(q);
  
  return OK;
}

单链表的整表创建

  • 与顺序存储结构不同,数据分散在内存的各个角落,增长是动态的
  • 对于每个链表,占用空间的大小和位置是不需要预先分配划定的,可以即时生成
  • 创建单链表是一个动态生成链表的过程,从“空表”的初始状态起,依次建立各元素结点并插入链表
算法思路
  • 声明一结点p和计数器变量i
  • 初始化一空链表L
  • 让L的头结点的指针指向NULL,即建立一个人带头结点的单链表
  • 循环实现后继结点的赋值和插入
头插法
  • 从一个空表开始,生成新结点,读取数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头上,直到结束为止

  • 简单说,就是把新加进的元素放在表头后的第一个位置:

    • 先让新结点的next指向头结点之后
    • 然后让表头的next指向新结点
  • 简单说就是现实中插队的现象

    void CreaatListHead(LinkList *L,int n)
    {
      LinkList p;
      int i;
      
      srand(time(0));//初始化随机种子
      
      *L = (LinkList)malloc(sizeof(Node));
      (*L)->next = NULL;
      
      for( i=0; i<n; i++)
      {
        p = (LinkList)malloc(sizeeof(Node));
        p->data = rand()%100+1;
        p->next = (*L)->next;
        (*L)->next = p;
      }
    }
    
尾插法
  • 把新结点插入到最后

    void CreateListTail(LinkList *L,int n)
    {
      LinkList p,r;
      int i;
      
      srand(time(0));
      *L = (LinkList)malloc(sizeof(Node));
      r = *L;
      
      for( i=0;i<n;i++)
      {
        p=(Node *)malloc(sizeof(Node));
        p->data = rand()%100+1;
        r->next = p;
        r = p;
      }
      
      r->next = NULL;
    }
    

单链表的整表删除

  • 当不再使用时,需要整表销毁释放内存
算法思路
  • 声明结点p和q

  • 将第一个结点赋值给p,下一结点赋值给q

  • 循环执行释放p和将q赋值给p的操作

    Status ClearList(LinkList *L)
    {
      LinkList p,q;
      
      p = (*L)->next;
      
      while(p)
      {
        q = p->next;
        free(p);
      	p=q;
      }
      
      (*L)->next = NULL;
      
      return OK;
    }
    

    单链表结构与顺序存储结构优缺点

    存储分配方式
    • 顺序存储结构一般用一段连续的存储单元一次存储线性表的数据元素
    • 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素
    • 时间性能
      • 查找
        • 顺序结构O(1)
        • 单链表O(n)
      • 插入和删除
        • 顺序存储结构需要平均移动表长一半的元素,时间为O(n)
        • 单链表在计算出某位置的指针后,插入和删除时间仅为O(1)
    • 空间性能
      • 顺序存储结构需要分配存储空间,大了空间浪费,笑了,容易溢出
      • 单链表不用分配存储空间,只要有就可以分配,元素个数也不受限制

    线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构

    需要频繁插入和删除时,宜采用单链表结构

静态链表

数组实现

  • 数组实现的链表又叫做静态链表,这种描述方法叫做游标实现法

静态链表定义

#define MAXSIZE 1000
typedef struct
{
  ElemType data;//数据域
  int cur;//指针域
}Component,StaticLinkList[MAXSIZE];

静态链表的初始化

Status InitList(StatusLinkList space)
{
  int i;
  for( i=0;i<MAXSZIZE-1;i++)
    space[i].cur = i+1;
  
  space[MAXSIZE-1].cur = 0;
  
  return OK;
}

总结

  • 对数组的第一个和最后一个元素特殊处理,他们的data不存放数据
  • 通常把为使用的数组元素称为备用链表
  • 数组的第一个元素,即下标为0的那个元素的cur就存放备用链表的第一个结点的下标
  • 数组的最后一个元素,即下标为MAXSIZE-1的cur则存放第一个有数值的元素的下标,相当于单链表中的头结点的作用

静态链表的插入操作

Malloc实现
int Malloc_SLL(StaticLinkList space)
{
  int i = space[0].cur;
  if( space[0].cur)
    space[0].cur = space[i].cur;
  //把它的下一个分量用来作为备用
  return i;
}
代码实现
Status ListInsert( StaticLinkList L,int i,ElemType e)
{
  int j,k,l;
  
  k = MAX_SIZE - 1;//数组的最后一个元素
  if(i<1 || i>ListLength(L)+1)
  {
    return ERROR;
  }
  
  j = Malloc_SLL(L);
  if(j)
  {
    L[j].data = e;
    for( l=1; l<=i-1; l++)
    {
      k = L[k].cur;
    }
    L[j].cur = L[k].cur;
    L[k].cur = j;
    
    return OK;
  }
  return ERROR;
}

总结

优点

在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点

缺点
  • 没有解决连续存储分配(数组)带来的表长难以确定的问题
  • 失去了顺序存储结构随机存取的特性

总的来说,静态链表其实是为了给了没有指针的编程语言设计的一种是子安单链表功能的方法

腾讯面试题
题目:快速找到未知长度单链表的中间节点
原理
  • 设置两个指针*search、 *mid都指向单链表的头结点
  • search的移动速度是*mid的两倍
  • 当*search指向末尾结点的时候,mid正好就在中间了,即标尺的思想
代码
Status GetNode(LinkList L,ElemType *e)
{
  LinkList search,mid;
  mid = search = L;
  
  while(search->next != NULL)
  {
    //search移动的速度是mid的2倍
    if(search->next->next != NULL)
    {
      search = search->next->next;
      mid = mid->next;
    }
    else
    {
      search = search->next;
    }
  }
  *e = mid->data;
  
  return OK;
}

循环链表

  • 由于单链表到了尾部标识就停止了向后链的操作,只能索引后继结点不能索引前驱结点
  • 改进
    • 将单链表中终端结点的指针端由空指针改为指向头结点
    • 将单链表中终端结点的指针端由空指针改为指向头结点。
  • 循环链表不一定有头结点
  • 与单链表的主要差异在于循环的判断空链表的条件上,原来判断head->next是否为null,现在则是head->是否等于head

初始化代码

//初始化循环链表
void ds_init(node **pNode)
{
	int item;
	node *temp;
	node *target;
	
	printf("请输入结点的值,输入0完成初始化")while(1)
	{
    scanf("%d",&item);
   	fflush(stdin);
   
     if(item == 0)
       return;

     if((*pNode) == NULL)
     {//循环链表中只有一个结点
       *pNode = (node*)malloc(sizeof(struct CLinkList));
       if(!(*pNode))
         exit(0);
       (*pNode)->data = item;
       (*pNode)->next = *pNode;
     }
     else
     {
       //找到next指向第一个结点的结点
       for(target = (*pNode); target->next != (*pNode); target = target->next)
         ;
       //生成一个新的结点
       temp = (node*)malloc(sizeof(struct CLunkList));
       
       if(!temp)
         exit(0);
       
       temp->data = item;
       temp->next = *pNode;
       target->next = temp;
     }

	}
}

插入代码

//链表存储结构的定义
typedef struct CLinkList
{
  int data;
  struct CLinkList *next;
}node;

//插入结点
//参数:链表的一个结点,插入的位置
void ds_insert(node **pNode ,int i)
{
  node *temp;
  node *target;
  node *p;
  int item;
  int j = 1;
  
  printf("请输入要插入结点的值:");
  scanf("%d",&item);
  
  if(i == 1)
  {
    //新插入的结点作为一个结点
    temp = (node *)malloc(sizeof(struct CLinList));
    
    if(!temp)
      exit(0);
    
    temp->data = item;
    
    //寻找到最后一个结点
    for(target = (*pNode);target->next != (*pNode); target = target->next);
    
    temp->next = (*pNode);
    target->next = temp;
    *pNode = temp;
  }
  else
  {
    target = *pNode;
    
    for( ;j<(i-1) ;++j)
    {
      target = target->next;
    }//target指向第三个元素
    
    temp = (node*)malloc(sizeof(struct CLinkList));
    
    if(!temp)
      exit(0);
    
    p = target->next;
    target->next = temp;
    temp->next = p;
  }
}

删除结点

void ds_delete(node **pNode,int i)
{
  node *taget;
  node *temp;
  int j = 1;
  
  if(i == 1)
  {
    //删除的第一个结点
    //找到最后一个结点
    for(target = *pNode; target->next != *pNode;target = target->next)
      ;
    
    temp = *pNode;
    *pNode = (*pNode)->next;
    target->next = *pNode;
    free(temp);
  }
  else
  {
    target = *pNode;
    
    for( ; j<i-1; ++j)
    {
      target = target->next;
    }
    
    temp = target->next;
    target->next = temp->next;
    free(temp);
  }
}

查找结点

//返回结点所在位置
int ds_search(node *pNode,int elem)
{
  node *target;
  int i = 1;
  
  for(target = pNode;target->data != elem && target->next != pNode;++i)
  {
    target = target->next;
  }
  
  if(target->next == pNode)
    return 0;
  else
    return 1;
}

单链表中是否有环

  1. 使用p、q两个指针,p总是向前走,但每次都从头开始走,对于每个结点,看p走的步数是否和q一样
  2. 使用快慢指针,p每次向前走一步,q每次向前走两步,若在某个时候p == q,则存在环
//随机产生n个元素的值,建立表头结点的单链表线性表L(尾插法)
void CreatListTail (LinkList *L,int n)
{
  LinkList p,r;
  int i;
  
  srand(time(0));//初始化随机数种子
  *L = (LinkList)malloc(sizeof(Node));//L为整个线性表
  r = *L;//r为指向尾部的结点
  
  for(i=0;i<n;i++)
  {
    p = (Node *)malloc(sizeof(Node));//生成新结点
    p->data = rand()%100+1;//随机生成100以内的数字
    r->next=p;//将表尾终端结点的指针指向新结点
    r = p;//将当前的新结点定义为表尾终端结点
  }
  
  r->next = (*L)->next->next;
  //比较步数的方法
  int HasLoop(LinkList L)
  {
    LinkList cur1 = L;
    int pos1 = 0;
    
    while(cur1)
    {
      LinkList cur2 = L;
      int pos2 = 0;
      while(cur2)
      {
        if(cur2 == cur1)
        {
          if(pos1 == pos2)
            break;
          else
          {
            printf("环的位置在第%d个结点处,\n\n",pos2);
            return 1;
          }
        }
        cur2 = cur2->next;
        pos2++;
      }
      cur1 = cur1->next;
      pos1++;
    }
    return 0;
  }
  //利用快慢指针的方法
  int HsdLoop2(LinkList L)
  {
    int step1 = 1;
    int step2 = 2;
    LinkList p = L;
    LinkList q = L;
    
    while(p!=NULL && q != NULL && q->next != NULL)
    {
     	p = p->next;
      if(q->next !=NULL)
        q = q->next->next;
      
      printf("p:%d,q:%d\n",p->data,q->data);
      
      if(p == q)
        return 1;
    }
    return 0;
  }
  
  int main()
  {
    LinkList L;
    Status i;
    char opp;
    ElemType e;
    int find;
    int tmp;
    
    i = InitList(&L);
    printf("初始化L后:ListLength(L) = %d\n",ListLength(L));
    
    printf("\n1.创建有环链表(尾插法)\n2.创建无环链表(头插法)\n3.判断链表是否有环\n0.退出")
  }
  while(opp != '0')
  {
    scanf("%c",&opp);
    switch(opp)
    {
      case '1':
        CreateListTail(&L,10);
        printf("成功创建有环L(尾插法)\n");
        printf("\n");
        break;
        
      case '2':
        CreateListHead(&L,10);
        printf("成功创建无环L(头插法)\n");
        printf("\n");
        break;
        
      case '3':
        printf("方法:\n\n");
        if(HashLoop(L))
        {
          printf("结论:链表有环\n\n\n");
        }
        else
        {
          printf("结论:链表无环\n\n\n");
        }
        
        printf("方法二:\n\n");
        if(HashLoop(L))
        {
          printf("结论:链表有环\n\n\n");
        }
        else
        {
          printf("结论:链表无环\n\n\n");
        }
        printf("\n");
        break;
        
      case '0':
        exit(0);
    }
  }
}

双向链表

结构代码

typedef struct DualNode
{
  ElemType data;
  struct DualNode *prior;//前驱结点
  struct DualNode *next;//后继结点  
}DualNode,*DuLinkList;

插入操作

  • 代码实现

    s->next = p;
    s->prior = p->prior;
    p->prior->next = s;
    p->prior = s;
    

    顺序不能乱

删除操作

  • 代码实现

    p->prior->next = p->next;
    p->next->prior =p->prior;
    free(p);
    

栈和队列

栈的定义

  • 定义:是一个后进先出的线性表,要求只在表尾进行删除和插入操作
  • 只能在表尾操作

栈的插入和删除操作

  • 栈的插入(Push):叫做进栈,也称压栈,入栈
  • 栈的删除操作(Pop):叫做出战,也称为弹栈

栈的顺序存储结构

typedef int ElemType 
typedef struct
{
  ElemType *base;//指向栈底的指针变量
  ElemType *top;//只想站定的指针变量
  int stackSize;//栈的当前可使用的最大容量
}sqStack;

创建一个栈

#define STACK_INIT_SIZE 100
initStack(aqStack *s)
{
  s->base = (ElemType*)malloc(STACK_INIT_SIZE*sizeof(ElemType));
  if(!s->base)
    exit(0);
  s->top = s->base;//最开始,栈顶就是栈底
  s->stackSize = STACK_INIT_SIZE;
}

入栈操作

  • 又称压栈操作,就是向栈中存放数据

  • 在栈顶进行,每次向栈中压入一个数据,top指针+1,直到栈满为止

    Push(sqStack *s,ElemType e)
    {
      //如果栈满,追加空间
      if(s->top - s->base >= s->stackSize)
      {
        s->base = (ElemType *)realloc(s->base,(s->stackSize + STACKINCREMENT) * sizeof(ElemType));
        if(!s->base)
          exit(0);
        
        s->top = s->base + s->stackSize;//设置栈顶
        s->stackSize = s->stackSize + STACKINCREMENT;//设置栈的最大容量
      }
      
      *(s->top) = e;
      s->top++;
    }
    

出栈操作

  • 出栈操作就是在栈顶取出数据,栈顶指针随之下移
  • 每当从栈内弹出一个数据,栈的当前容量就-1
Pop(sqStack *s,ElemType *e)
{
  if(s->top == s->base)//栈空
    return;
  *e = *--(s->top);
}

清空栈

  • 将栈中的元素全部作废,本身物理空间不发生改变(不是销毁)

  • s->top = s->base

    ClearStack(sqStack *s)
    {
      s->top = s->base;
    }
    

销毁栈

释放栈的物理内存空间,与清空不同

DestoryStack(sqStack *s){
  int i,len;
  len = s->stackSize;
  for( i=0; i<len; i++){
    free( s->base );
    s->base++;
  }
  s->base = s->top = NULL;
  s ->stackSize = 0;
}

计算栈的当前容量

  • 就是计算栈中元素的个数

  • 返回s.top-s.base

    int StackLen(sqStack s)
    {
      return(s.top - s.base);
    }
    

栈的实例(二进制转化为十进制)

  • 从最低位起用每一位乘以对应位的积,然后全部加起来

    #include "stdio.h"
    #include "stdlib.h"
    #include "math.h"
    
    #define STACK_INIT_SIZE 20
    #define STACKINCREMENT  10
    
    typedef char ElemType;
    typedef struct
    {
        ElemType *base;
        ElemType *top;
        int stackSize;
    }sqStack;
    
    void InitStack(sqStack *s)
    {
        s->base = (ElemType *)malloc(STACK_INIT_SIZE*sizeof(ElemType));
        if (!s->base)
        {
            exit(0);
        }
    
        s->top = s->base;
        s->stackSize = STACK_INIT_SIZE;
    }
    
    void Push(sqStack *s,ElemType e)
    {
        if (s->top - s->base >= s->stackSize)
        {
            s->base = (ElemType *)realloc(s->base,(s->stackSize + STACKINCREMENT)* sizeof(ElemType ));
            if(!s->base)
            {
                exit(0);
            }
        }
    
        *(s->top) = e;
        s->top++;
    }
    
    void Pop(sqStack *s,ElemType *e){
        if (s->top ==s->base)
        {
            return;
        }
        *e = *--(s->top);
    }
    
    int StackLen(sqStack s){
        return (s.top - s.base);
    }
    
    int main(){
        ElemType  c;
        sqStack  s;
        int len,i,sum=0;
    
        printf("请输入二进制数,输入#表示结束!\n");
        scanf("%c",&c);
        while (c!='#')
        {
            Push(&s,c);
            scanf("%c",&c);
        }
    
        getchar();//把'\n'从缓冲区去掉
    
        len = StackLen(s);
        printf("栈的当前容量是:%d\n",len);
    
        for (int i = 0; i < len; i++) {
            Pop(&s,&c);
            sum = sum + (c-48)*pow(2,i);
        }
        printf("转换为十进制数是:%d\n",sum);
    
        return 0;
    }
    

栈的链式存储结构

  • 通常用顺序存储结构,链式了解

  • 栈因为只是栈顶做插入删除,所以将栈顶放在单链表的头部,栈顶和单链表的头指针合二为一

    typedef struct StackNode
    {
      ElemType data;存放栈的数据
      struct StackNode *next;
    }StackNode,*LinkStackPtr;
    typedef struct LinkStack
    {
      LinkStackPrt top;//头指针
      int count;//栈元素计数器
    }
    

进栈操作

Status Push(LinkStack *s,ElemType e)
{
  LinkStackPtr p= (LinkStackPtr)malloc(sizeof(StackNode));
  p->data=e;
  p->next = s->top;
  s->top=p;
  s->count++;
  return OK;
}

出栈操作

Status Pop(LinkStack *s,ElemType *e)
{
  LinkStackPtr p;
  if(StavkEmpty(*S))//判断是否为空栈
    return ERROR;
  *e = s->top->data;
  p = s->top;
  s->top = s->top->next;
  free(p);
  s->coun--;
  return OK;
}

队列

定义

  • 是只允许在一端进行插入操作,在另一端进行删除操作的线性表
  • 与栈相反,队列是一种先进先出的线性表
  • 与栈相同的是,队列也是一种重要的线性结构
  • 实现一个队列同样需要顺序表或链表做为基础
  • 队列可以用链表实现,也可以用顺序表实现。
  • 与栈相反的是,队列一般用链表实现
  • 简称链队列

代码实现

typedef struct Qnode{
  ElemType data;
  struct Qnode *next; 
}Qnode,*QueuePrt;

typedef struct{
  QueuePrt front,rear;//队头、尾指针
}LinkQueue;
  • 将队头指针指向链队列的头结点,队尾指针指向终端结点
  • 头结点不存放数据
  • 空队列时,front和rear都指向头结点

创建一个队列

  • 在内存中创建一个头结点

  • 将队列的头指针和尾指针都指向这个生成的头结点

    initQueue (LinkQueue *q)
    {
      q->front= q->rear=(QueuePrt)malloc(sizeof(Qnode));
      if(!q->front)
        exit(0);
      q->front->next = NULL;
    }
    

入队列操作

InsertQueue(LinkQueue *q,ElemType e)
{
  QueuePtr p;
  p = (QueuePrt)malloc(sizeof(Qnode));
  if(p == NULL)
    exit(0);
  p->data = e;
  p->next = NULL;
  q->rear->next = p;
  q->rear = p;
}

出队列操作

  • 将队列中的第一个元素移出,队头指针不发生改变,改变头结点的next指针即可
  • 原队列只有一个元素,应该处理尾指针
DeleteQueue (LinkQueue *q,ElemType *e)
{
  QueuePrt p;
  if(q->front == q->rear)
    return;
  p = q->front->next;
  *e = p->data;
  q->front->next = p->next;
  if(q->rear == p)
    q->rear = q->front;
  free(p);
}

销毁队列

DestroyQueue(LinkQueue *q)
{
  while(q->front){
    q->rear = q->front->next;
    free(q->front);
    q->front = q->rear;
  }
}

队列的顺序存储结构

  • 假设一个队列有n个元素
  • 舜秀存储的队列需建立一个大于n的存储单元,并把队列的所有元素存储在数组的前n个单元
  • 数组下标为0的一段则是队头
  • 通过取模运算使指针从头开始
  • (rear+1)%QueueSize
  • (front+1)%QueueSize
代码
定义一个循环队列
#define MAXSIZE 100
typedef struct
{
  ElemType *base;//用于存放内存分配基地址,也可以用数组
  int front;
  int rear;
}
初始化
initQueue(cycleQueue *q)
{
  q->base = (ElemType *)malloc (MAXSIZE*sizeof(ElemType));
  if(!a->base)
    exit(0);
  q->front = q->rear = 0;
}
入队列操作
InsertQueue(cycleQueue *q,ElemType e)
{
  if((q->rear+1)%MAXSIZE == q->front)
    return;//队列已满
  q->base[q->rear] = e;
  q->rear = (q->rear+1)% MAXSIZE;
}
出队列操作
DeleteQueue(cycleQueue *q,ElemType *e)
{
  if(q->front == q->rear)
    return;//队列为空
  *e = q->base[q->front];
  q->front = (q->front+1)%MAXSIZE;
}

递归和分治

  • 斐波那契数列递归实现

    int Fib(int i){
      if(i<2)
        return i==0?0:1;
      return Fib(i-1)+Fib(i-2);
    }
    

定义

  • 把一个直接调用自己的函数称为递归函数
  • 递归必须至少有一个条件,当满足时不再进行
  • 能够使程序更加容易理解
  • 迭代使用循环结构,递归使用选择

汉诺塔问题

  • 先将前n-1哥盘子移动到y上,确保大盘在小盘下
  • 再将最底下的第n哥盘子移动到z上
  • 最后将y上的63个盒子移动到z上
#include <stdio.h>
//将n个盘子从x借助y移动到z
void move(int n,char x,char y,char z)
{
  if(i == 0)
    printf("%c-->%c\n",x,z);
  else
  {
    move(n-1,x,z,y);//将n-1个盘子从x借助z移动到y上
    printf("%c-->%c\n",x,z);//将第n个盘子从x移动到z上
    move(n-1,y,x,z);//将n-1个盒子从y借助x移动到z上
  }
}

int main(){
  int n;
  printf("请输入汉诺塔的层数:")scanf("%d",&n);
  printf("移动的步骤如下:\n");
  move(n,'x','y','z');
  return 0;
  
}

八皇后问题

  • 在8x8格国际象棋上摆放八个皇后,使其不能相互攻击,任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法
代码实现
#include <stdio.h>
int count = 0;

int notDanger(int row,int j,int (*chess)[8])
{
    int i,k,flag1 = 0,flag2=0,flag3=0,flag4=0,flag5=0;
    //判断列方向
    for(i=0;i<8;i++)
    {
        if(*(*(chess+i)+j)!=0)
        {
            flag1=1;
            break;
        }
    }
    //判断左上方
    for(i=row,k=j;i>=0&&k>=0;i--,k--)
    {
        if(*(*(chess+i)+k)!=0)
        {
            flag2 =1;
            break;
        }
    }
    //判断右下方
    for(i=row,k=j;i<8&&k<8;i++,k++)
    {
        if(*(*(chess+i)+k)!=0)
        {
            flag3 =1;
            break;
        }
    }
    //判断右上方
    for(i=row,k=j;i>=0&&k<8;i--,k++)
    {
        if(*(*(chess+i)+k)!=0)
        {
            flag4 =1;
            break;
        }
    }
    //判断左下方
    for(i=row,k=j;i<8&&k>=0;i++,k--)
    {
        if(*(*(chess+i)+k)!=0)
        {
            flag5 =1;
            break;
        }
    }
    if(flag1||flag2||flag3||flag4||flag5)
    {
        return 0;
    }
    else{
        return 1;
    }
}
//row:表示起始行
//n列数
//(*chess)[8]:指向棋盘每一行的指针
void EightQueen(int row,int n,int (*chess)[8])
{
    int chess2[8][8],i,j;//临时棋盘
    for(i=0 ;i<8;i++)//将主棋盘赋值给临时棋盘
    {
        for(j=0;j<8;j++)
        {
            chess2[i][j]=chess[i][j];
        }
    }
    if(8 == row)
    {
        printf("第%d种",count+1);
        for(i=0;i<8;i++)
        {
            for(j=0;j<8;j++)
            {
                printf("%d ",*(*(chess2+i)+j));
            }
            printf("\n");
        }
        printf("\n");
        count++;
    }
    else{
        for(j=0;j<n;j++)
        {
            if(notDanger(row,j,chess))//判断是否危险
            {
                for(i=0;i<8;i++)
                {
                    *(*(chess2+row)+i) = 0;
                }
                *(*(chess2+row)+j) = 1;
                EightQueen(row+1,n,chess2);
            }
        }
    }
}

int main()
{
    int chess[8][8],i,j;

    for(i=0;i<8;i++)//初始化
    {
        for(j=0;j<8;j++)
        {
            chess[i][j] = 0;
        }
    }

    EightQueen(0,8,chess);
    printf("总共有%d种解决方法!\n\n",count);
    return 0;
}

字符串

定义

  • 串是由零个或多个字符组成的有限序列,又称字符串
  • 串可以是空串,即没有字符直接由双引号表示,或者用Φ表示
  • 字串和主串

字符串比较

  • 比大小:每个字符的ASCII码大小
  • 重视相等

字符串的存储结构

  • 与线性表相同,也分为顺序存储结构和链式存储结构
  • 顺序存储结构是用一组地址连续的存储单元来存储串中的字符序列的
  • 按照预定义的大小,为每个定义的字符串变量分配一个固定长度的存储区,一般用定义长数组定义
  • 与线性表相比,固定存储区域,就存在空间分配不灵活的问题,但是字符串一般连着表述

BF算法

  • 属于朴素的匹配算法
算法思想
  • 有两个字符串S和T,长度为N和M。首先S[1]和T[1]比较,若想等,则在比较S[2]和T[2],一直到T[M]为止;
  • 若S[1]和T[1]不等,则T向右移动一个字符的位置,在依次进行比较
  • 算法最坏情况下要进行M*(N-M+1)次比较,时间复杂度为O(M * N)
  • S是主串,T是子串,这种子串的定位操作通常称作串的模式匹配
  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值