数据结构学习之路--一网打尽链表的相关操作(附C源码)

news2024/11/18 2:34:39

   嗨嗨大家~我们今天继顺序表内容来讲解链表。话不多说,让我们走进本期的学习吧!


目录

一、线性表的链式存储

1 链式存储结构

2 链表的定义 

3 链表的分类 

二、链表的实现过程

1 链表的打印 

2 结点的创建

3 链表的头插 

4 链表的头删 

5 链表的尾插 

6 链表的尾删 

7 链表元素的查找 

8 在pos位置之前插入一个结点 

9  在pos位置之后插入一个结点 

10 删除pos位置前的一个结点

11 删除pos位置后的一个结点

三、源代码 

四、链表面试题 

1 移除链表元素

2 反转链表 

3 链表的中间结点 

4 链表中倒数第k个结点 

5 链表分割 

五、链表与顺序表的对比

1 链表的优缺点

1.1 优点

1.2 缺点

2 顺序表的优缺点 

2.1 优点

2.2 缺点


 

一、线性表的链式存储

1 链式存储结构

   线性表链式存储结构的特点是:用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。 因此,为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素a来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。 这两部分信息组成数据元素ai的存储映像,称为结点(node)。它包括两个域:其中存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域。指针域中存储的信息称作指针或链。n个结点(ai+1(1<=i<=n) 的存储映像)链接成一个链表,又因为此链表的每个结点中仅包含一个指针域,故又称线性链表或单链表

  根据链表结点所含指针个数、指针指向和指针连接方式,可将链表分为单链表、循环链表、双向链表、二叉链表、十字链表、邻接表、邻接多重表等。这里说一下,单链表、循环链表和双向链表用于实现线性表的链式存储结构,其他形式多用于实现树和图等非线性结构。

2 链表的定义 

   概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑结构是通过链表中的指针链接次序来实现的。整个链表的存取必须从指针开始进行,头指针指示链表中第一个结点(即第一个数据元素的存储映像,也称首元结点)的存储位置。同时,由于最后一个数据元素没有直接后继,则单链表中最后一个结点的指针为空(NULL)。

注:链表的逻辑结构和物理结构是不一致的。 

3 链表的分类 

   链表的结构是多样化的,下面以附图解的形式来介绍8种链表组合结构:

虽然有多种链表结构,但我们在实际中最常用的只有两种结构,如下图:

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

二、链表的实现过程

1 链表的打印 

  • 思路:先定义一个结点指针指向头节点,往后依次遍历,与数组不同的是,不是cur++,而是让cur指向下一个节点,即cur=cur->next。

代码实现:

//打印
void SListPrint(SLTNode* plist)
{
   assert(plist);       //断言
   SLTNode* cur = plist;//让cur指向头结点进行遍历
   while (cur != NULL)//注:条件不是cur->next,当cur->next为空便不进入循环,尾元素访问不到
   {
      printf("%d->", cur->data);
      cur = cur->next;
   }
   printf("NULL\n");
}

附图解:

  

分析:时间复杂度:O(N);空间复杂度:O(1)。 

2 结点的创建

代码实现:

//创建结点
SLTNode* BuySLTNode(SLTDataType x)
{
   SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
   node->data = x;
   node->next = NULL;
   return node;
}

3 链表的头插 

代码实现:

//头插
void SListPushFront(SLTNode** pplist, SLTDataType x)
//为什么是**pplist?因为我们要改变的是一级指针plist,所以传参进来应该是一个二级指针(传址调用)
{
  //如果传入的是空指针也可以正常插入
   SLTNode* newnode = BuySLTNode(x);
   newnode->next = *pplist;
   *pplist = newnode;
}

附图解:

分析:时间复杂度:O(1);空间复杂度:O(1)。

4 链表的头删 

代码实现:

//头删
void SListPopFront(SLTNode** pplist)
{
   //1.没有结点的情况
   if (*pplist == NULL)
   {
      return;
   }
   //2.多个结点的情况
   else
   {
     //保存一份下一个元素的地址(否则free过后就找不到了)
     SLTNode* next = (*pplist)->next; //需要加括号,因为*和->优先级相同
     free(*pplist);
     *pplist = next;
   }
}

附图解:

分析: 时间复杂度:O(1);空间复杂度:O(1)。

