2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】

本文涉及的产品
视觉智能开放平台,图像资源包5000点
视觉智能开放平台,视频资源包5000点
视觉智能开放平台,分割抠图1万点
简介: 数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法

欢迎各位彦祖与热巴畅游本人专栏与博客

你的三连是我最大的动力

以下图片仅代表专栏特色 [点击箭头指向的专栏名即可闪现]

专栏跑道一

➡️网络空间安全——全栈前沿技术持续深入学习

image.gif

专栏跑道二

➡️ 24 Network Security -LJS

image.gif

image.gif

image.gif

专栏跑道三


➡️ MYSQL REDIS Advance operation

image.gif

专栏跑道四

➡️HCIP;H3C-SE;CCIP——LJS[华为、华三、思科高级网络]

image.gif

专栏跑道五

➡️RHCE-LJS[Linux高端骚操作实战篇]

image.png

专栏跑道六

➡️数据结构与算法[考研+实际工作应用+C程序设计]

image.gif

专栏跑道七

➡️RHCSA-LJS[Linux初级及进阶骚技能]

image.gif

image.gif

上节回顾



1.单链表的定义

1.1顺序表:

  • 每个结点中只存放数据元素
  • 优点:可随机存取,存储密度高
  • 缺点:要求大片连续空间,改变容量不方便
  • 1
  • 顺序表定义代码回忆
#define MaxSize 10      // 定义最大长度
typedef struct{
    ElemType data[MaxSize];     // 用静态的“数组”存放数据元素
    int length;     // 顺序表的当前长度
}SqList;
#include <stdio.h>
#define MaxSize 10
typedef struct{
    int data[MaxSize];
    int length;
}SqList;
void InitList(SqList &L)
{
    // 可以省略,但可能由于遍历时用到MaxSize有脏数据,要用length遍历
    for (int i = 0; i < MaxSize; i ++ )
        L.data[i] = 0;
    
    L.length = 0;       // 不可省略,顺序表初始长度为0
}
int main()
{
    SqList L;       // 声明一个顺序表
    InitList(L);        // 初始化顺序表
    
    return 0;
}
  • image.gif

1.2单链表:

  • 每个结点除了存放数据元素外,还要存储指向下一个结点的指针
  • 优点:不要求大片连续空间,改变容量方便
  • 缺点:不可随机存取,要耗费一定空间存放指针

2.定义一个单链表:

  • typedef关键字:数据类型重命名
  • typedef <数据类型> <别名>
  • typedef struct LNode LNode;
  • 之后可以用LNode代替struct LNode
  • 这样写每次都要有s t r u c t structstruct有些麻烦,所以教材中使用了t y p e d e f typedeftypedef关键字(C语言),可以把数据类型重命名
  • t y p e d e f < 数 据 类 型 > < 别 名 >

2.1 用代码定义一个单链表

struct LNode        // 定义单链表节点类型
{
    ElemType data;      // 每个节点存放一个数据元素
    struct LNode *next;     // 指针指向下一个节点
};
struct LNode *p = (struct LNode *)malloc(sizeof(struct LNode));      // 增加一个新的节点 :在内存中申请一个节点需要的空间,并用指针p指向这个节点
image.gif
struct LNode        // 定义单链表节点类型
{
    ElemType data;      // 每个节点存放一个数据元素
    struct LNode *next;     // 指针指向下一个节点
};
typedef struct LNode LNode;
LNode *p = (LNode *)malloc(sizeof(LNode));
  • image.gif
  • 教材中还有一种更简洁的方式
  • image.gif 编辑
typedef struct LNode    // 定义单链表节点类型
{
    ElemType data;
    struct LNode *next;
} LNode, *LinkList;
// LinkList - 单链表
// 上面这种写法等价于 :
struct LNode
{
    ElemType data;
    struct LNode *next;
}
typedef struct LNode LNode;
typedef struct LNode *LinkList;
  • image.gif

要表示一个单链表时,只需声明一个头指针L,指向单链表的第一个结点

