数据结构: 线性表(带头双向循环链表实现)

news2024/10/7 2:19:26

之前一章学习了单链表的相关操作, 但是单链表的限制却很多, 比如不能倒序扫描链表, 解决方法是在数据结构上附加一个域, 使它包含指向前一个单元的指针即可.

那么怎么定义数据结构呢? 首先我们先了解以下链表的分类

1. 链表的分类

链表的结构非常多样, 以下情况组合起来就有 8 中链表结构

  • 单向或者双向
    在这里插入图片描述

  • 带头或者不带头
    在这里插入图片描述

  • 循环或者非循环
    在这里插入图片描述


虽然有这么多的链表的结构, 但是我们实际上最常用的还是两种结构:

在这里插入图片描述

无头单向非循环链表

结构简单,一般不会单独用来存放数据.实际上更多是作为其他数据结构的子结构, 如哈希桶, 图的邻接表等等. 另外这种结构在笔试面试中出现很多

带头双向循环链表

结构最复杂, 一般用于单独存储数据.实际上使用的链表数据结构, 都是带头双向循环链表. 虽然结构复杂, 但是实现相关功能比较简单.

严格来说只用实现 InsertErase 两个功能, 就可以实现 “二十分钟” 写完一个链表的任务.

2. 带头双向循环链表

2.1 带头双向循环链表的定义

typedef int LTDataType;     //LTDataType = int
typedef struct ListNode
{
  LTDataType data;          //数据域
  struct ListNode* prev;    //指向前一个结点
  struct ListNode* next;    //指向后一个结点
}ListNode;  //重命名为 ListNode
  1. 相比较单链表的数据结构, 只需要多一个域用来指向前面一个结点就可以了.
  2. 这里使用ListNode来命名这个数据结构, 方便后续学习 STL 进行知识迁移

2.2 接口函数

// 创建并返回链表的头结点
ListNode* ListCreate();
// 双向链表销毁
void ListDestroy(ListNode* phead);
// 双向链表打印
void ListPrint(ListNode* phead);
// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* phead);
// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* phead);
// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x);
// 双向链表在 pos 的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除 pos 位置的结点
void ListErase(ListNode* pos);

3. 接口函数的实现

因为有虚拟结点的存在, 所以除了创建头结点的函数, 其余接口函数都不会修改结构体指针, 只是修改结构体.

为了统一接口形式, 统一使用一级指针作为函数的形参类型. 需要修改头结点的函数接口, 直接用返回值的方法达到修改头结点指针的目的.

3.1 创建并返回链表的头结点 (ListCreate)

在这里插入图片描述

创建链表即为创建头结点, 它是一个虚拟结点(dummy node), 实际的值没有意义.它的两个指针都指向自己.

  • ListList.h
#pragma once 

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int LTDataType;
typedef struct ListNode
{
  LTDataType data;        //数据域
  struct ListNode* prev;  //指向前一个结点
  struct ListNode* next;  //指向后一个结点
}ListNode;

// 创建并返回链表的头结点
ListNode* ListCreate();

修改头结点指针, 使用返回值接受头结点的指针

  • ListList.c
// 创建返回链表的头结点
ListNode* ListCreate()
{
  // 动态开辟空间创建头结点, 如果开辟失败直接结束程序
  ListNode* head = BuyListNode(0);
  
  // 处理链表数据, 数据域随机值, 两个指针指向自己
  head->next = head;
  head->prev = head;

  return head;
}
  1. 这里的 BuyListNode() 是一个我自己定义的静态函数, 方便后续复用. 函数的功能是用来从堆中申请空间用来创建一个新结点.
// 创建一个新结点
static ListNode* BuyListNode(LTDataType x)
{
  ListNode* newNode = (ListNode*)malloc(sizeof(struct ListNode));

  if (newNode == NULL)
  {
    perror("malloc fail");
    exit(-1);
  }

  newNode->data = x;
  newNode->next = NULL;
  newNode->prev = NULL;

  return newNode;
}
  1. 创建头结点后, 使头结点指向自己
  • test.c
void LinkListTest1()
{
  ListNode* head = ListCreate();

  free(head);
}

测试调试结果如下:
在这里插入图片描述

头结点创建成功, 并且头结点两个指针都指向了自己

3.2 双向链表打印 (ListPrint)

