带哨兵节点和不带哨兵节点的单链表操作的对比

哨兵节点:哨兵节点(sentinel)是一个哑元节点(dummy node),可以简化边界条件。是一个附加的链表节点,该节点作为第一个节点,它的值域中并不存储任何东西,只是为了操作的方便而引入的。如果一个链表有哨兵节点的话,那么线性表的第一个元素应该是链表的第二个节点。 
很多情况下,需要处理当前节点的前驱节点,如果是没有哨兵节点的链表,对第一个节点,即头节点,没有前驱节点。如果不作特殊处理,就可能出错;如果对它特别对待,就会增加代码复杂性,还会降低程序效率。而如果有哨兵节点的话, 线性表的每个位置的节点都有前驱节点,因此可以统一处理。 
当链表为空时,没有哨兵节点的链表的头节点为NULL,处理起来也和其他情况不同。带哨兵节点的链表,当其为一个空链表时,仅含哨兵节点,哨兵节点的指针域为空,和其他情况的表尾是一样的。

定义链表的节点:

typedef struct _node{
    int data;
    struct _node *next;
}node, *pNode;

 

定义链表结构体的数据类型。

创建一个链表。 
没有哨兵节点的情况:

复制代码
pNode createList(void)
{
    pNode temp;
    pNode prev;
    int input;
    pNode head = NULL;
    scanf("%d", &input);
    while(input != -1){
        temp = (pNode)malloc(sizeof(node));
        temp->data = input;
        temp->next = NULL;
        //需要考虑链表为空的情况
        if(head == NULL){
            head = temp;
            prev = temp;
        }else{
            prev->next = temp;      //
            prev = temp;
        }
        scanf("%d", &input);
    }

    return head;
}
复制代码

 

有哨兵节点的情况:

复制代码
pNode createListWithSentinel(void)
{
    pNode temp;
    pNode prev;
    int input;
    pNode head;
    head = (pNode)malloc(sizeof(node));
    head->next = NULL;
    prev = head;
    scanf("%d", &input);
    while(input != -1){
        temp = (pNode)malloc(sizeof(node));
        temp->data = input;
        temp->next = NULL;
        prev->next = temp;      //
        prev = temp;
        scanf("%d", &input);
    }

    return head;
}
复制代码

 

没有哨兵节点时,添加一个节点要先判断是否是第一个节点,并单独保留第一个节点的指针,以便于返回整个链表的头指针。有哨兵节点时,链表头是固定的,不可能为空,后续的节点都是链接在前一个节点的,不需要单独判断是否为头节点。

遍历输出链表。 
没有哨兵节点:

复制代码
void printList(const pNode head)
{
    pNode temp = head;
    while(temp){
        printf("%d, ", temp->data);
        temp = temp->next;
    }
    printf("\n");
}
复制代码

 

有哨兵节点:

复制代码
void printListWithSentinel(const pNode head)
{
    pNode temp = head;
    while(temp->next){
        printf("%d, ", temp->next->data);
        temp = temp->next;
    }
    printf("\n");
}
复制代码

 

差别不大。

在指定的位置前插入一个节点。第一个位置为0 
没有哨兵节点:

复制代码
pNode insertNodeN(pNode head, int pos, int value)
{
    int count;
    pNode temp;
    pNode prev = head;
    temp = (pNode)malloc(sizeof(node));
    temp->data = value;
    if(head == NULL || pos == 0){
        temp->next = head;
        return temp;
    }
    for(count = 1; count < pos && prev->next != NULL; count++){
        prev = prev->next;
    }
    temp->next = prev->next;
    prev->next = temp;      //

    return head;
}
复制代码

 

有哨兵节点:

复制代码
void insertNodeWithSentinelN(const pNode head, int pos, int value)
{
    int count;
    pNode temp;
    pNode prev = head;
    temp = (pNode)malloc(sizeof(node));
    temp->data = value;
    for(count = 0; count < pos && prev->next != NULL; count++){
        prev = prev->next;
    }
    temp->next = prev->next;
    prev->next = temp;      //
}
复制代码

 

有哨兵节点时,不需要判断链表为空和插入点在第一个位置节点的情况。

删除指定位置的节点。 
没有哨兵节点:

复制代码
pNode deleteNodeN(pNode head, int pos)
{
    int count;
    pNode temp;
    pNode prev = head;
    /*空表的情况*/
    if(head == NULL){
        return head;
    }
    /*删除第一个节点,即删除的是头节点的情况*/
    if(pos == 0){
        temp = head;
        head = head->next;
        free(temp);
        return head;
    }
    for(count = 1; count < pos && prev->next != NULL; count++){
        prev = prev->next;
    }
    temp = prev->next;
    if(temp != NULL){
        prev->next = temp->next;    //还没有到表尾
    }
    free(temp);
    return head;
}
复制代码

 

有哨兵节点:

复制代码
void deleteNodeWithSentinelN(const pNode head, int pos)
{
    int count;
    pNode temp;
    pNode prev = head;
    for(count = 0; count < pos && prev->next != NULL; count++){
        prev = prev->next;
    }
    temp = prev->next;
    if(temp != NULL){
        prev->next = temp->next;    //还没有到表尾
    }
    free(temp);
}
复制代码

 

有哨兵节点时,不需要判断链表为空和删除第一个位置节点的情况。

总结: 
带哨兵节点的链表,需要额外的一个节点,但插入和删除等操作不需要额外的判断;不带哨兵节点,在处理链表为空时,和其他情况不一样,需要单独判断一次。 
带哨兵节点的链表,插入或删除时,不论操作的位置,表头都不变,不需要额外的判断;不带哨兵节点的链表,插入或删除操作发生在第一个节点时,表头指针都要变化,需要额外的处理。

posted @   瘋耔  阅读(908)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
历史上的今天:
2016-06-23 ubuntu下7z文件的解压方法
2016-06-23 错误
2016-06-23 camera TSF开关
2015-06-23 minitools
2015-06-23 Linux上程序执行的入口--Main
跳至侧栏
点击右上角即可分享
微信分享提示