(二)链表——单向链表

1.  链表概述

1)概念

        数据链式结构按照顺序存储的方式

2)优缺点

        ① 优点:插入和删除比较方便

        ② 缺点:查找和替换麻烦

TIPS:刚好与顺序表相反

3)操作

        增删改查(遵循大原则:先练后断)

2.  函数声明部分

        单向链表是只能从头往后走的一个链表,定义单向链表结构体如下:

//单向链表结构体
typedef  struct link{
    datatype data;        //数据域
    struct link *next;    //指针域
}link_t;

创建函数声明文件link.h

#ifndef __LINK_H__
#define __LINK_H__

#include <stdio.h>
#include <stdlib.h>

/*单向链表*/

typedef int datatype;

typedef struct link{
	datatype data;      //数据域
	struct link *next;  //指针域           
}link_t;

link_t *link_init(void);                   //初始化

void link_insert_head(link_t *head,datatype data);    //头插

void link_insert_tail(link_t *p,datatype data);        //尾插

void link_del(link_t *p,datatype data);                //删除

void link_replace(link_t *p,datatype old,datatype new);    //替换

void display(link_t *p);        //遍历展示

#endif

3.  函数实现部分

创建文件link.c

 1)初始化

        单向链表的初始化包括创建头节点(一般不存数据),以及创建后序存储数据的节点,代码如下:

/***************************
	@brief:	初始化(创建头结点)
	@para:		无
	@retval:	结构体指针
***************************/
link_t *link_init(void)
{
    /*在堆中申请空间*/
    link_t *p = (link_t *)malloc(sizeof(link_t));
    if(p == NULL)
    {
        perror("malloc error");
        return NULL;
    }

    /*将next赋值为NULL*/
    p->next = NULL;
    
    return p;
}

/***************************
	@brief:	创建一个新的节点
	@para:		数据域的数据	
	@retval:	结构体指针
***************************/
static link_t *create_node(datatype d)
{
    /*在堆中申请空间*/
    link_t *p = (link_t *)malloc(sizeof(link_t));
    if(p == NULL)
    {
        perror("malloc error");
        return NULL;
    }

    /*将next赋值为NULL*/
    p->data = d;
    p->next = NULL;

    return p;
}

2)插入操作

        插入操作分为头插法以及尾插法。头插法是指将新节点插到头节点的后面,如输入1,2,3,4,5正序遍历输出的结果为5,4,3,2,1。而尾插法则相反,其将新节点插入到尾结点后面,同样的输入,而输出的结果为1,2,3,4,5。

        插入操作的函数如下:

/***************************
	@brief:	插入的功能函数,一个节点node插在另一个节点p的后面
	@para:		数据域的数据	
	@retval:	结构体指针
***************************/
static void insert_behind(link_t *p,link_t *node)
{
    /*遵循大原则:先连后断*/
    node->next = p->next;        //先将要插入的节点的next指向p节点的下一个节点(先连)
    p->next = node;              //再将p节点的next指向新插入的节点node(后断)
}

/***************************
	@brief:    头(被)插	
	@para:		数据域的数据	
	@retval:	结构体指针
***************************/
void link_insert_head(link_t *head,datatype data)
{
    /*创建节点*/
    link_t *node = creat_node(data);
    if(node == NULL)
    return;
}
    /*调用插入功能函数*/
    insert_behind(head,node);
}

/***************************
	@brief:    尾(被)插	
	@para:		数据域的数据	
	@retval:	结构体指针
***************************/
void link_insert_tail(link_t *p,datatype data)
{
    /*创建节点*/
    link_t *node = create_node(data);
    if(node == NULL)
    return;

    /*遍历链表找到尾结点*/
    while(p-next != NULL)
        p=p->next;

    /*调用插入功能函数*/
    insert_behind(p,node);
}

        通过画图来直观的理解链表插入的过程,依旧举商场货柜的例子,有一天柜员觉得用顺序表那种插入方法太麻烦了,因此想到了一种效率更高的方法。只需增加一个新的货柜放在前面或者后面就行了,这样就提高了效率。

        链表的插入操作必须要遵循先连后断的原则,否则会导致链表断裂,造成数据的丢失,因此在新的节点插入时,如果用头插法先将其的next指针指向头节点的下一个节点,后再将头节点的next指向新插入的节点。

