当前位置: 首页 > article >正文

C语言数据结构之双向链表(LIST)的实现

C语言数据结构之双向链表(LIST)的实现

  • 与单向链表相比,双向链表数据结构中多出一个指向链表前一个节点的节点指针prev!!!

双向链表数据类型的定义

  • 双向链表的创建 list_new
  • 双向链表的释放 list_free
  • 尾部追加 list_append
  • 头部追加 list_prepend
  • 逐项操作 list_foreach
  • 测试函数,字符串尾部追加结果:AAA BBB CCC DDD EEE
  • 字符串头部追加结果:EEE DDD CCC BBB AAA

代码如下:

/* filename : list.c */
#include <stdio.h>
#include <stdlib.h>

/**/
typedef void (*LSFunc) (void *data);

/**/
typedef struct _ListNode ListNode;
struct _ListNode {
  void *data;
  ListNode *prev, *next;
};

/**/
ListNode *
list_node_new (void *data)
{
  ListNode *node = (ListNode*) malloc (sizeof(ListNode));
  node->data = data;
  node->prev = NULL;
  node->next = NULL;
  return node;
}

/**/
void
list_free (ListNode *node)
{
  ListNode *tmp = node;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  while (tmp != NULL)
    {
      ListNode *pnode = tmp->next;
      free (tmp);
      tmp = pnode;
    }
}

/**/
ListNode *
list_append (ListNode *head, void *data)
{
  ListNode *tmp = head, *node;
  while (tmp->next != NULL)
    tmp = tmp->next;
  node = list_node_new (data);
  node->prev = tmp;
  tmp->next = node;
  return head;
}

/**/
ListNode *
list_prepend (ListNode *head, void *data)
{
  ListNode *tmp = head, *node;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  node = list_node_new (data);
  node->next = tmp;
  tmp->prev = node;
  return node;
}

/**/
void
list_foreach (ListNode *node, LSFunc lsfunc)
{
  ListNode *tmp = node;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  while (tmp != NULL)
    {
      lsfunc (tmp->data);
      tmp = tmp->next;
    }
}

/* ---------------------------------------- */

/**/
void
out_string (void *data)
{
  printf ("%s ", (char*)data);
}

/**/
void
test_list_append (void)
{
  ListNode *ls;
  char *buf[5] = {"AAA", "BBB", "CCC", "DDD", "EEE"};

  ls = list_node_new (buf[0]);

  for (int i = 1; i < 5; i++)
    ls = list_append (ls, buf[i]);

  printf ("[ ");
  list_foreach (ls, out_string);
  printf ("]\n");

  list_free (ls);
}

/**/
void
test_list_prepend (void)
{
  ListNode *ls;
  char *buf[5] = {"AAA", "BBB", "CCC", "DDD", "EEE"};

  ls = list_node_new (buf[0]);

  for (int i = 1; i < 5; i++)
    ls = list_prepend (ls, buf[i]);

  printf ("[ ");
  list_foreach (ls, out_string);
  printf ("]\n");

  list_free (ls);
}

/**/
int
main (int argc, char *argv[])
{
  test_list_append ();
  test_list_prepend ();
  return 0;
}
/* --[<\|/>]-- */

编译运行,检查内存,情况如下:

songvm@ubuntu:~/works/xdn/loo$ gcc list.c -o list
songvm@ubuntu:~/works/xdn/loo$ ./list
[ AAA BBB CCC DDD EEE ]
[ EEE DDD CCC BBB AAA ]
songvm@ubuntu:~/works/xdn/loo$ valgrind --leak-check=yes ./list
==3688== Memcheck, a memory error detector
==3688== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==3688== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==3688== Command: ./list
==3688== 
[ AAA BBB CCC DDD EEE ]
[ EEE DDD CCC BBB AAA ]
==3688== 
==3688== HEAP SUMMARY:
==3688==     in use at exit: 0 bytes in 0 blocks
==3688==   total heap usage: 11 allocs, 11 frees, 1,264 bytes allocated
==3688== 
==3688== All heap blocks were freed -- no leaks are possible
==3688== 
==3688== For counts of detected and suppressed errors, rerun with: -v
==3688== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
songvm@ubuntu:~/works/xdn/loo$ 

