数据结构—链表

链表

  • 前言
  • 链表
    • 链表的概念及结构
    • 链表的分类
  • 无头单向非循环链表的相关实现
  • 带头双向循环链表的相关实现
  • 顺序表和链表(带头双向循环链表)的区别

前言

顺序表是存在一些固有的缺陷的:

  1. 中间/头部的插入删除,时间复杂度为O(N),效率比较低(因为需要挪动数据)
  2. 增容(需要开辟新空间释放旧空间)需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
  3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

其中链表就可以很好的解决上面的问题。

链表

链表的概念及结构

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

在这里插入图片描述

数据结构中:

在这里插入图片描述
注:

  • 链表在逻辑上是线性,物理上不一定是线性的
  • phead一般是指向第一个节点的指针(头指针)

链表的分类

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

  1. 单向或者双向

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

在这里插入图片描述

注:

  • d1-头节点,head-哨兵位的头节点,这个节点不存储有效数据
  • 带哨兵位头节点的好处是如果头指针指向的是不带哨兵位的头节点时再做头插尾插等操作就得传二级指针,因为头插尾插等操作只要动到头节点就需要改变头指针。如果是带哨兵位的头节点就不用传二级指针,因为头指针永远都不会变一直指向带哨兵位的头节点,头插是在带哨兵位的头节点之后插入,尾插也一样。永远不会动到头指针因为带哨兵位的头节点没有存储有效数据。
  • 这里的带头指的是哨兵位的头节点,不存储有效数据
  1. 循环或者非循环
    在这里插入图片描述
    虽然有这么多的链表的结构,但是实际中最常用还是两种结构:无头单项非循环链表、带头双向循环链表

在这里插入图片描述

在这里插入图片描述

  1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。

无头单向非循环链表的相关实现

链表的定义

typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;  // int val;struct SListNode* next; //注意这个地方不能使用SLTNode* next;这个地方还没有typedef出来就开始使用SLTNode,编译器找定义时只会向上找,然而上面并没有SLTNode。	
}SLTNode;

链表的打印

链表的打印需要将头节点的地址进行传参,从指向头节点位置的指针开始依次循环遍历直到遇到NULL为止停止打印节点的数据

void SListPrint(SLTNode* phead)
{SLTNode* cur = phead;while (cur != NULL){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}

链表的尾插

链表的尾插首先通过循环的方式找到链表中的最后一个节点,接着要创建一个新节点,节点里面存放尾插的数据,最后将新节点的地址存放到最后一个节点中从而完成连接的过程。这时是最常见的一种情况,还有一种是当链表为空时(没有一个节点,头指针存放NULL),需要创建一个新节点并把新节点的地址存放到头指针中即可。

SLTNode* BuySListNode(SLTDataType x)
{SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));if (node == NULL){printf("malloc fail\n");exit(-1);}node->data = x;node->next = NULL;return node;
}void SListPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);//头指针可能为空,但是头指针的地址是不可能为空的if (*pphead == NULL){SLTNode* newnode = BuySListNode(x);*pphead = newnode;}else{SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}SLTNode* newnode = BuySListNode(x);tail->next = newnode;}
}

注意:

  • 这里不要使用一级指针进行传参,因为指针传值,相当于把plist指针变量得值拷贝给phead,phead里面赋值newnode,而phead的改变不会影响plist。因此想在函数内部修改外面plist的值就得使用二级指针进行传参。
  • 要改变传过来的指向第一个节点的指针就传二级;不改变传过来的指向第一个节点的指针就传一级

链表的头插

链表的头插只需要创建一个新节点,将头指针内存放的第一个节点的地址存储到新结点中,再将新节点的地址存放到头指针中(其中这两步顺序不能颠倒),从而完成头插的连接。注意当链表为空时这个头插不会出现问题。

void SListPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySListNode(x);newnode->next = *pphead;*pphead = newnode;
}

链表的尾删

