数据结构自学笔记(四):单链表,双链表,循环链表和静态链表

根据提供的图片内容,整理链表核心知识点笔记如下:


一.单链表

定义:通过指针串联节点的线性结构,每个节点包含数据域和指向后继节点的指针。

typedef struct LNode {
    ElemType data;			//数据域
    struct LNode *next;		//指针域(指向后继结点)
} LNode, *LinkList;			// LinkList为单链表头指针类型

特性:

  • 带头结点:空表判断 L->next == NULL,操作统一
  • 不带头结点:空表判断 L == NULL,首元结点需特殊处理
  • 逻辑结构线性,物理结构非连续(指针链接)

基本操作(带头结点)
  1. 按位序插入
bool ListInsert(LinkList &L, int i, ElemType e) {
    if (i < 1) return false;        // 位序合法性校验
    LNode *p = L;                   // p指向头结点
    int j = 0;                      // 当前位序(头结点为0)
    while (p && j < i-1) {          // 寻找第 i-1 个结点
        p = p->next; j++;
    }
    if (!p) return false;           // i超出表长
    LNode *s = (LNode*)malloc(sizeof(LNode));
    s->data = e;
    s->next = p->next;              // 新结点后继指向原第i个结点
    p->next = s;                    // 前驱结点指向新结点
    return true;
}

时间复杂度:O(n)

  1. 指定结点后插
bool InsertNextNode(LNode *p, ElemType e) {
    if (!p) return false;
    LNode *s = (LNode*)malloc(sizeof(LNode));
    if (!s) return false;           // 内存分配失败
    s->data = e;
    s->next = p->next;
    p->next = s;
    return true;
}

时间复杂度:O(1)

  1. 按位序删除
bool ListDelete(LinkList &L, int i, ElemType &e) {
    if (i < 1) return false;
    LNode *p = L; int j = 0;
    while (p && j < i-1) {          // 寻找第 i-1 个结点
        p = p->next; j++;
    }
    if (!p || !p->next) return false; // i不合法或超出表长
    LNode *q = p->next;             // q指向待删除结点
    e = q->data;
    p->next = q->next;              // 绕过q结点
    free(q);
    return true;
}

时间复杂度:O(n)

  1. 指定结点删除
bool DeleteNode(LNode *p) {
    if (!p || !p->next) return false; // 仅限非尾结点
    LNode *q = p->next;             // q指向p的后继
    p->data = q->data;              // 数据域覆盖(偷天换日)
    p->next = q->next;
    free(q);
    return true;
}

:尾结点需遍历找前驱,时间复杂度O(n)


查找与长度
  1. 按位查找
LNode* GetElem(LinkList L, int i) {
    if (i < 0) return NULL;         // 位序非法
    LNode *p = L; int j = 0;
    while (p && j < i) {            // 扫描到第i个结点
        p = p->next; j++;
    }
    return p;
}
  1. 按值查找
LNode* LocateElem(LinkList L, ElemType e) {
    LNode *p = L->next;             // 跳过头结点
    while (p && p->data != e) p = p->next;
    return p;                       // 找到返回结点,否则返回NULL
}
  1. 求表长
int Length(LinkList L) {
    int len = 0;
    LNode *p = L->next;             // 从首元结点开始
    while (p) {
        len++; p = p->next;
    }
    return len;
}

时间复杂度:O(n)


建立链表
  1. 头插法(逆序)
void CreateList_Head(LinkList &L) {
    L = (LNode*)malloc(sizeof(LNode)); // 创建头结点
    L->next = NULL;
    ElemType x;
    while (scanf("%d", &x) != EOF) {
        LNode *s = (LNode*)malloc(sizeof(LNode));
        s->data = x;
        s->next = L->next;          // 新结点指向原首元结点
        L->next = s;                // 头结点指向新结点
    }
}

应用:链表逆置

  1. 尾插法(顺序)
