我会尽量把每一步都说明清楚,每一行代码所表示的含义,以及会用直观的代码讲明白。
链表:是一种常见且重要的动态存储分布的数据结构,它由若干个同一结构体类型的“节点”组成,每一个节点含有存储数据的信息以及存放指向下一个节点的指针,我们称之为next指针,最后一个单元的Next指针指向NULL
链表的常用操作包括建立链表,链表的遍历,插入节点,删除节点,和查找等等。
下面是结点的结构图
链表的节点是用结构体类型的递归来定义的,一个单向链表节点的类型定义如下:
struct stu_node { int data; /*数据域*/ struct stu_node *next; //指向下一个结点的指针 };
为了方便理解我就不用下面的这种方式了
struct stu_node { int data;
struct stu_node *next; }stu_node,*Linklist;
单向链表的示意图:
单向链表的示意图
链表中有一个head头指针变量,头结点是为了处理空表的方便所引用的,用来存放链表中的第一节点的位置,它的data域不存放任何信息
链表的创建之前先假定定链表节点的类型定义为一个学生链表,定义如下
#include <stdio.h> #include <stdlib.h>
struct stu_node { int num ; char naem[20]; int score; struct stu_node *next; };
1.链表的建立:建立链表就是一个一个的输入各节点的数据,然后建立各节点之间的勾链关系
单向链表的建立有“插表头”,“插表尾”两种方法,插表头是将新节点作为新的表头插入链表,插表尾是将新节点作为表尾链接到链表的表尾,无论使用哪种方法,首先要建立一个空表,然后在此空表的表头或表尾插入新节点。
插表头建立 单向链表
插表头的算法如下:
① head=NULL; //头指针指向空,表示空链表
②指针p指向一个产生的新节点 ③p->next=head;head=p; //插表头操作(新节点的next指针指向原来的头指针head。head再指向p作为头指针)
④循环执行②③,可继续建立新节点
插表尾
插表尾建立单向链表,需要定义一个指针last一直指向表尾,指针p指向新的节点,将新节点直接链在表尾(last->next=p)
插表尾算法的描述如下:
①head=last=NUll //建立空表。last是表尾指针 ②指针p指向一个产生的一个新节点p->next=NULL; //新节点作表尾 ③如果head为NULL则 head=p; //在空表中插入第一个节点
否则last->next=p; //插表尾操作 ④last=p //表尾指针last指向新的表尾节点 ⑤循环执行②③④,可继续建立新节点
下面将采用插表尾的方法创建一个学生成绩信息的单向链表
#include<stdio.h> #include<stdlib.h>
struct stu_node { int num; char name[20]; int score; struct stu_node *next; } ; int n; //n表示链表的长度
struct stu_node * create_stu(){ struct stu_node *head,*last,*p; head=last=NULL; //建立空表
n=0; printf("输入学号姓名成绩(学号为0时停止输入)"); do { p=(struct stu_node *)malloc(sizeof(struct stu_node)); scanf("%d%s%d",&p->num,p->name,&p->score); p->next=NULL; //新节点作为表尾
if(p->num==0)break; //学号为0时停止循环
else if(head==NULL)head=p; //当表为空表时,直接加入新节点
else last->next=p; //新节点插入表尾
last=p; //表尾指针指向新的表尾节点p
n++; //表长加一
} while(1); free(p); //释放指针p
return head ; //返回头指针
}
链表的遍历:链表的遍历就是逐个扫描链表的节点,然后显示节点的信息,从头指针开始依次通过及节点的next成员访问后继节点(p=p->next)直到表尾
先将指针p指向head, 再通过p=p->next访问后一个节点,直到表尾。
以下是代码
void print_stu(struct stu_node *head) { struct stu_node *p; p=head; if(head==NULL){ printf("空表"); return; } printf("学号 姓名 成绩"); do{ printf("%2d %s%d\n",p->num,p->name,p->score); p=p->next; //指针指向下一个节点
} while(p!=NULL) }
链表的插入
代码如下
/*
插入指定节点的后面(此例是指定学号的节点)
*/
struct stu_node *insert(struct stu_node *head, int num) { struct stu_node *p; p=head; // 指针p指向第一个节点
struct stu_node *s=(struct stu_node *)malloc(struct stu_node); // 待插入的结点s
if(head==NULL) { head=s;s->next=NULL; //当链表为空时,新节点作为头结点插入
n+=1; 链表节点长度加1 return head; } else { while(p->num!=num&&p->next!=NULL)//p指向的结点不是所查找的,并且不是最后一个结点,继续往下找
{ p=p->next; if(p->num=num){ /*找到了想要插入的位置*/ s->next=p->next;
p->next=s;
n+=1; //节点总数加一 }
return head; } }
如果不好理解的话再换个方法吧 下面用两个指针来插入,操作如下图
下面是代码
/* 假设有一个学生成绩信息的单向链表,插入学号为num的节点 */
struct stu_node *insert(struct stu_node * head,struct stu_node * s) { //s 是将要插入的节点
struct stu_node *p,*q; p=head; //指针p 指向第一个节点
if(head==NULL) { head=s;s->next=NULL; //链表为空时,新节点作为头节点插入
} else{ while((s->num!=p->num)&&(p->next!=NULL)) { q=p;p=p->next; //移动pq指针 寻找插入点
} if(s->num==p->num){ //找到插入的节点后在指针p与q之间插入新节点
if(p==head)head=s; //新节点作为头节点插入
else q->next=s; //新节点插入q指向的节点之后
s->next=p; } else{ //新节点作为尾节点插入
p->next=s; s->next=NULL; } } n++; //链表节点长度加一
return head; }
链表的删除
删除应该很好理解:就是利用指针s从第一个节点开始找,指针p总是指向指针s所指节点的前驱。当指针s指向的节点就是待删除的节点时直接修改其前驱节点的成员值(p->next=s->next;),直接改变勾链关系,就可以实现节点删除
删除算法详细如下
①指针s指向表头s=head;
②当s指向的不是待删除的节点且还没有到表尾时,移动指针p=s;s=s->next;
③当找到删除节点时:
如果s==head,则:p=head;head=head->next; /* 当删除的节点是表头节点时*/
否则p->next=s->next; /*删除s所指向的节点*/
④free(s); /*释放删除节点的内存*/
//假设有一个学生成绩的单向链表,编写一个函数实现 删除学号为num的节点
struct stu_node *delete_stu(struct stu_node* head,int num)
{
struct stu_node *p,*s;
if(head==NULL) /* 当链表为空时*/
{
printf("链表为空");
}
s=head; /*指针s指向第一个节点,从表头开始查找*/
while(num!=s->num&&s->next!=NULL) /*查找的节点不是想要的且还没到达表尾*/
{
p=s;s=s->next; /*指针后移*/
}
if(num==s->num){ /*找到删除的节点*/
if(s==head)head=s->next; /*当删除的是头结点时*/
else p->next=s->next; /*当删除的节点不是头节点时*/
printf("已经删除:%d\n",num);
free(s); /*释放删除节点的内存*/
n--; /*节点长度减一*/
}
else printf("%d 表中无此点",num); /*当找不到符合的节点时*/
return head;
}
/*delete_stu()函数的形参num接收的是想删除的学号,函数返回值是头指针*/
emmmm接下来是关于链表的查找
链表的查找也是分为两种①按照序号查找②按值查找
①链表不是随机存取结构在链表中,即使知道被访问结点的序号i,也不能像顺序表中那样直接按序号i访问结点,而只能从链表的头指针出发,顺链域next逐个结点往下搜索,直至搜索到第i个结点为止。因此,链表不是随机存取结构。
头节点看做第0个节点,下面给的例子意思是 查找节点值为num的节点信息
/*链表的查询,查找特定节点的信息*/
struct stu_node *find_stu(struct stu_node* head,int num) { struct stu_node *p=head; int j=0; while(p->next&&j<num) { p=p->next; j++; } if(j==num) { printf("查找的节点为%d 学号 姓名 信息\n"); printf("%d\t%s\t%d\n",p->num,p->name,p->score); return p; } else
return NULL; }
②按值查找:从头节点出发依次逐个将节点的数据域和定值num比较(这里的num代表的指定学生的学号)若节点的数据域与定值num相同,打印该节点的信息,且返回该节点,若找不到匹配节点的数据域则返回NULL
下面是实现按值查找的算法
/*查询特定学号的学生的信息*/
struct stu_node *look_stu(struct stu_node *head,int num) { struct stu_node *p=head; while(p&&num!=p->num) { p=p->next; } printf("%2d\t%s\t%2d\n",p->num,p->name,p->score); return p; }
emmm关于链表的基本操作的介绍与操作已经介绍完下面我们进行测试;
测试代码如下
main() { struct stu_node* head=NULL,*p; int choise,num; do { printf("请选择(0-4)"); printf("1.建立链表\t2.插入\t3.删除\t4.遍历\t5.查找\t0.退出\n"); scanf("%d",&choise); switch(choise) { case 1: head=create_stu(); break; case 2: printf("输入待插入的学生的信息:"); p=(struct stu_node *)malloc(sizeof(struct stu_node)); scanf("%d%s%d",&p->num,p->name,&p->score); head=insert_stu(head,p); break; case 3:printf("删除的学生学号"); scanf("%d",&num); head=delete_stu(head,num); break; case 4:print_stu(head); case 5:printf("输入想要查找的节点序号"); scanf("%d",&num); head=look_stu(head,num); break; case 0: break; } }while(choise!=0); }
下面是测试的结果:
最后也是最重要的:谢谢你看我的博客 (如果有人看我的博客的话),如果有不足之处的话还请指出,后续有时间的话我会考虑增加新的功能的。 禁止转载