链表尾删的方式有两种:

  • 一种是定义两个指针(prev、tail),当tail指向下一个节点之前先将tail赋给prev,然后tail在指向下一个节点。这样以此往复直到tail指向最后一个节点停止,此时prev指向tail所指向节点的前一个节点。最后将tail所指向的节点释放掉,再将prev所指向节点的next置成NULL。
  • 另一种是定义一个指针(tail)利用单链表的特点完成链表的尾删,单链表的特点是只能向一个方向走,只要走完就回不去了。因此tail指针找得到下一个节点但是找不到当前节点的前一个节点。 那直接找前一个从而也就能找到它的下一个,单链表是在当前位置找不到前一个但是在前一个位置是可以找到后一个的。此时就需要看tail所指向当前节点的下一个节点的下一个节点是否为NULL,如此往复直到它为NULL停止就说明tail所指向当前节点的下一个节点就是单链表的最后一个节点,那tail所指向当前节点就是最后一个节点的前一个节点。最后将tail所指向当前节点的下一个节点释放掉,并将tail所指向当前节点的next置成NULL即可。

注:

  • 当链表尾删删除的是最后一个节点时需要注意头指针的指向问题(eg:phead)以及节点指针的野指针问题(eg:prev、tail->next)
  • 链表尾删时需要分三种情况:无节点、一个节点、多个节点。当链表为空时进行尾删此时需要对头指针进行判断(eg:assert、if)。当链表有一个节点时(即头节点的next存储的是NULL)直接对该节点进行释放并把头指针指向空。当链表有多个节点时按照上述方式处理即可。
//第一种:void SListPopBack(SLTNode** pphead)
{// 检查参数是否传错assert(pphead);// 没有节点断言报错 -- 处理比较激进// 无节点assert(*pphead);// 一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}// 多个节点else{SLTNode* tail = *pphead;while (tail->next->next != NULL){tail = tail->next;}free(tail);tail->next = NULL;}
}//第二种:
void SListPopBack(SLTNode** pphead)
{// 参数传错assert(pphead);// 无节点// 温和处理if (*pphead == NULL){return;}// 一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}// 多个节点else{SLTNode* prev = NULL;SLTNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);prev->next = NULL;}
}//第三种:
void SListPopBack(SLTNode** pphead)
{// 参数传错assert(pphead);// 无个节点// 链表为空了,还在调用尾删assert(*pphead);// 一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}// 多个节点else{SLTNode* prev = NULL;SLTNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);prev->next = NULL;}
}//第四种:
void SListPopBack(SLTNode** pphead)
{// 参数传错assert(pphead);// 无节点// 温和处理if (*pphead == NULL){return;}// 一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}// 多个节点else{SLTNode* prev = NULL;SLTNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);prev->next = NULL;}
}

注:pphead的意义是能够在增删的函数中改变外面的plist

链表的头删

链表的头删首先判断头指针的地址是否为空,其次再处理链表中无节点、一个节点、多个节点的情况即可。

注:

  • 链表头删处理多个节点(一个节点)时需要先对头节点的下一个节点进行保存,再对头节点进行删除,最后再将头节点的下一个节点的地址存放到头指针里即可。
  • 链表头删中的处理多个节点的情况也包含了一个节点的情况。
//第一种:void SListPopFront(SLTNode** pphead)
{// plist一定有地址,所以pphead不为空assert(pphead);// 链表为空assert(*pphead);// 只有一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}// 多个节点else{SLTNode* next = (*pphead)->next;free(*pphead);(*pphead) = next;}
}//第二种:void SListPopFront(SLTNode** pphead)
{// plist一定有地址,所以pphead不为空assert(pphead);//链表为空assert(*pphead);//只有一个节点//多个节点SLTNode* next = (*pphead)->next;free(*pphead);(*pphead) = next;
}

链表中节点的个数

可以通过循环的方式(终止条件是当前节点的地址是否为空)求链表中节点的个数。

注:phead不需要进行断言,因为phead有可能为空(链表为空)空链表可以求节点个数

int SListSize(SLTNode* phead)
{int size = 0;SLTNode* cur = phead;while (cur){size++;cur = cur->next;}return size;
}

链表判空

链表的判空只需对头指针进行判空即可,如果头指针为空返回true,否则返回false。

注:C语言中用bool类型需要引头文件stdbool.h