5 链表的尾插 

代码实现:

void SListPushBack(SLTNode** pplist, SLTDataType x)
{
   //创建一个新结点
   SLTNode* newnode = BuySLTNode(x);
   //如果链表为空,让尾插的结点变成该表的第一个元素
   if (*pplist == NULL)
   {
      *pplist = newnode;
   }
   else //非空, 直接尾插
   {
     //先遍历,找尾结点
     SLTNode* tail = *pplist;
     while (tail->next != NULL)
     {
         tail = tail->next;
     }
   tail->next = newnode;
   //该新结点定义时就指向NULL了,所以让tail的next指向该结点即可
    }
}

附图解:

分析: 时间复杂度:O(N);空间复杂度:O(1)。

6 链表的尾删 

代码实现:

//尾删
void SListPopBack(SLTNode** pplist)
{
   //1.没有结点的情况
    if (*pplist == NULL)
    {
        return;
    }
   //2.只有一个结点的情况
    else if ((*pplist)->next == NULL)
    {
       free(*pplist);
       *pplist = NULL;
    }
   //3.多个结点的情况
    else
    {
       //前驱指针
       SLTNode* prev = NULL;
       SLTNode* tail = *pplist;
       while (tail->next != NULL)
       {
          //找尾结点的前驱结点
          prev = tail;
          //找尾结点
          tail = tail->next;
       }
       free(tail);
       tail = NULL;
       prev->next = NULL;
     }
}

附图解:

分析: 时间复杂度:O(N);空间复杂度:O(1)。 

7 链表元素的查找 

代码实现:

//查找
SLTNode* SListFind(SLTNode* plist, SLTDataType x)
{
   SLTNode* cur = plist; //遍历查找
   //while (cur != NULL)
   while(cur)
   {
      if (cur->data == x)
          return cur;    //返回结点指针
      else
          cur = cur->next;
   }
   return NULL;          //没找到,返回NULL
}

 分析: 时间复杂度:O(N);空间复杂度:O(1)。

8 在pos位置之前插入一个结点 

代码实现:

//单链表在pos位置之前插入 (麻烦,不建议使用)
void SListInsert(SLTNode** pplist, SLTNode* pos, SLTDataType x)
{
   assert(pos);
   SLTNode* newnode = BuySLTNode(x);
   //在第一个位置插入 => 相当于头插
   if (pos == *pplist)
   {
      newnode->next = pos;
      *pplist = newnode;
   }
   else
   {
      SLTNode* prev = NULL; //前驱
      SLTNode* cur = *pplist;
      while (cur != pos)
      {
         prev = cur;
         cur = cur->next;
      }
      prev->next = newnode;
      newnode->next = pos;
    }
}

附图解:

 分析: 时间复杂度:O(N);空间复杂度:O(1)。 

9  在pos位置之后插入一个结点 

代码实现:

//单链表在pos位置之后插入
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
   assert(pos);
   SLTNode* newnode = BuySLTNode(x);
   //注意顺序
   newnode->next = pos->next;
   pos->next = newnode;
}

附图解:

10 删除pos位置前的一个结点

代码实现:

//删除pos之前的结点
void SListEraseFront(SLTNode** pplist, SLTNode* pos)
{
	assert(pos);
	assert(pos != *pplist);
	if (pos== (*pplist)->next)  //头删,第一种情况
	{
		free(*pplist);
		(*pplist) = pos;

		return;
	}
	SLTNode* cur = *pplist;
	while (cur)
	{
		if (cur->next->next == pos)   //找到pos前面的第二个结点
		{
			free(cur->next);
			cur->next = pos;     //链接

			return;
		}
		cur = cur->next;
	}

}

附图解:

 分析: 时间复杂度:O(N);空间复杂度:O(1)。  

11 删除pos位置后的一个结点

代码实现:

//如果需要在一个无头单链表的某一个结点的前面插入一个值x,如何插入?
//思路:先后插,然后跟他前一个结点的值(data)进行交换即可

//删除pos后的一个结点
void SListEraseAfter(SLTNode* pos)
{
    assert(pos);
   //pos后面无结点便没有意义
    if (pos->next == NULL)
    {
        return;
    }
    else
    {
        SLTNode* next = pos->next;
        pos->next = next->next; //链接
        free(next);
        next = NULL;
    }
}

 附图解:

 分析: 时间复杂度:O(1);空间复杂度:O(1)。  