双向链表求长度LENGTH 插入INSERT

  • 双向链表求长度 list_length
  • 双向链表插入节点 list_insert

代码如下:

/**/
int
list_length (ListNode *head)
{
  int len = 0;
  ListNode *tmp = head;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  while (tmp != NULL)
    {
      tmp = tmp->next;
      len++;
    }
  return len;
}

/**/
ListNode *
list_insert (ListNode *head, int nth, void *data)
{
  int idx = 0;
  ListNode *tmp = head;//, *node;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  while (tmp != NULL)
    {
      idx = idx + 1;
      if (idx == nth)
	{
	  ListNode *node = list_node_new (data);
	  node->next = tmp->next;
	  node->prev = tmp;
	  tmp->next = node;
	  break;
	}
      tmp = tmp->next; 
    }
  return head;
}
  • 测试函数,创建双向链表AAA BBB CCC DDD EEE,在第3个索引处插入XXXX,目标结果AAA BBB CCC XXXX DDD EEE

代码如下:

/**/
void
test_list_insert (void)
{
  ListNode *ls;
  char *buf[6] = {"AAA", "BBB", "CCC", "DDD", "EEE", "XXXX"};

  ls = list_node_new (buf[0]);

  for (int i = 1; i < 5; i++)
    ls = list_append (ls, buf[i]);

  printf ("[ ");
  list_foreach (ls, out_string);
  printf ("]\n");

  printf ("List length is %d\n", list_length (ls));

  ls = list_insert (ls, 3, buf[5]);

  printf ("[ ");
  list_foreach (ls, out_string);
  printf ("]\n");

  printf ("List length is %d\n", list_length (ls));

  list_free (ls);
}

编译运行,结果如预期,输出如下:

songvm@ubuntu:~/works/xdn/loo$ gcc list.c -o list
songvm@ubuntu:~/works/xdn/loo$ ./list
[ AAA BBB CCC DDD EEE ]
List length is 5
[ AAA BBB CCC XXXX DDD EEE ]
List length is 6
songvm@ubuntu:~/works/xdn/loo$ 

双向链表的连接concat

  • 双向链表连接函数list_concat,将两个双向链表连接成为一个双向链表

代码如下:

/**/
ListNode*
list_concat (ListNode *lsa, ListNode *lsb)
{
  ListNode *tmp = lsa, *node = lsb;
  while (tmp->next != NULL)
    tmp = tmp->next;
  while (node->prev != NULL)
    node = node->prev;
  tmp->next = node;
  node->prev = tmp;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  return tmp;
}
  • 测试将两个链表内容为:AAA BBB CCC DDD和XXX YYY ZZZ KKK,连接成为一个链表

代码如下:

/**/
void
test_list_concat (void)
{
  char *bufa[4] = {"AAA", "BBB", "CCC", "DDD"};
  char *bufb[4] = {"XXX", "YYY", "ZZZ", "KKK"};
  ListNode *lna, *lnb, *lnx;

  lna = list_node_new (bufa[0]);
  for (int i = 1; i < 4; i++)
    lna = list_append (lna, bufa[i]);

  printf ("[ ");
  list_foreach (lna, out_string);
  printf ("]\n");

  lnb = list_node_new (bufb[0]);
  for (int i = 1; i < 4; i++)
    lnb = list_append (lnb, bufb[i]);

  printf ("[ ");
  list_foreach (lnb, out_string);
  printf ("]\n");

  lnx = list_concat (lna, lnb);

  printf ("[ ");
  list_foreach (lnx, out_string);
  printf ("]\n");

  list_free (lnx);
}

编译运行,检查内存,结果如下:

songvm@ubuntu:~/works/xdn/loo$ gcc list.c -o list
songvm@ubuntu:~/works/xdn/loo$ ./list
[ AAA BBB CCC DDD ]
[ XXX YYY ZZZ KKK ]
[ AAA BBB CCC DDD XXX YYY ZZZ KKK ]
songvm@ubuntu:~/works/xdn/loo$ valgrind --leak-check=yes ./list
==2951== Memcheck, a memory error detector
==2951== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2951== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==2951== Command: ./list
==2951== 
[ AAA BBB CCC DDD ]
[ XXX YYY ZZZ KKK ]
[ AAA BBB CCC DDD XXX YYY ZZZ KKK ]
==2951== 
==2951== HEAP SUMMARY:
==2951==     in use at exit: 0 bytes in 0 blocks
==2951==   total heap usage: 9 allocs, 9 frees, 1,216 bytes allocated
==2951== 
==2951== All heap blocks were freed -- no leaks are possible
==2951== 
==2951== For counts of detected and suppressed errors, rerun with: -v
==2951== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
songvm@ubuntu:~/works/xdn/loo$ 

