数据结构与算法——第三节 链表(单向不循环不带头+双向循环带头 C实现+源码剖析+运行+思路分析)

简介: 可以看到 ,我如果要吧黑球插入到白球里面,显然,我要把7号位的球移到8号位,5号位的球移到6号位...然后最后才能把2号位的求插进去。如果有N个数据,那么它的算法的时间复杂度达到了O(N)!

目录


链表的存在意义和背景  


链表的构成与定义


链表的分类


双链表的实现


函数1:打印链表      void ListPrint(ListNode* phead);    


函数2:ListNode* BuyListNode(LTDataTYpe x);//创建新节点


函数3:ListNode* ListInit();//初始化链表


函数4:void ListNodePushFront(ListNode* phead, LTDataTYpe x);//头插


函数5:void ListNodePushBack(ListNode* phead, LTDataTYpe x);//尾插


函数6:void ListPopFront(ListNode* phead);//头删


函数7:void ListPopBack(ListNode* phead);//尾删


函数8:ListNode* ListFind(ListNode* phead, LTDataTYpe x); //寻找数据域为x的节点,返回该节点


函数9:void ListInsert(ListNode* pos, LTDataTYpe x);//在pos节点的后面插入一个元素


函数10:void ListErase(ListNode* pos);//删除pos节点


函数11:int ListEmpty(ListNode* phead);//判断链表是否为空


函数12:int ListSize(ListNode* phead);//判断链表的大小(头节点是不算的)


函数13:void ListDestory(ListNode* phead);//销毁链表


单链表的实现


函数1:void SListPrint(SListNode* plist);            //打印链表


函数2:SListNode* BuySListNode(SLTDataType x);  //创建新节点


函数3:void SListPushBack(SListNode** pplist, SLTDataType x);//尾插


函数4:SListNode* SListPushFront(SListNode** pplist, SLTDataType x);//头插


函数5:void SListPopBack(SListNode** pplist);          //尾删


函数6:void SListPopFront(SListNode** pplist);              //头删


函数7:SListNode* SListFind(SListNode* plist, SLTDataType x);  //寻找节点


函数8:void SListInsertAfter(SListNode* pos, SLTDataType x);   //在目标节点的后面插入


函数9:void SListInsertBefore(SListNode** pplist, SListNode* pos, SLTDataType x);//在目标节点的前面插入


函数10:void SListEraseAfter(SListNode* pos);          //删除pos节点的下一个节点


函数11:void SListEraseCur(SListNode** pplist, SListNode* pos); //删除pos节点


函数12:void SListDestory(SListNode** plist);//销毁链表


链表和顺序表的对比:


链表的存在意义和背景  

链表,又叫线性表的链式存储结构。


我们在前面曾经说过线性表;而线性表有其缺点,其缺点就是对于数据的增加和删除极为麻烦;


一个元素如果要增加或者删除,那么整个后面的元素都要移动。


如下图:

微信图片_20221208200125.gif



可以看到 ,我如果要吧黑球插入到白球里面,显然,我要把7号位的球移到8号位,5号位的球移到6号位...然后最后才能把2号位的求插进去。如果有N个数据,那么它的算法的时间复杂度达到了O(N)!


那有没有什么好的方法来解决这种问题呢?


当然是有的——链表就出现了。


链表的构成与定义

对于一个链表来说,和顺序表一样,我们认为其也需要节点,也是由一个个节点构成的。


在这样的一个节点中,我们需要存储一些数据,从而能够使得所有的节点连接起来形成一个链表。


在以前的顺序结构存储中,每个元素只需要存储元素信息就可以了。而对于链表而言,它不仅仅需要存储元素的信息,还要存储一个能够表示前后节点的关系的东西,这个东西我们用指针来实现。


所以总结来说,一个节点最少需要存储两样东西:数据信息和(下一个节点的)指针。


于是n个这样的节点就构成了一个链表,即线性表的链式存储结构。