三、源代码 

Slist.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
 
//方便存储其他类型
typedef int SLTDataType;
 
//定义一个链表结点
typedef struct SListNode
{
	SLTDataType data; //数据域
	struct SListNode* next; //指针域
}SLTNode;
 
//单向+不带头+循环
 
//尾插
void SListPushBack(SLTNode** plist, SLTDataType x);
//头插
void SListPushFront(SLTNode** pplist, SLTDataType x);
//尾删
void SListPopBack(SLTNode** pplist);
//头删
void SListPopFront(SLTNode** pplist);
 
//查找
SLTNode* SListFind(SLTNode* plist, SLTDataType x);
 
//单链表在pos位置之后插入
void SListInsertAfter(SLTNode* pos, SLTDataType x);
 
//并不建议在之前插入,麻烦
//单链表在pos位置之前插入(一般直接写Insert就是在之前插入)
void SListInsert(SLTNode** plist, SLTNode* pos, SLTDataType x);

//删除pos之前的结点
void SListEraseFront(SLTNode** pplist, SLTNode* pos)
 
//删除pos后的一个结点
void SListEraseAfter(SLTNode* pos);
 
//打印
void SListPrint(SLTNode* plist);
Slist.c
#include"Slist.h"
​//打印
void SListPrint(SLTNode* plist)
{
   assert(plist);       //断言
   SLTNode* cur = plist;//让cur指向头结点进行遍历
   while (cur != NULL)//注:条件不是cur->next,当cur->next为空便不进入循环,尾元素访问不到
   {
      printf("%d->", cur->data);
      cur = cur->next;
   }
   printf("NULL\n");
}
​​//创建结点
SLTNode* BuySLTNode(SLTDataType x)
{
   SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
   node->data = x;
   node->next = NULL;
   return node;
}
​​//头插
void SListPushFront(SLTNode** pplist, SLTDataType x)
//为什么是**pplist?因为我们要改变的是一级指针plist,所以传参进来应该是一个二级指针(传址调用)
{
  //如果传入的是空指针也可以正常插入
   SLTNode* newnode = BuySLTNode(x);
   newnode->next = *pplist;
   *pplist = newnode;
}
​​//头删
void SListPopFront(SLTNode** pplist)
{
   //1.没有结点的情况
   if (*pplist == NULL)
   {
      return;
   }
   //2.多个结点的情况
   else
   {
     //保存一份下一个元素的地址(否则free过后就找不到了)
     SLTNode* next = (*pplist)->next; //需要加括号,因为*和->优先级相同
     free(*pplist);
     *pplist = next;
   }
}
​​void SListPushBack(SLTNode** pplist, SLTDataType x)
{
   //创建一个新结点
   SLTNode* newnode = BuySLTNode(x);
   //如果链表为空,让尾插的结点变成该表的第一个元素
   if (*pplist == NULL)
   {
      *pplist = newnode;
   }
   else //非空, 直接尾插
   {
     //先遍历,找尾结点
     SLTNode* tail = *pplist;
     while (tail->next != NULL)
     {
         tail = tail->next;
     }
   tail->next = newnode;
   //该新结点定义时就指向NULL了,所以让tail的next指向该结点即可
    }
}
​​//尾删
void SListPopBack(SLTNode** pplist)
{
   //1.没有结点的情况
    if (*pplist == NULL)
    {
        return;
    }
   //2.只有一个结点的情况
    else if ((*pplist)->next == NULL)
    {
       free(*pplist);
       *pplist = NULL;
    }
   //3.多个结点的情况
    else
    {
       //前驱指针
       SLTNode* prev = NULL;
       SLTNode* tail = *pplist;
       while (tail->next != NULL)
       {
          //找尾结点的前驱结点
          prev = tail;
          //找尾结点
          tail = tail->next;
       }
       free(tail);
       tail = NULL;
       prev->next = NULL;
     }
}
/查找
SLTNode* SListFind(SLTNode* plist, SLTDataType x)
{
   SLTNode* cur = plist; //遍历查找
   //while (cur != NULL)
   while(cur)
   {
      if (cur->data == x)
          return cur;    //返回结点指针
      else
          cur = cur->next;
   }
   return NULL;          //没找到,返回NULL
}
​​//单链表在pos位置之前插入 (麻烦,不建议使用)
void SListInsert(SLTNode** pplist, SLTNode* pos, SLTDataType x)
{
   assert(pos);
   SLTNode* newnode = BuySLTNode(x);
   //在第一个位置插入 => 相当于头插
   if (pos == *pplist)
   {
      newnode->next = pos;
      *pplist = newnode;
   }
   else
   {
      SLTNode* prev = NULL; //前驱
      SLTNode* cur = *pplist;
      while (cur != pos)
      {
         prev = cur;
         cur = cur->next;
      }
      prev->next = newnode;
      newnode->next = pos;
    }
}
​​//单链表在pos位置之后插入
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
   assert(pos);
   SLTNode* newnode = BuySLTNode(x);
   //注意顺序
   newnode->next = pos->next;
   pos->next = newnode;
}
​
​​//删除pos之前的结点
void SListEraseFront(SLTNode** pplist, SLTNode* pos)
{
	assert(pos);
	assert(pos != *pplist);
	if (pos== (*pplist)->next)  //头删,第一种情况
	{
		free(*pplist);
		(*pplist) = pos;

		return;
	}
	SLTNode* cur = *pplist;
	while (cur)
	{
		if (cur->next->next == pos)   //找到pos前面的第二个结点
		{
			free(cur->next);
			cur->next = pos;     //链接

			return;
		}
		cur = cur->next;
	}

}
//删除pos后的一个结点
void SListEraseAfter(SLTNode* pos)
{
    assert(pos);
   //pos后面无结点便没有意义
    if (pos->next == NULL)
    {
        return;
    }
    else
    {
        SLTNode* next = pos->next;
        pos->next = next->next; //链接
        free(next);
        next = NULL;
    }
}
​
test.c
#include "Slist.h"
 