双向链表倒转REVERSE 取链表头节点FIRST 取链表尾节点LAST

  • 双向链表倒转 list_reverse
  • 取链表头节点 list_first
  • 取链表尾节点 list_last

代码如下:

/* list reverse tail to head */
ListNode*
list_reverse (ListNode *ls)
{
  ListNode *tmp = ls, *node = NULL;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  while (tmp != NULL)
    {
      ListNode *pn = tmp;
      tmp = tmp->next;
      pn->next = node;
      pn->prev = NULL;
      if (node != NULL)
	node->prev = pn;
      node = pn;
    }
  return node;
}

/* get list first node */
ListNode*
list_first (ListNode *ls)
{
  ListNode *tmp = ls;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  return tmp;
}

/* get list last node */
ListNode*
list_last (ListNode *ls)
{
  ListNode *tmp = ls;
  while (tmp->next != NULL)
    tmp = tmp->next;
  return tmp;
}
  • 测试函数,创建链表字符串AAA到HHH,倒转输出内容,再由尾部向前依次输出内容

代码如下:

/**/
void
test_list_reverse (void)
{
  char *buf[8] = {"AAA", "BBB", "CCC", "DDD", "EEE", "FFF", "GGG", "HHH"};
  ListNode *ls;

  ls = list_node_new (buf[0]);
  for (int i = 1; i < 8; i++)
    ls = list_append (ls, buf[i]);

  printf ("[ ");
  list_foreach (ls, out_string);
  printf ("]\n");

  ls = list_reverse (ls);

  printf ("[ ");
  list_foreach (ls, out_string);
  printf ("]\n");

  {
    ListNode *tmp = ls;
    while (tmp->next != NULL)
      tmp = tmp->next;
    printf ("[ ");
    while (tmp != NULL)
      {
	printf ("%s ", (char*)(tmp->data));
	tmp = tmp->prev;
      }
    printf ("]\n");
  }
  list_free (ls);
}

编译运行,达到预期,效果如下:

songvm@ubuntu:~/works/xdn/loo$ gcc list.c -o list
songvm@ubuntu:~/works/xdn/loo$ ./list
[ AAA BBB CCC DDD EEE FFF GGG HHH ]
[ HHH GGG FFF EEE DDD CCC BBB AAA ]
[ AAA BBB CCC DDD EEE FFF GGG HHH ]
songvm@ubuntu:~/works/xdn/loo$ 

双向链表删除指定节点 REMOVE_NTH

  • 双向链表删除指定节点 list_remove_nth

代码如下:

/**/
ListNode*
list_remove_nth (ListNode *ls, int nth)
{
  int idx = 0;
  ListNode *tmp = ls, *pn = ls;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  while (tmp != NULL)
    {
      if (idx == nth)
	{
	  if (idx == 0)
	    {
	      ls = tmp->next;
	      ls->prev = NULL;
	      free (tmp);
	      break;
	    }
	  pn->next = tmp->next;
	  tmp->next->prev = pn;
	  free (tmp);
	  break;
	}
      idx = idx + 1;
      pn = tmp;
      tmp = tmp->next;
    }
  return ls;
}
  • 测试函数,创建链表,删除第1第2节点,输出链表内容

代码如下:

/**/
void
test_list_remove (void)
{
  char *txt[5] = {"AAAA", "BBBB", "CCCC", "DDDD", "EEEE"};
  ListNode *ls;

  ls = list_node_new (txt[0]);
  for (int i = 1; i < 5; i++)
    ls = list_append (ls, txt[i]);

  list_foreach (ls, out_string); printf ("\n");

  ls = list_remove_nth (ls, 1);
  ls = list_remove_nth (ls, 2);

  list_foreach (ls, out_string); printf ("\n");
  list_free (ls);
}

编译运行,达到预期,效果如下:

songvm@ubuntu:~/works/xdn/loo$ gcc list.c -o list
songvm@ubuntu:~/works/xdn/loo$ ./list
AAAA BBBB CCCC DDDD EEEE 
AAAA CCCC EEEE 
songvm@ubuntu:~/works/xdn/loo$ 

判断链表是否为循环链表

判断链表是否为循环链表is_round

代码如下:

/* if list is a round return 1 else return 0 */
int
list_isround (ListNode *ls)
{
  ListNode *tmp = ls;
  while (tmp != NULL)
    {
      ListNode *node = tmp->next;
      if (ls == node) return 1;
      tmp = node;
    }
  return 0;
}
  • 测试函数,创建链表,取尾节点,尾节点next指针指向头节点,头节点的prev指针指向尾节点,形成循环链表

代码如下:

/**/
void
test_list_round (void)
{
  char *txt[5] = {"AAAA", "BBBB", "CCCC", "DDDD", "EEEE"};
  ListNode *head, *tail;
  head = list_node_new (txt[0]);
  for (int i = 1; i < 5; i++)
    head = list_append (head, txt[i]);

  tail = list_last (head);
  tail->next = head; //link round
  head->prev = tail; //link round

  if (list_isround (head))
    printf ("Double list is round!\n");
  else
    printf ("Double list not round!\n");

  tail->next = NULL; //cut round
  head->prev = NULL; //cut round

  if (list_isround (head))
    printf ("Double list is round!\n");
  else
    printf ("Double list not round!\n");

  list_free (head);
}

编译运行,达到预期效果,输出如下:

songvm@ubuntu:~/works/xdn/loo$ gcc list.c -o list
songvm@ubuntu:~/works/xdn/loo$ ./list
Double list is round!
Double list not round!
songvm@ubuntu:~/works/xdn/loo$ 

完整代码如下:

/* filename : list.c */
#include <stdio.h>
#include <stdlib.h>

/* compile : gcc list.c -o list */
/*     run : ./list             */

/* define function pointer for list foreach */
typedef void (*LSFunc) (void *data);

/* define ListNode datatype */
typedef struct _ListNode ListNode;
struct _ListNode {
  void *data;
  ListNode *prev, *next;
};

/* create a ListNode with data */
ListNode *
list_node_new (void *data)
{
  ListNode *node = (ListNode*) malloc (sizeof(ListNode));
  node->data = data;
  node->prev = NULL;
  node->next = NULL;
  return node;
}

/* free a list, from head to tail every node */
void
list_free (ListNode *node)
{
  ListNode *tmp = node;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  while (tmp != NULL)
    {
      ListNode *pnode = tmp->next;
      free (tmp);
      tmp = pnode;
    }
}

/* append a ListNode with data to head */
ListNode *
list_append (ListNode *head, void *data)
{
  ListNode *tmp = head, *node;
  while (tmp->next != NULL)
    tmp = tmp->next;
  node = list_node_new (data);
  node->prev = tmp;
  tmp->next = node;
  return head;
}

/* prev append a ListNode with data to head */
ListNode *
list_prepend (ListNode *head, void *data)
{
  ListNode *tmp = head, *node;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  node = list_node_new (data);
  node->next = tmp;
  tmp->prev = node;
  return node;
}

/* foreach listnode run lsfunc for data */
void
list_foreach (ListNode *node, LSFunc lsfunc)
{
  ListNode *tmp = node;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  while (tmp != NULL)
    {
      lsfunc (tmp->data);
      tmp = tmp->next;
    }
}

/* get list length */
int
list_length (ListNode *head)
{
  int len = 0;
  ListNode *tmp = head;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  while (tmp != NULL)
    {
      tmp = tmp->next;
      len++;
    }
  return len;
}

/* insert a ListNode with data at nth in head */
ListNode *
list_insert (ListNode *head, int nth, void *data)
{
  int idx = 0;
  ListNode *tmp = head;//, *node;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  while (tmp != NULL)
    {
      idx = idx + 1;
      if (idx == nth)
	{
	  ListNode *node = list_node_new (data);
	  node->next = tmp->next;
	  node->prev = tmp;
	  tmp->next = node;
	  break;
	}
      tmp = tmp->next; 
    }
  return head;
}