// 声明一个指向单链表第一个结点的指针
LNode *L;
// 等价于
LinkList L;     // 代码可读性更强,详见下面例子中,
// GetElem函数的LNode *和LinkList虽然两者时等价的
//但在这个函数中它最终要返回的是第i个结点,所以把返回值的类型定义为LNode *,
// 其实它LNode *就是想强调返回的是一个结点,而LinkList想强调这是一个单链表
image.gif
typedef struct LNode
{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;
LNode *GetElem(LinkList L, int i)
{
    int j = 1;
    LNode *p = L -> next;
    
    if (i == 0)
        return L;
    if (i < 1)
        return NULL;
    
    while (p != NULL && j < i)
    {
        p = p -> next;
        j ++ ;
    }
    
    return p;
}
image.gif

补充说明:

  • 强调这是一个单链表 - 使用LinkList
  • 强调这是一个结点 - 使用LNode *

3.不带头结点的单链表: image.gif

typedef struct LNode
{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;
bool InitList(LinkList &L)      // 注意传入引用
{
    L = NULL;       // 空表,暂时还没有任何结点   防止脏数据!!!
    return true;
}
void test()
{
    LinkList L;     // 声明一个指向单链表的指针     注意,此处并没有创建一个结点!!!
    // 初始化一个空表
    InitList(L);
}
// (不带头结点)
bool Empty(LinkList L)
{
    if (L == NULL)
        return true;
    else
        return false;
}
// 或者
// (不带头结点)
bool Empty(LinkList L)
{
    return (L == NULL);
}
/*如果不带头结点 :
头指针所指向的下一个结点就是实际用于存放数据的结点;而如果带头结点的话 :
头指针所指向的这个结点把它称为头结点,
这个头结点是不存放实际数据元素的,
只有这个头结点之后的下一个结点才用于存放数据*/
image.gif

3.1带头结点的单链表: image.gif

typedef struct LNode
{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;
// 初始化一个单链表(带头结点)
bool InitList(LinkList &L)
{
    L = (LNode *)malloc(sizeof(LNode));     // 分配一个头结点
    if (L == NULL)      // 内存不足,分配失败
        return false;
    
    L -> next = NULL;       // 头节点之后暂时还没有结点
    return true;
}
void test()
{
    LinkList L;     // 声明一个指向单链表的指针
    InitList(L);        // 初始化一个空表
}
// 判断单链表是否为空(带头节点)
bool Empty(LinkList L)
{
    if (L -> next == NULL)
        return true;
    else
        return false;
}
image.gif

image.gif 编辑

3.2区别:

  • 不带头结点,写代码更麻烦
  • 对第一个数据结点和后续数据结点的处理需要用不同的代码逻辑
  • 对空表和非空表的处理需要用不同的代码逻辑
  • 我们一般使用的都是带头结点的单链表

4.单链表的插入、删除

按位序插入(带头结点):

ListInsert(&L,i,e):

  • 插入操作,在表L中的第i个位置上插入指定元素e
  • 找到第i-1个结点,将新结点插入其后
  • 若带有头结点,插入更加方便,头结点可以看作“第0个”结点直接做上面的操作即可

image.gif 编辑

  •   若i插在表中则与插在表头一样进行操作,可以插入成功
  • 若i插在表尾则s->next为NULL(在表的定义时规定的),可以插入成功
  • 若i插在表外(i>Lengh)则p指针指向NULL(While循环一直指向p->next),不能插入成功
  • 最好时间复杂度= O(1)
  • 最坏时间复杂度= O(n)
  • 平均时间复杂度= O(n)

image.gif 编辑 按位序插入(带头结点)代码书写:

// 在第i个位置插入元素e(带头结点)
bool ListInsert(LinkList &L, int i, ElemType e)
{
    if (i < 1)
        return false;
    
    LNode *p = L;       // 指针p指向当前扫描到的结点
    int j = 0;      // 当前p指向的是第几个结点
    
    while (p != NULL && j < i - 1)      // 循环找到第i-1个结点
    {
        j ++ ;
        p = p -> next;
    }
    
    if (p == NULL)      // i值不合法
        return false;
    
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s -> data = e;
    s -> next = p -> next;
    p -> next = s;
    
    return true;
}
image.gif

按位序插入(不带头结点):

ListInsert(&L,i,e):

  • ListInsert(&L, i, e) :插入操作,在表L中的第i个位置上插入指定元素e
  • 找到第i-1个结点,将新结点插入其后
  • 不存在“第0个”结点,因此i=1时需要特殊处理
  • 不带头结点,则插入、删除第1个元素时,需要更改头指针L

image.gif 编辑 image.gif 编辑

bool ListInsert(LinkList &L, int i, ElemType e)
{
    if (i < 1)
        return false;
    
    if (i == 1)
    {
        LNode *s = (LNode *)malloc(sizeof(LNode));
        s -> data = e;
        s -> next = L;
        L = s;
        
        return true;
    }
    
    LNode *p = L;
    int j = 1;      // 注意这里是1!!!不带头结点
    
    while (p != NULL && j < i - 1)
    {
        p = p -> next;
        j ++ ;
    }
    
    if (p == NULL)
        return false;
    
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s -> data = e;
    s -> next = p -> next;
    p -> next = s;
    
    return true;
}
image.gif
  • i!=1则处理方法和带头结点一模一样
  • 值得注意的是int j =1而非带头结点的0(带头结点的头结点为第0个结点)
  • 综上结论:不带头结点写代码更不方便,推荐用带头结点

指定结点的后插操作: image.gif

image.gif 编辑

bool InsertNextNode(LNode *p, ElemType e)
{
    if (p == NULL)
        return false;
    
    LNode *s = (LNode *)malloc(sizeof(LNode));
    
    if (s == NULL)      // 内存分配失败,考试可以不写
        return false;
    
    s -> data = e;
    s -> next = p -> next;
    p -> next = s;
    
    return true;
}
image.gif
  • 这一段代码是按位序插入中插入的那一部分代码
  • 也可以直接调用InsertNextNode来执行
  • 封装代码,以此提高代码复用性,让代码更容易维护

指定结点的前插操作:

image.gif 编辑

  • 因为仅知道指定结点的信息和后继结点的指向,因此无法直接获取到前驱结点
  • 方法1:获取头结点然后再一步步找到指定结点的前驱
  • 方法2:将新结点先连上指定结点p的后继,接着指定结点p连上新结点s,将p中元素复制到s中,将p中元素覆盖为要插入的元素e

方法1:前叉操作伪代码实现 image.gif 编辑

// 前插操作 :在p结点之前插入元素e
// O(1)
bool InsertPriorNode(LNode *p, ElemType e)
{
    if (p == NULL)
        return false;
    
    LNode *s = (LNode *)malloc(sizeof(LNode));
    
    if (s == NULL)
        return false;
    
    s -> next = p -> next;
    p -> next = s;
    s -> data = p -> data;
    p -> data = e;
    
    return true;
}
image.gif
// 王道书版本
bool InsertPriorNode(LNode *p, LNode *s)
{
    if (p == NULL || s == NULL)
        return false;
    
    s -> next = p -> next;
    p -> next = s;
    
    ElemType temp = p -> data;      // 交换数据域部分
    p -> data = s -> data;
    s -> data = temp;
    
    return true;
}
image.gif

方法2:按位序删除(带头结点):

image.gif 编辑

ListDelete(&L,i,&e):

  • 删除操作,删除表L中第i个位置的元素,并用e返回删除元素的值。
  • 找到第i-1个结点,将其指针指向第i+1个结点,并释放第i个结点
bool ListDelete(LinkList &L, int i, ElemType &e)
{
    if (i < 1)
        return false;
    
    LNode *p = L;
    int j = 0;
    
    while (p != NULL && j < i - 1)
    {
        p = p -> next;
        j ++ ;
    }
    
    if (p == NULL)
        return false;
    if (p -> next == NULL)      // 第i-1个结点之后已无结点
        return false;
    
    LNode *q = p -> next;
    e = q -> data;
    p -> next = q -> next;
    free(q);
    
    return true;
}
image.gif

指定结点的删除

image.gif 编辑

  • 删除结点p,需要修改其前趋结点的next指针
  • 方法1 :传入头指针,循环寻找p的前趋结点
  • 方法2 :偷天换日(类似于结点前插的实现)

指定结点的删除代码:

bool DeleteNode(LNode *p)
{
    if (p == NULL)
        return false;
    
    LNode *q = p -> next;
    p -> data = p -> next -> data;
    p -> next = q -> next;
    
    free(q);
    return true;
}
image.gif

方法2注意!!!

如果要删除的结点p是最后一个结点

  • 只能从表头开始依次寻找p的前驱,时间复杂度O(n)
  • 这就体现了单链表的局限性:无法逆向检索,有时候不太方便

image.gif

5.单链表的查找【只探讨“带头结点”的情况】

按位查找:

  • GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。
  • 实际上单链表的插入中找到i-1部分就是按位查找i-1个结点,如下图

image.gif

  • 因此查找第i个结点如下图 image.gif

单链表的按位查找代码:

LNode * GetElem(LinkList L, int i)
{
    if (i < 0)
        return false;
    
    LNode *p = L;
    int j = 0;
    
    while (p != NULL && j < i)
    {
        p = p -> next;
        j ++ ;
    }
    
    return p;
}
image.gif
  • 如果i=0则直接不满足j<i则指针p直接返回头结点L
  • 如果i超界则当时p指向了NULL,指针p返回NULL
  • 平均时间复杂度:O(n)

按值查找:

image.gif

按值查找代码

LNode * LocateElem(LinkList L, ElemType e)
{
    LNode *p = L -> next;
    while (p != NULL && p -> data != e)
        p = p -> next;
    return p;
}
image.gif
  • 能找到的情况:p指向了e值对应的元素,返回该元素
  • 不能找到的情况:p指向了NULL,指针p返回NULL
  • 平均时间复杂度:O(n)

求表的长度

image.gif 编辑

求表的长度代码:

int Length(LinkList L)
{
    LNode *p = L;
    int len = 0;
    while (p != NULL)
    {
        p = p -> next;
        len ++ ;
    }
    return len;
}
image.gif

image.gif 编辑

6.单链表的建立

尾插法:

  • 每次插入元素都插入到单链表的表尾
  • 方法1:套用之前学过的位序插入,每次都要从头开始往后面遍历,时间复杂度为O(n^2)

image.gif 编辑

//设置变量length记录链表长度,用ListInsert,O(n^2);设置一个表尾指针,只要每次对r指针进行后插操作InsertNextNode,然后把表尾指针往后移
// O(n)
LinkList List_TailInsert(LinkList &L)
{
    int x;
    L = (LinkList)malloc(sizeof(LNode));        // 建立头结点
    LNode *s, *r = L;
    
    scanf("%d", &x);
    while (x != 9999)
    {
        s = (LNode *)malloc(sizeof(LNode));
        s -> data = x;
        r -> next = s;
        r = s;
        scanf("%d", &x);
    }
    r -> next = NULL;
    return L;
}
image.gif
  • 方法2:增加一个尾指针r,每次插入都让r指向新的表尾结点,时间复杂度为O(n) image.gif 编辑

头插法:

  • 每次插入元素都插入到单链表的表头
  • 头插法和之前学过的单链表后插操作是一样的,可以直接套用
  • L->next=NULL;可以防止野指针

image.gif 编辑

总结:

  • 头插法、尾插法:核心就是初始化操作、指定结点的后插操作
  • 注意设置一个指向表尾结点的指针
  • 头插法的重要应用:链表的逆置

7.双链表

为什么要要使用双链表:

  • 单链表:无法逆向检索,有时候不太方便
  • 双链表:可进可退,但是存储密度更低一丢丢

双链表的初始化(带头结点)代码实现: image.gif

image.gif 编辑

双链表的初始化(带头结点): image.gif

 双链表的初始化(带头结点)代码:

typedef struct LNode        // 定义单链表结点类型
{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;
// 初始化一个循环单链表
bool InitList(LinkList &L)
{
    L = (LinkList)malloc(sizeof(LNode));
    if (L == NULL)
        return false;
    
    L -> next = L;      // 头结点next指向头结点
    return true;
}
// 判断循环单链表是否为空
bool Empty(LinkList L)
{
    if (L -> next == L)
        return true;
    else
        return false;
}
// 判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L, LNode *p)
{
    if (p -> next == L)
        return true;
    else
        return false;
}
image.gif

双链表的插入

image.gif

双链表的插入代码实现:

// 在p结点之后插入s结点(后插),以下是前插法,但由于是双链表,如果要前插,只要找到前一个用后插就可以
bool InsertNextNode(DNode *p, DNode *s)
{
    if (p == NULL || s == NULL)
        return false;
    
    s -> next = p -> next;
    if (p -> next != NULL)      // 如果p结点有后继结点
        p -> next -> prior = s;
    s -> prior = p;
    p -> next = s;
    
    return p;
}
image.gif
  • 小心如果p结点为最后一个结点产生的空指针问题因此循环链表应运而生(详见后面的循环链表插入删除)
  • 注意指针的修改顺序

双链表的删除:

image.gif

双链表的删除代码实现:

// 删除p结点的后继结点
bool DeleteNextNode(DNode *p)
{
    if (p == NULL)
        return false;
    DNode *q = p -> next;
    if (q == NULL)
        return false;
    p -> next = q -> next;
    if (q -> next != NULL)
        q -> next -> prior = p;
    free(q);
    return true;
}
void DestroyList(DLinkList &L)
{
    while (L -> next != NULL)
        DeleteNextDNode(L);
    free(L);        // 释放头结点
    L = NULL;       // 头结点指向NULL
}
image.gif

双链表的遍历:

image.gif

image.gif 编辑

循环链表

循环单链表与单链表的区别:

单链表:

  • 表尾结点的next指针指向NULL
  • 从一个结点出发只能找到后续的各个结点

循环单链表:

  • 表尾结点的next指针指向头结点
  • 从一个结点出发可以找到其他任何一个结点

循环单链表初始化:

image.gif

  • 从头结点找到尾部,时间复杂度为O(n)
  • 如果需要频繁的访问表头、表尾,可以让L指向表尾元素插入、删除时可能需要修改L
  • 从尾部找到头部,时间复杂度为O(1)

循环双链表的初始化:

image.gif

image.gif 编辑

image.gif 编辑

循环双链表的初始化代码实现:

typedef struct DNode
{
    ElemType data;
    struct DNode *prior, *next;
}DNode, *DLinkList;
bool InitDLinkList(DLinkList &L)
{
    L = (LinkList)malloc(sizeof(LNode));
    if (L == NULL)
        return false;
    L -> prior = L;
    L -> next = L;
    return true;
}
bool Empty(DLinkList L)
{
    if (L -> next == L)
        return true;
    else
        return false;
}
bool isTail(LinkList L, DNode *p)
{
    if (p -> next == L)
        return true;
    else
        return false;
}
image.gif

循环链表的插入:

  • 对于双链表来说如果p结点为最后一个结点,因为next结点为null,p->next->prior=s会产生的空指针问题
  • 循环链表规避因为最后结点的next结点为头结点因此不会发生问题

image.gif 编辑

循环双链表的插入代码实现

  • 其实是双链表插入的缩减版
bool InsertNextDNode(LNode *p, LNode *s)
{
    s -> next = p -> next;
    p -> next -> prior = s;
    s -> prior = p;
    p -> next = s;
}
  • image.gif

循环链表的删除:

  • 与循环链表的插入相同。
    image.gif 编辑

循环双链表的删除代码实现

//同上
p -> next = q -> next;
q -> next -> prior = p;
free(q);
image.gif

image.gif 编辑

注意点:

写代码时候注意以下几点,以此规避错误:

  • 如何判空
  • 如何判断结点p是否是表尾/表头元素(后向/前向遍历的实现核心)
  • 如何在表头、表中、表尾插入/删除一个结点

8.静态链表

什么是静态链表:

  • 分配一整片连续的内存空间,各个结点集中安置
  • 每个结点由两部分组成:data(数据元素)和next(游标)
  • 0号结点充当“头结点”,不具体存放数据
  • 游标为-1表示已经到达表尾
  • 游标充当“指针”,表示下个结点的存放位置,下面举一个例子:
  • 每个数据元素4B,每个游标4B(每个结点共8B),设起始地址为addr,e1的存放地址为addr + 8*2(游标值)

定义静态链表:

方法1:

image.gif 编辑

定义静态链表代码实现:

#define MaxSize 10;
struct Node
{
    ElemType data;
    int next;
};
void testSLinkList()
{
    struct Node a[MaxSize];         // 数组a作为静态链表
}
image.gif

方法2:

image.gif 编辑

基本操作:

初始化:

  1. 把a[0]的next设为-1
  2. 把其他结点的next设为一个特殊值用来表示结点空闲,如-2

插入位序为i的结点:

  1. 找到一个空的结点,存入数据元素(设为一个特殊值用来表示结点空闲,如-2)
  2. 从头结点出发找到位序为i-1的结点
  3. 修改新结点的next
  4. 修改i-1号结点的next

删除某个结点:

  1. 从头结点出发找到前驱结点
  2. 修改前驱结点的游标
  3. 被删除结点next设为-2

总结:

  • 静态链表:用数组的方式实现的链表
  • 优点:增、删操作不需要大量移动元素
  • 缺点:不能随机存取,只能从头结点开始依次往后查找;容量固定不可变
  • 适用场景:(1)不支持指针的低级语言;(2)数据元素数量固定不变的场景(如操作系统的文件分配表FAT)

9.顺序表和链表的比较

顺序表和链表的比较

顺序表 链表
逻辑结构 都属于线性表,都是线性结构 都属于线性表,都是线性结构
存储结构
  • 优点:支持随机存取、存储密度高
  • 缺点:大片连续空间分配不方便,改变容量不方便
  • 优点:离散的小空间分配方便,改变容量方便
  • 缺点:不可随机存取,存储密度低

开放式问题的解题思路

问题: 请描述顺序表和链表的bla bla bla…实现线性表时,用顺序表还是链表好?

答案:

  • 顺序表和链表的逻辑结构都是线性结构,都属于线性表。
  • 但是二者的存储结构不同,顺序表采用顺序存储…(特点,带来的优点缺点);链表采用链式存储…(特点、导致的优缺点)。
  • 由于采用不同的存储方式实现,因此基本操作的实现效率也不同。
  • 当初始化时…;当插入一个数据元素时…;当删除一个数据元素时…;当查找一个数据元素时

...

相关文章
|
3天前
|
弹性计算 双11 开发者
阿里云ECS“99套餐”再升级!双11一站式满足全年算力需求
11月1日,阿里云弹性计算ECS双11活动全面开启,在延续火爆的云服务器“99套餐”外,CPU、GPU及容器等算力产品均迎来了全年最低价。同时,阿里云全新推出简捷版控制台ECS Lite及专属宝塔面板,大幅降低企业和开发者使用ECS云服务器门槛。
|
20天前
|
存储 弹性计算 人工智能
阿里云弹性计算_通用计算专场精华概览 | 2024云栖大会回顾
阿里云弹性计算产品线、存储产品线产品负责人Alex Chen(陈起鲲)及团队内多位专家,和中国电子技术标准化研究院云计算标准负责人陈行、北京望石智慧科技有限公司首席架构师王晓满两位嘉宾,一同带来了题为《通用计算新品发布与行业实践》的专场Session。本次专场内容包括阿里云弹性计算全新发布的产品家族、阿里云第 9 代 ECS 企业级实例、CIPU 2.0技术解读、E-HPC+超算融合、倚天云原生算力解析等内容,并发布了国内首个云超算国家标准。
阿里云弹性计算_通用计算专场精华概览 | 2024云栖大会回顾
|
2天前
|
人工智能 弹性计算 文字识别
基于阿里云文档智能和RAG快速构建企业"第二大脑"
在数字化转型的背景下,企业面临海量文档管理的挑战。传统的文档管理方式效率低下,难以满足业务需求。阿里云推出的文档智能(Document Mind)与检索增强生成(RAG)技术,通过自动化解析和智能检索,极大地提升了文档管理的效率和信息利用的价值。本文介绍了如何利用阿里云的解决方案,快速构建企业专属的“第二大脑”,助力企业在竞争中占据优势。
|
5天前
|
存储 安全 Oracle
【灵码助力安全3】——利用通义灵码辅助智能合约漏洞检测的尝试
本文探讨了智能合约的安全性问题,特别是重入攻击、预言机操纵、整数溢出和时间戳依赖性等常见漏洞。文章通过实例详细分析了重入攻击的原理和防范措施,展示了如何利用通义灵码辅助检测和修复这些漏洞。此外,文章还介绍了最新的研究成果,如GPTScan工具,该工具通过结合大模型和静态分析技术,提高了智能合约漏洞检测的准确性和效率。最后,文章总结了灵码在智能合约安全领域的应用前景,指出尽管存在一些局限性,但其在检测和预防逻辑漏洞方面仍展现出巨大潜力。
|
7天前
|
负载均衡 算法 网络安全
阿里云WoSign SSL证书申请指南_沃通SSL技术文档
阿里云平台WoSign品牌SSL证书是由阿里云合作伙伴沃通CA提供,上线阿里云平台以来,成为阿里云平台热销的国产品牌证书产品,用户在阿里云平台https://www.aliyun.com/product/cas 可直接下单购买WoSign SSL证书,快捷部署到阿里云产品中。
1847 6
阿里云WoSign SSL证书申请指南_沃通SSL技术文档
|
10天前
|
Web App开发 算法 安全
什么是阿里云WoSign SSL证书?_沃通SSL技术文档
WoSign品牌SSL证书由阿里云平台SSL证书合作伙伴沃通CA提供,上线阿里云平台以来,成为阿里云平台热销的国产品牌证书产品。
1789 2
|
19天前
|
编解码 Java 程序员
写代码还有专业的编程显示器?
写代码已经十个年头了, 一直都是习惯直接用一台Mac电脑写代码 偶尔接一个显示器, 但是可能因为公司配的显示器不怎么样, 还要接转接头 搞得桌面杂乱无章,分辨率也低,感觉屏幕还是Mac自带的看着舒服
|
26天前
|
存储 人工智能 缓存
AI助理直击要害,从繁复中提炼精华——使用CDN加速访问OSS存储的图片
本案例介绍如何利用AI助理快速实现OSS存储的图片接入CDN,以加速图片访问。通过AI助理提炼关键操作步骤,避免在复杂文档中寻找解决方案。主要步骤包括开通CDN、添加加速域名、配置CNAME等。实测显示,接入CDN后图片加载时间显著缩短,验证了加速效果。此方法大幅提高了操作效率,降低了学习成本。
5385 15
|
13天前
|
人工智能 关系型数据库 Serverless
1024,致开发者们——希望和你一起用技术人独有的方式,庆祝你的主场
阿里云开发者社区推出“1024·云上见”程序员节专题活动,包括云上实操、开发者测评和征文三个分会场,提供14个实操活动、3个解决方案、3 个产品方案的测评及征文比赛,旨在帮助开发者提升技能、分享经验,共筑技术梦想。
1130 152
|
21天前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1585 14

热门文章

最新文章

  • 1
    2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
    11
  • 2
    2024重生之回溯数据结构与算法系列学习(11)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
    6
  • 3
    2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    8
  • 4
    2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    10
  • 5
    2024重生之回溯数据结构与算法系列学习(8)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    8
  • 6
    2024重生之回溯数据结构与算法系列学习(7)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    7
  • 7
    2024重生之回溯数据结构与算法系列学习之王道第2.3章节之线性表精题汇总二(5)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    6
  • 8
    23
    6
  • 9
    2024重生之回溯数据结构与算法系列学习之单双链表精题(4)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    7
  • 10
    2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    6