从第一个结点开始遍历链表每个结点, 并且将结点的数据域打印出来, 直到遇到头结点结束
在这里插入图片描述

  • ListList.h
void ListPrint(ListNode* phead);
  • ListList.c
// 双向链表打印
void ListPrint(ListNode* phead)
{
  assert(phead);  //确保头结点存在

  printf("head <=> ");

  ListNode* cur = phead->next;  //从第一个结点开始遍历, 直到遇到头结点结束
  while (cur != phead)
  {
    printf("%d <=> ", cur->data);
    cur = cur->next;
  }

  printf("\n");
}
  1. 确保头结点存在
  1. cur第一次定位到头结点后面一个结点, 即第一个有效结点
  1. 顺序遍历并且打印
  • test.c

后续调试其他函数功能都会使用到 ListPrint 函数, 这里就先不调试了.

3.3 双向链表尾插 (ListPushBack)

先找到链表尾结点的位置, 在尾结点和头结点之间插入一个新结点

在这里插入图片描述

  • ListList.h
void ListPushBack(ListNode* phead, LTDataType x);
  • ListList.c
// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
  assert(phead);  //确保头结点存在

  ListNode* newNode = BuyListNode(x); //创建新结点 
  ListNode* tail = phead->prev;       //找到尾结点
  
  //更改链接关系
  tail->next = newNode;
  newNode->prev = tail;
  phead->prev = newNode;
  newNode->next = phead;

}
  1. 确保头结点存在
  1. 更改链接关系, 需要修改一共四根指针的指向关系
  • test.c
void LinkListTest1()
{
  ListNode* head = ListCreate();
  ListPushBack(head, 1);
  ListPushBack(head, 2);
  ListPushBack(head, 3);
  ListPushBack(head, 4);
  ListPushBack(head, 5);
  ListPrint(head);
}

测试结果如下:
在这里插入图片描述

3.4 双向链表尾删 (ListPopBack)

找到新的尾结点位置, 更改链接关系后将原尾结点删除
在这里插入图片描述

  • ListList.h
void ListPopBack(ListNode* phead);
  • ListList.c
// 双向链表尾删
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); //释放空间
}
  • test.c
void LinkListTest1()
{
  ListNode* head = ListCreate();
  ListPushBack(head, 1);
  ListPushBack(head, 2);
  ListPushBack(head, 3);
  ListPushBack(head, 4);
  ListPushBack(head, 5);
  ListPrint(head);

  ListPopBack(head);
  ListPrint(head);

  ListPopBack(head);
  ListPrint(head);
  
  ListPopBack(head);
  ListPopBack(head);
  ListPopBack(head);
  ListPrint(head);
  
  ListPopBack(head);
  ListPrint(head);
  
  ListDestroy(head);
}

测试结果如下:
在这里插入图片描述

3.5 双链表头插 (ListPushFront)

找到原第一个有效节点, 在头结点和这个结点之间插入一个新结点
在这里插入图片描述

  • ListList.h
void ListPushFront(ListNode* phead, LTDataType x);
  • ListList.c
// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x)
{
  assert(phead);  //确保头结点存在

  ListNode* newNode = BuyListNode(x); //创建新结点
  ListNode* first = phead->next;      //找到原来的第一个结点

  //更新链接关系
  phead->next = newNode;
  newNode->prev = phead;
  newNode->next = first;
  first->prev = newNode;
}
  1. 确保头结点存在
  1. 在头结点和第一个有效结点之间插入新结点
  • test.c
void LinkListTest2()
{
  ListNode* head = ListCreate();

  ListPushFront(head, 1);
  ListPushFront(head, 2);
  ListPushFront(head, 3);
  ListPushFront(head, 4);
  ListPushFront(head, 5);
}

测试运行结果如下:
在这里插入图片描述

3.6 双链表头删 (ListPopFront)

先找到第一个和第二个有效结点, 更新头结点和第二个有效结点之间的链接关系后, 释放第一个结点的空间.
在这里插入图片描述

  • ListList.h
void ListPopFront(ListNode* phead);
  • ListList.c
// 双向链表头删
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); //释放空间
}
  • test.c