//尾插、头插、头删、尾删
void TestSList1()
{
	SLTNode * plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	//SListPrint(plist);//1->2->3->4->NULL
 
	SListPushFront(&plist, 0);
	//SListPrint(plist);//0->1->2->3->4->NULL
 
	SListPopBack(&plist);
	//SListPrint(plist);//0->1->2->3->NULL
 
	SListPopFront(&plist);
	SListPrint(plist);//1->2->3->NULL
}
 
//查找
void TestSList2()
{
	SLTNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
 
	SLTNode* pos = SListFind(plist, 3);
	if (pos != NULL)
	{
		printf("找到了\n");
	}
	else
	{
		printf("没找到\n");
	}
	//找到了
	//同时,可以通过pos对该位置的值进行修改
	pos->data = 10;
	SListPrint(plist);//1->2->10->4->NULL
}
 
//在pos之后、之前插入结点,删除po之前、之后的结点
void TestSList3()
{
	SLTNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
 
	SLTNode* pos = SListFind(plist,3);
    SListInsertAfter(pos, 30);
    //SListPrint(plist); //1->2->3->30->4->NULL

    SListInsert(&plist, pos, 300);
    //SListPrint(plist); //1->2->300->3->30->4->NULL

    SListEraseAfter(pos);
    //SListPrint(plist); //1->2->300->3->4->NULL
  
    SListEraseFront(&plist,3);
    //SListPrint(plist); //1->2->3->4->NULL

}
int main()
{
  TestSList3();
  return 0;
}

​​如果需要在一个无头单链表的某一个结点的前面插入一个值x,如何插入?
思路:先后插,然后跟他前一个结点的值(data)进行交换即可。

四、链表面试题 

1 移除链表元素

题目描述:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。【注 : 若未明确说明,题目所给单链表都是不带头单链表】

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 
struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode* cur = head;
    struct ListNode* prev = NULL;
 
    while(cur)
    {
        if(cur->val == val)
        {
            struct ListNode* next = cur->next;
            
            //如果第一个元素就需要删除,则需要单独处理
            //该情况下,cur是头
            if(prev == NULL)
            {
                //删除该结点, cur和head指向下一个结点
                free(cur);
                head = next;
                cur = next;
            }
            else
            {
                //删除该结点
                prev->next = next;
                free(cur);
                //cur指向被删除结点的下一个结点
                cur = next;
            }
        }
        else
        {
            prev = cur;
            cur = cur->next;
        }
    }
    return head;
}