/* concat lsa and lsb and return a new ListNode pointer */
ListNode*
list_concat (ListNode *lsa, ListNode *lsb)
{
  ListNode *tmp = lsa, *node = lsb;
  while (tmp->next != NULL)
    tmp = tmp->next;
  while (node->prev != NULL)
    node = node->prev;
  tmp->next = node;
  node->prev = tmp;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  return tmp;
}

/* list reverse tail to head */
ListNode*
list_reverse (ListNode *ls)
{
  ListNode *tmp = ls, *node = NULL;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  while (tmp != NULL)
    {
      ListNode *pn = tmp;
      tmp = tmp->next;
      pn->next = node;
      pn->prev = NULL;
      if (node != NULL)
	node->prev = pn;
      node = pn;
    }
  return node;
}

/* get list first node */
ListNode*
list_first (ListNode *ls)
{
  ListNode *tmp = ls;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  return tmp;
}

/* get list last node */
ListNode*
list_last (ListNode *ls)
{
  ListNode *tmp = ls;
  while (tmp->next != NULL)
    tmp = tmp->next;
  return tmp;
}

/**/
ListNode*
list_remove_nth (ListNode *ls, int nth)
{
  int idx = 0;
  ListNode *tmp = ls, *pn = ls;
  while (tmp->prev != NULL)
    tmp = tmp->prev;
  while (tmp != NULL)
    {
      if (idx == nth)
	{
	  if (idx == 0)
	    {
	      ls = tmp->next;
	      ls->prev = NULL;
	      free (tmp);
	      break;
	    }
	  pn->next = tmp->next;
	  tmp->next->prev = pn;
	  free (tmp);
	  break;
	}
      idx = idx + 1;
      pn = tmp;
      tmp = tmp->next;
    }
  return ls;
}

/* if list is a round return 1 else return 0 */
int
list_isround (ListNode *ls)
{
  ListNode *tmp = ls;
  while (tmp != NULL)
    {
      ListNode *node = tmp->next;
      if (ls == node) return 1;
      tmp = node;
    }
  return 0;
}

/* ---------------------------------------- */

/**/
void
out_string (void *data)
{
  printf ("%s ", (char*)data);
}

/**/
void
test_list_append (void)
{
  ListNode *ls;
  char *buf[5] = {"AAA", "BBB", "CCC", "DDD", "EEE"};

  ls = list_node_new (buf[0]);

  for (int i = 1; i < 5; i++)
    ls = list_append (ls, buf[i]);

  printf ("[ ");
  list_foreach (ls, out_string);
  printf ("]\n");

  list_free (ls);
}

/**/
void
test_list_prepend (void)
{
  ListNode *ls;
  char *buf[5] = {"AAA", "BBB", "CCC", "DDD", "EEE"};

  ls = list_node_new (buf[0]);

  for (int i = 1; i < 5; i++)
    ls = list_prepend (ls, buf[i]);

  printf ("[ ");
  list_foreach (ls, out_string);
  printf ("]\n");

  list_free (ls);
}

/**/
void
test_list_insert (void)
{
  ListNode *ls;
  char *buf[6] = {"AAA", "BBB", "CCC", "DDD", "EEE", "XXXX"};

  ls = list_node_new (buf[0]);

  for (int i = 1; i < 5; i++)
    ls = list_append (ls, buf[i]);

  printf ("[ ");
  list_foreach (ls, out_string);
  printf ("]\n");

  printf ("List length is %d\n", list_length (ls));

  ls = list_insert (ls, 3, buf[5]);

  printf ("[ ");
  list_foreach (ls, out_string);
  printf ("]\n");

  printf ("List length is %d\n", list_length (ls));

  list_free (ls);
}

/**/
void
test_list_concat (void)
{
  char *bufa[4] = {"AAA", "BBB", "CCC", "DDD"};
  char *bufb[4] = {"XXX", "YYY", "ZZZ", "KKK"};
  ListNode *lna, *lnb, *lnx;

  lna = list_node_new (bufa[0]);
  for (int i = 1; i < 4; i++)
    lna = list_append (lna, bufa[i]);

  printf ("[ ");
  list_foreach (lna, out_string);
  printf ("]\n");

  lnb = list_node_new (bufb[0]);
  for (int i = 1; i < 4; i++)
    lnb = list_append (lnb, bufb[i]);

  printf ("[ ");
  list_foreach (lnb, out_string);
  printf ("]\n");

  lnx = list_concat (lna, lnb);

  printf ("[ ");
  list_foreach (lnx, out_string);
  printf ("]\n");

  list_free (lnx);
}