void LinkListTest2()
{
  ListNode* head = ListCreate();

  ListPushFront(head, 1);
  ListPushFront(head, 2);
  ListPushFront(head, 3);
  ListPushFront(head, 4);
  ListPushFront(head, 5);

  ListPrint(head);
  
  ListPopFront(head);
  ListPrint(head);

  ListPopFront(head);
  ListPopFront(head);
  ListPopFront(head);
  ListPopFront(head);
  ListPrint(head);

  ListPopFront(head);
  ListPrint(head);

  ListDestroy(head);
}

测试结果如下:
在这里插入图片描述

3.7 双链表查找 (ListFind)

从第一个有效结点开始向后遍历链表, 判断是否有想要查找的数据, 直到遇到头结点. 未查找到返回空指针, 查找到返回该结点的地址
在这里插入图片描述

  • ListList.h
ListNode* ListFind(ListNode* phead, LTDataType x);
  • ListList.c
// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
  assert(phead);  //确保哨兵结点存在

  ListNode* cur = phead->next;

  while (cur != phead)
  {
    if (cur->data == x)
    {
      return cur;
    }
    cur = cur->next;
  }

  return NULL;
}

  • test.c
void LinkListTest3()
{
  ListNode* head = ListCreate();
  ListPushBack(head, 1);
  ListPushBack(head, 2);
  ListPushBack(head, 3);
  ListPushBack(head, 4);
  ListPushBack(head, 5);
  ListPrint(head);

  ListNode* pos;
  pos = ListFind(head, 2);
  printf("pos <=> ");
  while (pos && pos != head)
  {
    printf("%d <=> ", pos->data);
    pos = pos->next;
  }
  puts("\n");

  pos = ListFind(head, 6);
  printf("pos <=> ");
  while (pos && pos != head)
  {
    printf("%d <=> ", pos->data);
    pos = pos->next;
  }
  puts("\n");
}

测试运行结果如下:
在这里插入图片描述

3.8 双向链表在 pos 的前面进行插入 (LinkInsert)

先找到 pos 的前面一个结点的位置, 随后在这个结点和 pos 之间插入新结点
在这里插入图片描述

  • LinkList.h
void ListInsert(ListNode* pos, LTDataType x);
  • LinkList.c
// 双向链表在 pos 之前插入
void ListInsert(ListNode* pos, LTDataType x)
{
  assert(pos);  //确保pos合法

  ListNode* newNode = BuyListNode(x); //创建新结点
  ListNode* posPrev = pos->prev;      //找到 pos 前一个结点的位置

  //更新链接方式
  posPrev->next = newNode;
  newNode->prev = posPrev;

  newNode->next = pos;
  pos->prev = newNode;
}
  • test.c
void LinkListTest3()
{
  ListNode* head = ListCreate();

  ListPushBack(head, 1);
  ListPushBack(head, 2);
  ListPushBack(head, 3);
  ListPushBack(head, 4);
  ListPushBack(head, 5);
  ListPrint(head);

  ListNode* pos;

  pos = ListFind(head, 1);
  if (pos)
  {
    ListInsert(pos, -1);
    ListPrint(head);
  }

  pos = ListFind(head, 4);
  if (pos)
  {
    ListInsert(pos, -4);
    ListPrint(head);
  }

  pos = ListFind(head, 6);
  if (pos)
  {
    ListInsert(pos, -6);
    ListPrint(head);
  }

  ListDestroy(head);
}

测试运行结果如下:
在这里插入图片描述

3.9 双向链表删除 pos 位置的结点 (ListErase)

先找到 pos 的前后两个结点的位置, 随后更新两个结点之间的链接关系, 最后删除 pos 结点
在这里插入图片描述

  • LinkList.h
void ListErase(ListNode* pos);
  • LinkList.c
// 双向链表删除 pos 位置的结点
void ListErase(ListNode* pos)
{
  assert(pos);  //确保 pos 合法

  ListNode* posPrev = pos->prev;    //找到 pos 前一个结点的位置
  ListNode* posNext = pos->next;    //找到 pos 后一个结点的位置

  //更新链接方式
  posPrev->next = posNext;
  posNext->prev = posPrev;

  free(pos);  //释放空间
}
  • test.c