2 反转链表 

题目描述:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 
//思路一:直接使用三个指针翻转
//eg:1->2->3->4->NULL
//=> NULL<- 1 <- 2 <- 3 <- 4
//最开始 : n1指向NULL,n2指向头,n3指向头的下一个结点
//每一次都让n2指向n1(n3用于记录下次n2的位置),再挪动n1、n2、n3
//当n2为空时循环结束(画图理解),此时的n1就是反转链表的头
struct ListNode* reverseList(struct ListNode* head)
{
    //如果是空链表或者只有一个结点,直接返回
    if(head == NULL || head->next == NULL)
        return head;
    
    struct ListNode* n1 = NULL;
    struct ListNode* n2 = head;
    struct ListNode* n3 = head->next;
    while(n2)
    {
        //翻转
        n2->next = n1;
        //迭代
        n1 = n2;
        n2 = n3;
        if(n3)//当执行到最后一次时,n3已经为空,如果继续执行n3->next就会出错,所以需要做判断
            n3 = n3->next;
    }
 
    return n1;
}
 
//思路二: 头插法 (注: 这里的头插并不创建新结点)
//取原链表中的结点,头插到新结点
struct ListNode* reverseList(struct ListNode* head)
{   
    //当链表为空或只有一个结点并不会出现问题,无需单独判断
    struct ListNode* cur = head;
    struct ListNode* newHead = NULL;
    while(cur)
    {
      struct ListNode* next=cur->next;
      //头插
      cur->next=newHead;
      //newHead始终指向当前的头
      newHead=cur;
      //迭代
      cur=next;
    }
    return newHead;
}

3 链表的中间结点 

题目描述:给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 
//快慢指针
//慢指针一次走一步,慢指针一次走两步
struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

4 链表中倒数第k个结点 

 题目描述:输入一个链表,输出该链表中倒数第k个结点。

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
{
    struct ListNode* fast = pListHead, *slow = pListHead;
    //fast先走k步
    while(k--)
    {
        //要注意k比链表总长度还要大的情况,以及链表为空的情况
        if(fast == NULL)
        {
            return NULL;
        }
        fast = fast->next;
    }
    while(fast)
    {
        slow = slow->next;
        fast = fast->next;
    }
    return slow;
}

5 链表分割 

题目描述:现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

 ListNode* partition(ListNode* pHead, int x) 
    {
        //开辟两个链表的头结点
        ListNode* lessHead, *lessTail, *greaterHead, *greaterTail;
        lessHead = lessTail =(struct ListNode*)malloc(sizeof(struct ListNode));
        greaterHead = greaterTail = (struct ListNode*)malloc(sizeof(struct ListNode));
        lessTail->next = NULL;
        greaterTail->next = NULL;
        
        struct ListNode* cur = pHead;
        //分成两个链表
        while(cur)
        {
            if(cur->val < x)
            {
                lessTail->next = cur;
                lessTail = lessTail->next;
            }
            else
            {
                greaterTail->next = cur;
                greaterTail = greaterTail->next;
            }
            cur = cur->next;
        }
        //链接两个链表
        lessTail->next = greaterHead->next;
        //关键
        greaterTail->next = NULL;//很重要的一步,不然会出现死循环
        
        pHead = lessHead->next;
        free(lessHead);
        free(greaterHead);
        return pHead;
}

注:在链接两个链表的时候(L2链接到L1),要注意要把L2的尾指针的next指向空。因为L2是由原链表分出来的,L2的尾结点可能并不是原链表的尾结点,其后面可能还链接着其它结点,如果next不置空可能会出现死循环。 

五、链表与顺序表的对比

1 链表的优缺点

1.1 优点
  •    按需申请内存,需要存一个数据就申请一块内存,不存在空间浪费。
  •    任意位置O(1)时间插入删除数据
1.2 缺点
  •    不支持下标的随机访问

2 顺序表的优缺点 

2.1 优点
  •    空间连续,可以按下标进行随机访问;
  •    顺序表的CPU高速缓存命中率比较高。
2.2 缺点
  •    空间不够需要增容,增容有一定的性能消耗,且可能存在一定的空间浪费;
  •    头部或者中间插入删除数据,需要挪动数据,效率比较低 -> O(N)。

