C语言链表分析

链表的引入

从数组的缺陷说起

1、数组有两个缺陷,一个是数组中的所有元素的类型必须相同,第二个是数组的元素个数必须事先指定并且一但指定后就不能更改。
2、如何解决数组的第一个缺陷:数组的第一个缺陷靠结构体来解决。结构体允许其中的元素的类型不相同,因此解决了数组的第一个缺陷。所以说结构体是因为数组不能解决某些问题所以才发明的。
3、如何解决数组的第二个缺陷?我们希望数组的大小能够实时扩展。譬如我刚开始定了一个元素个数是10,后来程序运行时觉得不够因此动态扩展为20.普通的数组显然不行,我们可以对数组进行封装以达到这种目的;我们还可以使用一个新的数据结构来解决,这个新的数据结构就是链表
总结:几乎可以这样理解:链表就是一个元素个数可以实时变大/变小的数组。

链表是什么样子的?

1、链表就是用锁链连接起来的表。这里的表指的是一个一个的节点(一个节点就是一个房子),节点中有一些内存可以用来存储数据(所以叫表,表就是数据表);这里的锁链指的是链接各个表的方法,C语言中用来连接2个表(其实就是2块内存)的方法就是指针。
2、链表是由若干个节点组成的(链表的各个节点结构是完全类似的)节点是由有效数据和指针组成的,有效数据区域用来存储有效数据的,指针区域用于指向链表的下一个节点从而构成链表。

链表是用来干嘛的?

1、时刻谨记:链表就是用来解决数组的大小不能动态扩展的问题,所以链表其实就是当数组用的。直白点:链表能完成的任务用数组也能完成,数组能完成的任务用链表也能完成。但是灵活性不一样。
2、简单说:链表就是用来存储数据的。链表用来存数据相对于数组来说优点就是灵活性,需要多少个动态分配多少个,不占用额外的内存。数组的优势是使用简单(简单粗暴)。

单链表的实现

单链表的节点构成

1、链表是由节点组成的,节点中包含:有效数据和指针。
2、定义的struct node只是一个结构体,本身并没有变量生成,也不占用内存。结构体定义相当于为链表节点定义了一个模板,但是还没有一个节点,将来在实际创建链表时需要一个节点时用这个模板来复制一个即可。

堆内存的申请和使用

1、链表的内存要求比较灵活、不能用栈、也不能用data数据段。只能用堆内存。
2、使用堆内存来创建一个链表节点的步骤:
1)、申请堆内存,大小为一个节点的大小(检查申请结果是否正确);
2)、清理申请到的堆内存;
3)、把申请到的堆内存当作一个新节点;
4)、填充那个新节点的有效数据和指针区域。

链表的头指针

1、头指针并不是节点,而是一个普通指针,只占4字节。头指针的类型是struct node *类型的,所以它才能指向链表的节点。
2、一个典型的链表的实现就是:头指针指向链表的第1个节点,然后第1个节点中的指针指向下一个节点,然后依次类推一直到最后一个节点。这样就构成了一个链。

例题:创建一个节点

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

//结构体
struct node
{
	int data;              //有效数据区
	struct node *pnext;    //指向下一个节点的指针
};



int main(void)
{
	//头节点
	struct node *phead = NULL;
	
	//创建一个链表节点
	struct node *p = (struct node *)malloc(sizeof(struct node));
	if(NULL == p)
	{
		printf("malloc error!\n");
		return -1;
	}
	//清零
	bzero(p, sizeof(struct node));
	
	//填充
	p->data  = 1;
	p->pnext = NULL;
	
	phead = p;           //将本节点于她前面的头指针关联起来
	
	return 0;
	
}

单链表的算法之插入节点

1、只能用头指针,不能用各个节点自己的指针。因为在实际当中我们保存链表的时候是不会保存各个节点的指针的,只能通过头指针来访问链表节点。
2、前一个节点内部的pNext指针能帮助我们找到下一个节点。

将创建节点的代码封装成一个函数

1、封装时的关键点就是函数的接口(函数参数和返回值)的设计

例题:

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

struct node
{
	int data;              //有效数据区
	struct node *pnext;    //指向下一个节点的指针
};

//作用:创建一个链表节点
struct node * create_node(int data)
{
	//创建一个链表节点
	struct node *p = (struct node *)malloc(sizeof(struct node));
	if(NULL == p)
	{
		printf("malloc error!\n");
		return NULL;
	}
	//清零
	bzero(p, sizeof(struct node));
	//填充
	p->data  = data;
	p->pnext = NULL;
	printf("p->data = %d.\n", p->data);
	return p;
}
int main(void)
{
	//头节点
	struct node *phead = NULL;
	phead = create_node(1);                       //将本节点于她前面的头指针关联起来	
	phead->pnext = create_node(2);                //将本节点于她前面的头指针关联起来
	phead->pnext->pnext = create_node(3);         //将本节点于她前面的头指针关联起来
	return 0;
	
}