void LinkListTest3()
{
  ListNode* head = ListCreate();

  ListPushBack(head, 1);
  ListPushBack(head, 2);
  ListPushBack(head, 3);
  ListPushBack(head, 4);
  ListPushBack(head, 5);
  ListPrint(head);

  ListNode* pos;

  pos = ListFind(head, 1);
  if (pos)
  {
    ListErase(pos);
    ListPrint(head);
  }

  pos = ListFind(head, 4);
  if (pos)
  {
    ListErase(pos);
    ListPrint(head);
  }

  pos = ListFind(head, 6);
  if (pos)
  {
    ListErase(pos);
    ListPrint(head);
  }

  ListDestroy(head);
}

测试运行结果如下:
在这里插入图片描述

3.10 双向链表销毁 (ListDestroy)

  • LinkList.h
void ListDestroy(ListNode* phead);
  • LinkList.c
// 双向链表销毁
void ListDestroy(ListNode* phead)
{
  assert(phead);  //确保哨兵结点存在

  ListNode* cur = phead->next;

  while (cur != phead)
  {
    ListNode* nextNode = cur->next;
    free(cur);
    cur = nextNode;
  }
  free(phead);
}

4. 总结

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持: O ( 1 ) O(1) O(1)不支持: O ( N ) O(N) O(N)
任意位置插入或者删除元素可能需要搬移元素, 效率低 O ( N ) O(N) O(N)只需要修改指针指向
插入动态顺序表, 空间不够时, 需要扩容没有容量的概念
应用场景元素高效存储 + 频繁访问任意位置插入和删除频繁
缓存利用率

5. 完整代码

  • LinkList.h
#pragma once 

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int LTDataType;
typedef struct ListNode
{
  LTDataType data;        //数据域
  struct ListNode* prev;  //指向前一个结点
  struct ListNode* next;  //指向后一个结点
}ListNode;

// 创建并返回链表的头结点
ListNode* ListCreate();
// 双向链表销毁
void ListDestroy(ListNode* phead);
// 双向链表打印
void ListPrint(ListNode* phead);
// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* phead);
// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* phead);
// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x);
// 双向链表在 pos 的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除 pos 位置的结点
void ListErase(ListNode* pos);

  • LinkList.c
#include "LinkList.h"

// 创建一个新结点
static ListNode* BuyListNode(LTDataType x)
{
  ListNode* newNode = (ListNode*)malloc(sizeof(struct ListNode));

  if (newNode == NULL)
  {
    perror("malloc fail");
    exit(-1);
  }

  newNode->data = x;
  newNode->next = NULL;
  newNode->prev = NULL;

  return newNode;
}

// 创建返回链表的头结点
ListNode* ListCreate()
{
  // 动态开辟空间创建头结点, 如果开辟失败直接结束程序
  ListNode* head = BuyListNode(0);
  
  // 处理链表数据, 数据域随机值, 两个指针指向自己
  head->next = head;
  head->prev = head;

  return head;
}

// 双向链表打印
void ListPrint(ListNode* phead)
{
  assert(phead);  //确保哨兵结点存在

  printf("head <=> ");

  ListNode* cur = phead->next;  //从头结点开始遍历, 直到遇到哨兵结点结束
  while (cur != phead)
  {
    printf("%d <=> ", cur->data);
    cur = cur->next;
  }

  printf("\n");
}

// 双向链表销毁
void ListDestroy(ListNode* phead)
{
  assert(phead);  //确保哨兵结点存在

  ListNode* cur = phead->next;

  while (cur != phead)
  {
    ListNode* nextNode = cur->next;
    free(cur);
    cur = nextNode;
  }
  free(phead);
}

// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
  assert(phead);  //确保哨兵结点存在

  ListNode* newNode = BuyListNode(x); //创建新结点 
  ListNode* tail = phead->prev;       //找到尾结点
  
  //更改链接关系
  tail->next = newNode;
  newNode->prev = tail;
  phead->prev = newNode;
  newNode->next = 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); //释放空间
}

// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x)
{
  assert(phead);  //确保哨兵结点存在

  ListNode* newNode = BuyListNode(x); //创建新结点
  ListNode* first = phead->next;      //找到原来的头结点

  //更新链接关系
  phead->next = newNode;
  newNode->prev = phead;
  newNode->next = first;
  first->prev = newNode;

}

// 双向链表头删
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); //释放空间
}

// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
  assert(phead);  //确保哨兵结点存在

  ListNode* cur = phead->next;

  while (cur != phead)
  {
    if (cur->data == x)
    {
      return cur;
    }
    cur = cur->next;
  }

  return NULL;
}