总结:这两个数据结构是相辅相成的,他们互相弥补对方的缺点,需要用谁存数据,需要看具体场景。


   今天分享的内容已经结束啦,希望此篇文章可以给你们带来方向。如果博主的分享对大家有所帮助,记得给博主一个三连哈,你的支持是我创作的最大动力!夜色难免黑凉,前行必有曙光,漫漫亦灿灿!与君共勉~ 我们下期再见。 

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

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

相关文章

vue列表列表过滤

对已知的列表进行数据过滤(根据输入框里面的内容进行数据过滤) 编写案例 通过案例来演示说明 效果就是这样的 输入框是模糊查询 想要实现功能&#xff0c;其实就两大步&#xff0c;1获取输入框内容 2根据输入内容进行数据过滤 绑定收集数据 我们可以使用v-model去双向绑定 …

LazyVim开发vue2

neovim 0.5刚出来的时代&#xff0c;那时刚有lua插件我很狂热。每天沉迷于打造自己的IDE之中。写过一堆相关的博客&#xff0c;也录过一些视频教程。后来发现neovim的接口和插件更新的很快&#xff0c;导致配置文件要不定期的修改&#xff0c;才能保证新版本的插件的适配。我也…

深入理解DES算法:原理、实现与应用

title: 深入理解DES算法&#xff1a;原理、实现与应用 date: 2024/4/14 21:30:21 updated: 2024/4/14 21:30:21 tags: DES加密对称加密分组密码密钥管理S盒P盒安全性分析替代算法 DES算法简介 历史 DES&#xff08;Data Encryption Standard&#xff09;算法是由IBM研发&…

llamafactory:unified efficient fine-tuning of 100+ lanuage models

1.introduction llamafactory由三个主要模块组成&#xff0c;Model Loader&#xff0c;Data Worker&#xff0c;Trainer。 2.Efficient fine-tuning techniques 2.1 Efficient Optimization 冻结微调&#xff1a;冻结大部分参数&#xff0c;同时只在一小部分解码器层中微调剩…

Python怎么算平方

Python怎么算平方&#xff1f;下面是算平方的三种方法&#xff1a; 方法一&#xff1a;使用内置模块 >>> import math >>> math.pow(12, 2) # 求平方 144.0 方法二&#xff1a;使用表达式 >>> 12 ** 2 # 求平方 144 方法三&#…

量子城域网系列(三):搭建一个点对点量子保密通信网络

各位小伙伴周末愉快呀&#xff0c;今天是4月14日世界量子日&#xff0c;至于为今天是世界量子日可以围观我之前的文章&#xff1a;关于世界量子日。 之前的文章中我们讨论了量子密钥在通信系统各层协议中的应用&#xff0c;那在实际工程中如何真正落地一个量子加密网络呢&a…

minikube环境搭建

&#x1f4d5;作者简介&#xff1a; 过去日记&#xff0c;致力于Java、GoLang,Rust等多种编程语言&#xff0c;热爱技术&#xff0c;喜欢游戏的博主。 &#x1f4d8;相关专栏Rust初阶教程、go语言基础系列、spring教程等&#xff0c;大家有兴趣的可以看一看 &#x1f4d9;Jav…

Jmeter杂记:测试计划参数详解

测试计划各参数详解 1&#xff0c;用户自定义变量&#xff0c;是全局变量&#xff0c;供所有线程组使用&#xff0c;可用配置元件&#xff1a;用户自定义变量替代 2&#xff0c;连续的运行线程组&#xff0c;默认不勾选&#xff0c;则随机的运行多个线程组中的取样器&#xff…

什么是享元模式,有哪些具体应用

一、定义 享元模式是一种通过尽可能多地共享数据来最小化内存使用和对象数量&#xff0c;从而提高性能的设计模式。在享元模式中&#xff0c;如果需要相同数据的多个对象&#xff0c;则共享这些对象而不是创建新的对象&#xff0c;从而提高系统的效率。 其实有很多应用场景&am…

spring-cloud-alibaba微服务Sentinel

Sentinel 官方网站 sentinel-dashboard-1.8.7.jar包下载地址 在window通过命令行启动&#xff08;java -Dserver.port8080 -Dproject.namesentinel-dashboard -jar sentinel-dashboard-1.8.7.jar&#xff09;&#xff0c;可以通过 -Dserver.port修改控制台的端口 使用的版本最好…

