数据结构(初阶3.单链表)

2024-07-14 1283阅读

文章目录

一、单链表

  1.1概念与结构

  1.2结点

  1.3链表的性质

  1.4链表的打印

二、实现单链表

  SList.h

  SList.c

    申请新节点

    尾插

    头插

    尾删

    头删

    在指定位置之前插入数据

    在指定位置之后插入数据

    删除指定位置结点

    删除指定位置之后的结点

    销毁结点

  test.c


一、单链表

1.1概念与结构

概念:链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 我们可以用火车类比一下: 数据结构(初阶3.单链表) 淡季时⻋次的⻋厢会相应减少,旺季时⻋次的⻋厢会额外增加⼏节。只需要将⽕⻋⾥的某节⻋厢去掉/ 加上,不会影响其他⻋厢,每节⻋厢都是独⽴存在的。 在链表⾥,每节“⻋厢”是什么样的呢? 数据结构(初阶3.单链表)

1.2结点

与顺序表不同的是,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“结点/结点” 结点的组成主要有两个部分:当前结点要保存的数据和保存下⼀个结点的地址(指针变量)。 图中指针变量 plist保存的是第⼀个结点的地址,我们称plist此时“指向”第⼀个结点,如果我们希望 plist“指向”第⼆个结点时,只需要修改plist保存的内容为0x0012FFA0。 链表中每个结点都是独⽴申请的(即需要插⼊数据时才去申请⼀块结点的空间),我们需要通过指针变量来保存下⼀个结点位置才能从当前结点找到下⼀个结点。 (注意:在链表中,没有增容的概念,需要插入新的数据,直接申请一个结点大小的空就可以了)

1.3链表的性质

1、链式机构在逻辑上是连续的,在物理结构上不⼀定连续 2、结点⼀般是从堆上申请的 3、从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续 结合前⾯学到的结构体知识,我们可以给出每个结点对应的结构体代码:
#include
#include
//定义链表的结构
typedef int SLTDataType;      // 方便后面一键替换
typedef struct SListNode {
	SLTDataType data;         //数据类型
	struct SListNode* next;   // 下个节点的起始地址
	
	
}SLNode;