// 双向链表在 pos 之前插入
void ListInsert(ListNode* pos, LTDataType x)
{
  assert(pos);  //确保pos合法

  ListNode* newNode = BuyListNode(x); //创建新结点
  ListNode* posPrev = pos->prev;      //找到 pos 前一个结点的位置

  //更新链接方式
  posPrev->next = newNode;
  newNode->prev = posPrev;

  newNode->next = pos;
  pos->prev = newNode;
}
// 双向链表删除 pos 位置的结点
void ListErase(ListNode* pos)
{
  assert(pos);  //确保 pos 合法

  ListNode* posPrev = pos->prev;    //找到 pos 前一个结点的位置
  ListNode* posNext = pos->next;    //找到 pos 后一个结点的位置

  //更新链接方式
  posPrev->next = posNext;
  posNext->prev = posPrev;

  free(pos);  //释放空间
}

  • test.c
#include "LinkList.h"

void LinkListTest1()
{
  ListNode* head = ListCreate();
  ListPushBack(head, 1);
  ListPushBack(head, 2);
  ListPushBack(head, 3);
  ListPushBack(head, 4);
  ListPushBack(head, 5);
  ListPrint(head);

  ListPopBack(head);
  ListPrint(head);

  ListPopBack(head);
  ListPrint(head);
  
  ListPopBack(head);
  ListPopBack(head);
  ListPopBack(head);
  ListPrint(head);
  
  ListPopBack(head);
  ListPrint(head);
  
  ListDestroy(head);
}

void LinkListTest2()
{
  ListNode* head = ListCreate();

  ListPushFront(head, 1);
  ListPushFront(head, 2);
  ListPushFront(head, 3);
  ListPushFront(head, 4);
  ListPushFront(head, 5);

  ListPrint(head);
  
  ListPopFront(head);
  ListPrint(head);

  ListPopFront(head);
  ListPopFront(head);
  ListPopFront(head);
  ListPopFront(head);
  ListPrint(head);

  ListPopFront(head);
  ListPrint(head);

  ListDestroy(head);
}

void LinkListTest3()
{
  ListNode* head = ListCreate();

  ListPushBack(head, 1);
  ListPushBack(head, 2);
  ListPushBack(head, 3);
  ListPushBack(head, 4);
  ListPushBack(head, 5);
  ListPrint(head);

  ListNode* pos;

  pos = ListFind(head, 1);
  ListInsert(pos, 0);
  ListErase(pos);
  ListPrint(head);

  pos = ListFind(head, 4);
  ListInsert(pos, 10);
  ListPrint(head);

  pos = ListFind(head, 11);
  ListInsert(pos, 12);
  ListPrint(head);

  ListDestroy(head);
}

void LinkListTest4()
{
  ListNode* head = ListCreate();

  ListPushBack(head, 1);
  ListPushBack(head, 2);
  ListPushBack(head, 3);
  ListPushBack(head, 4);
  ListPushBack(head, 5);

  ListNode* pos;

  pos = ListFind(head, 2);
  printf("pos <=> ");
  while (pos && pos != head)
  {
    printf("%d <=> ", pos->data);
    pos = pos->next;
  }
  puts("\n");

  pos = ListFind(head, 6);
  printf("pos <=> ");
  while (pos && pos != head)
  {
    printf("%d <=> ", pos->data);
    pos = pos->next;
  }
  puts("\n");
}

void LinkListTest5()
{
  ListNode* head = ListCreate();

  ListPushBack(head, 1);
  ListPushBack(head, 2);
  ListPushBack(head, 3);
  ListPushBack(head, 4);
  ListPushBack(head, 5);
  ListPrint(head);

  ListNode* pos;

  pos = ListFind(head, 1);
  if (pos)
  {
    ListInsert(pos, -1);
    ListPrint(head);
  }

  pos = ListFind(head, 4);
  if (pos)
  {
    ListInsert(pos, -4);
    ListPrint(head);
  }

  pos = ListFind(head, 6);
  if (pos)
  {
    ListInsert(pos, -6);
    ListPrint(head);
  }

  ListDestroy(head);
}