从链表头部插入新节点

1、插入一个节点

例题

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


struct node
{
	int data;              //有效数据区
	struct node *pnext;    //指向下一个节点的指针
};


//作用:创建一个链表节点

struct node * create_node(int data)
{
	//创建一个链表节点
	struct node *p = (struct node *)malloc(sizeof(struct node));
	if(NULL == p)
	{
		printf("malloc error!\n");
		return NULL;
	}
	//清零
	bzero(p, sizeof(struct node));
	
	//填充
	p->data  = data;
	p->pnext = NULL;
	
	printf("p->data = %d.\n", p->data);
	
	return p;
}

//尾部插入
void insert_tail(struct node *pH, struct node *new)
{
	int cnt = 0;
	//第一步,先找到链表中的最后一个节点
	struct node *p = pH;
	while(NULL != p->pnext)
	{
		p = p->pnext;        //往后走一个节点
		
		cnt++;
	}
	//第二步 将新创建的节点插入到最后一个节点的尾部
	p->pnext = new;
	
	pH->data = cnt + 1;
	
}

int main(void)
{
	//创建一个头节点
	struct node *phead = create_node(0);
	
    insert_tail(phead, create_node(1));          //打印1
	insert_tail(phead, create_node(2));          //打印2 
	insert_tail(phead, create_node(3));          //打印3
	
	printf("phead->data = %d.\n", phead->data);     //打印创建了几个节点
	
	return 0;
	
}

2、头部插入
例题:

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


struct node
{
	int data;              //有效数据区
	struct node *pnext;    //指向下一个节点的指针
};


//作用:创建一个链表节点

struct node * create_node(int data)
{
	//创建一个链表节点
	struct node *p = (struct node *)malloc(sizeof(struct node));
	if(NULL == p)
	{
		printf("malloc error!\n");
		return NULL;
	}
	//清零
	bzero(p, sizeof(struct node));
	
	//填充
	p->data  = data;
	p->pnext = NULL;
	
	printf("p->data = %d.\n", p->data);
	
	return p;
}

//头部插入
void insert_head(struct node *pH, struct node *new)
{
	//第一步:新节点的next指向原来的第一个节点
	new->pnext = pH->pnext;
	
	//第二步:头节点的next指向新节点的地址
	pH->pnext = new;
	
	//第三步:头节点中的计数加一
	
	pH->data += 1;
}

int main(void)
{
	//创建一个头节点
	struct node *phead = create_node(0);
	
	insert_head(phead, create_node(1));       //打印1
	insert_head(phead, create_node(3));       //打印3
	insert_head(phead, create_node(2));       //打印2
	
	printf("phead->data = %d.\n", phead->data);     //打印创建了几个节点
	
	return 0;
	
}

注意:
1、注意写代码过程中的箭头符号,和说话过程中的指针指向。这是两码事,容易搞混。箭头符号实际上是用指针方式来访问结构体,所以箭头符号的实质是访问结构体中的成员。更清楚一点说程序中的箭头和链表的连接没有任何关系;链表中的节点通过指针指向来连接,编程中表现为一个赋值语句(用=来进行连接),实质是把后一个节点的首地址,赋值给前一个节点中的pNext元素做为值。
2、链表可以从头部插入,也可以从尾部插入。也可以两头插入。头部插入和尾部插入对链表来说几乎没有差别。对链表本身无差别,但是有时候对业务逻辑有差别。

单链表的算法之遍历节点

什么是遍历?

1、遍历就是把单链表中的各个节点挨个拿出来,就叫遍历。
2、遍历的要点:一是不能遗漏、二是不能重复、追求效率。

如何遍历单链表?

1、分析一个数据结构如何遍历,关键是分析这个数据结构本身的特点。然后根据本身特点来制定它的遍历算法。
2、单链表的特点就是由很多个节点组成,头指针+头节点为整个链表的起始,最后一个节点的特征是它内部的pNext指针值为NULL。从起始到结尾中间由各个节点内部的pNext指针来挂接。由起始到结尾的路径有且只有一条。单链表的这些特点就决定了它的遍历算法。
3、遍历方法:从头指针+头节点开始,顺着链表挂接指针依次访问链表的各个节点,取出这个节点的数据,然后再往下一个节点,直到最后一个节点,结束返回。

例题:

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


struct node
{
	int data;              //有效数据区
	struct node *pnext;    //指向下一个节点的指针
};


//作用:创建一个链表节点

struct node * create_node(int data)
{
	//创建一个链表节点
	struct node *p = (struct node *)malloc(sizeof(struct node));
	if(NULL == p)
	{
		printf("malloc error!\n");
		return NULL;
	}
	//清零
	bzero(p, sizeof(struct node));
	