这样,我们也就可以用一个结构体来表示节点中的内容了:


(同样的,还是将int 类型重新命名)

typedef int SLTDataType;
typedef struct SListNode
{
  SLTDataType data;
  SListNode* next;
}SListNode;


这是最简单的节点的构成。


链表的分类

对于链表而言,我们有单向不循环的链表:就是像刚刚所写的节点那样。


这样的节点之间的关系可以表示为:

image.png



(第一个节点的地址我没有写;最后一个节点的指针指向的为空)


(该图为单向不循环无头节点)


但是,上述的节点只是能够访问下一个节点的内容。(因为其指针是指向下一个节点)


有的时候,我如果想要访问上一个节点怎么办呢?


于是乎,我们就又出现了双向链表;即在一个链表当中,既存在一个指针指向下一个节点,又存在一个指针指向上一个节点。


image.png


(双向不循环无头结点)


如果我们想让连表有一个头呢?


这样我们在调用的时候想改变链表,我们就直接可以传头指针就可以了,否则还需要传二级指针,非常麻烦。


于是乎,又出现了一种新的链表:

image.png

双向带头不循环:


(如上图,为双向带头不循环链表)


那有没有这样一种办法:


我通过末尾的节点就能够直接找到头节点呢?


我把最后一个节点的next指针利用起来,让其指向头指针,并且同时把头节点的prev指针利用起来,让它指向末尾的节点,可以吗?


答案是肯定的。


这样的话,我们就构成了一种新的链表类型——双向带头循环链表

image.png



所以我们可以来做一下总结:


不带头               单向            不循环


带头                  双向              循环


这样的话,总共有2*2*2=8种不同的链表类型。


而我们今天,主要来讲    不带头单向不循环链表    和     带头双向循环链表


一种 是最简单的,还有一种是最复杂的。


掌握了这两种写法呢,其他的就基本上拼拼凑凑,就出来了。


(我们上面所画出来的结构,叫逻辑结构;链表实际在内存中存储的方式叫做物理结构)


话不多说,我们开始实现:


由于笔者觉得双链表在实现起来比单链表简单(在代码的实现上),故笔者决定先说双链表,再说单链表。


注:本文所说的单链表指的都是单向不带头不循环链表;


所说的双链表指的都是带头双向循环链表。


双链表的实现

我们这里的双链表,指的就是双向带头循环链表


我们同样,定义一个节点:



如上图,next是指向下一个节点的指针;


prev是指向下一个节点的指针。

#pragma once
#include<malloc.h>
#include<assert.h>
#include<stdio.h>
typedef int LTDataTYpe;
typedef struct ListNode
{
  struct ListNode* next;
  struct ListNode* prev;
  LTDataTYpe data;  
}ListNode;


我们接下来要依次实现下列函数

void ListPrint(ListNode* phead);    //函数1:打印链表
ListNode* BuyListNode(LTDataTYpe x);//函数2:创建新节点
ListNode* ListInit();               //函数3:初始化链表
void ListNodePushFront(ListNode* phead, LTDataTYpe x);//函数4:头插
void ListNodePushBack(ListNode* phead, LTDataTYpe x);//函数5:尾插
void ListPopFront(ListNode* phead);                  //函数6:头删
void ListPopBack(ListNode* phead);                   //函数7:尾删
ListNode* ListFind(ListNode* phead, LTDataTYpe x);   //函数8:寻找节点
void ListInsert(ListNode* pos, LTDataTYpe x);        //函数9:在pos的后面插入节点
void ListErase(ListNode* pos);                       //函数10:删除pos节点
int ListEmpty(ListNode* phead);                      //函数11:判断链表是否为空
int ListSize(ListNode* phead);                       //函数12:求链表的大小
void ListDestory(ListNode* phead);                   //函数13:销毁链表



我们一网打尽,和顺序表一样的方式来介绍:


函数1:打印链表      void ListPrint(ListNode* phead);    

这个就是送分题,非常的简单。我们麻溜点,直接上代码了。

void ListPrint(ListNode* phead)
{
  ListNode* cur = phead->next;
  while (cur != phead)               //循环遍历,逐个打印
  {
  printf("%d", cur->data);
  if (cur->next != phead)
  {
    printf("->");
  }
  cur = cur->next;
  }
  printf("\n");
}



在这里,想请读者注意一下:


由于这里我们是双向循环链表,就是说,最后一个节点的next所存放的地址不再为空指针;而是头节点的地址。


所以我们这里的判断条件都是cur->next != phead,或者cur != phead(因为其转了一圈回来又回到头指针上面去了)


函数2:ListNode* BuyListNode(LTDataTYpe x);//创建新节点

该函数和我们在单链表中的实现方式本质上一样。

ListNode* BuyListNode(LTDataTYpe x)
{
  ListNode* node = (ListNode*)malloc(sizeof(ListNode));
    //动态开辟一块空间
  node->next = NULL;
  node->prev = NULL;//两个指针都先置空
  node->data = x;   //数据域放进去
  return node;      //返回新创建的该指针
}



函数3:ListNode* ListInit();//初始化链表


该函数的主要作用是创建头节点;从而达到初始化链表的目的。

ListNode* ListInit()
{
  ListNode* phead = BuyListNode(0);
    //创建一个节点,作为头节点。数据域可以随便赋值
  phead->next = phead;
  phead->prev = phead;//头节点的next和prev指针都指向phead
  return phead;       //返回该头节点
}


函数4:void ListNodePushFront(ListNode* phead, LTDataTYpe x);//头插

void ListNodePushFront(ListNode* phead, LTDataTYpe x)
{              //注意该头插指的是插到头节点的后面
  ListNode* newnode = BuyListNode(x);//创建新节点
  ListNode* first = phead->next;     //第一个节点(非头节点记为first节点)
  phead->next = newnode;             
  newnode->prev = phead;
  newnode->next = first;
  first->prev = newnode;            //三节点交换指针指向关系
}



三节点交换指针指向关系可以用下面的动图 来演示:


微信图片_20221208200705.gif


函数5:void ListNodePushBack(ListNode* phead, LTDataTYpe x);//尾插


这里就相当于插入到了头节点的前面


原理和头插一模一样,不再赘述

void ListNodePushBack(ListNode* phead, LTDataTYpe x)
{
  ListNode* tail = phead->prev;
  ListNode* newnode = BuyListNode(x);
  tail->next = newnode;
  newnode->next = phead;
  phead->prev = newnode;
  newnode->prev = tail;
}


函数6:void ListPopFront(ListNode* phead);//头删

就是把头节点后面的节点删除

void ListPopFront(ListNode* phead)
{
  assert(phead);
  assert(phead->next != phead);   //两步断言一下
  ListNode* first = phead->next;    
  ListNode* second = first->next;
  phead->next = second;
  second->prev = phead;  //同样的道理,三指针交换
  free(first);           //注意要free
}
这里的三指针交换可用下面的动图来演示:

函数7:void ListPopBack(ListNode* phead);//尾删

同理即可,就是相当于删除头节点前面的那个节点

不再赘述

void ListPopBack(ListNode* phead)
{
  assert(phead);
  assert(phead->next != phead);
  ListNode* tail = phead->prev;
  ListNode* tailPrev = tail->prev;
  tailPrev->next = phead;
  phead->prev = tailPrev;
  free(tail);
}


image.png

函数8:ListNode* ListFind(ListNode* phead, LTDataTYpe x); //寻找数据域为x的节点,返回该节点