void LinkListTest6()
{
  ListNode* head = ListCreate();

  ListPushBack(head, 1);
  ListPushBack(head, 2);
  ListPushBack(head, 3);
  ListPushBack(head, 4);
  ListPushBack(head, 5);
  ListPrint(head);

  ListNode* pos;

  pos = ListFind(head, 1);
  if (pos)
  {
    ListErase(pos);
    ListPrint(head);
  }

  pos = ListFind(head, 4);
  if (pos)
  {
    ListErase(pos);
    ListPrint(head);
  }

  pos = ListFind(head, 6);
  if (pos)
  {
    ListErase(pos);
    ListPrint(head);
  }

  ListDestroy(head);
}

int main(void)
{
  //LinkListTest1();
  //LinkListTest2();
  //LinkListTest3();
  //LinkListTest4();
  //LinkListTest5();
  LinkListTest6();
  return 0;
}

本章完.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/848637.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Plecs最新安装免费版下载链接/Plecs 电力仿真软件4.7.5版本下载/实测能用

Plecs最新安装免费版下载链接 Plecs 电力仿真软件4.7.5版本下载 实测能用&#xff1a;

信息安全:认证技术原理与应用.

信息安全&#xff1a;认证技术原理与应用. 认证机制是网络安全的基础性保护措施&#xff0c;是实施访问控制的前提&#xff0c;认证是一个实体向另外一个实体证明其所声称的身份的过程。在认证过程中&#xff0c;需要被证实的实体是声称者&#xff0c;负责检查确认声称者的实体…

校园跑腿小程序为什么这么受欢迎呢?

校园跑腿小程序是一种在校园内提供快递、代购、代拿快递、代办事项等服务的手机应用程序。它通常由学生或校园内的志愿者组成&#xff0c;通过该应用程序接受用户的委托&#xff0c;并且根据用户的需求完成任务。校园跑腿小程序通过与学校或社区合作&#xff0c;提供便利的服务…

关于SQL查询面试题,测试会这些基本够用了

Sutdent表的定义 Score表的定义 1、创建student和score表 CREATE TABLE student ( id INT(10) NOT NULL UNIQUE PRIMARY KEY , name VARCHAR(20) NOT NULL , sex VARCHAR(4) , birth YEAR, department VARCHAR(20) , address VARCHAR(50) ); 创建score表…

大数据-玩转数据-Flink-Transform

一、Transform 转换算子可以把一个或多个DataStream转成一个新的DataStream.程序可以把多个复杂的转换组合成复杂的数据流拓扑. 二、基本转换算子 2.1、map&#xff08;映射&#xff09; 将数据流中的数据进行转换, 形成新的数据流&#xff0c;消费一个元素并产出一个元素…

研究人员发现特斯拉汽车能被越狱,可免费解锁付费功能

Bleeping Computer 网站披露&#xff0c;柏林工业大学&#xff08;Technical University of Berlin&#xff09;的研究人员开发出一种新技术&#xff0c;可以破解特斯拉近期推出所有车型上使用的基于 AMD 的信息娱乐系统&#xff0c;并使其运行包括付费项目在内的任何软件。 实…

22、springboot的Profile(通过yml配置文件配置 profile,快速切换项目的开发环境)

springboot的Profile ★ 何谓Profile 应用所在的运行环境发生切换时&#xff0c;配置文件常常就需要随之修改。Profile&#xff1a;——就是一组配置文件及组件的集合。可以整个应用在不同的profile之间切换&#xff08;设置活动profile&#xff09;&#xff0c;整个应用都将使…

QGIS二次开发三:显示Shapefile

Shapefile 为 OGR 所支持的最重要的数据格式之一&#xff0c;自然可以被 QGIS 加载。那么该如何显示Shapefile呢&#xff1f; 一、先上代码 #include <qgsapplication.h> #include <qgsproviderregistry.h> #include <qgsmapcanvas.h> #include <qgsvec…

HDFS中snapshot快照机制

HDFS中snapshot快照机制 介绍作用功能实现相关命令和操作相关命令 介绍 snapshot是数据存储的某一时刻的状态记录&#xff0c;备份&#xff08;backup&#xff09;则是数据存储的某一个时刻的副本HDFS snapshot快照是整个文件系统或某个目录在某个时刻的镜像&#xff0c;该镜像…

黑马机器学习day1