//第一种:bool SListEmpty(SLTNode* phead)
{return phead == NULL;
}//第二种:bool SListEmpty(SLTNode* phead)
{if (phead == NULL){return true;}else{return false;}
}//第三种:bool SListEmpty(SLTNode* phead)
{return phead == NULL ? true : false;
}

链表的查找

链表的查找是通过循环遍历的方式进行查找,如果找到了返回该节点的地址,否则返回NULL。

SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}else{cur = cur->next;}}return NULL;
}

链表的插入

链表的前插入分两种情况处理:头插、后面插入。这里注意后面插入时首先需要找到pos位置之前的节点的位置(通过循环的方式找到pos位置之前的节点的位置),其次创建要插入的新节点,最后进行插入使其新节点的next指向pos位置的节点(next存储pos的值)并使前一个节点的next指向新节点(next存储新节点的地址)即可。

// 在pos位置之前去插入x
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);// 头插if (*pphead == pos){SListPushFront(pphead, x);}// 后面插入else{// 找到pos位置的前一个节点SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuySListNode(x);newnode->next = pos;prev->next = newnode;}
}

链表的后插入首先要创建一个新节点,其次使其新节点的next指向pos位置上的节点的下一个节点,最后将pos位置上节点的next指向新节点。

// 在pos位置后面去插入x
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);//注意插入顺序,不可颠倒SLTNode* newnode = BuySListNode(x);newnode->next = pos->next;pos->next = newnode;
}

注:单链表不适合在某个位置之前插入,因为需要找前一个位置。单链表适合在某个位置之后插入。

链表的删除

链表删除当前位置的节点分两种情况:头删、后面删除。这里注意后面删除首先需要找当前位置节点的前一个节点,其次使其前一个节点的next指向当前位置节点的后一个节点,最后再将pos位置上的节点释放掉。

// 删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);// 头删if (pos == *pphead){SListPopFront(pphead);}// 后面节点删除else{// 找到pos位置的前一个节点SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}

链表删除当前位置的之后的节点,只需将当前位置的节点的next指向当前位置的节点的下一个节点的下一个节点即可,不过在此之前需要对当前位置的节点的下一个节点进行保存避免找不到该节点。最后再将该节点释放掉即可。

void SListEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);SLTNode* next = pos->next;pos->next = next->next;free(next);next = NULL;
}

链表的销毁

链表的销毁是将所有节点释放掉,通过循环的方式对链表进行销毁。这里注意释放节点时需要对下一个节点的地址进行保存避免找不到下一个节点的地址,最后需要将头指针置空即可。

注:节点的释放会引发一些问题会把这块节点的使用权还给操作系统并且指向这块空间上的值会被置成随机值

void SListDestory(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;while (cur){SLTNode* next = cur->next;free(cur);cur = next;}*pphead = NULL;
}

带头双向循环链表的相关实现

链表的定义

typedef int LTDataType;typedef struct ListNode
{struct ListNode* next;struct ListNode* prev;LTDataType data;
}LTNode;

链表的初始化

链表初始化首先要创建一个哨兵位的头节点,其次将哨兵位的头节点的前驱和后继都指向该头节点即可

LTNode* BuyListNode(LTDataType x)
{LTNode* node = (LTNode*)malloc(sizeof(LTNode));if (node == NULL){printf("malloc fail\n");exit(-1);}node->next = NULL;node->prev = NULL;node->data = x;return node;
}//第一种:void ListInit(LTNode** pphead)
{*pphead = BuyListNode(-1);(*pphead)->next = *pphead;(*pphead)->prev = *pphead;
}//第二种:LTNode* ListInit()
{LTNode* phead = BuyListNode(0);phead->next = phead;phead->prev = phead;return phead;
}

链表的打印

链表打印通过循环的方式从头节点的下一个节点开始进行打印节点(带有哨兵位的头节点是无效节点,不存储有效数据),注意当循环遍历到头节点时结束循环。

void ListPrint(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){printf("%d ", cur->data);cur = cur->next;}printf("\n");
}

链表的尾插

链表尾插首先通过头节点的前驱找到尾节点的地址,其次创建新节点,然后修改头节点、新节点、尾节点三者的连接关系即可