	//填充
	p->data  = data;
	p->pnext = NULL;
	
	printf("p->data = %d.\n", p->data);
	
	return p;
}
//头部插入
void insert_head(struct node *pH, struct node *new)
{
	//第一步:新节点的next指向原来的第一个节点
	new->pnext = pH->pnext;
	
	//第二步:头节点的next指向新节点的地址
	pH->pnext = new;
	
	//第三步:头节点中的计数加一
	
	pH->data += 1;
}
//链表的遍历
void bianli(struct node *pH)
{
	//pH->data;                   //头节点的数据
	
	struct node *p = pH;          //这个pH指向的是头节点
	
	//struct node *p = pH->pnext;      //p直接走到下一个节点
	while(NULL != p->pnext)
	{
		p = p->pnext;             //走到下一个节点,p->pnext存放的是下一个节点的指针
		printf("p->data = %d.\n",p->data);
	}
	
}


int main(void)
{
	//创建一个头节点
	struct node *phead = create_node(0);
	
	insert_head(phead, create_node(1));
	insert_head(phead, create_node(3));
	insert_head(phead, create_node(2));
	
	//遍历
	bianli(phead);
	
	printf("phead->data = %d.\n", phead->data);     //打印创建了几个节点
	
	return 0;
	
}

单链表的算法之删除节点

为什么要删除节点

1、一直在强调,链表到底用来干嘛的?
2、有时候链表节点中的数据不想要了,因此要删掉这个节点。

删除节点的步骤

第一步:找到要删除的节点;
第二步:删除这个节点。

如何找到待删除的节点

1、通过遍历来查找节点。从头指针+头节点开始,顺着链表依次将各个节点拿出来,按照一定的方法比对,找到我们要删除的那个节点。

如何删除一个节点

1、待删除的节点不是尾节点的情况:首先把待删除的节点的前一个节点的pNext指针指向待删除的节点的后一个节点的首地址(这样就把这个节点从链表中摘出来了),然后再将这个摘出来的节点free。
2、待删除的节点是尾节点的情况:首先把待删除的尾节点的前一个节点的pNext指针指向null(这时候就相当于原来尾节点前面的一个节点变成了新的尾节点),然后将摘出来的节点free掉。

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


struct node
{
	int data;              //有效数据区
	struct node *pnext;    //指向下一个节点的指针
};


//作用:创建一个链表节点

struct node * create_node(int data)
{
	//创建一个链表节点
	struct node *p = (struct node *)malloc(sizeof(struct node));
	if(NULL == p)
	{
		printf("malloc error!\n");
		return NULL;
	}
	//清零
	bzero(p, sizeof(struct node));
	
	//填充
	p->data  = data;
	p->pnext = NULL;
	
	printf("p->data = %d.\n", p->data);
	
	return p;
}

//尾部插入
void insert_tail(struct node *pH, struct node *new)
{
	int cnt = 0;
	//第一步,先找到链表中的最后一个节点
	struct node *p = pH;
	while(NULL != p->pnext)
	{
		p = p->pnext;        //往后走一个节点
		
		cnt++;
	}
	//第二步 将新创建的节点插入到最后一个节点的尾部
	p->pnext = new;
	
	pH->data = cnt + 1;
	
}

//头部插入
void insert_head(struct node *pH, struct node *new)
{
	//第一步:新节点的next指向原来的第一个节点
	new->pnext = pH->pnext;
	
	//第二步:头节点的next指向新节点的地址
	pH->pnext = new;
	
	//第三步:头节点中的计数加一
	
	pH->data += 1;
}


void bianli(struct node *pH)
{
	//pH->data;                   //头节点的数据
	
	struct node *p = pH;          //这个pH指向的是头节点
	
	//struct node *p = pH->pnext;      //p直接走到下一个节点
	while(NULL != p->pnext)
	{
		p = p->pnext;             //走到下一个节点,p->pnext存放的是下一个节点的指针
		printf("p->data = %d.\n",p->data);
	}
	
}