void CreateList_Tail(LinkList &L) {
    L = (LNode*)malloc(sizeof(LNode));
    LNode *r = L;                   // 尾指针
    ElemType x;
    while (scanf("%d", &x) != EOF) {
        LNode *s = (LNode*)malloc(sizeof(LNode));
        s->data = x;
        r->next = s;                // 尾结点指向新结点
        r = s;                      // 更新尾指针
    }
    r->next = NULL;                 // 尾结点置空
}

优缺点
优点:动态分配内存,空间利用率高,插入删除操作方便(只需修改指针)
缺点:仅支持单向遍历,查找前驱节点需从头遍历


二.双链表

定义:每个节点包含数据域、前驱指针(prior)和后继指针(next),支持双向遍历。

typedef struct DNode {
    ElemType data;
    struct DNode *prior, *next;     // 前驱和后继指针
} DNode, *DLinkList;
核心操作
  1. 初始化(带头结点)
bool InitDLinkList(DLinkList &L) {
    L = (DNode*)malloc(sizeof(DNode));
    if (!L) return false;
    L->prior = NULL;                // 头结点前驱永为NULL
    L->next = NULL;                 // 空表
    return true;
}

空表判断L->next == NULL

  1. 插入(后插)
bool InsertNextDNode(DNode *p, DNode *s) {
    if (!p || !s) return false;
    s->next = p->next;
    if (p->next) p->next->prior = s; // 后继非空时修改其前驱
    s->prior = p;
    p->next = s;
    return true;
}

关键:处理边界(尾结点插入时后继为NULL)

  1. 删除(后删)
bool DeleteNextDNode(DNode *p) {
    if (!p || !p->next) return false;
    DNode *q = p->next;             // 待删除结点
    p->next = q->next;
    if (q->next) q->next->prior = p; // 若q非尾结点
    free(q);
    return true;
}
  1. 销毁链表
void DestroyList(DLinkList &L) {
    while (L->next) DeleteNextDNode(L); // 逐个删除数据结点
    free(L); L = NULL;               // 释放头结点
}
  1. 遍历
  • 后向遍历while (p) { p = p->next; }
  • 前向遍历while (p->prior) { p = p->prior; } (跳过头结点)
  1. 优缺点
    优点:支持双向遍历,可快速访问前驱和后继节点,插入删除操作更灵活
    缺点:结构复杂,需额外存储空间存储 prior 指针

三.循环链表

  1. 循环单链表

    • 尾结点 next 指向头结点
    • 判空L->next == L(带头结点)
  2. 循环双链表

    • 头结点 prior 指向尾结点,尾结点 next 指向头结点
    • 判空L->next == L && L->prior == L
  3. 优缺点
    优点:从任意节点可遍历整个链表,尾节点操作更高效(无需遍历找尾)
    缺点:需注意循环终止条件,避免死循环


四.静态链表

定义:首尾相连的链表,分为循环单链表(尾节点 next 指向头节点)和循环双链表(尾节点 next 指向头节点,头节点 prior 指向尾节点)。

#define MAXSIZE 100
typedef struct {
    ElemType data;
    int next;                      // 游标(模拟指针)
} SLinkList[MAXSIZE];

特点:

  • 用数组模拟链表,next=-1 表示表尾
  • 支持顺序存取(无随机存取)
  • 适用场景:不支持指针的低级语言、文件系统管理

优缺点:
优点:无需指针,通过数组下标操作,适合嵌入式等无动态内存的环境
缺点:长度固定(受 MaxSize 限制),插入删除需移动大量元素


五.对比总结

类型 特点 优势 劣势
单链表 单向链接 节省空间 逆向访问困难
双链表 双向指针 支持双向遍历 空间开销大
循环链表 首尾相连 任意位置出发遍历全表 判空条件复杂
静态链表 数组实现,游标代替指针 无内存分配开销 长度固定,灵活性低

核心要点:

  1. 链表操作需严格处理边界(头/尾结点)
  2. 插入/删除注意指针修改顺序(防断链)
  3. 双链表插入/删除需同步更新前驱和后继指针
  4. 静态链表通过 next 游标逻辑链接,物理存储连续

你可能感兴趣的:(数据结构自学笔记(四):单链表,双链表,循环链表和静态链表)