c语言数据结构:单链表

c语言数据结构:单链表
在这里插入图片描述

***各位朋友们,伙计们,大家好!***
 第一次在CSDN里发帖,新人请大家多多关照,那么如标题看到,今天要说的就是单链表,我将用尽我所学,来帮助大家理解单链表的各种操作,创建链表、创建结点、插入结点、查询结点、修改结点数据、删除结点、输出链表、销毁链表,我会尽量讲的生动有趣些,不会让大家感到枯燥乏味,那么话不多说,就让我们开始学习吧! 
01
创建链表
 链表链表,顾名思义,就是像链子一样连在一起的表,咳咳,虽说有些废话,但确实如此,它是结构体与结构体之间以指针互相连接着,上一个结构体的指针指向下一个结构体,这样就形成了一个链表,它相对于数组有什么好处呢,大家都知道,数组内的内存空间是预先分配好的,而且也不能在使用过程中动态的改变其空间大小,这样就会有一个很大的弊端,比如给一个int型数组分配了100个内存空间,那么它就只能最多存100个数据,超过了就完蛋,存少了还浪费内存,就很烦,然而链表就没有这个问题,你有多少数据就开辟多少内存空间,不要某个数据还可以释放掉其内存空间,而且支持多个不同类型的数据在一块,多好!所以,学会了链表,数组只能躲在一边默喊mmp。
咳咳,话不多说,上代码! 
#include <stdio.h>
#include <stdlib.h> //此头文件包函malloc动态申请内存函数
//创建一个名为list的结构体
typedef struct list
{
  //结构体数据,你想多整几个也行,我在这只放了一个,不是我想偷懒啊
  int data; 
  //弄一个指向下一个结点的结构体的尾指针
  struct list* next;
}L; //用typedef让L来代替struct list,节约点按键盘次数//创建链表
//L* 为此函数的类型为L*也就是struct list*,返回结构体函数指针
L* creatorList()
{
  //定义一个结构体指针并分配内存空间,分配大小为此结构体所占内存大小
  L* headlist = (L*)malloc(sizeof(L));
  //表头只是起到一个引导作用,所以不用给其赋值
  //刚创建的链表为空,所以需要让表头的尾结点指向NULL
  headlist->next = NULL;
  //返回创建好的链表
  return headlist;
}

02
创建结点
 既然链表已经创建好了,那么接下来就是创建结点,结点里存放我们数据,结点的创建跟上面的操作类似,如果这块有啥问题可以直接来问我就可以了。
话不多说,上代码! 
//创建结点
L* creatorNode(int data)
{
  //跟之前创建链表的时候相同,结点也需要申请内存空间
  L* newNode = (L*)malloc(sizeof(L));
  //不过,之前创建的链表只有表头,只起到了引导作用
  //所以,结点里面就可以放数据了
  newNode->data = data;
  //将结点的尾指针指向NULL
  newNode->next = NULL;
  //返回结点
  return newNode;
}

03
插入结点
 结点的插入方式好几种,在此我向大家展示三种结点插入方法:尾插法、头插法、排序插法。大家可要仔细看好理解了啊,如果有啥不清楚的可以直接给在评论区发表问题!
头插法:
//函数参数为要插入的链表和要插入其中的结点
void insertNode(L* head,L* newNode)
{
  //首先先让新结点的尾指针指向头结点所指的结点
  newNode->next = head->next;
  //再让头结点指向新结点
  head->next = newNode;
}

尾插法:

void insertNode(L* head,L* newNode)
{
  //首先再定义一个指针指向链表的头结点
  L* pMove = head; //此做法是让head一直是链表头结点
  while(pMove->next!=NULL) //判断pMove是否移动到了链表的最后一个结点
    pMove = pMove->next; //使pMove移动至下一个节点
  //循环完毕之后表示pMove已经指向了链表的最后一个结点
  pMove->next = newNode; //将pMove,即尾结点的尾指针指向新结点
}

排序插法:

void insertNode(L* head,L* newNode)
{
  L* pMove = head; //此做法是让head一直是链表头结点
  //判断pMove是否移动到了尾结点
  while(pMove->next!=NULL)
  {
    //判断pMove所指结点的数据的数是否小于新结点的数据,然后再判
    //断pMove所指结点的尾指针所指的结点的数据是否大于新结点数据
    if(pMove->data<newNode->data&&pMove->next->data>newNode->data)
    {
      //条件成立,此下操作就跟头插法差不多
      //让新结点的尾指针指向pMove所指结点的尾指针所指的结点
      newNode->next = pMove->next;
      //让pMove所指结点的尾指针指向新结点
      pMove->next = newNode;
      //插入完成,return返回
      return;
    }
    //让pMove指向下一个结点
    pMove = pMove->next;
  }
  //循环完毕后,此时pMove为链表尾结点,让pMove所指结点的尾指针指向新结点
  pMove->next = newNode;
}