1.sklearn数据集 sklearn.datasets datasets.load_*() 获取小规模的数据集 datasets.fetch_*(data_homeNone) 获取大规模数据集 函数的第一个参数是data_home,标识数据集下载目录&#xff0c;默认/scikit_learn_data/ 1.1sklearn小数据集 sklearn.da…

无涯教程-Perl - fcntl函数

描述 该函数是系统fcntl()函数的Perl版本。使用FILEHANDLE上的SCALAR执行FUNCTION指定的功能。 SCALAR包含函数要使用的值,或者是任何返回信息的位置。 语法 以下是此函数的简单语法- fcntl FILEHANDLE, FUNCTION, SCALAR返回值 该函数返回0,但如果fcntl()的返回值为0,则返…

Vue2:组件高级(下)

Vue2&#xff1a;组件高级&#xff08;下&#xff09; Date: May 25, 2023 Sum: 自定义指令、插槽、商品列表、动态组件 目标&#xff1a; 自定义指令 基础概念&#xff1a; 概念&#xff1a; 内置指令&#xff1a;vue 官方提供了 v-for、v-model、v-if 等常用的内置指令。…

整数规划——第七章 分支定界算法

整数规划——第七章 分支定界算法 目前大部分整数规划商业软件如CPLEX&#xff0c;Gurobi和BARON等都是基于分枝定界算法框架的。 7.1 最优性条件和界 考虑下列一般线性整数规划问题&#xff1a; (IP) min ⁡ c T x , s . t . A x ≤ b , x ∈ Z n (7.1) \text{(IP)}\quad…

部署-打包并提交代码到Git服务器

前期准备工作 vue.config.js const { defineConfig } require(vue/cli-service) module.exports defineConfig({// 项目出Bug,点击错误可以跳到对应的位置&#xff0c;实际生成环境是不需要的 默认为trueproductionSourceMap: false,lintOnSave: false,publicPath: process.…

720度全视角!VR直播遇上法院庭审,你体验过吗?

在元宇宙发展背景之下&#xff0c;VR直播技术同样驶入了发展的快车道&#xff0c;以沉浸式、交互式体验为特点的VR技术&#xff0c;将原本就受欢迎的直播变得更加立体、真实、可靠&#xff0c;观众只需要一部手机就可以实现全方位、无死角地观看直播现场&#xff0c;体验宛如身…

小白带你学习linux的Redis基础(三十二)

目录 前言 一、概述 1、NoSQL 2、Redis 二、安装 1、基础配置 2、编译安装 3、RPM安装网络源安装 三、目录结构 1、rpm -ql redis 2、/etc/redis.conf 四、命令解析 1、redis-server 2、redis-cli 2、redis-check-rdb 3、redis-check-aof 五、redis登录更改 …

ES面试题

前言 1、面试突击正确的学习姿势 老师在给你讲面试突击的时候&#xff0c;是有课件的&#xff0c;而且是有准备的。你在面试的时候&#xff0c;是没有笔记课件的&#xff0c;而且问题是由面试官提问的&#xff0c;具有一定的随机性面试突击课程的目标不是听懂&#xff0c;而是…

文件上传漏洞(webshell)

一、防护 1、防护 1、判断文件后缀&#xff0c;为图片的话才让上传成功。 2、解析文件内容&#xff08;文件幻数&#xff09;判断文件头和文件尾部是否一致 幻数 常见的 3、隐藏按钮&#xff08;带上code唯一值&#xff09; 4、二次渲染&#xff08;类似拿着你的图片&#xff…

cannot import name ‘container_abcs‘ from ‘torch._six‘

进行模型训练的时候&#xff0c;报错&#xff1a; 原因是&#xff1a;torch1.8版本之后container_abcs被移除了&#xff0c;所以我们需要修改timm库中调用container_abcs的调用方式&#xff1a; timm库中调用container_abcs的文件是“~\Conda\envs\pytorch\Lib\site-packages\…

Java后台生成ECharts图片,并以Base64字符串返回

前言 通过echarts的jar包&#xff0c;Java后台生成一张图片&#xff0c;并把图片插入到word中。关于word插图片的代码在下一章。 需要用到的工具PhantomJS,Echarts-convert.js,jquery.js,echarts.js。 1.PhantomJS 介绍 PhantomJS是一个不需要浏览器的富客户端。 官方介绍&…