ListNode* ListFind(ListNode* phead, LTDataTYpe x)
{
  assert(phead);        //先断言
  ListNode* cur = phead->next;   //然后将cur存储phead后面的节点的内容
  while (cur != phead)           //循环遍历去寻找
  {
  if (cur->data == x)        //找到了就返回那个节点
  {
    return cur;
  }
        cur = cur->next;
  }
  return NULL;                   //没找到就返回空
}



函数9:void ListInsert(ListNode* pos, LTDataTYpe x);//在pos节点的后面插入一个元素

这个类比头插尾插就可以了


不做过多赘述

void ListInsert(ListNode* pos, LTDataTYpe x)
{
  assert(pos);
  ListNode* prev = pos->prev;
  ListNode* newnode = BuyListNode(x);
  prev->next = newnode;
  newnode->prev = prev;
  newnode->next = pos;
  pos->prev = newnode;
}


函数10:void ListErase(ListNode* pos);//删除pos节点


同样的道理,类比尾删、头删就可以了,没有必要赘述。

void ListErase(ListNode* pos)
{
  assert(pos);
  ListNode* prev = pos->prev;
  ListNode* next = pos->next;
  prev->next = next;
  next->prev = prev;
  free(pos);
}


函数11:int ListEmpty(ListNode* phead);//判断链表是否为空


就是直接判断一下phead的next和prev指针指向的是不是自己就可以了


是空就返回1;不是空就返回0

int ListEmpty(ListNode* phead)
{
  assert(phead);
  ListNode* nnext = phead->next;
  ListNode* nprev = phead->prev;
  if (nnext == phead && nprev == phead)
  {
  return 1;
  }
  else
  {
  return 0;
  }
}


函数12:int ListSize(ListNode* phead);//判断链表的大小(头节点是不算的)


函数13:void ListDestory(ListNode* phead);//销毁链表

void ListDestory(ListNode* phead)
{
  assert(phead);
  ListNode* cur = phead->next;
  while (cur != phead)                 //循环遍历销毁
  {
  ListNode* pos = cur;
  cur = cur->next;
  free(pos);
  }
  free(phead);                        //再销毁头节点
}


ok,截至此,我们将所有需要调用的函数逐个介绍完毕。


我们接下来就是用我们刚刚所写的函数来实现一下其功 能。看看我们所写的链表能不能用


我们来写一个小程序:

微信图片_20221208203010.png


#include"List.h"
void Test1()
{
  ListNode* plist = NULL;    //初始化一个结构体指针,并置空
  plist = ListInit();        //赋成头指针 
  ListNodePushBack(plist, 1);  //尾插
  ListNodePushBack(plist, 2);  //尾插
  ListNodePushBack(plist, 3);  //尾插
  ListNodePushBack(plist, 4);  //尾插
  ListNodePushBack(plist, 5);  //尾插
  ListPrint(plist);            打印一下(第一次打印)
  ListNodePushFront(plist, 6);//头插
  ListNodePushFront(plist, 7);//头插
  ListNodePushFront(plist, 8);//头插
  ListPrint(plist);           //打印一下(第二次打印)
  ListNode* pos = ListFind(plist, 3);  //寻找数据域为3的元素
  if (pos == NULL)
  {
  printf("没找到\n");
  }
  else
  {
  ListInsert(pos, 9);        //如果找到那就在后面插入9;
  ListPrint(plist);          //打印一下 (第三次打印)
  ListErase(pos);            //再删除该节点(数据域为3的)
  ListPrint(plist);          //再打印一下 (第四次打印)
  }
  ListDestory(plist);           //销毁链表
}
int main()
{
  Test1();
  return 0;
}



我们的运行截图看一下:



完全如我们所愿。


那么,大功告成。


单链表的实现

有了上面的基础,我们再来看这个就很简单了,只是其没有头节点,用起来可能不是那么方便。


我们同样的道理,还是先创建一个节点:

#include<stdio.h>
#include<malloc.h>
typedef int SLTDataType;
typedef struct SListNode
{
  SLTDataType data;             //数据域
  struct SListNode* next;       //指针域
}SListNode;



