带头节点:head-> p1->p2->p3 ->p1->p2->p3-> p1.....
不带头节点: p1->p2->p3 ->p1->p2->p3-> p1.....
却别还不明显吗?带头节点可以方便,快速的定位链表第1个节点
比如循环链表的时候,删除p1 的时候:
head->next = p2;
p3->next = head->next; ok?
free(p1);
思路很清晰,链表开始的第1个节点现在就是head->next 即 p2
否则没有头节点:
p3->next = p1->next;
free(p1);
会不会有一种链表第1个节点到底是哪个的感觉?
当然单向循环这个不明显,如果你写个双向循环,就会很方便,麻烦,我就写单向吧``
就是方便,清晰,明了,也不是一定要用,要看情况,看需要对链表做什么操作,来决定要不要带头节点。你不写几个链表没法体会的。
在带头节点的单链表中,头指针只有一个域,即链指针,它指向头节点,头节点有两个域,一个是数据域,值为0 (NULL),还有一个域,链指针,这个指针链指向单链表的第一个数据元素
而在不带头节点的单链表中,头节点也只有一个链指针,但它指向单链表的第一个数据元素
什么时候要使用带头节点的单链表?
为了在第一个数据元素前面加入新元素或者删除第一个节点时头指针的值不变,在第一个数据前面要加一个所谓的头节点
带头结点初始化
Node *head; //声明头结点
void InitList(Node *head){
head=(Node *)malloc( sizeof(Node));
head->next=NULL;
}
带头结点尾插入,统一操作。
方式一:
void CreatList(Node **head){
Node *r=*head,*s;
int a;
while(scanf("%d",&a)){
if(a!=0){
s=(Node *)malloc(sizeof(Node));
s->value=a;
r->next=s;
r=s;
}
else{
r->next=NULL;
break;
}
}
}
调用CreatList(&head);
方式二:
void CreatList(Node *head){
Node *r=head,*s;
... //下面的都一样
}
调用CreatList(head);
不带头结点初始化
方式一:
void InitList(Node **head){
*head=NULL;
}
调用InitList(&head);
方式二:
void InitList(Node *head){
head=NULL;
}
调用InitList(head);
不带头结点尾插入,第一个节点与其他节点分开操作。
void CreatList(Node **head){
Node *p,*t; /*p工作指针,t临时指针*/
int a,i=1;
while(scanf("%d",&a)){
if(a!=0){
t=(Node *)malloc(sizeof(Node));
t->value=a;
if(i==1){
*head=t;
}
else{
p->next=t;
}
p=t;
}
else{
p->next=NULL;
break;
}
i++;
}
}
调用CreatList(&head);
两种初始化方法的区别
不带头结点的单链表对于第一个节点的操作与其他节点不一样,需要特殊处理,这增加了程序的复杂性和出现bug的机会,因此,通常在单链表的开始结点之前附设一个头结点。
带头结点的单链表,初始时一定返回的是指向头结点的地址,所以一定要用二维指针,否则将导致内存访问失败或异常。
带头结点与不带头结点初始化、插入、删除、输出操作都不样,在遍历输出链表数据时,带头结点的判断条件是while(head->next!=NULL),而不带头结点是while(head!=NULL),虽然头指针可以在初始时设定,但是如1所述,对于特殊情况如只有一个节点会出现问题。
为什么不带头结点初始化有2种方式,而带头结点只有1种方式呢?
因为不带头结点声明Node *head 时,C编译器将其自动初始化为NULL,于是根本不需要调用InitList(head); 也即不带头结点的初始化是个伪操作。而带头结点的初始化在堆开辟了一段内存,需要修改head指针变量指向的地址(即head的值),所以要修改head的值,必须传保存head变量的地址(即二维指针)。而直接调用CreatList(head);相当于传head变量的值,函数修改的是head的副本,无法真正改变head的值。
注:这里可以将head指针看成一个变量(不管它保存的是地址),就比较好理解了。
这其实本质上还是传值,传址的问题,只不过指针本身保存的地址,让这个过程变得有点纠结。在函数调用需要修改指针变量的指向(value)时,应该传递指针变量的地址(address)。
另外,对于函数的形参是指针时,只要该参数不在左边(即都是右值操作),二维指针(形参)就可以简化为一维指针。如上面带头结点的尾插简化版本。