一.题目一览
二.问题及改进
三.总结
1.题目一:建立一个链表,每个结点包括:学号,姓名,性别,年龄。输入一个年龄,如果节点所包含的年龄等于此年龄,将此结点删去。
这是我第一次写的丑陋 的代码
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
typedef struct student
{
long num;
char name[20];
char sex[2];
int age;
struct student* next;
}student, * Student;
//创建链表(包含了初始化)
student* creat()
{
student *s,*head=NULL; //*s 新创建的结点
int n = 0;
student r; //r 尾指针
s = r=(student)malloc(sizeof(student));
printf(“请输入:\n”);
scanf(“%ld%s%s%d”, &s->num, s->name, &s->sex, &s->age);
while (s->num != 0) //改进?
{
n = n + 1;
if (n == 1) head = s; //?
else r->next = s; //原来的结点指向新结点
r = s;
s = (student)malloc(sizeof(student));
scanf(“%ld%s%s%s”, &s->num, s->name, &s->sex, &s->age);
}
r->next = NULL;
return head;
}
student* search(student* head, int x)
{
student* p; //循环所用的临时指针
p = head;
while (p != NULL) //判断头指针指向的链表有结点,否则为空
{
if (p->age == x)
return p;
p = p->next;
}
return NULL;
}
student* del(student* head, int x)
{
student* s,* r;
if (head == NULL) //空链表
{
printf(“null\n”);
return NULL;
}
s = head; //s用来找到要删除的结点 从首结点开始
while (s->age != x && s->next != NULL) //
{
s = s->next; //向后遍历
r=s; //记录当前结点的位置
}
if (s->age == x)
{
if (s == head) //首结点
head = s->next; //删除此结点 头指针向后移 释放此空间
else r->next = s->next; //r也跟着往后移
free(s);
}
else printf(“no find\n”);
return head;
}
void print(student* head)
{
student* p;
p = head;
while (p != NULL)
{
printf(" % ld % s % s % d", p->num, p->name, p->sex, p->age);
p = p->next;
}
}
int main()
{
student* head;
head = creat();
print(head);
int x;
scanf(“%d”, &x);
do
{
head = del(head, x);
} while (search(head, x) != NULL);
print(head);
return 0;
}
大家会发现有明显的漏洞:
没有头结点 导致在进行链表的查找 删除 等等都非常麻烦!
所以我写了一个带头结点的链表
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
typedef struct student
{
long num;
char name[100];
char sex[100];
int age;
struct student* next;
} LNode, *LinkList;
//创建链表(包含了初始化)
LinkList creat()
{
LinkList H = (LinkList)malloc(sizeof(LNode));
H -> next = NULL; //创建头结点
LNode *r = H;
LinkList s = (LinkList)malloc(sizeof(LNode));
while(1)
{
scanf(“%ld%s%s%d”,&s->num,s->name,s->sex,&s->age);
if(s->num==-1)
{
free(s);
break;
}
r->next=s;
r=s;
s=(LNode*)malloc(sizeof(LNode));
}
r->next=NULL;
return H;
}
void del(LinkList H, int x)
{
LinkList p,q; //p指向第i-1的位置 q指向i的位置
p=q=(LNode*)malloc(sizeof(LNode));
p=H;
int j=0;
while(j<x-1&&p)
{
p=p->next;
j++;
}
if(pNULL||p->nextNULL)
printf(“结点不存在!\n”);
else
{
q=p->next;
p->next=q->next;
free(q);
}
}
void print(LinkList H)
{
LNode* p;
p = H->next;
while §
{
printf(" % ld % s % s % d\n", p->num, p->name, p->sex, p->age);
p = p->next;
}
}
int main()
{
printf(“请输入学号、姓名、性别、年龄\n”);
LinkList H = creat();
print(H);
printf(“请输入要删除的年龄\n”); //按值删除
int x;
scanf(“%d”, &x);
del(H,x);
print(H);
return 0;
}
可是!有没有发现这个是按位置查找并删除的!我们需要的是按值查找!修改del函数如下:
void del(LinkList H,int x)
{
LinkList t,p=H->next,pre=H;
while§
{
while(p&&p->age!=x)
{
p=p->next;
pre=pre->next;
}
t=p;
pre->next=p->next;
free(t);
p=pre;
}
}
看起来没有问题,可是实际运行不出来!问题就出在结点删除后free后面的p=pre!因为p位置的结点已经释放了,那么p要是再跟着pre走,p->next即pre->next,这时p实际应该指向的下一个结点已经没有了,只靠pre输出了…所以不可以!那么为了每次删除成功并继续向后找,可以
1 .预存p->next结点,防止丢失
2 .每次删除后,p直接从删除后的下一个结点开始再寻找(更推荐)
3.注意!p走p的,pre走pre的,两个指针不要相互赋值!
改进如下
t=p;
pre->next=p->next;
free(t);
//p=pre; //错误的!因为释放掉t 即p后 pre的next结点已经丢了!
p=pre->next;
运行成功!!!泪目
整体代码如下
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
typedef struct student
{
long num;
char name[100];
char sex[100];
int age;
struct student* next;
} LNode, *LinkList;
//创建链表(包含了初始化)
LinkList creat()
{
LinkList H = (LinkList)malloc(sizeof(LNode));
H -> next = NULL; //创建头结点
LNode *r = H;
LinkList s = (LinkList)malloc(sizeof(LNode));
while(1)
{
scanf("%ld%s%s%d",&s->num,s->name,s->sex,&s->age);
if(s->num==-1)
{
free(s);
break;
}
r->next=s;
r=s;
s=(LNode*)malloc(sizeof(LNode));
}
r->next=NULL;
return H;
}
void del(LinkList H,int x)
{
LinkList t,p=H->next,pre=H;
while(p)
{
while(p&&p->age!=x)
{
p=p->next;
pre=pre->next;
}
t=p;
pre->next=p->next;
free(t);
//p=pre; //错误的!因为释放掉t 即p后 pre的next结点已经丢了!
p=pre->next;
}
}
void print(LinkList H)
{
LNode* p;
p = H->next;
while (p)
{
printf(" % ld % s % s % d\n", p->num, p->name, p->sex, p->age);
p = p->next;
}
}
int main()
{
printf("请输入学号、姓名、性别、年龄\n");
LinkList H = creat();
print(H);
printf("请输入要删除的年龄\n"); //按值删除
int x;
scanf("%d", &x);
del(H,x);
print(H);
return 0;
}
2.建立两个单链表a,b,从a中删除在b中存在的结点。
第一次失败的代码
LinkList1 creat1()
{
LinkList1 H1=(LNode1*)malloc(sizeof(LNode1));
H1->next=NULL;
LinkList1 r=H1,s=(LNode1*)malloc(sizeof(LNod));
while(s->a!=-1){
s=(LNode1*)malloc(sizeof(LNode1));
s->next=r->next;
r=s;
scanf(“%d”,&s->a);}
r->next=NULL;
}
return H1;
我们会发现这样输入数据后,-1也会被输入,这不是我们想要的结果。改!!
while(1)
{
scanf(“%d”, &(s->a));
if (s->a == -1)
{
free(s); //防止把-1写进链表里
break;
}
r->next = s;
r = s;
s = (LNode*)malloc(sizeof(LNode));
}
这样其实稍微有一点点麻烦,因为对于数据域只有数字的结点,我们可以直接判断输入的数字,而不用对s->data判断。
LinkList creat()
{
LinkList H=(LNode*)malloc(sizeof(LNode));
H->next=NULL;
LinkList r = H,s;
int a;
scanf(“%d”,&a);
while(a!=-1)
{
s=(LNode*)malloc(sizeof(LNode));
s->a=a;
r->next=s;
r=s;
scanf(“%d”,&a);
}
r->next=NULL;
return H;
}
然后删除的部分和第一道题目差不多,改动的是需要建立两个结构体指针,分别对a,b的结点寻找,记得free后让p,pre分别指向它们的下一个结点!
整体代码如下:
#include<stdio.h>
#include<malloc.h>
typedef struct data
{
int a;
struct data *next;
}LNode,*LinkList;
LinkList creat()
{
LinkList H=(LNode*)malloc(sizeof(LNode));
H->next=NULL;
LinkList r = H,s;
int a;
scanf("%d",&a);
while(a!=-1)
{
s=(LNode*)malloc(sizeof(LNode));
s->a=a;
r->next=s;
r=s;
scanf("%d",&a);
}
r->next=NULL;
return H;
}
void print(LinkList H)
{
LinkList p=H->next;
while(p)
{
printf("%d ",p->a);
p=p->next;
}
printf("\n");
}
void del(LinkList H1,LinkList H2)
{
LinkList t,p=H1->next,pre=H1;
LinkList q=H2;
while(p)
{
q=H2->next; //q每次从首结点开始找
while(q)
{
if(p->a==q->a)
{
t=p; //p就是要删的结点
pre->next=p->next;
p=pre->next; //因为可能有多组重复要删的结点 所以p一直往后走 而每次删完后从删的后一个开始走
free(t); //最好最后再释放 防止结点丢失
}
q=q->next;
}
pre=pre->next; //pre p 都往后找
p=p->next; //寻找p的下一个结点是否需要删去
}
}
int main()
{
LinkList H1=creat();
LinkList H2=creat();
//print1(H1);
//print1(H2);
del(H1,H2);
print(H1);
return 0;
}
3.删除链表中相同元素的结点。
- 已排序的链表
void del(LinkList H)
{
LNode *p,*q,*t,*pre;
p=H->next;
q=p;
while(p)
{
q=p->next; //p是q的前驱
while(q)
{
if(p->data==q->data)
{
t=q;
p->next=q->next;
free(t);
q=q->next;
}
else
{
p=q; //因为已经排序了,所以直接两两相比即可
q=q->next;
}
}
}
- 未排序的链表
void del(LinkList H)
{
LNode *p,*q,*s;
p=H;
s=p; //s用来第n次遍历时的在while(q)内部代替p向后移动 不能在里面移动头指针p p是在最外面控制遍历次数的
while(p) //将第n个结点和它之后的所有结点比较是否相同 遍历n次
{
q=p->next; //q存p的下一个结点
while(q)
{
if(p->data==q->data)
{
s->next=q->next;
free(q);
q=s->next;
}
else
{
s=q; //相当于s往后移动 因为q一直在移动
q=q->next;
}
}
p=p->next;
}
}
(注意:要用另一个变量控制每一趟遍历时内部的指针的移动)
4.删除链表中值大于x小于y的元素
void del(LinkList *H,int x,int y)
{
LinkList *p,*q,*pre;
pre=H;
p=H->next;
while(p&&p->data<y)
{
if(p->data<=x) //继续向后找
{
//pre=pre->next; 也可以
pre=p; //pre要先被赋值 然后p指向下一个结点 这时达到了pre是p前一个结点的效果
p=p->next;
}
else
{
pre->next=p->next;
q=p; //q是个中间结点 负责释放空间
free(q);
p=p->next;
}
}
}
只要掌握删除节点的方法即可
5.合并两个有序的单链表
#include<stdio.h>
#include<malloc.h>
typedef struct s
{
int x;
struct s* next;
}s,*LinkList;
void merge(LinkList HA,LinkList HB)
{
LinkList HC;
LinkList *p,*q,*r;
p=HA->next;
q=HB->next;
r=HC=HA;
while(p&&q)
{
if(p->x<=q->next)
{
r->next=p;
r=p;
p=p->next;
}
else
{
r->next=q;
r=q;
q=q->next;
}
}
if(p) r->next=p;
if(q) r->next=q;
free(HB); //将结果保存在HA中
}
6.n个人,围成圈,按1-n报号,号码是m的人退出,如此往复,直到剩最后一个人。
#include<stdio.h>
#include<malloc.h>
typedef struct node
{
int data;
struct node* next;
}node,*LinkList;
int baoshu(int m)
{
int i;
LinkList H,r,p,s;
H=(LinkList)malloc(sizeof(node));
//H->next=H;
H->data=1; //不可以为空!因为循环链表 万一指向了头结点的数据域 就为随机值了
r=H;
for(i=2;i<=m;i++)
{
s=(LinkList)malloc(sizeof(node));
s->data=i;
r->next=s;
r=s;
}
r->next=H; //尾结点指向头结点
p=r->next; //p是找到的要删的 r是p的前驱结点
int k=1; //报数
while(m>1)
{
if(k!=3)
{
k++;
r=p; //遍历
p=p->next;
}
else
{
k=1;
m--;
r->next=p->next;
free(p);
p=r->next; //p从删除的结点的下一个开始找
}
}
return (r->data); //r始终指向被删结点的前驱结点 故只剩下最后一人 r就指向这个人
}
int main()
{
int n;
scanf("%d",&n);
printf("%d",baoshu(n));
return 0;
}
需要注意的就是头结点也要放数据,不然可能最后到了这个随机值。
总结!
删除一个结点时,(我的方法)需要引入一个指针,负责free,需要引入前驱指针,它和一直遍历的指针相互配合。
t=p;
pre->next=p->next;
p=p->next;
free(t);
我认为free最后再写更保险一些
总之就是,p和pre各走各的就行,这样可以保证链表不断,总是能找到下一个结点。
祝我们都能早日拿下链表!