数 据 结 构 进 阶:哨 兵 位 的 头 结 点 如 何 简 化 链 表 操 作

数 据 结 构 进 阶:哨 兵 位 的 头 结 点 如 何 简 化 链 表 操 作

  • [哨 兵 位 的 头 结 点](#哨 兵 位 的 头 结 点)
  • [第 一 题 - - - 合 并 两 个 有 序 链 表](#第 一 题 - - - 合 并 两 个 有 序 链 表)
    • [方 法 一 - - - 不 带 哨 兵 位 的 头 节 点](#方 法 一 - - - 不 带 哨 兵 位 的 头 节 点)
    • [方 法 二 - - - 带 有 哨 兵 位 的 头 节 点](#方 法 二 - - - 带 有 哨 兵 位 的 头 节 点)
  • [第 二 题 - - - 链 表 分 割](#第 二 题 - - - 链 表 分 割)
  • [总 结](#总 结)

💻作 者 简 介:曾 与 你 一 样 迷 茫,现 以 经 验 助 你 入 门 数据 结 构。

💡个 人 主 页:@笑口常开xpr 的 个 人 主 页

📚系 列 专 栏:硬 核 数 据 结 构 与 算 法

✨代 码 趣 语:哨 兵 位 是 数 据 结 构 中 的 一 个 特 殊 存 在,它 如 同 一 个 忠 诚 的 卫 士,守 护 着 数 据 结 构 的 边 界 与 秩 序。

💪代 码 千 行,始 于 坚 持,每 日 敲 码,进 阶 编 程 之 路。

📦gitee 链 接:gitee

在 数 据 结 构 的 世 界 里,每 一 种 设 计 都 可 能 孕 育 出 惊 人 的 效 率 变 革。你 是 否 深 思 过,一 组 精 心 组 织 的 数 据 究 竟 能 创 造 怎 样 的 奇 迹?每 一 次 挖 掘 底 层 原 理,都 是 与 计 算 机 智 慧 的 巅 峰 对 话;每 一 次 剖 析 存 储 模 式,都 在 破 解 数 据 世 界 的 终 极 密 码。准 备 好 迎 接 这 场 盛 宴 了 吗?让 我 们 一 同 探 寻 链 表 中 哨 兵 位 的 头 结 点 的 无 尽 奥 秘,见 证 它 如 何 重 塑 数 字 时 代 的 运 行 法 则!


哨 兵 位 的 头 结 点

定 义

在 普 通 链 表 中,头 指 针 直 接 指 向 第 一 个 结 点。而 在 带 有 哨 兵 位 头 结 点 的 链 表 中,我 们 在 链 表 的 最 前 面 额 外 添 加 一 个 节 点,这 个 节 点 不 存 储 实 际 的 数 据,仅 仅 作 为 一 个 标 记,也 就 是 所 谓 的 "哨 兵",即 这 个 节 点 起 到 站 岗 作 用,用 来 简 化 链 表 操 作。如 果 一 个 链 表 有 哨 兵 节 点 的 话,那 么 线 性 表 的 第 一 个 元 素 应 该 是 链 表 的 第 二 个 节 点。这 个 哨 兵 结 点 的 指 针 域 指 向 链 表 的 第 一 个 实 际 节 点。


优 势

1、统 一 空 链 表 和 非 空 链 表 的 处 理

在 普 通 链 表 中,空 链 表 和 非 空 链 表 的 插 入、删 除 操 作 可能 需 要 特 殊 处 理,因 为 头 指 针 在 空 链 表 时 为 NULL,而 非 空 时 指 向 第 一 个 节 点。而 使 用 哨 兵 位 头 结 点 后,无 论 链 表 是 否 为 空,头 结 点 始 终 存 在,操 作 更 加 统 一。

2、简 化 边 界 条 件 处 理

在 删 除 操 作 中,如 果 要 删 除 第 一 个 节 点,普 通 链 表 需 要 修 改 头 指 针,而 使 用 哨 兵 位 头 结 点 后,删 除 第 一 个 节 点 的 操 作 和 删 除 其 他 节 点 的 操 作 完 全 一 致,无 需 特 殊 处 理。

3、提 高 代 码 健 壮 性

哨 兵 位 头 结 点 减 少 了 对 空 指 针 的 判 断,降 低 了 代 码 出 错 的 可 能 性,使 代 码 更 加 健 壮。


使 用 场 景

哨 兵 位 头 结 点 适 用 于 需 要 频 繁 插 入 和 删 除 链 表 头 部 节 点 的 情 况 下。


注 意 事 项

(1)、哨 兵 位 头 结 点 不 存 储 实 际 数 据,它 的 存 在 仅 仅 是 为 了 简 化 操 作。

(2)、在 释 放 链 表 时,需 要 记 得 释 放 哨 兵 位 头 结 点 的 内 存。

(3)、在 遍 历 链 表 时,要 从 哨 兵 位 头 结 点 的 下 一 个 节 点 开 始, 而 不 是 从 哨 兵 位 头 结 点 本 身 开 始。

第 一 题 - - - 合 并 两 个 有 序 链 表

合 并 两 个 有 序 链 表


描 述:将 两 个 升 序 链 表 合 并 为 一 个 新 的 升 序 链 表 并 返 回。新 链 表 是 通 过 拼 接 给 定 的 两 个 链 表 的 所 有 节 点 组 成 的。

示例 1:

输 入:l1 = [1,2,4], l2 = [1,3,4]

输 出:[1,1,2,3,4,4]

示 例 2:

输 入:l1 = [ ], l2 = [ ]

输 出:[ ]

示 例 3:

输 入:l1 = [ ], l2 = [ 0 ]

输 出:[0]

提 示:

两 个 链 表 的 节 点 数 目 范 围 是 [0, 50]

-100 <= Node.val <= 100
l1 和 l2 均 按 非 递 减 顺 序 排 列


方 法 一 - - - 不 带 哨 兵 位 的 头 节 点


思 路 分 析

针 对 这 道 题,可 以 利 用 链 表 已 经 有 序 的 特 性,通 过 比 较 两 个 链 表 当 前 节 点 的 值,将 较 小 值 的 节 点 依 次 连 接 起 来,形 成 一 个 新 的 有 序 链 表。


解 题 步 骤

1、处 理 边 界 情 况

若 任 一 链 表 为 空,直 接 返 回 另 一 链 表。

2、初 始 化 指 针

使 用 双 指 针 cur1 和 cur2 分 别 遍 历 两 个 链 表。

通 过 head 和 tail 指 针 维 护 新 链 表 的 头 节 点 和 尾 节 点。

3、比 较 并 合 并 节 点
循 环 条 件 :同 时 遍 历 两 个 链 表,直 至 其 中 一 个 链 表 遍 历 完 毕。
节 点 选 择:比 较 cur1 和 cur2 的 值,选 择 较 小 值 的 节 点 加 入 新 链 表。

4、尾 插 操 作

若 新 链 表 为 空(head为 NULL),初 始 化 head 和 tail。

否 则 将 选 中 节 点 连 接 到 tail 之 后,并 更 新 tail。

5、处 理 剩 余 节 点

将 未 遍 历 完 的 链 表 的 剩 余 部 分 直 接 连 接 到 新 链 表 尾 部。


温 馨 提 示:读 者 们 ,先 自 己 写 代 码,这 是 提 升 编 程 能 力 的 好 机 会。若 未 达 要 求 ,别 气 馁 ,参 考 下 文 解 释 会 有 新 收 获。


下 面 展 示代 码 示 例

javascript 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
{
    struct ListNode* cur1 = list1;
    struct ListNode* cur2 = list2;
    struct ListNode* head = NULL;
    struct ListNode* tail = NULL;
    if(cur1==NULL)
    {
        return cur2;
    }
    if(cur2==NULL)
    {
        return cur1;
    }
    while(cur1&&cur2)
    {
        if(cur1->val<cur2->val)
        {
            if(head==NULL)
            {
                head=tail=cur1;
            }
            else
            {
                tail->next=cur1;
                tail=tail->next;
            }
            cur1=cur1->next;
        }
        else
        {
            if(head==NULL)
            {
                head=tail=cur2;
            }
            else
            {
                tail->next=cur2;
                tail=tail->next;
            }
            cur2=cur2->next;
        }
    }
    if(cur1!=NULL)
    {
        tail->next=cur1;
    }
    if(cur2!=NULL)
    {
        tail->next=cur2;
    }
    return head;
}

复 杂 度 分 析
时 间 复 杂 度 :O (m+n),其 中 m 和 n 分 别 为 两 个 链 表 的长 度。
空 间 复 杂 度: O (1),仅 需 常 数 级 额 外 空 间。


方 法 二 - - - 带 有 哨 兵 位 的 头 节 点

思 路 分 析

可 以 引 入 哨 兵 位 的 头 结 点 优 化 合 并 两 个 有 序 链 表 的 过 程。哨 兵 位 是 链 表 中 常 用 的 技 巧,能 够 简 化 边 界 条 件 处 理,使 代 码 更 加 简 洁 和 健 壮。


解 题 步 骤

1、哨 兵 位 引 入

创 建 一 个 虚 拟 节 点(哨 兵 位)作 为 合 并 后 链 表 的 起 始 点,不 存 储 实 际 数 据。

使 用 tail 指 针 动 态 维 护 链 表 的 尾 部,初 始 时 指 向 哨 兵 位。

2、比 较 并 合 并 节 点

同 时 遍 历 两 个 链 表,比 较 当 前 节 点 的 值,将 较 小 值 的 节 点 连 接 到 tail 之 后。

每 次 连 接 后 更 新 tail 指 针,使 其 始 终 指 向 链 表 的 最 后 一 个 节 点。

3、处 理 剩 余 节 点

任 一 链 表 遍 历 完 后,将 另 一 链 表 的 剩 余 部 分 直 接 连 接 到 tail 之 后。

4、释 放 哨 兵 位
合 并 完 成 后,哨 兵 位 的 下 一 个 节 点 即 为 实 际 链 表 的 头 节 点。
释 放 哨 兵 位 占 用 的 内 存,避 免 内 存 泄 漏。


温 馨 提 示:读 者 们 ,先 自 己 写 代 码,这 是 提 升 编 程 能 力 的 好 机 会。若 未 达 要 求 ,别 气 馁 ,参 考 下 文 解 释 会 有 新 收 获。
下 面 展 示代 码 示 例

javascript 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
{
    struct ListNode* cur1 = list1;
    struct ListNode* cur2 = list2;
    struct ListNode* guard = NULL;
    struct ListNode* tail = NULL;
    // 带哨兵位的头结点
    guard = tail = (struct ListNode*)malloc(sizeof(struct ListNode));
    tail->next = NULL;
    while (cur1 && cur2) 
    {
        if (cur1->val < cur2->val) 
        {
            tail->next = cur1;
            tail = tail->next;
            cur1 = cur1->next;
        } 
        else 
        {
            tail->next = cur2;
            tail = tail->next;
            cur2 = cur2->next;
        }
    }
    if (cur1)
    {
        tail->next = cur1;
    } else 
    {
        tail->next = cur2;
    }
    struct ListNode* head = guard->next;
    free(guard);
    return head;
}

复 杂 度 分 析

时 间 复 杂 度 :O (m+n),其 中 m 和 n 分 别 为 两 个 链 表 的长 度。
空 间 复 杂 度: O (1),仅 需 常 数 级 额 外 空 间。


第 二 题 - - - 链 表 分 割

链 表 分 割


描 述

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


思 路 分 析

要 将 链 表 按 值 x 分 割,可 以 使 用 两 个 辅 助 链 表 分 别 存 储 小 于 x 的 节 点 和 大 于 等 于 x 的 节 点,然 后 将 这 两 个 链 表 连 接 起 来形 成 最 终 结 果。这 种 方 法 的 核 心 在 于 利 用 两 个 哨 兵 位 头 结 点 来 简 化 链 表 操 作,避 免 处 理 空 链 表 的 边 界 情 况。


解 题 步 骤

1、创 建 两 个 辅 助 链 表

一 个 链 表 用 于 存 储 所 有 小 于 x 的 节 点(称 为 " 较 小 值 链 表")。

另 一 个 链 表 用 于 存 储 所 有 大 于 等 于 x 的 节 点(称 为 " 较 大 值 链 表")。

2、遍 历 原 链 表

将 每 个 节 点 根 据 其 值 的 大 小 添 加 到 对 应 的 辅 助 链 表 尾 部。

3、连 接 两 个 辅 助 链 表

将 较 小 值 链 表 的 尾 部 连 接 到 较 大 值 链 表 的 头 部。

将 较 大 值 链 表 的 尾 部 置 为 NULL,确 保 链 表 正 确 结 束。

4、释 放 哨 兵 位 头 结 点

释 放 两 个 辅 助 链 表 的 哨 兵 位 头 结 点,避 免 内 存 泄 漏。

温 馨 提 示:读 者 们 ,先 自 己 写 代 码,这 是 提 升 编 程 能 力 的 好 机 会。若 未 达 要 求 ,别 气 馁 ,参 考 下 文 解 释 会 有 新 收 获。
下 面 展 示代 码 示 例

javascript 复制代码
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class Partition 
{
  public:
    ListNode* partition(ListNode* pHead, int x) 
    {
        struct ListNode *lesshead, *lesstail, *greaterhead, *greatertail;
        lesshead = lesstail = (struct ListNode*)malloc(sizeof(struct ListNode));
        greaterhead = greatertail = (struct ListNode*)malloc(sizeof(struct ListNode));
        lesshead->next = lesstail->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;
        pHead=lesshead->next;
        greatertail->next=NULL;//阻止链表带环,形成循环链表
        free(lesshead);
        free(greaterhead);
        return pHead;
    }
};

代 码 分 析

1、初 始 化 辅 助 链 表

创 建 两 个 哨 兵 位 头 结 点 lesshead 和 greaterhead,分 别 作 为 较 小 值 链 表 和 较 大 值 链 表 的 头 部。

使 用 lesstail 和 greatertail 指 针 分 别 指 向 两 个 链 表 的 尾 部,初 始 时 都 指 向 各 自 的 哨 兵 位 头 结 点。

2、遍 历 原 链 表 并 分 区

使 用 指 针 cur 遍 历 原 链 表 的 每 个 节 点。

若 当 前 节 点 的 值 小 于 x,将 其 添 加 到 较 小 值 链 表 的 尾 部 ,并 更 新 lesstail 指 针。

若 当 前 节 点 的 值 大 于 等 于 x,将 其 添 加 到 较 大 值 链 表 的 尾 部,并 更 新 greatertail 指 针。

3、连 接 两 个 链 表

将 较 小 值 链 表 的 尾 部(lesstail->next)连 接 到 较 大 值 链 表 的 头 部(greaterhead->next)。

将 较 大 值 链 表 的 尾 部(greatertail->next)置 为 NULL,防 止 形 成 环。

4、获 取 结 果 并 释 放 内 存

最 终 链 表 的 头 结 点 为 较 小 值 链 表 哨 兵 位 的 下 一 个 节 点(lesshead->next)。

释 放 两 个 哨 兵 位 头 结 点 的 内 存。


复 杂 度 分 析
时 间 复 杂 度 :O (n),其 中 n 是 链 表 的 长 度。
空 间 复 杂 度:O (1),只 需 要 常 数 级 的 额 外 空 间。


总 结

至 此,关 于 链 表 的 哨 兵 位 的 头 结 点 探 索 暂 告 一 段 落,但 你 的 编 程 征 程 才 刚 刚 启 航。编 写 代 码 是 与 计 算 机 逻 辑 深 度 对 话,过 程 中 虽 会 在 结 构 设 计、算 法 实 现 的 困 境 里 挣 扎,但 这 些 磨 砺 加 深 了 对 代 码 逻 辑 和 数 据 组 织 的 理 解。愿 你 合 上 电 脑 后,灵 感 不 断,在 数 据 结 构 的 世 界 里 持 续 深 耕,书 写 属 于 自 己 的 编 程 传 奇,下 一 次 开 启,定 有 全 新 的 精 彩 等 待。小 编 期 待 重 逢,盼 下 次 阅 读 时 见 证 你 们 更 大 的 进 步,共 赴 代 码 之 约!

相关推荐
怀旧,几秒前
【数据结构】6. 时间与空间复杂度
java·数据结构·算法
积极向上的向日葵13 分钟前
有效的括号题解
数据结构·算法·
Java 技术轻分享1 小时前
《树数据结构解析:核心概念、类型特性、应用场景及选择策略》
数据结构·算法·二叉树··都差速
chao_7892 小时前
链表题解——两两交换链表中的节点【LeetCode】
数据结构·python·leetcode·链表
曦月逸霜3 小时前
第34次CCF-CSP认证真题解析(目标300分做法)
数据结构·c++·算法
吴声子夜歌6 小时前
OpenCV——Mat类及常用数据结构
数据结构·opencv·webpack
@我漫长的孤独流浪7 小时前
数据结构测试模拟题(4)
数据结构·c++·算法
YGGP11 小时前
吃透 Golang 基础:数据结构之 Map
开发语言·数据结构·golang
weixin_4196583112 小时前
数据结构之栈
数据结构