关于链表中哨兵结点问题的深入剖析

最近正在学习UC Berkeley的CS61B这门课,主要是采用Java语言去实现一些数据结构以及运用数据结构去做一些project。这门课不仅告诉你这个东西怎么做,而且一步一步探寻为什么要这样做以及为什么会有这些功能。我们有时在接触某段代码或功能的实现时,可能直接就看到了它最终的面貌,而不知道如何一步步演化而来,其实每一个功能的添加或优化都是对应一个问题的解决。下面就这门课中关于链表中哨兵结点的相关问题进行总结。

什么是哨兵结点

哨兵顾名思义有巡逻、检查的功能,在我们程序中通过增加哨兵结点往往能够简化边界条件,从而防止对特殊条件的判断,使代码更为简便优雅,在链表中应用最为典型。

单链表中的哨兵结点

首先讨论哨兵结点在单链表中的运用,如果不加哨兵结点在进行头尾删除和插入时需要进行特殊判断。比如在尾部插入结点的代码如下:

void addLast(int x) {
    if (first == null) {
        first = new Node(x, null);
        return;
    }
    Node p = first;
    while (p.next != null) {
        p = p.next;
    }
    p.next = new Node(x, null);
}

如上所示需要对结点为空的特殊情况进行判断,头部加了一个哨兵结点后就可以不需要判断了(不会为空)

双链表中的哨兵结点

Version 1: 双哨兵

在双链表中需要能够在头部和尾部分别进行插入删除操作(可以实现双端队列),为了能快速在尾部进行插入删除,需要引入指向尾部的指针。截图如下(图片来自CS61B)

上述增加了一个指向尾部的last结点,从上图可以看出一个问题,last结点有时指向哨兵结点,有时指向实际结点。这会导致特殊情况的出现,比如在进行addFirst操作时,last指向哨兵结点时插入后需要将last往后移动一个,而第二张图指向实际结点时在头部插入结点后并不需要改变last指针。这时需要在尾部后也引入一个哨兵结点,以使其一致。相应示意图如下:

Version 2:循环双链表

上述Version1需要两个哨兵结点,可以对其进行改进。可以使用头部结点的prev指针指向尾部,尾部结点的next指针指向哨兵,这样就只需要一个哨兵结点,使链表变成循环链表,比Version1更为简洁优雅。

在对如上所示进行插入和删除操作时一定要格外注意,自己在写的时候很容易就漏掉某个指针的关系设置,最好在纸上自己画一遍。(对于要改变的连接可能会影响其他的,这时可将其暂存或最好设置)

在头部插入的代码如下:

   public void addFirst(Item item) {
        Node node = new Node(item);
        node.prev = sentinel;
        node.next = sentinel.next;
        sentinel.next.prev = node;
        sentinel.next = node;
        size++;
    }

尾部插入代码如下:

    public void addLast(Item item) {
        Node node = new Node(item);
        node.prev = sentinel.prev;
        node.next = sentinel;
        sentinel.prev.next = node;
        sentinel.prev = node;
        size++;
    }

头部删除代码如下:

   public Item removeFirst() {
        Item item = sentinel.next.item;
        sentinel.next = sentinel.next.next;
        sentinel.next.prev = sentinel;
        size--;
        return item;
    }

尾部删除代码如下

    public Item removeLast() {
        Item item = sentinel.prev.item;
        Node sl = sentinel.prev.prev;
        sl.next = sl.next.next;
        sl.next.prev = sl;
        size--;
        return item;
    }

总结与感想

(1)虽然看起来很小很简单的事情,但实现起来却有很多细小问题可以考虑,学会把一件小事做的很漂亮。(small but smart)

(2)学会分析一个东西的来龙去脉,为什么会有这部分,以及怎么改进的。

参考:

1.cs61b:https://joshhug.gitbooks.io/hug61b/content/chap2/chap23.html

2.算法导论10.2链表

原文地址:https://www.cnblogs.com/litexy/p/9749544.html

时间: 2024-10-09 00:13:50

关于链表中哨兵结点问题的深入剖析的相关文章

链表《4》删除链表中的结点

