本周任务:
- 完成一个demo
- 学习C语言线性表到单链表的插入删除等;
- C语言知识总结
单链表
若在链表中,每个结点只有一个指针,所有结点都是单线联系,除了末尾结点指针以空外,每个结点的指针都指向下一个结点,一环扣一环形成一条线性链,则称此链表为单向线性链表,简称单链表。
特点:
- 有一个 head 指针变量,它存放在头结点的地址,称之为头指针。
- 头结点的指针域
head->next
存放首元结点的地址。 - 从头指针 head 开始,head 指向头结点,头节点指向首元结点,首元结点指向第二个结点,直到最后一个结点。所有结点都是单线联系。
- 最后一个结点不再指向其他结点,称为“表尾节点”,它的指针域为空指针NULL,表示链表到此结束。指向表尾结点的指针称为尾指针。
- 链表各结点之间的顺序关系由指针域 next 来确定,并不要求逻辑上相邻的结点物理位置上也相邻。即链表依靠指针相连不需要占用一片连续的内存空间。
1 初始化
由于链表的每个结点都包含数据域和指针域,即每个节点都要包含不同类型的数据,所以结点的数据类型必须选用结构体类型。且结构体中必须有一个成员的类型是指向本结构体类型的指针类型。
单链表的初始化就是创建一个头结点,头结点的数据域可以不使用,头结点的指针域为空,表示空单链表。
- 先定义一个需要用的结构体类型:
typedef struct node {
int number; //数据域
char name[20]; //数据域
struct node *next; //递归定义指向struct node类型结构体的指针变量next。
}NODE,*LinkList;
- 然后才开始链表的初始化。单链表的初始化就是创建一个头结点,头结点的数据域可以不使用,头结点指针域为空,表示空单链表。
LinkList List() {
LinkList head; //定义头指针变量
head = (NODE*)malloc(sizeof(NODE)); //头指针指向分配的头结点内存空间
head->next = NULL; //头结点的指针域为空
return head; //返回头结点的地址,即头指针
}
2 建立
单链表的建立就是在程序的运行过程中,从无到有的建立一个链表,即一个一个的分配结点的内存空间,然后输入结点中的数据,并建立结点间的相连关系。
单链表的建立可以分为两种方法:尾插法和头插法。
尾插法
:在单链表的尾部插入新结点。
从一个空表开始,重复读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表尾上,直至读入结束标志为止。
void(CreatByRear(LinkList)) {
NODE *r,*s; //s用于创建新结点
int number;
char name[20];
r = head; //head指向头结点,故r指向头结点
printf("请输入学生的学号和姓名:\n");
while(1) {
scanf("%d",&number);
scanf("%s",name);
if(number == 0) break;
s = (node *)malloc(sizeof(NODE)); //分配结点的内存空间
s->number = number;
strcpy(s->name,name);
r->next = s; //原来的结点指向新结点
r = s; //r指向新结点
}
r->next = NULL; //链表的尾结点指针为空
}
- 头插法:在单链表的头部插入新结点
从一个空表开始,重复读入数据,生成新结点,将读入的数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头节点之后,直至读入结束标志为止。
void CreatByHead(LinkList head) {
NODE *S;
int number;
char name[20];
printf("请输入学生学号和姓名;\n");
while(1) {
scanf("%d",&number);
scanf("%s",name);
if(number == 0) break;
s = (NODE *)malloc(sizeof(NODE)); //分配节点的内存空间
s->number = number;
strcpy(s->name,name);
s->next = head->next; //让新结点指向首元结点
head->next = s; //让头结点指向新结点
}
}
如下图:
3 遍历
直接上代码:
void OutPut(LinkList head) {
NODE *p; //循环所用的临时指针
p = head->next; //p指向链表的首元结点
while(p) {
printf("学号:%d\n",p->number);
printf("姓名:%s\n",p->name);
p = p->next; //移动临时指针到下一个结点
}
}
- 函数定义了一个临时指针p用来进行循环操作,使其指向要输出链表的首元结点。
- 在while循环中,每输出一个结点的内容后,就移动临时指针p到下一个节点的位置。如果是最后一个结点,指针指向NULL,表示链表中的节点都已经输出,循环结束。
4 插入
链表的插入操作可以在链表的头指针位置进行插入,也可以在链表中某个结点的位置进行插入,或者在链表的最后面添加结点。在头指针位置和在最后面插入的思路与头插法和尾插法的思路相同。
以下写一段在链表中某个结点的位置插入新结点的代码示例:
void Insert(LinkList head,int i) {
NODE *P = head,*s;
int j = 0;
while(j < i-1 && p) { //从头结点开始,故开始j=0,头结点是第0个结点,遍历找到第i-1个结点的地址
p = p->next;
j++;
}
if(p) {
printf("请输入待添加学生的学号和姓名:\n");
s = (NODE *)malloc(sizeof(NODE)); //定义s指向新分配的空间
scanf("%d",&s->number);
scanf("%d",s->name);
s->next = p->next; //新结点指向原来的第i个结点
p->next = s; //新结点成为原来的第i个结点
}
}
5 删除
在创建单链表删除某个结点的函数时需要两个参数,一个表示链表的头指针head,另一个表示要删除的节点在链表中的位置。
代码示例:
void Delete(LinkList head,int pos) {
NODE *p = head,*q;
int j = 0;
printf("删除第%d个学生的信息",pos);
while(j < pos-1 && p) { //通过循环,找到第pos-1个结点的地址
p = p->next;
j++;
}
if(p == NULL || p->next == NULL) printf("the pos is error"); //第pos个结点不存在
else {
q = p->next; //q 指向第pos个结点
p->next = q->next; //连接所要删除结点两边的结点
free(q); //释放所要删除结点的内存空间
}
}
6 查询
在创建单链表查询某个结点的函数时需要两个参数,一个表示链表的头指针head,另一个表示要查找的值。
代码示例:
NODE *Search(LinkList head,char name[]) { //在单链表head中找到值为name的结点
NODE *p = head->next;
while(p) {
if(strcmp(s->name,name)!=0) p = p->next; //判断结点的值是不是name的值,若不是则移动p指向下一个结点
else break; //查找成功!
}
if(p == NULL)
printf("未找到值为%d的结点",name);
return p;
}
7 单链表的长度
单链表的长度是隐形表示的,当从首元结点开始,依次遍历链表的所有结点,并同时统计结点个数,最后返回结点个数值。
代码示例:
int Length(LinkList head) {
int count;
NODE *p;
p = head->next; //指针变量p指向链表的首元结点
while(p) { //结点存在,表示链表没有遍历结束
count++; //结点个数累加器加1
p = p->next; //指向当前结点的下一结点
}
return count; //返回链表结点的个数
}
8 不带头结点的单链表
不带头结点的单链表,在操作过程中必须针对第一个结点和其余结点分别进行操作。
- 插入
分为在链表的首位置插入和不在链表的首位置插入两种情况。
在链表首位置:
插入时,首先为插入的新结点分配内存,然后将新结点的指针指向原来的首结点,最后将头指针指向新结点。需要注意的是,在这种情况下,头指针发生了改变,所以需要返回新的头指针。
不在链表首位置: 例如要在第 i 个结点插入新结点
需要先通过循环找到链表的第 i-1个结点的地址p。如果该结点存在,则可以在第 i-1 个结点后面插入第i个结点。为插入的新结点分配内存,然后向新结点输入数据。插入时,首先将新结点的指针指向原来第 i 个结点,然后将第 i-1 个结点指向新结点。完成。
- 删除
分为删除首结点和删除其他结点两种情况。
删除首结点:
定义指针变量去指向待删除的结点,再让头指针指向原来的第二个结点,成为新的首结点。最后释放原来的首结点的内存空间。在这种情况下,头指针也发生了改变,所以需要返回新的头指针。
删除不是头结点的结点:
定义整型变量 j 来控制循环次数,然后定义指针变量p表示该结点之前的结点。接着利用循环找到要删除的结点之前的结点p;如果该结点存在并且待删除结点存在,则将指针变量q指向待删除的结点,再连接要删除结点两边的结点。,并使用free函数将q指向的内存空间进行释放。