若使用尾插,则比头插多了一步遍历找到最后一个节点的操作。

3) 剪切操作

        剪切操作指的是把链表中的某个节点“拿”出来,再编程中我们通常剪切p后面的节点,函数实现如下:

static link_t *cut_behind(link_t *p)
{
    /*剪切时p和p->next不能为NULL*/
    if(p == NULL || p->next == NULL)
        return NULL;

    /*保存需要剪切的节点*/
    link_t *node = p->next;
    
    /*将p的next指向node的next*/
    p->next = node->next;

    /*为了安全,让node的next指向NULL*/
    node->next = NULL;

    return node;
}

        将西瓜节点剪切出来,画图如下

4) 删除操作

        删除操作是在剪切操作的基础上,遍历查找所需要的节点,然后将其剪切出来,用free()将剪切出来的节点释放。代码如下

/***************************
	@brief:    删除
	@para:		数据域的数据	
	@retval:	结构体指针
***************************/
void link_del(link_t *p,datatype data)
{
    link_t *node;
    /*遍历*/
    while(p->next != NULL)
    {
        /*查找比对*/
        if(p->next->data == data)
        {
            /*将需要的节点剪切出来*/
            node = cut_behind(p);
            if(node == NULL)
                return;

            /*释放剪切出来的节点*/
            free(node);

            continue;
        }
        //继续移动去遍历后续可能存在的符合条件数据,如果只需删除一个则continue改为break。
        p = p->next;
    }
}

5)替换操作

        替换操作指的时在链表中,将某个节点替换成另一个新的节点。函数实现代码如下:

/***************************
	@brief:    替换	
	@para:		节点的结构体指针	
	@para:		旧数据	
	@para:		新数据	
	@retval:	结构体指针
***************************/
void link_replace(link_t *p,datatype old,datatype new)
{
    link_t * old_node = NULL;
    link_t * new_node = NULL;

    /*遍历*/
    while(p->next != NULL)
    {
        /*匹配数据*/
        if(p->next->data == old)
        {
            /*保存旧节点*/
            old_node = p->next;
   
            /*创建新节点*/
            new_node = create_node(new);
            if(new_node == NULL)
                return;

            /*新节点替换旧节点*/
            new_node->next = old_node->next;
            p->next = new_node;

            /*释放旧节点*/
            old_node->next = NULL;
            free(old_node);

            continue;

        }
        p = p->next;
    }
}

用画图来理解:

6)遍历展示

        通过遍历操作将运行结构展示出来,通过从头节点遍历,依次将节点的数据域data进行输出,函数实现代码如下:

/***************************
	@brief:    遍历展示	
	@para:		数据域的数据	
	@retval:	结构体指针
***************************/
void display(link_t *p)
{
    printf("遍历结果为:\n")
    while(p-next != NULL)
    {
        p = p->next;
        printf("%d ->",p->data);
    }
    printf("\n");
}
        

4.  main函数实现

创建文件main.c

#include "link.h"

int main(void)
{
	/*初始化头节点head*/
	link_t *head = link_init();
	if(head == NULL)
	{
		return -1;
	}

	datatype d;
	int ret;

    //头插
	printf("开始头插\n");
	while(1)
	{
		ret = scanf("%d",&d);
		if(ret == 0)
		  break;

		/*插入数据*/
		link_insert_head(head,d);
		display(head);
	}

	getchar();
    
    //尾插
	printf("开始尾插\n");
	while(1)
	{
		ret = scanf("%d",&d);
		if(ret == 0)
		  break;

		/*插入数据*/
		link_insert_tail(head,d);
		display(head);
	}

	getchar();
    
    //删除
	printf("开始删除\n");
	while(1)
	{
		ret = scanf("%d",&d);
		if(ret == 0)
		  break;

		/*删除数据*/
		link_del(head,d);
		display(head);
	}
	getchar();

    //替换
	datatype old,new;
	printf("开始替换\n");
	while(1)
	{
		ret = scanf("%d %d",&old,&new);
		if(ret == 0)
		  break;

		/*替换数据*/
		link_replace(head,old,new);
		display(head);
	}
	return 0;
}

  • 23
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值