下图是一个创建好的链表 下面我们需要删除一个结点,例如删除第3个结点 首先定义一个指针p,并且将p指向第二个结点 然后定义一个指针q,将q指向需要删除的结点 将p指向的结点和q指向的结点相连 p->pNext = q->pNext 清空q指向的结点 free(q); q = NULL; 删除后的链表 程序代码: #include <stdio.h> #include <stdlib.h> typedef struct Node//结点结构 { int data;//数据

已有a,b两个链表,每个链表中的结点包括学号,成绩。要求把两个链表合并,按学号升序排列。

#include <stdio.h> #include <stdlib.h> #define N 10 typedef struct student {     int num;     float score;     struct student *next; }stu; stu *creat() {     int i;     stu *p,*head = NULL,*tail = head;     for (i = 0; i < N; i++)     {    

删除有序链表中的重复结点

一,问题描述 请自己构造一个简单的有序单链表,然后实现删除链表中的重复结点.比如: 二,问题分析 首先要实现一个单链表,因此需要定义一个节点类Node.其次,实现向链表中添加结点的方法(使用尾插法)addNode 删除重复结点的实现思路: 定义两个指针:pre 和 next.初始时,pre指向链表中的第一个元素,next指向链表中的第二个元素.如果 pre 的值与 next 的值不相等,则两个指针分别都向后移一个结点:若相等,则删除 next 指针指向的结点即可. 三,整个代码实现 // del

在O(1)的时间内删除链表的指定结点

题目:给定单项链表的头指针和一个结点指针,定义一个函数在o(1)的时间删除该结点,链表的定义如下: struct ListNode{ int value; ListNode* next; }; 函数定义:void DeleteNode(ListNode** PListHead,ListNode* pToBedelete); 如上图所示,想要删除一个单链表的中的某个结点有两种方式: 1.如上图中的( b)所示:要删除i结点,必须从头扫描,扫到h(i的前一个结点),然后让其next值指向j(i的下一

13.单链表中,取出环的起始点

我们平时碰到的有环链表是下面的这种:(图1) 上图中环的起始点1. 但有可能也是下面的这种:(图2) 此时,上图中环的起始点是2. 方法1: 这里我们需要利用到上面第8小节的取出环的长度的方法getCycleLength,用这个方法来获取环的长度length.拿到环的长度length之后,需要用到两个指针变量first和second,先让second指针走length步:然后让first指针和second指针同时各走一步,当两个指针相遇时,相遇时的结点就是环的起始点. 注:为了找到环的起始点,我

55删除链表中重复的结点

题目描述 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针. 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5 思路:这题首先熟悉哨兵节点的用法.自己对模板还是没有很好的节点. //哨兵节点 ListNode dummyNode(-1); dummyNode.next = head; head = &dummyNode; /*理解:这样写的好处可以自动回收多余的这个节点,因为是一

链表中倒数第K个结点

题目:输入一个链表,输出改链表倒数第K个结点. 分析:常规方法可能就是,先遍历一遍链表,找到链表长度length,那么我们只需要第二次遍历length-k+1个结点就可以找到倒数第k个结点. 比较好的方法是采用两个指针,让一个指针先走K-1步,后面的指针再跟上.这样只需要遍历一遍. 注意:1.提高容错性,在链表为空 或者k为空.还有k大于链表长度. 2.链表下一个结点,我们采用p=p->next.指针指向的数组我们采用p++; typedef int Type; struct listNode{

剑指offer | 链表中倒数第k个结点

先做做一些简单题找找手感,还有赞一个牛客的OJ,这样的提交方式很好,ACM那种对输入出处格式对于没有接触过ACM的人来说比较头疼,华为OJ那种格式描述不清楚的就更头疼了,卡在输入输出上就悲剧了,当然,输入输出也是需要了解的. 顺便吐槽一下华为SO挑战赛:题意有模糊的地方,挑战赛的题目没有区分度,作弊监管太松,判分规则未公开说明. 题目描述:输入一个链表,输出该链表中倒数第k个结点. 题目解析:设两个同步后移.相距k的指针就行,前面的指针触底,后面的指针即是倒数k个节点. 代码如下: 1 /* 2

链表中倒数第k个结点(剑指offer)

链表中倒数第k个结点 参与人数:1699时间限制:1秒空间限制:32768K 通过比例:21.37% 最佳记录:0 ms|8552K(来自  无声) 题目描述 输入一个链表,输出该链表中倒数第k个结点. 题目链接:http://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking 阐