注:链表尾插的过程中没有修改头指针

//第一种:void ListPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* tail = phead->prev;LTNode* newnode = BuyListNode(x);tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;
}//第二种://利用函数复用只需将头节点的指针传给链表的插入函数(相当于在头节点之前插入(链表的尾插))即可。
void ListPushBack(LTNode* phead, LTDataType x)
{assert(phead);ListInsert(phead, x);
}

注:时间复杂度为O(1)

链表的头插

利用函数复用只需将头节点的后继传给链表的插入函数(相当于在头节点的后继所指向的节点之前插入(链表的头插))即可。

void ListPushFront(LTNode* phead, LTDataType x)
{assert(phead);ListInsert(phead->next, x);
}

补充:C语言中复用函数是指在编程过程中重复使用代码段的一种方法。每个程序中都存在很多相似的或者相同的代码段,把这些相同的代码段抽取出来,形成一个独立的函数就叫做复用函数。复用函数的好处在于可以有效提高程序的可维护性、可读性,提高代码的可复用性,还可以明显减少程序的开发时间,简化程序的结构。

链表的尾删

链表尾删通过头节点的前驱找到尾节点的地址,再通过尾节点的前驱找到尾节点的前一个节点的地址,然后删除尾节点,最后完成头节点和尾节点的前一个节点的连接关系即可。

//第一种:void ListPopBack(LTNode* phead)
{assert(phead);assert(!ListEmpty(phead));LTNode* tail = phead->prev;LTNode* tailPrev = tail->prev;free(tail);tailPrev->next = phead;phead->prev = tailPrev;
}//第二种://利用函数复用只需将头节点的前驱传给链表的删除函数(相当于在头节点的前驱所指向的节点进行删除(链表的尾删))即可。
void ListPopBack(LTNode* phead)
{assert(phead);assert(!ListEmpty(phead));ListErase(phead->prev);
}

链表的判空

链表的判空通过判断头节点的后继是否是本身如果是返回真否则返回假。

bool ListEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}

链表中有效节点的个数

求链表中有效节点的个数通过循环遍历的方式实现即可。

size_t ListSize(LTNode* phead)
{assert(phead);size_t n = 0;LTNode* cur = phead->next;while (cur != phead){++n;cur = cur->next;}return n;
}

链表的头删

利用函数复用只需将头节点的后继传给链表的删除函数(相当于在头节点的后继所指向的节点进行删除(链表的头删))即可。

void ListPopFront(LTNode* phead)
{assert(phead);assert(!ListEmpty(phead));ListErase(phead->next);
}

链表的插入(在某个位置之前插入节点)

链表的插入首先通过pos位置上节点的前驱找到前一个节点,再创建一个新节点,最后将pos位置上的节点、新节点、pos位置的节点的前一个节点三个节点完成连接即可。

void ListInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = BuyListNode(x);LTNode* prev = pos->prev;// prev newnode posprev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;
}

链表的删除(删除某个位置的节点)

链表的删除首先找到pos位置上的节点的前一个节点,其次再找到pos位置上的节点的后一个节点,然后释放pos位置上的节点,最后将pos位置上的节点的前一个节点和pos位置上的节点的后一个节点进行连接即可。

// 删除pos位置
void ListErase(LTNode* pos)
{assert(pos);LTNode* prev = pos->prev;LTNode* next = pos->next;free(pos);pos = NULL;prev->next = next;next->prev = prev;
}

链表的查找

链表的查找是通过循环遍历的方式进行查找,如果找到了返回该节点的地址,否则返回NULL。