vue--双向数据绑定原理

Vue采用数据劫持 发布者-订阅者模式实现双向数据绑定&#xff0c;实现逻辑图如下所示&#xff1a; 数据劫持 Vue 借助Object.defineProperty()来劫持各个属性&#xff0c;这样一来属性存取过程都会被监听到 发布者-订阅者模式 主要实现三个对象&#xff1a;Observer&#…

docker 安装 nginx + httpd + php-fpm

原文地址&#xff1a;http://www.taoluyuan.com/index.php/archives/30/#2 展示 1.安装 1.1安装docker 1.2安装nginx 1.3安装apache-httpd 1.4安装php-fpm 2.配置nginx反向代理 httpdphp-fmp 1.安装 1.1安装docker 移除旧的版本&#xff1a; sudo yum remove docker 安装…

Java编程练习之多重继承

在Java中类不允许多重继承&#xff0c;但使用接口可以实现多重继承&#xff0c;因为一个类可以同时实现多个接口&#xff0c;这样可以将所有需要实现的接口放在implements关键字后&#xff0c;并使用英文逗号隔开&#xff0c;但这可能会在一个类中产生庞大的代码量&#xff0c;…

String类为什么是不可变类

为什么说String对象一旦创建&#xff0c;其值是不可修改的 在Java中将String设计成不可变的是综合考虑到各种因素的结果&#xff0c;需要综合考虑内存、同步、数据结构以安全方面的考虑。 String被设计成不可变的主要目的是为了安全和高效(效率)。 1&#xff09;字符串常量池…

LeetCode第22题:生成括号【22/1000 python 递归|动态规划】

作者介绍&#xff1a;10年大厂数据\经营分析经验&#xff0c;现任大厂数据部门负责人。 会一些的技术&#xff1a;数据分析、算法、SQL、大数据相关、python 欢迎加入社区&#xff1a;码上找工作http://t.csdnimg.cn/Q59WX作者专栏每日更新&#xff1a; LeetCode解锁1000题: 打…

从零自制docker-10-【cgroup进行容器资源限制】

文章目录 目的导入包的相关公开原则当前进程的挂载信息deferfor scanner.Scan()判断字符串包含新建的cgroup的默认文件cpu相关配置对应到ubuntu 22.04版本的cpu相关配置top注意查看你可使用的cpu注意坑启动后的top查看显示进程使用的cpu序号代码结果 目的 启动容器时通过-mem、…

Vol.45 这个壁纸网址,功能简单,每月37.7万访问量

哈咯&#xff0c;大家好&#xff0c;我是欧维&#xff0c;今天要给大家分享的网站是&#xff1a;极简壁纸&#xff0c;一个专门做电脑壁纸的网站&#xff1b; 它的网址是&#xff1a;极简壁纸_海量电脑桌面壁纸美图_4K超高清_最潮壁纸网站 网站的壁纸质量很高&#xff0c;页面…

Java 模块化开发

前言 之前在 Github 下载的好多代码发现都是 Java 模块化开发出来的&#xff0c;模块化是 JDK9 引入的&#xff0c;所以在 JDK9 及其后续的版本中&#xff0c;都可以采用模块化开发的方法来进行项目的开发。尤其是Java桌面应用开发&#xff0c;虽然这只是我的一个业余爱好&…

WindowsServer 2022 AD域控-006-安装副域控

试验拓扑图&#xff1a; 一、测试单域控故障&#xff0c;用户无法修改密码&#xff1b; 域控断网&#xff0c;Win10测试; 二、WindowsServer2022 DC02加入域控&#xff1b; 加入成功 此时域控上只有DC02这台服务器&#xff0c;但DC02并不是域控&#xff1b; 三、WindowsS…

防汛物资仓库管理系统|实现应急物资仓库三维可视化

系统概述 智慧应急物资仓库可视化系统&#xff08;智物资DW-S300&#xff09;采用了 B/S 架构的设计&#xff0c;通过浏览器即可快速登录操作。实现对库房内的应急物资从申购入库、出库、调拨、库内环境监测、维修保养、检测试验、处置报废等全周期、科学、规范的管理。系统以…