04
查询结点
 大家用过数组吧,其实链表的查询方法就和数组差不多,比如我想在数组里找一个数据,我会用循环来让数组里的元素和我要查找的数一一比较,直到找到为止,而链表也是如此,不过只是将数组元素换成了链表结点。
上代码!!! 
void queryNode(L* head,int data)
{
  //定义一个整形i的目的是显示其所查询的数据在第几个结点
  int i=1;
  L* pMove = head->next;
  while(pMove)
  {
    //判断数据是否匹配
    if(pMove->data==data)
    {
      //输出查找到的结点数据和它所在结点位置
      printf("已找到数据:%d,它在第%d结点",pMove->data,i);
      //已查找到,return返回,不让继续执行下面程序
      return;
    }
    i++;
    pMove = pMove->next;
  }
  //循环结束表示没有找到
  printf("未找到匹配数据\n");
}

05
修改结点数据
 修改结点数据首先要确定所想要修改的结点,这里所定义结构体里只放了一个整型数据,所以只需要先在链表中找到这个数据所在的结点,然后就可以修改这个节点里的数据(如果结构体里的有好几个成员变量,那么只需其中一个成员变量与输入相同就表示找到了要找的结点)。
上代码!!! 
void changeNode(L* head,int data)
{
  L* pMove = head->next;
  while(pMove)
  {
    //如果数据匹配
    if(pMove->data==data)
    {
      printf("请输入修改后的数据:");
      scanf("%d",&data);
      //将此结点数据修改
      pMove->data = data;
      printf("修改完成!\n");
      return;
    }
    pMove = pMove->next;
  }
  //循环结束表示没有找到匹配结点
  printf("未找到匹配结点\n");
}

06
删除结点
 话不多说,上代码!!! 
void deteleNode(L* head,int data)
{
  L* pMove = head;
  //定义一个结构体指针,指向pMove所指结点的下一个节点
  L* dpMove = pMove->next;
  while(dpMove)
  {
    //如果结点数据匹配
    if(dpMove->data==data)
    {
      //这里的操作就跟头插入差不多
      //让pMove所指结点的尾指针指向dpMove所指结点的尾指针所指的结点
      pMove->next = dpMove->next;
      //释放dpMove所指的结点内存
      free(dpMove);
      printf("删除完成!\n");
      return;
    }
    pMove = pMove->next;
    dpMove = pMove->next;
  }
  //循环结束表示没有找到匹配结点
  printf("未找到匹配结点\n");
}

07
输出链表
 上代码!!! 
void printlist(L* head)
{
  //让PMove指向头结点的下一个结点
  L* pMove = head->next;
  //如果PMove所指结点为NULL,则表示其链表为空
  if(pMove==NULL)
  {
    printf("链表为空\n");
    return;
  }
  //这里的头是表示头结点,它起一个引导作用
  printf("头");
  //当pMove不为NULL,同不为0时
  while(pMove)
  {
    //输出结点数据
    printf("->%d",pMove->data);
    //让pMove指向下一个结点
    pMove = pMove->next;
  }
  //结点输出完输出NULL的意义为,能更直观的看到链表的结构
  printf("->NULL\n");
}

08
销毁链表
 可能少数的读者会问了,为什么要销毁链表呢?我不销毁链表程序执行也没啥问题呀。
 哼哼,年轻人,我只想说,你只是数据比较少而已,如果非常多的话,你的电脑非得卡死崩了不成,首先要知道啊,我们的运行内存分为代码区、全局区、栈区、堆区这四大区,代码区用来存放加载你所写的代码,全局区也称静态区,用来存放全局变量和静态变量,栈区用来存放局部变量,堆区就是malloc开辟内存的区域,前三区开辟的内存空间它们很懂事,知道自己收拾自己的烂摊子(自动释放内存),而堆区不一样,需要你自己手动去释放,如果你不去自己释放的话,它就会一直在那里,直到你电脑重启为止,这种类似的事件就比如:你手机或电脑突然卡得很,然后你重启一下,就又好了。 
