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

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

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

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.rhkb.cn/news/76762.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

爬虫---练习源码

选取的是网上对一些球员的评价&#xff0c;来评选谁更加伟大一点 import csv import requests import re import timedef main(page):url fhttps://tieba.baidu.com/p/7882177660?pn{page}headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/53…

AMEYA360:瑞萨电子MCU和MPU产品线将支持Microsoft Visual Studio Code

全球半导体解决方案供应商瑞萨电子宣布其客户现可以使用Microsoft Visual Studio Code&#xff08;VS Code&#xff09;开发瑞萨全系列微控制器&#xff08;MCU&#xff09;和微处理器&#xff08;MPU&#xff09;。瑞萨已为其所有嵌入式处理器开发了工具扩展&#xff0c;并将其…

分布式开源监控Zabbix实战

Zabbix作为一个分布式开源监控软件&#xff0c;在传统的监控领域有着先天的优势&#xff0c;具备灵活的数据采集、自定义的告警策略、丰富的图表展示以及高可用性和扩展性。本文简要介绍Zabbix的特性、整体架构和工作流程&#xff0c;以及安装部署的过程&#xff0c;并结合实战…

分布式异步任务处理组件(七)

分布式异步任务处理组件底层网络通信模型的设计--如图&#xff1a; 使用Java原生NIO来实现TCP通信模型普通节点维护一个网络IO线程&#xff0c;负责和主节点的网络数据通信连接--这里的网络数据是指组件通信协议之下的直接面对字节流的数据读写&#xff0c;上层会有另一个线程负…

Linux下安装VMware虚拟机

目录 1. 简介 2. 工具/原料 2.1. 下载VMware 2.2. 安装 1. 简介 ​ VMware Workstation&#xff08;中文名“威睿工作站”&#xff09;是一款功能强大的桌面虚拟计算机软件&#xff0c;提供用户可在单一的桌面上同时运行不同的操作系统&#xff0c;和进行开发、测试 …

为什么list.sort()比Stream().sorted()更快?

真的更好吗&#xff1f; 先简单写个demo List<Integer> userList new ArrayList<>();Random rand new Random();for (int i 0; i < 10000 ; i) {userList.add(rand.nextInt(1000));}List<Integer> userList2 new ArrayList<>();userList2.add…

从零开始:手把手搭建 RocketMQ 单节点、集群节点实例

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

240. 搜索二维矩阵 II

240. 搜索二维矩阵 II 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a; 原题链接&#xff1a; 240. 搜索二维矩阵 II https://leetcode.cn/problems/search-a-2d-matrix-ii/description/ 完成情况&#xff1a; 解题思路&#xff1a; 从…

配置root账户ssh免密登录并使用docker-machine构建docker服务

简介 Docker Machine是一种可以在多种平台上快速安装和维护docker运行环境&#xff0c;并支持多种平台&#xff0c;让用户可以在很短时间内在本地或云环境中搭建一套docker主机集群的工具。 使用docker-machine命令&#xff0c;可以启动、审查、停止、重启托管的docker 也可以…

《向量数据库指南》——腾讯云向量数据库Tencent Cloud Vector DB正式上线公测!提供10亿级向量检索能力

8月1日,腾讯云向量数据库(Tencent Cloud Vector DB)已正式上线公测。在腾讯云官网上搜索“向量数据库”,就可以正式体验该产品。 腾讯云向量数据库不仅能为大模型提供外部知识库,提高大模型回答的准确性,还可广泛应用于推荐系统、文本图像检索、自然语言处理等 AI 领域。…

ChatGPT已打破图灵测试,新的测试方法在路上

生信麻瓜的 ChatGPT 4.0 初体验 偷个懒&#xff0c;用ChatGPT 帮我写段生物信息代码 代码看不懂&#xff1f;ChatGPT 帮你解释&#xff0c;详细到爆&#xff01; 如果 ChatGPT 给出的的代码不太完善&#xff0c;如何请他一步步改好&#xff1f; 全球最佳的人工智能系统可以通过…

K8S kubeadm搭建

kubeadm搭建整体步骤 1&#xff09;所有节点进行初始化&#xff0c;安装docker引擎和kubeadm kubelet kubectl 2&#xff09;生成集群初始化配置文件并进行修改 3&#xff09;使用kubeadm init根据初始化配置文件生成K8S的master控制管理节点 4&#xff09;安装CNI网络插件&am…

【Ubuntu 18.04 搭建 DHCP 服务】

参考Ubuntu官方文档&#xff1a;https://ubuntu.com/server/docs/how-to-install-and-configure-isc-dhcp-server dhcpd.conf 手册页 配置&#xff1a;https://maas.io/docs/about-dhcp 实验环境规划 Ubuntu 18.04&#xff08;172.16.65.128/24&#xff09;dhcp服务端Ubuntu…

GD32F103VE点灯

GD32F103VE点灯主要用来学习端口引脚的输出配置。它由LED.c&#xff0c;LED.h&#xff0c;SoftDelay.c和main.c组成。 #include "gd32f10x.h" //使能uint8_t,uint16_t,uint32_t,uint64_t,int8_t,int16_t,int32_t,int64_t #include "SoftDelay.h"#include …

后端整理(集合框架、IO流、多线程)

1. 集合框架 Java集合类主要有两个根接口Collection和Map派生出来 Collection派生两个子接口 List List代表了有序可重复集合&#xff0c;可以直接根据元素的索引进行访问Set Set代表无序不可重复集合&#xff0c;只能根据元素本身进行访问 Map接口派生 Map代表的是存储key…

数据结构 二叉树(C语言实现)

绪论 雄关漫道真如铁&#xff0c;而今迈步从头越。 本章将开始学习二叉树&#xff08;全文共一万两千字&#xff09;&#xff0c;二叉树相较于前面的数据结构来说难度会有许多的攀升&#xff0c;但只要跟着本篇博客深入的学习也可以基本的掌握基础二叉树。 话不多说安全带系好&…

在windows下安装ruby使用gem

在windows下安装ruby使用gem 1.下载安装ruby环境2.使用gem3.gem换源 1.下载安装ruby环境 ruby下载地址 选择合适的版本进行下载和安装&#xff1a; 在安装的时候&#xff0c;请勾选Add Ruby executables to your PATH这个选项&#xff0c;添加环境变量&#xff1a; 安装Ruby成…

二进制安装K8S(单Master集群架构)

目录 一&#xff1a;操作系统初始化配置 1、项目拓扑图 2、服务器 3、初始化操作 二&#xff1a; 部署 etcd 集群 1、etcd 介绍 2、准备签发证书环境 3、master01 节点上操作 &#xff08;1&#xff09;生成Etcd证书 &#xff08;2&#xff09;创建用于存放 etcd 配置文…

.Net6 Web Core API --- Autofac -- AOP

目录 一、AOP 封装 二、类拦截 案例 三、接口拦截器 案例 AOP拦截器 可开启 类拦截器 和 接口拦截器 类拦截器 --- 只有方法标注 virtual 标识才会启动 接口拦截器 --- 所有实现接口的方法都会启动 一、AOP 封装 // 在 Program.cs 配置 builder.AddAOPExt();//自定义 A…

企业电子招标采购系统源码Spring Boot + Mybatis + Redis + Layui + 前后端分离 构建企业电子招采平台之立项流程图 tbms

&#xfeff; 项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&am…