/**/
void
test_list_reverse (void)
{
  char *buf[8] = {"AAA", "BBB", "CCC", "DDD", "EEE", "FFF", "GGG", "HHH"};
  ListNode *ls;

  ls = list_node_new (buf[0]);
  for (int i = 1; i < 8; i++)
    ls = list_append (ls, buf[i]);

  printf ("[ ");
  list_foreach (ls, out_string);
  printf ("]\n");

  ls = list_reverse (ls);

  printf ("[ ");
  list_foreach (ls, out_string);
  printf ("]\n");

  {
    ListNode *tmp = ls;
    while (tmp->next != NULL)
      tmp = tmp->next;
    printf ("[ ");
    while (tmp != NULL)
      {
	printf ("%s ", (char*)(tmp->data));
	tmp = tmp->prev;
      }
    printf ("]\n");
  }
  list_free (ls);
}

/**/
void
test_list_remove (void)
{
  char *txt[5] = {"AAAA", "BBBB", "CCCC", "DDDD", "EEEE"};
  ListNode *ls;

  ls = list_node_new (txt[0]);
  for (int i = 1; i < 5; i++)
    ls = list_append (ls, txt[i]);

  list_foreach (ls, out_string); printf ("\n");

  ls = list_remove_nth (ls, 1);
  ls = list_remove_nth (ls, 2);

  list_foreach (ls, out_string); printf ("\n");
  list_free (ls);
}

/**/
void
test_list_round (void)
{
  char *txt[5] = {"AAAA", "BBBB", "CCCC", "DDDD", "EEEE"};
  ListNode *head, *tail;
  head = list_node_new (txt[0]);
  for (int i = 1; i < 5; i++)
    head = list_append (head, txt[i]);

  tail = list_last (head);
  tail->next = head; //link round
  head->prev = tail; //link round

  if (list_isround (head))
    printf ("Double list is round!\n");
  else
    printf ("Double list not round!\n");

  tail->next = NULL; //cut round
  head->prev = NULL; //cut round

  if (list_isround (head))
    printf ("Double list is round!\n");
  else
    printf ("Double list not round!\n");

  list_free (head);
}

/**/
int
main (int argc, char *argv[])
{
  //test_list_append ();
  //test_list_prepend ();
  //test_list_insert ();
  //test_list_concat ();
  //test_list_reverse ();
  //test_list_remove ();
  test_list_round ();
  return 0;
}
/* --[<\|/>]-- */

下一步研究栈(Stack)数据结构!!!


http://www.kler.cn/news/361219.html

相关文章:

  • Pytorch 实现图片分类
  • windows连接linux服务器上的jupyter lab
  • AI大模型平台详解与AI创作示范
  • 复习:react 中的 refs,怎么使用,有哪些使用场景
  • 秃姐学AI系列之:语义分割 + 数据集 | 转置卷积 + 代码
  • 如何使用 Browserless 抓取动态网站?
  • 独立构件风格
  • 二分图染色法
  • 帝国CMS – AutoTitlePic 自动生成文章标题图片插件
  • Centos7 安装 Openssl 和 Nginx
  • 微分方程(Blanchard Differential Equations 4th)中文版Exercise 1.4
  • postgresql14主从同步流复制搭建
  • 跨域问题和前端攻击
  • 【开源免费】基于SpringBoot+Vue.JS母婴商城系统 (JAVA毕业设计)
  • 【Flutter】基础组件:Container
  • 逐行讲解大模型生成解码超参数源码(temperature、top-k、top-p等)
  • 【Flutter】配置:远程开发
  • 循环移位的学习
  • 【部署篇】rabbitmq-01介绍
  • FPGA 小鸟避障游戏
  • 磁编码器的工作原理和特点
  • 练习题(动态规划)
  • curl支持ssl报错:SSL certificate problem: unable to get local issuer certificate
  • 设置故障恢复机制
  • 2024 年某科技公司薪资 5k 前端开发岗位面试真题以及题解、知识点分析
  • 搭建自己的Docker(容器)镜像加速器