OK,上代码!!!
尾删法:
void detelelist(L* head)
{
  L* pMove;
  L* detele;
  while(1)
  {
    pMove = head;
    //如果下面的条件成立,表示链表为空,跳出循环
    if(pMove->next==NULL)
      break;
    //判断pMove所指结点的尾指针所指的尾指针所指的结点是否为NULL
    //此做法是为了给detele留个位位好释放它所指的尾结点
    while(pMove->next->next!=NULL)
      pMove = pMove->next;
    //此时pMove所指的结点的尾指针所指的结点为尾结点
    //让detele指向它!!!
    detele = pMove->next;
    //让pMove所指的结点的尾结点指向detele所指结点的尾指针所指,即NULL
    pMove->next = detele->next;
    //释放它!!!
    free(detele);
    //每释放一次输出一次链表
    printlist(head);
  }
  //此时链表为空,将头结点指针置为NULL
  head = NULL;
}

头删法:

void detelelist(L* head)
{
  L* pMove = head->next;
  while(pMove)
  {
    //让头结点的尾指针指向pMove所指结点的尾指针所指的结点
    head->next = pMove->next;
    //釈放する!!(释放它!!)
    free(pMove);
    //让pMove指向下一个结点
    pMove = head->next;
    //每释放一个结点输出一次链表
    printlist(head);
  }
  //此时链表为空,将头结点指针置为NULL
  head = NULL;
}

好了,以上就是单链表操作的全部内容了,下面请欣赏完整代码。
#include <stdio.h>
#include <stdlib.h>
typedef struct list
{
	int data;
	struct list* next;
}L;

L* creatorList()
{
	L* headlist = (L*)malloc(sizeof(L));
	headlist->next = NULL;
	return headlist;
}

L* creatorNode(int data)
{
	L* newNode = (L*)malloc(sizeof(L));
	newNode->data = data;
	newNode->next = NULL;
	return newNode;
}

void insertNode(L* head,L* newNode)
{
	L* pMove = head;
	while(pMove->next!=NULL)
	{
		if(pMove->data<newNode->data&&pMove->next->data>newNode->data)
		{
			newNode->next = pMove->next;
			pMove->next = newNode;
			return;
		}
		pMove = pMove->next;
	}
	pMove->next = newNode;
}

void queryNode(L* head,int data)
{
	int i=1;
	L* pMove = head->next;
	while(pMove)
	{
		if(pMove->data==data)
		{
			printf("已找到数据:%d,它在第%d结点",pMove->data,i);
			return;
		}
		i++;
		pMove = pMove->next;
	}
	printf("未找到匹配数据\n");
}

void changeNode(L* head,int data)
{
	L* pMove = head->next;
	while(pMove)
	{
		if(pMove->data==data)
		{
			printf("请输入修改后的数据:");
			scanf("%d",&data);
			pMove->data = data;
			printf("修改完成!\n");
			return;
		}
		pMove = pMove->next;
	}
	printf("未找到匹配结点\n");
}

void deteleNode(L* head,int data)
{
	L* pMove = head;
	L* dpMove = pMove->next;
	while(dpMove)
	{
		if(dpMove->data==data)
		{
			pMove->next = dpMove->next;
			free(dpMove);
			printf("删除完成!\n");
			return;
		}
		pMove = pMove->next;
		dpMove = pMove->next;
	}
	printf("未找到匹配结点");
}

void printlist(L* head)
{
	L* pMove = head->next;
	if(pMove==NULL)
	{
		printf("链表无数据\n");
		return;
	}
	printf("头");
	while(pMove)
	{
		printf("->%d",pMove->data);
		pMove = pMove->next;
	}
	printf("->尾\n");
}

void detelelist(L* head)
{
	L* pMove = head->next;
	while(pMove)
	{
		head->next = pMove->next;
		free(pMove);
		pMove = head->next;
		printlist(head);
	}
	head = NULL;
}

void main()
{
	L* list = creatorList();
	L* newNode;
	int data;
	while(1)
	{
		printf("请输入结点数据(0为停止输入):");
		scanf("%d",&data);
		if(data==0)
			break;
		newNode = creatorNode(data);
		insertNode(list,newNode);
		printf("输出链表:\n");
		printlist(list);
	}
	printf("请输入所要查找的数据:");
	scanf("%d",&data);
	queryNode(list,data);
	printf("\n请输入所要修改的结点数据:");
	scanf("%d",&data);
	changeNode(list,data);
	printlist(list);
	printf("请输入所要删除的结点:");
	scanf("%d",&data);
	deteleNode(list,data);
	printlist(list);
	getchar();getchar();
	printf("删除链表数据:\n");
	detelelist(list);
}

运行测试
 代码里插入结点函数我用了排序插入法,销毁链表函数我用了头删法,大家可以使用上面的其他方法,希望此篇文章能对正在学习单链表的码农们能有所帮助,欢迎在评论区里讨论,谢谢大家!!! 


至此,所有单链表操作完结!!撒花!!
  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值