头指针:
是指链表指向第一个结点的指针(若有头节点,则指向头结点,否则指向首元结点)。
头指针具有标识作用,用头指针冠以链表的名字;每次访问链表时都可以从头指针依次遍历链表中的每个元素
头指针意义在于,在访问链表时,总要知道链表存储在什么位置(从何处开始访问),即头指针相当于记录了链表的起始物理地址。然后由于链表的特性(next指针),知道了第一个结点地址,那么整个链表的元素都能够被访问,所以头指针是必须存在的。
头结点:
可有可无。为了操作的统一与方便而设立的,放在第一个元素结点之前,其数据域一般无意义(当然有些情况下也可存放链表的长度)
首元结点:
即链表的第一个元素结点,它是头结点后边的第一个结点。
单链表分为两种:
(1)不带头节点的链表:此种链表的head即保存第一个数据,访问时从head开始。不利于删除或者添加指定位置数据的操作。
(2)带头节点的链表:此种链表保存数据是从head->next开始的,head中并未保存有数据,访问时自然head->next开始,优点就是方便操作。
单链表的插入:
向链表中插入一个新的节点时,首先由新建一个节点p,将其指针域赋值为空指针(p->next = NULL),然后在链表中寻找适当的位置执行节点的插入操作。
1、若链表为空:
无头结点时:则将新节点p作为首元节点,让head指向新节点p
head = p
有头结点时:
*r = *head
r->next = p
r = p
其中,r作为移动指针,每次都移动至链表最后一个节结点,方便后续尾插入
2、若链表不为空:
a、头插法(有头节结点,head指向头结点)(类似与中间插入)
p->next = Head->next;
Head->next = p;
如何理解该段代码:
Head 是头指针,其实代表头结点的物理地址
Head->next 代表头结点的指针域,里面存储的是头结点下一个结点(首元节点)的物理地址(可以说成Head->next代表首元结点的地址)
p->next 是p结点的指针域,里面需要储存的是p结点的下一个结点的物理地址
p->next = Head->next 把Head->next赋值给p->next,即先在p->next也指向了首元结点
Head->next = p 再把头结点指向新节点p,此时p结点变为首元结点
链表代码中的 = 可以理解为“指向”,即“左边”“指向”“右边”
b、头插法(无头结点,head指向首元结点)
p->next = Head;
Head = p;
c、尾插法
for(t = Head; t->next; t=t->next); //结束时t指向尾节点
p->next = NULL; //进行插入
t->next = p;
t = p;//t指向尾结点
整体代码
带头结点初始化:带头结点尾插入,统一操作。
Node *head; //声明头结点
void InitList(Node **head){
*head=(Node *)malloc( sizeof(Node));
(*head)->next=NULL;
}
方式1:调用CreatList(&head);
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;
}
}
}
方式2 :调用CreatList(head);
void CreatList(Node *head){
Node *r=head,*s;
... //下面的都一样
}
不带头结点初始化:
方式1:调用InitList(&head);
void InitList(Node **head){
*head=NULL;
}
方式2:调用InitList(head);
void InitList(Node *head){
head=NULL;
}
不带头结点尾插入,第一个节点与其他节点分开操作。
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++;
}
}