这是我们接下来要实现的函数:

void SListPrint(SListNode* plist);            //打印链表函数
SListNode* BuySListNode(SLTDataType x);       //创建新节点
void SListPushBack(SListNode** pplist, SLTDataType x);//尾插
void SListPushFront(SListNode** pplist, SLTDataType x);//头插
void SListPopBack(SListNode** pplist);               //尾删
void SListPopFront(SListNode** pplist);              //头删
SListNode* SListFind(SListNode* plist, SLTDataType x);  //寻找节点
void SListInsertAfter(SListNode* pos, SLTDataType x);   //在目标节点的后面插入
void SListInsertBefore(SListNode** pplist, SListNode* pos, SLTDataType x);//在目标节点的前面插入
void SListEraseAfter(SListNode* pos);          //删除pos节点的下一个节点     
void SListEraseCur(SListNode** pplist, SListNode* pos); //删除pos节点
void SListDestory(SListNode* plist);             //销毁链表



(我们的思路是:给上一个创建节点函数,这样以后,在头增或者尾增等就可以直接调用该函数)


函数1:void SListPrint(SListNode* plist);            //打印链表

void SListPrint(SListNode* plist);

就一个参数:头节点的指针。

void SListPrint(SListNode* plist)
{
  SListNode* cur = plist;  //创建一个新的节点,然后将其存储起来
  while (cur != NULL)
  {
  printf("%d ", cur->data);  //依次打印
  cur = cur->next;
  }
  printf("\n");
}


函数2:SListNode* BuySListNode(SLTDataType x);  //创建新节点

SListNode* BuySListNode(SLTDataType x);

实现方法:


其实也是比较简单的,我们还是老样子,上代码,然后逐行解释:

SListNode* BuySListNode(SLTDataType x)
{
  SListNode* node = (SListNode*)malloc(sizeof(SListNode));
    //动态开辟一块空间(就是一个结构体)
  node->data = x;   //讲该节点的数据插入进其数据域中
//注意,在调用这个函数的时候是有一个参数x作为要增加的数据的
  node->next = NULL;//先将其指针域置空
  return node;      //返回该动态开辟的空间
}

函数3:void SListPushBack(SListNode** pplist, SLTDataType x);//尾插

void SListPushBack(SListNode** pplist, SLTDataType x);


注意一下这个函数的参数:第一个参数是第一个节点的二级指针(之所以传二级指针,是因为我们可能需要改变其指针域;而改变指针就需要传递二级指针)第二个参数就是需要传的数据域的值。

void SListPushBack(SListNode** pplist, SLTDataType x)
{
  SListNode* newnode = BuySListNode(x);//创建新节点
  if (*pplist == NULL)           //如果pplist为空
  {
  *pplist = newnode;        //那么newnode就是尾节点
  }
  else                            //如果不是空
  {
  SListNode* tail = *pplist;  //先将pplist的内容先存储在tail里
  while (tail->next != NULL)  
  {
    tail = tail->next;     //向后不断遍历,直到找到尾节点
  }
  tail->next = newnode;      //在尾节点的后面插入新节点
  }
}


函数4:SListNode* SListPushFront(SListNode** pplist, SLTDataType x);//头插

SListNode* SListPushFront(SListNode** pplist, SLTDataType x);

同样的道理,第一个参数是一个头节点的二级指针,第二个参数就是要传的数据域的值。


实现方法:

SListNode* SListPushFront(SListNode** pplist, SLTDataType x)
{
  SListNode* newnode = BuySListNode(x);//创建新节点
  if (*pplist == NULL)                 //如果其为空
  {
  *pplist = newnode;               //那么其刚刚创建的节点就是头节点
  }
  else                                //如果不为空
  {
  SListNode* head = *pplist;      //那么我们先创建一个head节点,让其存储pplist的内容
  newnode->next = head;           //让刚刚创建的节点的指针域指向head(即头节点) 
  } 
  return newnode;                 //返回头节点   
}