LTNode* ListFind(LTNode* phead, LTDataType x)
{LTNode* cur = phead->next;while (cur != phead){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}

链表的销毁

链表的销毁是将所有节点释放掉(包括带哨兵位的头节点),通过循环的方式对链表进行销毁。这里注意释放节点时需要对下一个节点的地址进行保存避免找不到下一个节点的地址(最后需要将头指针置空)即可。

注:节点的释放会引发一些问题会把这块节点的使用权还给操作系统并且指向这块空间上的值会被置成随机值

//第一种:void ListDestroy(LTNode* phead)
{LTNode* cur = phead->next;while (cur != phead){LTNode* next = cur->next;free(cur);//cur = NULL;cur = next;}free(phead);//phead = NULL;  // 这里其实置空不置空都可以的,因为出了函数作用域,没人能访问phead// 其次就是phead形参的置空,也不会影响外面的实参
}//第二种:void ListDestroy(LTNode** pphead)
{LTNode* cur = (*pphead)->next;while (cur != *pphead){LTNode* next = cur->next;free(cur);cur = next;}free(*pphead);*pphead = NULL;
}

注:链表销毁函数的参数可以用一级指针也可以用二级指针。参数用一级指针,外面用了以后实参存在野指针的问题。但是这个函数保持了接口的一致性。参数用二级指针,虽然解决了野指针问题但是从接口设计的角度有点混乱。

顺序表和链表(带头双向循环链表)的区别

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

注:

  • 链表和顺序表它们是不能比较出谁更优的,它们各有优缺点,相辅相成。
  • 扩容有一定的消耗(拷贝数据释放空间),以及扩容后可能存在一定的空间浪费。
  • 尾插尾删多并且经常随机访问用顺序表合适。随时需要任意位置插入删除用链表合适。

存储体系结构以及局部原理性。

在这里插入图片描述

  • 寄存器和三级缓存是围绕在CPU附近的
  • 主存(运行内存)是带电存储的,磁盘是不带电存储
  • CPU运算速度快,取内存速度跟不上。CPU一般就不会直接访问内存,而是把要访问的数据先加载的到缓存体系。如果是小于等于8字节的数据会直接到寄存器,而大数据就会到三级缓存。CPU直接跟缓存交互。
  • CPU要访问内存中某个地址的数据首先要去缓存中去找,如果发现找不到(没有命中),此时就会把主存中这个地址开始的一段空间都读进来缓存(缓存预加载某个部分的空间和一段空间的成本(时间成本)是一样的),便于下一次访问。
  • 缓存利用率(缓存命中率)低会造成一定的缓存污染。

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

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

相关文章

windows C++多线程同步<2>-事件

windows C多线程同步<2>-事件 事件对象和关键代码段不同,它是属于内核对象;又分为人工重置事件对象和自动重置事件对象; 同一个线程不允许在不释放事件的情况下多次获取事件; 相关API 白话来讲&#xff1…

认识 springboot 并了解它的创建过程 - 1

前言 本篇介绍什么是SpringBoot, SpringBoot项目如何创建,认识创建SpringBoot项目的目录,了解SpringBoo特点如有错误,请在评论区指正,让我们一起交流,共同进步! 文章目录 前言1.什么是springboot?2.为什么…

Rust之通用集合类型

在Rust语言中包含了一系列被称为集合的数据结构。大部分的数据结构都代表着某个特定的值,但集合却可以包含多个值。与内置的数组与元组类型不同,这些集合将自己持有的数据存储在了堆上。这意味着数据的大小不需要在编译时确定,并且可以随着程…

PKG内容查看工具:Suspicious Package for Mac安装教程

Suspicious Package Mac版是一款Mac平台上的查看 PKG 程序包内信息的应用,Suspicious Package Mac版支持查看全部包内全部文件,比如需要运行的脚本,开发者,来源等等。 suspicious package mac使用简单,只需在选择pkg安…

农业中的计算机视觉 2023

物体检测应用于检测田间收割机和果园苹果 一、说明 欢迎来到Voxel51的计算机视觉行业聚焦博客系列的第一期。每个月,我们都将重点介绍不同行业(从建筑到气候技术,从零售到机器人等)如何使用计算机视觉、机器学习和人工智能来推动…

网络安全-防御需知

目录 网络安全-防御 1.网络安全常识及术语 资产 漏洞 0day 1day 后门 exploit APT 2.什么会出现网络安全问题? 网络环境的开放性 协议栈自身的脆弱性 操作系统自身的漏洞 人为原因 客观原因 硬件原因 缓冲区溢出攻击 缓冲区溢出攻击原理 其他攻击…

网络安全行业相关证书

一:前言 对于考证这个话题,笔者的意见是:“有比没有好,有一定更好,但不一定必须;纸上证明终觉浅,安全还得实力行”。很多人对于各种机构的考证宣传搞得是云里雾里,不知道网络安全行业…

Codeforces 1579G DP / 二分 + bitset

题意 传送门 Codeforces 1579G Minimal Coverage 题解 DP d p [ i 1 ] [ j ] dp[i1][j] dp[i1][j] 代表 0 ⋯ i 0\cdots i 0⋯i 次移动后所在位置与覆盖区域最左侧位置相差 j j j 时,覆盖区域的最小值。枚举左右方向递推即可。总时间复杂度 O ( n ⋅ max ⁡ …

flex盒子 center排布,有滚动条时,拖动滚动条无法完整显示内容

文章目录 问题示例代码解决问题改进后的效果 问题 最近在开发项目的过程中,发现了一个有趣的事情,与flex盒子有关,不知道算不算是一个bug,不过对于开发者来说,确实有些不方便,感兴趣的同学不妨也去试试。 …

设计模式-建造者模式

在前面几篇文章中,已经讲解了单例模式、工厂方法模式、抽象工厂模式,创建型还剩下一个比较重要的模式-建造者模式。在理解该模式之前,我还是希望重申设计模式的初衷,即为解决一些问题而提供的优良方案。学习设计模式遗忘其初衷&am…

关于Spring中的@Configuration中的proxyBeanMethods属性

Configuration的proxyBeanMethods属性 在Configuration注解中,有两个属性: value配置Bean名称proxyBeanMethos,默认是true 这个proxyBeanMethods的默认属性是true。 直接说:当Configuration注解的proxyBeanMeathods属性是true…

VLAN原理(Virtual LAN 虚拟局域网)

VLAN(Virtual LAN 虚拟局域网) 1、广播/广播域 2、广播的危害:增加网络/终端负担,传播病毒, 3、如何控制广播?? ​ 控制广播隔离广播域 ​ 路由器物理隔离广播 ​ 路由器隔离广播缺点&…

rocketmq 5.13任意时间延迟消息

原理是采用timewhile 实现的,源码分析可以参考 https://blog.csdn.net/sinat_14840559/article/details/129266105 除了useDelayLevel 已经默认改为false private boolean useDelayLevel false;官方示意代码在public class TimerMessageProducer for (int i 0;…

解决在云服务器开放端口号以后telnet还是无法连接的问题

这里用阿里云服务器举例,在安全组开放了对应的TCP端口以后。使用windows的cmd下的telnet命令,还是无法正常连接。 telnet IP地址 端口号解决方法1: 在轻量服务器控制台的防火墙规则中添加放行端口。 阿里云-管理防火墙 如图,开放…

基于java在线个人网站源码设计与实现

摘 要 随着社会及个人社交应用平台的飞速发展,人们的沟通成本逐渐降低,互联网信息的普及也进一步提升了人们对于信息的需求度,通过建立个人网站的方式来展示自己的生活信息同时利用平台结交新的朋友,借助个人网站平台的搭建不仅可…

右击不显示TortoiseGit图标处理方法

第一种 右键--》TortoiseGIt--》setting--》Icon Overlays--》Status cache,按照下图设置,然后重启电脑。 第二种 进入注册信息,按照步骤找到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIden…

C#设计模式之---建造者模式

建造者模式(Builder Pattern) 建造者模式(Builder Pattern)是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式使得建造代码与表示代码的分离,可以使客户端不必知道…

AI帮你制作海报

介绍 Microsoft Designer是由微软推出的图像处理软件,能够通过套用模板等方式快速完成设计加工,生成能够在社交媒体使用的图片。Designer的使用更为简单便捷,用户能够通过套用模板等方式快速完成设计加工,生成能够在社交媒体使用…

python离散仿真器

文章目录 类图示例 类图 示例

Stability AI推出Stable Diffusion XL 1.0,文本到图像模型

Stability AI宣布推出Stable Diffusion XL 1.0,这是一个文本到图像的模型,该公司将其描述为迄今为止“最先进的”版本。 Stability AI表示,SDXL 1.0能生成更加鲜明准确的色彩,在对比度、光线和阴影方面做了增强,可生成…