链表
简单链表的构成:头指针(Header),若干个节点(节点包括了数据域和指针域),最后一个节点要指向空(NULL)。
实现原理:头指针指向链表的第一个节点,然后第一个节点中的指针域指向下一个节点,然后依次指到最后一个节点,这样就构成了一条链表。
链表的数据结构:
struct list_node
{
int data;//数据域,用于存储数据
struct list_node *next;//指针,可以访问节点数据,也可以遍历,指向下一个节点
};
链表使用中:不知道如何声明?
1.先声明头指针:list_single node*=NULL;
2.然后再去开辟空间:node=(list_single*)malloc(sizeof(list_single));
3.利用memset库函数来快速清空结构体:
void *memset(void *s,int c,size_t n)
—>它的意思是:
将已经开辟内存空间 s 的首 n 字节的值设为值 c 。
即:memset(node,0,sizeof(list_single));//清理一下
4.链表赋值:
node->data=100;
node->next=NULL;
5.使用链表:printf("%d\n",node->data);
6.释放掉空间,结束程序。free(node);
创建一个节点:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct list_node
{
int data;
struct list_node *next;
};
typedef struct list_node list_single;
int main(void)
{
list_single *node=NULL;
node=(list_single*)malloc(sizeof(list_single));
if(node==NULL){//?
printf("malloc fair!");
}
memset(node,0,sizeof(list_single));
node->data=100;
node->next=NULL;
printf("%d,%s",node->data,node->next);
return 0;
}
这只是创建了一个链表的节点,我们可将创建的过程封装成函数,创建时调用即可!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct list_node
{
int data;
struct list_node *next;
};
typedef struct list_node list_single;
list_single *creat_list_node(int data);//返回一个指针,即地址!
int main(void)
{
int data=100;
list_single *node=creat_list_node(data);
printf("%d,%s",node->data,node->next);
return 0;
}
list_single *creat_list_node(int data)
{
list_single *node=NULL;
node=(list_single*)malloc(sizeof(list_single));
memset(node,0,sizeof(list_single));
node->data=data;
node->next=NULL;
return node;
}
我们现在只是创建了一个链表节点,此时这个单节点链表在内存是这个样子的:定义了一个头指针指向(data,next)这个数据域,这个数据域的指针域next指向了NULL。
( * node)->(data,next)->NULL
下面我们来创建多个单链表节点,实现增删改查!!!
1.首先顶一个单链表的数据结构!
与上面相同,只需要把上面的crate_list_node函数创建即可。
2.单链表的尾插:(链表的连接)
即实现:header->next=new
第一步:获取当前节点的位置,也就是访问头节点
struct list * p=header;
第二步:判断是否为最后一个节点,如果不是,移动到下一个节点,如果是,将数据插入尾部。
while(NULL!=p->next) p=p->next;
p->next=new;
3.单链表的头插:(链表的插入)
头插就是把新的结点插在原来的节点和原来的节点的下一个节点之间的节点。
第一步:获取当前节点的位置,也就是访问头节点
struct list * p=header;
第二步:新的节点的下一个节点设置为原来 头节点的下一个节点(先让要插入的指向后面的节点)
new ->next=p->next;
第三步:原来的头节点的下一个节点设置为新插入的头节点。
4.单链表的遍历:
我们知道一条单向链表是由头节点和若干个节点组成,最后一个节点为NULL。
思考:
1.我们需要打印头节点吗?(头节点不用打印,因为我们是为了操作方便而设置的一个节点)
2.这条链表有多少个节点我怎么知道?(通过判断该节点是否为尾节点,因为尾节点指向NULL)
那么我们现在就可以遍历我们的链表:
第一步:获取当前节点的位置,也就是访问头节点
struct list * p=header;
第二步:由于头节点我们不需要去打印它,这时候,初始化打印的节点需要从第一个结点开始。
p=p->next;
第三步:判断是否为最后一个节点,如果不是,就先打印第一个节点的数据(1),然后移动到下一个节点(2),重复这两个步骤。
1.while(NULL!=p-next){printf("node:%d\n",p->data);p=p->next;}
printf(“node:%d\n”,p->data);//打印最后一个指向null的数据域;
2.也可以一步打印:while(NULL!=p>next){p=p->next;printf("node:%d\n",p->data);}
5、单链表的删除
删除的原型可以定义为:
int * detele_list_node(struct list* pH,int data);//指针、位置、数据
单链表的删除要考虑两种情况:
一种的普通节点的删除(当然,头节点不能算)即:改变指向,释放内存!
还有一种是尾节点的前一个节点删除的情况,注意,删除完节点还要释放对应节点的内存空间。即:指向NULL,释放空间。
设计流程:
第一步:先定义两个指针,一个表示当前的节点,另一个表示当前节点的下一个节点。
struct list *p=header;//当前节点
struct list *prev=NULL;//当前节点的上一个节点
第二步:遍历整个链表,同时保存当前节点的前一个节点
while(NULL!=p->next)
{
//保存了当前节点的前一个节点
prev=p;
//保存当前偏移的节点
p=p->next;
return 0;
}
第三步:在遍历的过程中查找要删除的数据
while(NULL != p->next)
{
//保存了当前的节点的前一个节点
prev = p ;
//保存当前偏移的节点
p = p->next ;
//查找到了数据
if(p->id == data)
{
}
return 0 ;
}
第四步:查找到数据分两种情况:
普通节点的删除
if(p->id == data)
{
prev->next = p->next ;
free(p);
}
考虑尾节点的下一个节点为NULL的节点删除
if(p->id == data)
{
if(p->next == NULL)
{
prev->next = NULL ;
free(p);
}
}
单链表的逆序:
逆序步骤:
第一步:设置两个指针,一个保存当前的第一个节点的位置,一个表示链表的第一个有效节点
//保存当前第一个节点的位置
struct list *p=pH->next;
//表示链表的第一个有效节点
struct list *pBack;
第二步:当没有有效节点或者中有一个有效节点的时候,不做任何操作。
if(p==NULL||p->next==NULL)
return ;
第三步:链表遍历
1.保存第一个节点的下一个节点的地址
pBock=p->next;
2.对第一个有效节点做特殊处理,也就是把第一个有效节点放到末尾去。
if(p==pH->next)p->next=NULL;
3.如果不是第一个有效点,则进行头插操作,把当前的节点当作一个新的节点:
p->next=pH->next;//尾部连接
pH->next=p//头部连接
4.继续走下一个节点
p=pBack;
第四步:手动插入最后一个节点
top_insret(pH,p);
单链表的基本流程就搞懂了,下面写一个程序!!!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct slist
{
int id ;
struct slist *next ;
}L;
//创建一个节点
L *create_node(int data)
{
//给每个节点分配结构体一样的空间大小
L *p = malloc(sizeof(L));
if(NULL == p)
{
printf("malloc error!\n");
return NULL ;
}
//由于结构体在未初始化的时候一样是脏数据,所以要清
memset(p,0,sizeof(L));
//初始化第一个节点
p->id = data ;
//将节点的后继指针设置为NULL
p->next = NULL ;
}
//链表的尾插
void tail_insert(L *pH , L *new)
{
//获取当前的位置
L *p = pH ;
//如果当前位置的下一个节点不为空
while(NULL != p->next)
{
//移动到下一个节点
p = p->next ;
}
//如果跳出以上循环,所以已经到了NULL的这个位置
//此时直接把新插入的节点赋值给NULL这个位置
p->next = new ;
}
//链表的头插
void top_insert(L *pH , L *new)
{
L *p = pH ;
new->next = p->next ;
p->next = new ;
}
//链表的遍历
void Print_node(L *pH)
{
//获取当前的位置
L *p = pH ;
//获取第一个节点的位置
p = p->next ;
//如果当前位置的下一个节点不为空
while(NULL != p->next)
{
//(1)打印节点的数据
printf("id:%d\n",p->id);
//(2)移动到下一个节点,如果条件仍为真,则重复(1),再(2)
p = p->next ;
}
//如果当前位置的下一个节点为空,则打印数据
//说明只有一个节点
printf("id:%d\n",p->id);
}
//删除链表中的节点
int detele_list_node(L * pH , int data)
{
//获取当前头节点的位置
L *p = pH ;
L *prev = NULL;
while(NULL != p->next)
{
//保存当前节点的前一个节点的指针
prev = p ;
//然后让当前的指针继续往后移动
p = p->next ;
//判断,找到了要删除的数据
if(p->id == data)
{
//两种情况,一种是普通节点,还有一种是尾节点
if(p->next != NULL) //普通节点的情况
{
prev->next = p->next ;
free(p);
}
else //尾节点的情况
{
prev->next = NULL ; //将这个尾节点的上一个节点的指针域指向空
free(p);
}
return 0 ;
}
}
printf("没有要删除的节点\n");
return -1 ;
}
void trave_list(L * pH)
{
//保存第一个节点的位置
L *p = pH->next;
L *pBack;
int i = 0 ;
if(p->next == NULL || p == NULL)
return ;
while(NULL != p->next) //遍历链表
{
//保存第一个节点的下一个节点
pBack = p->next ;
//找到第一个有效节点,其实就是头指针的下一个节点
if(p == pH->next)
{
//第一个有效节点就是最后一个节点,所以要指向NULL
p->next = NULL ;
}
else
{
/*
new->next = p->next ;
p->next = new ;
*/
p->next = pH->next ; //尾部连接
}
pH->next = p ; //头部连接
p = pBack ; //走下一个节点
}
top_insert(pH,p); //插入最后一个节点
}
int main(int argc , char **argv)
{
//创建第一个节点
int i ;
L *header = create_node(0);
for(i = 1 ; i < 10 ; i++)
{
tail_insert(header,create_node(i));
}
Print_node(header);
detele_list_node(header,5);
putchar('\n');
Print_node(header);
putchar('\n');
trave_list(header);
Print_node(header);
return 0 ;
}
相关参考连接:
https://blog.youkuaiyun.com/morixinguan/article/details/68951912