1.4链表的打印

  • 首先在头文件SList.h中定义并且在SList.中实现方法
    void SLTPrint(SLNode* phead)
    {
    	SLNode* pcur = phead;                //指向第一个结点 node1 的地址;
    	while (pcur)
    	{
    		printf("%d->", pcur->data);     // 第一次打印 node1—> data =1;
    		pcur = pcur->next;              // 1)pcur 指针变量保存第一个结点的地址
    		                                // 2)对 pcur 解引用拿到 next 指针变量中的地址(下一个结点 node2 的地址)
    										// 3)赋值给 pcur ,此时 pcur 保存的地址为 node1->next 的地址,即 pcur “指向了下一个结点(node2)” 
    	}
    	printf("NULL\n");
    }
    • 然后在测试文件 test.c 中申请节点空间
      #include"SList.h"
      void creatslist() {
      	SLNode* node1 = (SLNode*)malloc(sizeof(SLNode));
      	node1->data = 1;
      	SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));
      	node2->data = 2;
      	SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));
      	node3->data = 3;
      	SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));
      	node4->data = 4;
      	node1->next = node2;
      	node2->next = node3;
      	node3->next = node4;
      	node4->next = NULL;
      	//第一个结点的地址作为参数传递过去
      	SLNode* plist = node1;
      	SLTPrint(plist);
      }
      int main()
      {
      	creatslist();
      }

      数据结构(初阶3.单链表)

      二、实现单链表

      SList.h

      #include
      #include
      //定义链表的结构
      typedef int SLTDataType;      // 方便后面一键替换
      typedef struct SListNode {
      	SLTDataType data;         //数据类型
      	struct SListNode* next;   // 下个节点的起始地址
      	
      	
      }SLNode;
      //打印链表
      void SLTPrint(SLNode* phead)
      //头部插⼊删除/尾部插⼊删除
       void SLTPushBack(SLNode** pphead, SLTDataType x);
       void SLTPushFront(SLNode** pphead, SLTDataType x);
       void SLTPopBack(SLNode** pphead);
       void SLTPopFront(SLNode** pphead);
      //查找
       SLTNode* SLTFind(SLNode* phead, SLTDataType x);
      //在指定位置之前插⼊数据
       void SLTInsert(SLNode** pphead, SLNode* pos, SLTDataType x);
      //删除pos结点
       void SLTErase(SLNode** pphead, SLNode* pos);
      //在指定位置之后插⼊数据
       void SLTInsertAfter(SLNode* pos, SLTDataType x);
      //删除pos之后的结点
       void SLTEraseAfter(SLNode* pos);
      //销毁链表
       void SListDestroy(SLNode** pphead);

      SList.c

      申请新节点
      //申请新节点
      SLNode* SLTBuyNode(SLTDataType x)
      {
      	SLNode* node = (SLNode*)malloc(sizeof(SLNode));
      	if (node == NULL)
      	{
      		perror("malloc fail!");
      	}	
      	node->data = x;
      	node->next = NULL;
      	return node;
      }
      
      尾插

      (补充:形参的改变要影响实参,必须传实参的地址)

      举个简单的例子: 

      int a = 1;
      &a;             //取地址
      int* pa = &a;   
      &pa;            //去指针pa变量的地址
      
      void SLTPushBack(SLNode** pphead, SLTDataType x)
      {
      	assert(pphead);
      	//申请新节点
      	SLNode* newnode = SLTBuyNode(x);
      	if (*pphead == NULL)      //指向第一个节点的指针为空
      	{
      		*pphead = newnode;
      	}
      	else 
      	{
      			//找尾结点
      	SLNode* pcur = *pphead;
      	while (pcur->next)
      	{
      		pcur = pcur->next;
      	}
      	pcur->next = newnode;
      	}
      }
      •                                     第一个结点:*plist ——————> **pphead
      •                   指向第一个结点的指针: plist ——————>   *pphead
      • 指向指向第一个结点的指针的地址:&pilst——————>    pphead
        头插
        void SLTPushFront(SLNode** pphead, SLTDataType x)
        {
        	//申请新节点
        	SLNode* newnode = SLTBuyNode(x);
        	newnode->next = *pphead;               //新结点指向的是原来头结点的地址
        	*pphead = newnode;                     //新插入的结点变成头节点
        }
         尾删
        void SLTPopBack(SLNode** pphead)
        {
        	//链表为空,不可删除
        	assert(pphead && *pphead);
        	
        	//处理链表只有一个结点的情况,当只有一个结点,删除的就是头结点
        	if ((*pphead)->next == NULL)                  // *pphead 头结点指向下一个地址为空,说明只有一个结点
        	{
        		free(*pphead);
        		*pphead = NULL;
        	}
        	else
        	{	
        		SLNode* ptail = *pphead;                    //创建一个用于遍历链表的指针 ptail
        		SLNode* prev = NULL;                        //创建一个空指针 prev
        	                  
        		while (ptail->next != NULL)
        		{
        			prev = ptail;                           //将 ptail 指向下一个的结点赋值给 prev
        			ptail = ptail->next;                    // ptail 继续指向下一个几点,一直循环赋值,直到 prev 指向的下一个结点为 NULL后释放删除
        		}
        		prev->next = NULL;
        		free(ptail);
        		ptail = NULL;
        				
        	}
        }
        头删
        void SLTPopFornt(SLNode** pphead)
        {
        	//链表为空,不可删除
        	assert(pphead && *pphead);
        	SLNode* next = (*pphead)->next;                //创建指针 next 为 *phead 指向的下一个结点
        	free(*pphead);                                 //先释放
        	*pphead = next;                                //后赋值
        }
        在指定位置之前插入数据
        void SLTInsert(SLNode** pphead, SLNode* pos, SLTDataType x)
        {
        	assert(pphead);
        	assert(pos);
        	if (pos == *pphead)                             //pos 和 头结点相等,说明是在头结点前插入数据,相当于头插
        	{
        		SLTPushFront(pphead, x);
        	}
        	else
        	{
        		SLNode* newnode = SLTBuyNode(x);            //创建一个结点   
        		SLNode* prve = *pphead;                      //创建指针 pcur 遍历链表    
        		while (prve->next != pos)
        		{
        			prve = prve->next;
        		}
        		newnode->next = pos;                          //当 prve -> next = pos 也就是到达 pos 前的结点,插入 newnode ,让 newnode->mext = pos
        		prve->next = newnode;                         //零 prve->next = newnode
        	}
        }
         
        在指定位置之后插入数据
        void SLTInsertAfter(SLNode*pos, SLTDataType x)
        {
        	assert(pos);
        	SLNode* newnode = SLTBuyNode(x);            //创建一个结点 
        	newnode->next = pos->next;                  //先让 newnode 指向的下一个结点为原来 pos 指向的结点
        	pos->next = newnode;                        //再让 newnode 赋值称为 原来 pos 指向的下一个结点 
        }  

         

        删除指定位置结点
        void SLTErase(SLNode** pphead, SLNode* pos)
        {
        	assert(pphead && *pphead);                
        	assert(pos);
        	if (pos == *pphead)                         //处理链表只有一个结点的情况,当只有一个结点,删除的就是头结点
        	{
        		SLTPopFornt(pphead);
        	}
        	else
        	{
        		SLNode* prve = *pphead;                 //创建 prve 结点遍历链表
        		while (prve->next != pos)               
        		{
        			prve = prve->next;
        		}
        		prve->next = pos->next;                  //当 prve->next 的结点就是 pos 时,prve->next = pos->next(意思就是跳过pos 结点)
        		free(pos);                               //释放pos ,NULL    
        		pos = NULL;
        	}
        }
        删除指定位置之后的结点
        void SLTEraseAfter( SLNode* pos)
        {
        	assert(pos && pos->next);                    //指定位置和指定位置之后的结点不能为空
        	SLNode* del = pos->next;                     //创建 del 结点为 pos->next 的节点
        	pos->next = pos->next->next;                 //跳过 del 结点
        	free(del);                                   //释放 del ,NULL 
        	del = NULL;
        }
        销毁结点
        void SLTDestory(SLNode** pphead)
        {
        	assert(pphead && *pphead);
        	SLNode* pcur = *pphead;
        	while (pcur)
        	{
        		SLNode* next = pcur->next;
        		free(pcur);
        		pcur = next;
        	}
        	*pphead = NULL;
        }

        test.c

        #include"SList.h"
        void SListtest()
        {
        	SLNode* plist = NULL;
        	SLTPushBack(&plist, 4);
        	SLTPushBack(&plist, 3);
        	SLTPushBack(&plist, 2);
        	SLTPushBack(&plist, 1);
        	printf("尾插后的数据:");
        	SLTPrint(plist);
        	printf("\n");
        	SLTPushFront(&plist, 5);
        	printf("头插数据5   :");
        	SLTPrint(plist);
        	printf("\n");
        	SLTPopBack(&plist);
        	printf("尾删后的数据:");
        	SLTPrint(plist);
        	printf("\n");
        	SLTPopFornt(&plist);
        	printf("头删后的数据:");
        	SLTPrint(plist);
        	printf("\n");
        	SLTFind(plist, 3);
        	SLNode* find1 = SLTFind(plist, 3);
        	SLTInsert(&plist, find1, 6);
        	SLTInsertAfter(find1, 7);
        	printf("在结点数据为3之前插入结点数据6,结点数据为3之后插入结点数据7:");
        	SLTPrint(plist);
        	printf("\n");
        	SLTEraseAfter(find1);
        	SLTErase(&plist, find1);
        	printf("删除结点数据为3以及结点数据为3之后的结点:");
        	SLTPrint(plist);
        	printf("\n");
        	SLTDestory(&plist);
        	printf("销毁链表plist:");
        	SLTPrint(plist);
        	
        }
        int main()
        {
        	SListtest();
        	return 0;
        }

        数据结构(初阶3.单链表)

        未完待续~~

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]