关于链表的基础题目

一.题目一览

二.问题及改进

三.总结

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各走各的就行,这样可以保证链表不断,总是能找到下一个结点。

祝我们都能早日拿下链表!
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值