函数5:void SListPopBack(SListNode** pplist);          //尾删

微信图片_20221208203452.png


这里的函数参数就是头指针(第一个节点的指针)

void SListPopBack(SListNode** pplist)
{
  if (*pplist == NULL)  //如果头节点其为空
  {
  return ;          //直接返回
  }
  else if((*pplist)->next == NULL) //如果只有一个头节点
  {
  free(*pplist);              //释放、置空、返回
  *pplist = NULL;
  }
  else                           //否则
  { 
  SListNode* prev = NULL;   
  SListNode* tail = *pplist;  //设置 两个节点
  while (tail->next != NULL)  // 遍历,找尾
  {
    prev = tail;
    tail = tail->next;
  }
  free(tail);
  tail = NULL;              //删除尾节点
  prev->next = NULL;
  }
}


函数6:void SListPopFront(SListNode** pplist);              //头删


void SListPopFront(SListNode** pplist)
{
  if (*pplist == NULL)
  {
  return;   //如果没有节点,直接返回
  }
  else
  {
  SListNode* next = (*pplist)->next;  //把头节点的next节点存储起来
  free(*pplist);                    //释放头节点
  *pplist = next;
  }
}


函数7:SListNode* SListFind(SListNode* plist, SLTDataType x);  //寻找节点

和双链表几乎一模一样,这里就不过多赘述

SListNode* SListFind(SListNode* plist, SLTDataType x)
{
  SListNode* cur = plist;
  while (cur != NULL)
  {
  if (cur->data == x)
  {
    return cur;
  }
  cur = cur->next;
  }
  return NULL;
}


函数8:void SListInsertAfter(SListNode* pos, SLTDataType x);   //在目标节点的后面插入


这个比较简单,直接创建一个节点,然后就是三节点之间的关系(参照上面的动画)

void SListInsertAfter(SListNode* pos, SLTDataType x)
{
  assert(pos);
  SListNode* newnode = BuySListNode(x);
  newnode->next = pos->next;
  pos->next = newnode;
}


函数9:void SListInsertBefore(SListNode** pplist, SListNode* pos, SLTDataType x);//在目标节点的前面插入

void SListInsertBefore(SListNode** pplist, SListNode* pos, SLTDataType x)
{
  assert(pos);
  SListNode* newnode = BuySListNode(x);
  if (pos == *pplist)                  //判断pos节点是否是头节点
  {
  newnode->next = pos;
  *pplist = newnode;
  }
  else                             //如果不是,那就找pos的前面的那个节点
  {
  SListNode* prev = NULL;
  SListNode* cur = *pplist;
  while (cur != pos)
  {
    prev = cur;
    cur = cur->next;
  }
  prev->next = newnode;
  newnode->next = pos;        //然后老方法,三结点的关系交换
  }
}




函数10:void SListEraseAfter(SListNode* pos);          //删除pos节点的下一个节点


微信图片_20221208204308.png

这个也是比较简单的,我们 就不再赘述。

void SListEraseAfter(SListNode* pos)
{
  assert(pos);
  if (pos->next == NULL)
  {
  return ;
  }
  else
  {
  SListNode* next = pos->next; 
  pos->next = next->next;
  free(next);
  }
}


函数11:void SListEraseCur(SListNode** pplist, SListNode* pos); //删除pos节点

void SListEraseCur(SListNode** pplist, SListNode* pos)
{
  //找pos的前一个
  SListNode* cur = *pplist;
  if (cur == pos)
  {
  cur = pos->next;
  free(pos);
  pos = NULL;
  //头删
  }
  else                         //先要找到pos节点的前一个位置,然后将pos的信息(主要指指针域) 
                                  存储起来,让pos的前一个节点指向pos节点指向的节点
  {                             然后再把pos节点删掉。
  SListNode* prev = NULL;
  while (cur != pos)
  {
    prev = cur;
    cur = cur->next;  
  }
  SListNode* next = (pos->next);
  prev->next = next;
  free(pos);
  pos = NULL;
  }
}


