写在前面:

一直以来,带头结点和不带头节点的单链表的操作实现困扰着我。在没有理解和实现之前,光凭思考真的是很难理解清楚
1.两者之间代码的差异;
2.带不带头结点的区别;
3.带头结点之后,什么情况下形参必须传二级指针(或者一级指针的引用);

所以,当我攻克掉这个难题之后,就准备一次性捋清楚这些点,一来以便自己回顾用;而来,也希望对陌生的有需要的你有相关的帮助。

OK,先简单摆出代码:

不带头结点的单链表操作:

//SList.h
#pragma oncetypedef int SLDataType ;typedef struct SListNode
{int data ;struct SListNode* next ;
}SListNode ;typedef struct SList
{struct SListNode* first ;
}SList ;//初始化&销毁
void SListInit(SList* list) ;//销毁
void SListDestroy(SList* list) ;//头插
void SListPushFront(SList* list, SLDataType data) ;//头删
void SListPopFront(SList* list) ;//打印
void SListPrint(SList* list) ;//尾插
void SListPushBack(SList* list, SLDataType data) ;//尾删
void SListPopBack(SList* list) ;//查找
SListNode* SListFind(SList* list, SLDataType data) ;//在pos位置的节点后插入元素
void SListInsertAfter(SListNode* pos, SLDataType data) ;//删除pos位置后的第一个节点
void SListEraseAfter(SListNode* pos) ;//删除遇到的指定的第一个节点
void SListRemove(SList* list, SLDataType data) ;
//**************************************************
//SList.c
#include "SList.h"
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>//初始化
void SListInit(SList* list)
{assert(list != NULL) ;list->first = NULL ;
}//销毁
void SListDestroy(SList* list)
{SListNode* next ;SListNode* cur ;for(cur=list->first; cur!=NULL; cur=next){next = cur->next ;free(cur) ;}list->first = NULL ;
}//头插
void SListPushFront(SList* list, SLDataType data)
{SListNode* node = (SListNode*) malloc (sizeof(SListNode)) ;assert(node) ;node->data = data ;node->next = list->first ;//更新头指针的指向;list->first = node ;
}//头删
void SListPopFront(SList* list)
{SListNode* old_first = list->first ;assert(list) ; //有无链表assert(list->first != NULL) ; //有链表,链表里是否有元素,//若链表为空,则删除失败list->first = list->first->next ;free(old_first) ;
}//打印
void SListPrint(SList* list)
{SListNode* cur ;assert(list) ;for(cur=list->first; cur!=NULL; cur=cur->next){printf("%d-->", cur->data) ;}printf("NULL\n") ;
}//尾插
void SListPushBack(SList* list, SLDataType data)
{SListNode* node = (SListNode*) malloc (sizeof(SListNode)) ;SListNode* lastone = list->first ;assert(list != NULL) ;for( ; lastone->next != NULL; lastone=lastone->next){}assert(node != NULL) ;node->data = data ;node->next = lastone ->next ;lastone->next = node ;
}//尾删
void SListPopBack(SList* list)
{SListNode* cur ;SListNode* m ;assert(list != NULL) ;assert(list->first != NULL) ;if(list->first->next  == NULL){//若链表为空,则尾删即为头删SListPopFront(list) ;return ;}for(cur=list->first; cur->next->next != NULL; cur=cur->next){}//找到倒数第二个节点m = cur->next ;cur->next = m->next ;free(m) ;
}//查找
SListNode* SListFind(SList* list, SLDataType data)
{SListNode* cur = list->first ;for( ; cur!=NULL; cur=cur->next){if(cur->data == data) {return cur ;}}return NULL ;
}//在pos位置的节点后插入元素
void SListInsertAfter(SListNode* pos, SLDataType data)
{SListNode* node = (SListNode*) malloc (sizeof(SListNode)) ;assert(node != NULL ) ;node->data = data ;node->next = pos->next ;pos->next = node ;
}//删除pos位置后的第一个节点
void SListEraseAfter(SListNode* pos)
{SListNode* node = pos->next->next ;free(pos->next) ;pos->next = node ;
}//删除遇到的指定的第一个节点
void SListRemove(SList* list, SLDataType data)
{SListNode* prev = NULL ;SListNode* cur = list->first ;while(cur != NULL && cur->data != data){prev = cur ;cur = cur->next ;}//要删除的节点不存在if(cur == NULL ){return ;}//要删除的节点若就是第一个节点if(prev == NULL){//即头删SListPopFront(list) ;return ;}prev->next = cur->next ;free(cur) ;
}

带头结点的单链表简单操作:

//带头结点的单链表的实现
#pragma once#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
typedef int DataType;typedef struct ListNode{DataType _data;struct ListNode* next;}Node,* PNode;PNode buy_new_node(int x)
{PNode _node = (PNode)malloc(sizeof(Node));_node->_data = x;_node->next = NULL;return _node;}//初始化
void init(PNode head)
{PNode node = buy_new_node(0);head->next = node;}
//头插
void push_front(PNode* head,DataType x)
{assert(*head != NULL);PNode _node = buy_new_node(x);_node->_data = x;_node->next = (*head)->next;(*head)->next = _node;}//头删
void pop_front(PNode* head)
{assert(*head != NULL);PNode tmp = (*head)->next;(*head)->next = (*head)->next->next;free(tmp);}//尾插
void push_back(PNode head,DataType x)
{assert(head != NULL);//r如果链表为空,那么就是进行头插if ((head->next) == NULL){push_front( &head, x);}//链表不为空PNode cur = NULL;for (cur = head->next; cur->next != NULL; cur = cur->next){}PNode _node = buy_new_node(x);_node->_data = x;_node->next = cur->next;cur->next = _node;}//尾删
void pop_back(PNode head)
{assert(head != NULL);//如果只有一个元素 ,那么进行头删if ((head->next->next) == NULL){pop_front(&head);}//否则PNode cur = NULL;for (cur = head->next; cur->next->next != NULL; cur = cur->next){}PNode tmp = cur->next;cur->next = tmp->next;free(tmp);}
//pos位置之后插入  更改头插和头删的代码//打印
void print(PNode head)
{assert(head != NULL);if (head->next == NULL)return;PNode cur = NULL;for (cur = head->next;cur->next!=NULL;cur=cur->next){printf("%d--> ", cur->_data);}printf("NULL");}

OK,先简单的展示出相关代码,暂时解决掉第一个问题。下来我们分析第二个问题:带不带头结点操作的区别又在哪里呢?

第一点:

不带头节点: p1->p2->p3 ->p1->p2->p3-> p1…
       我们先创建头指针,初始化的时候只需要将头指针赋NULL就可以了;大家想想,如果在这种情况下,我们进行头删操作,最后一步必须得做的事就是: 更新头指针的指向
或者换句话说,一旦对首元节点(头指针指向首元结点)进行操作,首尾工作必定得进行更新

带头节点:head-> p1->p2->p3 ->p1->p2->p3-> p1…
       与上边相反,每次进行涉及到首元结点的操作后,更新的过程就会很简单。我们不需要再去移动头指针,只需要固定的更新头结点中的指针域就可以了。

第二点:

再者,带头节点可以方便,快速的定位链表第1个节点。
比如循环链表的时候,删除p1 的时候:

head->next = p2;
p3->next = head->next;
free(p1);

思路很清晰,链表开始的第1个节点现在就是head->next 即 p2

否则没有头节点:

p3->next = p1->next;
free(p1);

会不会有一种链表第1个节点到底是哪个的感觉?

第三点:

不带头结点的单链表对于第一个节点的操作与其他节点不一样,需要特殊处理,这增加了程序的复杂性和出现bug的机会,因此,通常在单链表的开始结点之前附设一个头结点。

Ok,趁热打铁,最后我们分析带头结点后的传参二级指针(或一级指针的引用)问题。
这个问题一度困扰我很久,想明白之后其实也觉得没什么。
1.我们先分析什么时候需要形参是二级指针?
答: 在对带头结点的单链表进行涉及到头指针的操作,例如头插,头删等操作时,形参必须是二级指针。

2.为什么?
大家还记得最初学习的时候,我们应该都写过一个交换两个数字的swap()函数吧。我们形参如果是 int a ,int b;实参传swap(a,b),我们发现执行swap()之后,并没有达到交换的结果。原因就是在swap函数中形参是实参的一份临时拷贝,swap栈帧销毁之后,对应产生的临时变量也就销毁了。那么,我们是怎么解决的呢?

//我们形参这么去定义
swap(int *a,int* b);
...
...
//实参传的是两个数对应的地址
swap(&a,&b);

对。我们实参传递的是这两个int类型的地址。
我们想要修改int类型的变量,形参必须得传int类型的地址,形参用int ※接收;
那么同理,我们现在想要修改头结点中的指针域(指向首元结点),是node※类型。那么我们就理应传地址过去,形参对应的用 node※※接收。

node* head;
push_front(&head, 1);  //head取址,形参用二级指针接收

形参的类型是由传递的实参决定的。

到这里,三个问题都已经做出了相应的解答,希望可以对有需要的您有所帮助。也欢迎大家在评论区补充指正。

单链表:带头结点和不带头结点 总结相关推荐

  1. 删除带头结点单链表中倒数第k个结点

    [问题描述] 设有头结点单链表,删除单链表中倒数第k个结点. [输入形式] 第一行重复输入整数建立带头结点的单链表,输入字符结束. 第二行输入一个整数k,表示删除倒数第k个结点. [输出形式] 输出删 ...

  2. 删除单链表中倒是第K个结点

    [问题描述] 设有头结点单链表,删除单链表中倒数第k个结点. [输入形式] 第一行输入若干个整数建立带头结点的单链表(以输入字符作为结束). 第二行输入一个整数k,表示删除倒数第k个结点. [输出形式 ...

  3. 单链表中倒数第K个结点

    单链表中倒数第K个结点 链表结点定义如下: typedef int ElemType;typedef struct Node {ElemType data; struct Node *next; }H ...

  4. 算法设计 删除无序单链表中的值域重复的结点

    删除无序单链表中的值域重复的结点 题目: 有一个带头结点的单链表head,其中可能出值域重复的结点,设计一个算法删除值域重复的结点.要求在主函数中调用设计的算法,给出结果. 思路:删除某个结点值的重复 ...

  5. 数据结构c/c++ 头插法尾插法建立带头结点的单链表,以数组创建带头结点的单链表和不带头结点的单链表,输出打印单链表

    // // Created by 焦娇 on 2021/9/17. //#ifndef CHAPTER2_LINELINK_LLK_H #define CHAPTER2_LINELINK_LLK_H# ...

  6. c语言带头节点单链表创建,C语言建立带头结点的单链表

    满意答案 TS老妹儿 2017.08.31 采纳率:57%    等级:9 已帮助:1719人 单链表的生成有2种方式:头插法和尾插法. 1.头插法/************************* ...

  7. 单链表-在带头结点的单链表L中删除一个最小值结点(四指针)

    单链表的存储结构: typedef struct LinkList{int data;LinkList * next;} 分析: 要删除一个链表的最小值节点,首先想到的是肯定是要定义两个指针,但是 , ...

  8. 单链表——单链表逆置(不带头结点)

    本博客主要记录两种解决方法 (1)三指针 (3)双指针(三指针优化) (2)双指针之头插法思想 一.三指针 思想: p1主要指向前面的一个结点 p2指向中间的结点 p2->next = p1; ...

  9. 单链表的创建(有头结点和无头结点)

    前言 在学链表的时候,对链表创建的过程一知半解.目前现在刷题的阶段,发现这部分很重要,所以这次完全解决这个知识点. 1 带头结点的链表 为了方便,创建带有10个结点的链表,链表的数据域为整数类型,取随 ...

  10. 试编写在带头结点的单链表1中删除一个最小值结点的高效算法(假设最小值结点是唯一的)

    #include"initList/initList.h" #include"initList/initList.h" LinkList* del_min(Li ...

最新文章

  1. 2021年大数据Hadoop(十五):Hadoop的联邦机制 Federation
  2. iOS设计模式之原型模式
  3. SQL查询效率注意事项
  4. Spire.XLS 教程:从C#的Excel形状中提取文本和图像
  5. centos6.4安装及升级gcc 4.8.2(已实践)
  6. matlab 的cat函数
  7. matlab meshc函数_MATLAB三维图形
  8. Java中J.U.C扩展组件之ForkJoinTask和ForkJoinPool
  9. 手撕Vue-Router
  10. bzoj千题计划290:bzoj3143: [Hnoi2013]游走
  11. 逐像元地表反射率计算(GF4)
  12. Qt QDir 递归获取文件夹中的所有文件
  13. C#自动注册dll方法
  14. 小米路由器 R1D 可用 java JRE openjdk
  15. Easy Touch 5 简单使用
  16. 【编译原理】 根据语法树 写出对应的短语 直接短语 句柄 构造产生式
  17. 【第八期送书活动】+ 购书福利
  18. 吐血分享:QQ群霸屏技术教程2017(维护篇)
  19. 设计模式 : 访问者模式
  20. ​自动驾驶什么时候才会凉凉,估计还要多久?

热门文章

  1. 理想国matplotlib入门教程
  2. Android Studio安装更新终极解决方式,flutter页面跳转防止页面重复刷新
  3. 一个资深系统管理员的O2O实践(四)
  4. 知乎高赞:大厂面试的2个关键点
  5. 2022考研笔记-政治(当代时政及规划)
  6. java int.tryparse_【转载】 C#中使用int.TryParse方法将字符串转换为整型Int类型
  7. html 显示 数据库图片.js,html实时显示数据 怎么让数据库的数据在html显示出来
  8. SMC 压缩空气速度调节阀air speed
  9. 普罗米修斯监控linux,普罗米修斯监控简单搭建
  10. Docker 菜鸟教程 1 简介