//从链表中删除节点,待删除的节点的数据区存储的是等于data
int delete_node(struct node *pH, int data)
{
	struct node *p = pH;          //这个pH指向的是头节点
	struct node *pPrev = NULL;          //这个pPrev指向的是当前节点的前一个节点
	
	while(NULL != p->pnext)
	{
		pPrev = p;                //把当前节点的指针保存起来,就是在走到下一个节点保存起来
		p = p->pnext;             //走到下一个节点,p->pnext存放的是下一个节点的指针
		if(p->data == data)
		{
			//找打就删除该节点
			if(NULL == p->pnext)
			{
				//删除的是尾节点
				pPrev->pnext = NULL;
				free(p);
			}
            else
			{
                //删除的不是尾节点
				pPrev->pnext = p->pnext; //就是把要删除的前一个节点和后一个节点相连
				free(p);
				
			}
            pH->data--;			

            return 0;
		}
		else
		{
			//删除完后就退出循环
			break;
		}
		
	}

	printf("没有找到要删除的节点\n");
	return -1;
}
int main(void)
{
	//创建一个头节点
	struct node *phead = create_node(0);
	
	insert_tail(phead, create_node(1));
	insert_tail(phead, create_node(2));
	insert_tail(phead, create_node(3));
	insert_tail(phead, create_node(4));
	
	bianli(phead);
	
	delete_node(phead,4);
	
	printf("删除前\n");
	
	bianli(phead);
	
	printf("phead->data = %d.\n", phead->data);     //打印创建了几个节点
	
	return 0;
	
}

单链表的算法之逆序

什么是链表的逆序

1、链表的逆序又叫反向,意思就是把链表中所有的有效节点在链表中的顺序给反过来。

单链表逆序算法分析

1、当我们对一个数据结构进行一个操作时,我们就需要一套算法。这就是数据结构和算法的关系。
2、我总结:算法有2个层次。第一个层次是数学和逻辑上的算法;第二次个层次是用编程语言来实现算法。
3、从逻辑上来讲,链表的逆序有很多种方法。这些方法都能实现最终的需要,但是效率是不一样的。彼此的可扩展性、容错性等不同。
4、思路:首先遍历原链表,然后将原链表中的头指针和头节点作为新链表的头指针和头节点,原链表中的有效节点挨个依次取出来,采用头插入的方法插入新链表中即可。
5、链表逆序 = 遍历 + 头插入

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


struct node
{
	int data;              //有效数据区
	struct node *pnext;    //指向下一个节点的指针
};


//作用:创建一个链表节点

struct node * create_node(int data)
{
	//创建一个链表节点
	struct node *p = (struct node *)malloc(sizeof(struct node));
	if(NULL == p)
	{
		printf("malloc error!\n");
		return NULL;
	}
	//清零
	bzero(p, sizeof(struct node));
	
	//填充
	p->data  = data;
	p->pnext = NULL;
	
	printf("p->data = %d.\n", p->data);
	
	return p;
}

//尾部插入
void insert_tail(struct node *pH, struct node *new)
{
	int cnt = 0;
	//第一步,先找到链表中的最后一个节点
	struct node *p = pH;
	while(NULL != p->pnext)
	{
		p = p->pnext;        //往后走一个节点
		
		cnt++;
	}
	//第二步 将新创建的节点插入到最后一个节点的尾部
	p->pnext = new;
	
	pH->data = cnt + 1;
	
}

//头部插入
void insert_head(struct node *pH, struct node *new)
{
	//第一步:新节点的next指向原来的第一个节点
	new->pnext = pH->pnext;
	
	//第二步:头节点的next指向新节点的地址
	pH->pnext = new;
	
	//第三步:头节点中的计数加一
	
	pH->data += 1;
}

//链表的遍历
void bianli(struct node *pH)
{
	//pH->data;                   //头节点的数据
	
	struct node *p = pH;          //这个pH指向的是头节点
	
	//struct node *p = pH->pnext;      //p直接走到下一个节点
	while(NULL != p->pnext)
	{
		p = p->pnext;             //走到下一个节点,p->pnext存放的是下一个节点的指针
		printf("p->data = %d.\n",p->data);
	}
	
}
//链表的逆序
void reverse_linkelist(struct node *pH)
{
	struct node *p = pH->pnext;          //p直接走到第一个有效节点
	struct node *pb;                     //这个pb指向的是当前节点的前一个节点
	
	if((NULL == p)||(NULL == p->pnext))  //判断是否只有头节点和只有一个有效节点
	{
		return;
	}
	
	while(NULL != p->pnext)        //是不是最后一个节点
	{
		pb = p->pnext;		
		if(p == pH->pnext)
		{
			//原链表的第一个有效节点
			p->pnext = NULL;
			
		}
		else
		{
			p->pnext  = pH->pnext;
            			
		}
		pH->pnext = p;
        p = pb;  
        
	}
	insert_head(pH, p);
	
	return;
}


int main(void)
{
	//创建一个头节点
	struct node *phead = create_node(0);
	
	insert_tail(phead, create_node(1));
	insert_tail(phead, create_node(2));
	insert_tail(phead, create_node(3));
	insert_tail(phead, create_node(4));
	
	bianli(phead);
	
	reverse_linkelist(phead);
	
	printf("删除前\n");
	
	bianli(phead);
	
	printf("phead->data = %d.\n", phead->data);     //打印创建了几个节点
	
	return 0;
	
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值