函数12:void SListDestory(SListNode** plist);//销毁链表

剧情和双链表一样的,就不再展示一遍了。

void SListDestory(SListNode** plist)
{
  SListNode* pf = *plist;
  if (pf == NULL)
  {
  return;
  }
  else
  {
  while (pf->next != NULL)
  {
    SListNode* cur = pf;
    pf = pf->next;
    free(cur);
  }
  free(pf);
  pf = NULL;
  }
}


ok,我们现在来测试一下:


void TestSList1()
{
  SListNode* plist = NULL;
  SListPushBack(&plist, 1);
  SListPushBack(&plist, 2);
  SListPushBack(&plist, 3);
  SListPushBack(&plist, 4);       //尾插
  SListPrint(plist);              //打印
  SListPushFront(&plist, 5);      //头插
  SListPrint(plist);              //打印
  SListPopBack(&plist);           //头删
  SListNode* pos = SListFind(plist, 3);  //找节点
  if (pos)
  {
  printf("找到了\n");
  }
  else
  {
  printf("没找到\n");
  }
  SListInsertAfter(pos, 10);       //指定插入
  SListPrint(plist);
  SListInsertBefore(&plist, pos, 20); //指定插入
  SListPrint(plist);
  SListEraseCur(&plist, pos);    //指定删除
  SListPrint(plist);      
  SListDestory(&plist);        //销毁链表    
}
int main()
{
  TestSList1();
  return 0;
}


到此为止,我们的所有函数全部完成,大功告成了。


如果你还想写更多的接口、实现更多的功能;或者是在现有的接口上玩出花样,那就留给读者自己了。


链表和顺序表的对比:

顺序表:


顺序表:一白遮百丑


白:空间连续、支持随机访问


丑:1.中间或前面部分的插入删除时间复杂度O(N)


      2.增容的代价比较大。


链表:一(黑)毁所有


黑:以节点为单位存储,不支持随机访问


所有:


1.任意位置插入删除时间复杂度为O(1)


2.没有增容问题,插入一个开辟一个空间。


好啦,本期的内容就到这里啦,我们下期再见!!!



目录
相关文章
|
26天前
|
机器学习/深度学习 算法 搜索推荐
从理论到实践,Python算法复杂度分析一站式教程,助你轻松驾驭大数据挑战!
【10月更文挑战第4天】在大数据时代,算法效率至关重要。本文从理论入手,介绍时间复杂度和空间复杂度两个核心概念,并通过冒泡排序和快速排序的Python实现详细分析其复杂度。冒泡排序的时间复杂度为O(n^2),空间复杂度为O(1);快速排序平均时间复杂度为O(n log n),空间复杂度为O(log n)。文章还介绍了算法选择、分而治之及空间换时间等优化策略,帮助你在大数据挑战中游刃有余。
51 4
|
4天前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
25 4
|
5天前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
5天前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
9天前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
21天前
|
存储 Java
HashMap之链表转红黑树(树化 )-treefyBin方法源码解读(所有涉及到的方法均有详细解读,欢迎指正)
本文详细解析了Java HashMap中链表转红黑树的机制,包括树化条件(链表长度达8且数组长度≥64)及转换流程,确保高效处理大量数据。
58 1
|
24天前
|
算法 Java
数据结构与算法学习五:双链表的增、删、改、查
双链表的增、删、改、查操作及其Java实现,并通过实例演示了双向链表的优势和应用。
15 0
数据结构与算法学习五:双链表的增、删、改、查
|
4天前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
16 0
|
16天前
|
算法
PID算法原理分析
【10月更文挑战第12天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。
|
22天前
|
算法
PID算法原理分析及优化
【10月更文挑战第6天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。