链式存储结构:逻辑上相邻、物理上不一定相邻。
1.1 理解
由于单链表不要求物理上相邻,因此在设计结点时,该结点不仅需要保存自身的数据,也需要保存下一个结点的地址。所以单链表设计有效结点时,需要包含两个域:数据域、指针域(存放下一个结点的地址)。
1.2 代码实现
//单链表的结构体设计
typedef int ELEM_TYPE;
typedef struct Node{
ELEM_TYPE data;//用来存放该结点的数据
struct Node* next;//存储下一个结点的地址(指向下一个结点)
}Node,*PNode;
1.3 说明
本文章的单链表带有辅助结点,对于辅助结点也采用此结构体设计,只是不使用结点的数据域。
辅助结点在栈区申请,因此不需要malloc申请空间。
1.理解:对辅助结点进行初始化(因为辅助结点在栈区,所以不需要malloc申请)
①将结点的指针域置空
②结点的数据域不作处理
2.代码设计:
void Init_list(PNode head)
{
//将结点的指针域置为空
head->next=NULL;
}
1.理解:每次插入都是在辅助结点之后插入。
①malloc申请新的结点。并对新结点赋值。
②将插入结点的指针域指向辅助结点的下一个结点。
③将辅助结点的指针域指向插入结点。
注意:②③的位置不能改变,改变后将找不到链表的剩余结点。
2.代码实现:
bool Insert_head(PNode head,ELEM_TYPE val)
{
//malloc申请新结点,并判断结点是否申请成功 ,申请成功后赋值
struct Node*pnewnode=(struct Node*)malloc(sizeof(struct Node));
if(pnewnode==NULL)
exit(1);
pnewnode->data=val;
pnewnode->next=NULL;
//先改变插入结点的指向
pnewnode->next=head->next;
//改变辅助结点的指向
head->next=pnewnode;
return true;
}
3.说明:
因为头插每次都是在辅助结点之后插入(可理解为第一个位置),所以打印出来的结果通常是逆置的。
eg:
插入顺序:1、2、3、4
输出顺序:4、3、2、1
1.理解:插入位置在最后一个结点之后。
①malloc申请新的结点。并对新结点赋值。
②找插入位置:从辅助结点出发,遍历链表至尾结点。(因为插入操作要改变插入位置前一个结点的指针域,所以需要先找到插入位置的前驱结点)
③先修改插入结点的指针域:插入结点的指针域指向前驱结点的后继结点。
④修改前驱结点的指针域:使其指向插入结点。
2.代码实现:
bool Insert_tail(PNode head,ELEM_TYPE val)
{
//malloc申请新结点,并判断结点是否申请成功 ,申请成功后赋值
struct Node*pnewnode=(struct Node*)malloc(sizeof(struct Node));
if(pnewnode==NULL)
exit(1);
pnewnode->data=val;
pnewnode->next=NULL;
//找插入位置:遍历至尾结点
PNode* p=head;
for(;p->next!=NULL;p=p->next) ;
//先改变插入结点的指向
pnewnode->next=p->next;
//改变辅助结点的指向
p->next=pnewnode;
return true;
}
1.理解:根据指定的位置插入结点。
①malloc申请新的结点,并对新节点赋值。
②找插入位置:
先判断插入位置是否合法(此处的合法位置从0开始,到链表长度。[0,length])
合法则再找到插入位置的前驱结点。
③修改插入结点的指针域:将插入结点指向前驱结点的后继结点。
⑤修改前驱结点的指针域:使其指向插入结点。
2.代码实现:
bool Insert_Pos(PNode head,ELEM_TYPE val,int pos)
{
struct Node*pnewnode=(struct Node*)malloc(sizeof(struct Node));
if(pnewnode==NULL)
exit(1);
pnewnode->data=val;
pnewnode->next=NULL;
//判断插入位置是否合法
if(pos<0||pos>Get_length(head))
return false;
//找到插入位置的前驱结点
struct Node*p=head;
for(int i=0;inext;
}
//修改插入位置的指针域
pnewnode->next=p->next;
//修改前驱结点的后继结点
p->next=pnewnode;
return true;
}
①插入时,先申请一个新的结点,并为新节点赋值。
②找到插入位置的前驱结点。
③先修改插入位置的指针域,再修改前驱结点的指针域。
1.理解:删除单链表的第一个有效结点。
①判断单链表是否为空,如果不为空,执行②。
②找到删除位置前驱结点:头插不需要找删除位置。
③修改删除位置前驱结点的指针域:使指向删除位置的后继结点。
④释放删除位置的指针域,并置为空。
2.代码实现:
bool Del_head(PNode head)
{
//判断链表是否为空
if(Is_empty(head))
return false;
//定义指针指向待删除的结点
Node*p=head->next;
//修改前驱结点的指针域
head->next=p->next;
//释放待删除结点
free(p);
p=NULL;
return true;
}
1.理解:删除单链表的最后一个结点(尾结点)。
①判断单链表是否为空,如果不为空,则执行②。
②找到删除位置的前驱结点:(单链表只能向后遍历)
方法1:从头到尾遍历链表,找到尾结点。再从头到尾,找到尾结点的前驱结点。
方法2:从头到尾遍历链表,先找到尾结点的前驱结点,再找到尾结点。(每次判断两个指针域。)
③修改删除位置前驱结点的指针域:让其指向删除结点的后继结点。
④释放删除位置的指针域,并将其置为空。
2.代码实现:
bool Del_tail(PNode head)
{
//判断链表是否为空
if(Is_empty(head))
return false;
//方法1:
/*//先找到尾结点
Node*q=head;
for(;q->next!=NULL;q=q->next);
//再找尾节点的前驱结点
Node*p=head;
for(;p->next!=q;p=p->next) ;*/
//方法2:
//先找到待删除节点(尾结点)的前驱结点
Node*p=head;//指向待删除结点的前驱
for(;p->next->next!=NULL;p=p->next);
//再找到待删除结点
Node*q=p->next;//指向待删除结点
//修改前驱结点的指针域
p->next=q->next;
//释放待删除结点
free(q);
q=NULL;
return true;
}
1.理解:根据指定的位置删除。
①先判断链表是否为空,如果不为空,则执行②。
②判断删除位置是否有效,有效位置[0,length)。此处有效位置不包括length,与按位置插入区别。
③找到删除位置的前驱结点:通过for循环遍历,找到删除位置前驱结点。
③修改删除位置前驱结点的指针域:让其指向删除结点的后继结点。
④释放删除位置的指针域,并将其置为空。
2.代码实现:
bool Del_Pos(PNode head,int pos)
{
//判断链表是否为空
if(Is_empty(head))
return false;
//判断插入位置是否合法
if(pos<0||pos>=Get_length(head))
return false;
//找到删除位置的前驱结点
Node*p=head;
for(int i=0;inext;
}
Node*q=p->next;
//修改前驱结点的指针域
p->next=q->next;
//释放待删除结点
free(q);
q=NULL;
return true;
}
1.理解:根据对应的值,找到删除的位置,并删除。
①先判断链表是否为空,如果不为空,则执行②。
②判断链表中是否能够找到该值,如果能够找到该值,则执行③。
③找到删除位置的前驱结点:通过for循环遍历,找到删除位置前驱结点。
③修改删除位置前驱结点的指针域:让其指向删除结点的后继结点。
④释放删除位置的指针域,并将其置为空。
2.代码实现:
//按值删(删除第一次出现的位置)
bool Del_val(PNode head,ELEM_TYPE val)
{
//判断链表是否为空
if(Is_empty(head))
return false;
//判断是否能够找到该值
struct Node*q=Search(head,val);
if(q==NULL)
return false;
//找到删除结点的前驱结点
Node*p=head;
for(;p->next!=q;p=p->next);
//修改前驱结点的指针域
p->next=q->next;
//释放待删除结点
free(q);
q=NULL;
return true;
}
①先判断链表是否为空。
②找到删除结点的前驱结点,使其指向删除节点的后继结点。
③释放删除结点,并将删除结点置空。
1.理解:从头到尾遍历单链表,判断是否能够找到该值所在的结点。
2.代码实现:
struct Node*Search(PNode head,ELEM_TYPE val)
{
//判断链表是否为空
if(Is_empty(head))
return false;
//遍历整个链表判断
Node*p=head->next;
for(;p!=NULL;p=p->next)
{
if(p->data==val)
{
return p;
}
}
return NULL;
}
1.理解:单链表的清空就是销毁。
2.代码实现:
void Clear(PNode head)
{
Destory1(head);
}
1.理解:借用辅助结点,不断进行头删,直到单链表为空。
2.代码实现:
void Destory1(PNode head)
{
while(!Is_empty(head))
{
Del_head(head);
}
}
1.理解:使用两个变量一个指向当前结点,一个指向当前结点的后继结点,不断修改当前结点为后继结点,直到当前结点遍历到最后一个结点。
2.代码实现:
void Destory2(PNode head)
{
Node*p=head->next;//指向当前结点
Node*q=NULL;//q指针表示当前结点的后继结点
while(p!=NULL)
{
q=p->next;//指向后继结点
free(p);//删除当前结点
p=q;//当前结点变为后继结点
}
//销毁完毕将辅助结点的指针域置空
head->next=NULL;
}
1.理解:如果辅助结点的指针域为空,则代表单链表为空。
2.代码实现:
bool Is_empty(PNode head)
{
return head->next==NULL;
}
1.理解:定义一个变量,从头到尾遍历链表,每次遍历,变量+1。
2.代码实现:
int Get_length(PNode head)
{
Node*p=head->next;
int count=0;
for(;p!=NULL;p=p->next)
{
count++;
}
return count;
}
1.理解:从头到尾遍历链表,并将每个结点的数据域打印。
2.代码实现:
void Show(PNode head)
{
Node*p=head->next;
for(;p!=NULL;p=p->next)
{
printf("%d ",p->data);
}
printf("\n");
}