C语言学习之单链表整合

572 阅读12分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、单链表的实现

1.1链表的节点

节点里面有两个区域,一个数据域,一个指针域。

struct node
{
	int data;				//数据域,可以再加数据以及数据类型。
	struct node *Next;		//指向下一个节点的指针
};

struct node只是一个结构体,不占用内存,本身也没有变量生成。只是一节点的模板,在以后实际需要中直接调用这个模板。

1.2 堆内存的使用

为什么要用堆内存,前面关于内存的时候说过,堆的内存需要程序员自己释放,比较灵活。但是如果使用栈,程序自己释放的话,不够灵活,无法与我们的链表相匹配。

堆内存创建链表步骤:

  1. 申请堆内存,大小作为一个节点的大小。
  2. 清理堆内存,因为内存是脏的。否者可能会有一些乱七八糟的数据。
  3. 把申请的堆内存当做一个1新的节点。
  4. 填充新节点到数据域和指针域。

1.3 链表的初期创建

#include <stdio.h>
#include <stdlib.h>
struct node
{
	int data;				//数据域,可以再加数据以及数据类型。
	struct node *Next;		//指向下一个节点的指针
};
int main()
{
	/*为节点申请空间,现在malloc前面不加类型了,因为好像在最近的C标准中,malloc自己会转类型1,你加了反而还可能会产生影响,当然加上也没啥大问题1*/
	struct node *p = malloc(sizeof(struct node));	
	if(NULL == p)
	{
		prrintf("malloc error!!\n");
		return -1;
	}
	//清理申请的内存(一般没用,我自己也很少用)
	bzero(p,sizeof(struct node ));
	//将数据放入内存中
	p->data = 1;		//将值放入数据域中。
	p->Next = NULL;  //指向下一个节点的首地址。
}

1.4 完善链表,并赋值

#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
struct node
{
	int data;				//数据域,可以再加数据以及数据类型。
	struct node *Next;		//指向下一个节点的指针
};
int main()
{
	struct node *Head = NULL;		 //头指针
	/*为节点申请空间,现在malloc前面不加类型了,因为好像在最近的C标准中,malloc自己会转类型,你加了反而还可能会产生影响,当然加上也没啥大问题,看个人爱好吧*/
	struct node *p =malloc(sizeof(struct node));	
	Head = p;						 //将第一个节点的值赋值给头节点
	if(NULL == p)
	{
		prrintf("malloc error!!\n");
		return -1;
	}
	//清理申请的内存
	bzero(p,sizeof(struct node ));
	//将数据放入内存中
	p->data = 1;		//将值放入数据域中。
	p->Next = NULL;  //指向下一个节点的首地址。
/**************************************/
	
	struct node *p1 = malloc(struct node);	
	if(NULL == p1)
	{
		prrintf("malloc error!!\n");
		return -1;
	}
	//清理申请的内存
	bzero(p1,sizeof(struct node ));
	//将数据放入内存中
	p1->data = 2;		//将值放入数据域中。
	p->Next = p1;  //指向下一个节点的首地址。
	/***********************************************/
	struct node *p2 = malloc(struct node);	
	if(NULL == p2)
	{
		prrintf("malloc error!!\n");
		return -1;
	}
	//清理申请的内存
	bzero(p1,sizeof(struct node ));
	//将数据放入内存中
	p2->data = 3;		//将值放入数据域中。
	P1->Next = p2;  //指向下一个节点的首地址。
}

这样我们就创建了一个有3个节点的链表了。 那么我们来简单访问一下这节链表


#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
struct node
{
	int data;				//数据域,可以再加数据以及数据类型。
	struct node *Next;		//指向下一个节点的指针
};
int main()
{
	struct node *Head = NULL;		 //头指针
	/*为节点申请空间,现在malloc前面不加类型了,因为好像在最近的C标准中,malloc自己会转类型,你加了反而还可能会产生影响,当然加上也没啥大问题,看个人爱好吧*/
	struct node *p = malloc(sizeof(struct node));
	Head = p;						 //将第一个节点的值赋值给头节点
	if(NULL == p)
	{
		printf("malloc error!!\n");
		return -1;
	}
	//清理申请的内存
	bzero(p,sizeof(struct node ));
	//将数据放入内存中
	p->data = 1;		//将值放入数据域中。
	p->Next = NULL;  //指向下一个节点的首地址。
/**************************************/
	
	struct node *p1 = malloc(sizeof(struct node));
	if(NULL == p1)
	{
		printf("malloc error!!\n");
		return -1;
	}
	//清理申请的内存
	bzero(p1,sizeof(struct node ));
	//将数据放入内存中
	p1->data = 2;		//将值放入数据域中。
	p->Next = p1;  //指向下一个节点的首地址。
	/***********************************************/
	struct node *p2 = malloc(sizeof(struct node));
	if(NULL == p2)
	{
		printf("malloc error!!\n");
		return -1;
	}
	//清理申请的内存
	bzero(p2,sizeof(struct node ));
	//将数据放入内存中
	p2->data = 3;		//将值放入数据域中。
	p1->Next = p2;  //指向下一个节点的首地址。
	/*****************************************************/
	/****访问******/
	printf("node p data = %d\n",p->data);
	printf("node p1 data = %d\n",p->Next->data);
	printf("node p2 data = %d\n",p->Next->Next->data);
}

在这里插入图片描述

1.5 链表优化

刚才的代码看起来繁杂臃肿,因为我们刚才代码重复运用了开辟空间这个函数,我们可以把他封装起来,然后调用就好了。后期我们都需要改善代码,所以我们需要这里先完善一下

#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
struct node
{
	int data;				//数据域,可以再加数据以及数据类型。
	struct node* Next;		//指向下一个节点的指针
};
struct node *create(int data)
{
	struct node* p = malloc(sizeof(struct node));		
	if (NULL == p)
	{
		printf("malloc error!!\n");
		return NULL;
	}
	bzero(p, sizeof(struct node));
	p->data = data;		
	p->Next = NULL;  
	return  p;
}	
int main()
{
	struct node* Head = NULL;
	Head = create(1);
	Head->Next = create(2);
	Head->Next->Next = create(3);
	/****访问******/
	printf("node p data = %d\n", Head->data);
	printf("node p1 data = %d\n", Head->Next->data);
	printf("node p2 data = %d\n", Head->Next->Next->data);
}

这里我们先用头节点去一一访问,如果不用头节点去访问,那么我们的链表就没有任何意义,因为链表的作用就是通过上一个节点去访问下一个节点。

二、链表基本操作

2.1 链表的插入

2.1.1 尾插法

尾部插入较简单,因为不用去更改前面的数据。 我们先看一幅图,可以方便我们理解一下链表的插入: 在这里插入图片描述 虽然图丑,但是还是很明白的,我们需要一个头结点,来方便我们找到链表的第一个节点,头结点不算入链表长度内。 尾插法第一步就是找到尾巴,然后把尾节点的指向空改为指向新节点的数据域,把新节点的指针域指向空。

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

struct node
{
	int data;
	struct node* Next;
};
 /*构建节点,开辟空间*/
struct node *create_node(int data)
{
	struct node* p = malloc(sizeof(struct node));
	if (NULL == p)
	{
		printf("malloc error!\n");
		return - 1;
	}
	p->data = data;
	p->Next = NULL;
}
 /*尾插法函数*/
void insert_tail(struct node *Head,struct node *New)
{
	
	struct node *p = Head;	//避免产生误会
    /*遍历链表找到尾节点*/
    while(NULL != p->Next)
    {
		p = p->Next;
    }
    /*找到尾节点后,改变他指针域的指向*/
    p->Next = New;
    New->Next = NULL;
}
int main()
{
	struct node* Head = create_node(0); 		//头结点,方便我们找到链表的第一个节点
     /*尾插法通过头节点插入*/
	insert_tail(Head,create_node(1));
	insert_tail(Head,create_node(2));
   insert_tail(Head,create_node(3));
    /*打印头结点的值*/
	printf("P1 = %d\n",Head->data);
    /*打印第一个点的值*/
	printf("P2 = %d\n", Head->Next->data);
    /*打印第二个点的值*/
	printf("p3 = %d\n", Head->Next->Next->data);
    /*打印第三个点的值*/
   printf("p4 = %d\n", Head->Next->Next->Next->data);
	system("pause");
	return 0;
}

2.1.2头插法

头插法就是不停的新节点作头结点插入。 同样的先看图: 在这里插入图片描述

这个顺序很重要,先把现在新节点的指针域指向原来的第一个节点,再把原来的头结点指向新的数据域。

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


struct node
{
	int data;
	struct node* Next;
};
 /*构建节点,开辟空间*/
struct node *create_node(int data)
{
	struct node* p = malloc(sizeof(struct node));
	if (NULL == p)
	{
		printf("malloc error!\n");
		return - 1;
	}

	p->data = data;
	p->Next = NULL;
}
 /*头插法函数*/
void insert_hand(struct node *Head,struct node *New)
{
	 /*头插法不需要遍历直接找第一个节点就完事
     先把新节点的指针指向原来的第一个节点*/
     New->Next = Head->Next;
     /*原来的头结点指向现在的新节点*/
     Head->Next = New;
}
int main()
{
	struct node* Head = create_node(0); 		//头结点,方便我们找到链表的第一个节点
     /*尾插法通过头节点插入*/
	insert_hand(Head,create_node(1));
	insert_hand(Head,create_node(2));
    insert_hand(Head,create_node(3));
    /*打印头结点的值*/
	printf("P1 = %d\n",Head->data);
    /*打印第一个点的值*/
	printf("P2 = %d\n", Head->Next->data);
    /*打印第二个点的值*/
	printf("p3 = %d\n", Head->Next->Next->data);
    /*打印第三个点的值*/
    printf("p4 = %d\n", Head->Next->Next->Next->data);
	system("pause");
	return 0;
}

在这里插入图片描述

这里打印了头结点的值,你打印链表的话可以不用在意这个。

三、链表的遍历

什么是遍历:就相当于你从篮子里面把鸡蛋一个一个的提出来,不能一次拿两个,你也不能把同一个鸡蛋拿两次,按照一定的规律拿,先拿第一层,再拿第二层。 对于链表来说,就是把节点挨个拿出来。 对于数组来说,就是把数组的第一个到最后一个挨个拿出来。 遍历的要点: 1)不能遗漏 2)不能重复 3)追求效率(别人只要10s你来个一分钟,完都完了)

如何遍历: 你想要遍历链表,首先你得找到头和尾吧,你要知道之间的关系,然后从头到尾,链表的尾节点特征就是他的指针域指向的是空。 链表就这个意思,我们直接写代码搞。

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

struct node
{
	int data;
	struct node* Next;
};
 /*构建节点,开辟空间*/
struct node *create_node(int data)
{
	struct node* p = malloc(sizeof(struct node));
	if (NULL == p)
	{
		printf("malloc error!\n");
		return - 1;
	}
	p->data = data;
	p->Next = NULL;
}
 /*尾插法函数*/
void insert_tail(struct node *Head,struct node *New)
{
	
	struct node *p = Head;	//避免产生误会
    /*遍历链表找到尾节点*/
    while(NULL != p->Next)
    {
		p = p->Next;
    }
    /*找到尾节点后,改变他指针域的指向*/
    p->Next = New;
    New->Next = NULL;
}
 /*头插法函数*/
void insert_hand(struct node *Head,struct node *New)
{
	 /*头插法不需要遍历直接找第一个节点就完事
     先把新节点的指针指向原来的第一个节点*/
     New->Next = Head->Next;
     /*原来的头结点指向现在的新节点*/
     Head->Next = New;
}
/*遍历链表*/
void bianli(struct Node *Head)
{
	struct node* p = Head;
	while (NULL != p->Next)
	{
		p = p->Next; //一个一个节点往后移动往后移动;
		printf("node data = %d \n",p->data);//打印每一个节点
	}
}
int main()
{
	struct node* Head = create_node(0); 		//头结点,方便我们找到链表的第一个节点
     /*尾插法通过头节点插入*/
	insert_tail(Head,create_node(1));
	insert_tail(Head,create_node(2));
  insert_tail(Head,create_node(3));
	bianli(Head);
	system("pause");
	return 0;
}

在这里插入图片描述

其实这里有个关键点: 在这里插入图片描述

想一下为什么p = p->Next;要写在打印的前面。如果写在后面会造成什么影响。

在这里插入图片描述 这是不是将我们的头结点打印出来了。但是我们的最后一个节点没有打印出来。其实这就是一点,那个链表有无头节点指向第一个指针。

四、链表节点删除

链表删除的步骤: 1)遍历链表,不然你怎么找到对应的链表呢 2)和链表节点进行判断,如果一样,那就找到了,然后删除 3)判断是不是尾节点。 4)使用一个节点,指向我们遍历节点的前一个节点,这样方便我们进行操作 5)释放删除的节点的空间,否则造成内存泄漏 直接上代码

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

struct node
{
	int data;
	struct node* Next;
};
 /*构建节点,开辟空间*/
struct node *create_node(int data)
{
	struct node* p = malloc(sizeof(struct node));
	if (NULL == p)
	{
		printf("malloc error!\n");
		return NULL;
	}
	p->data = data;
	p->Next = NULL;
	return p;
}

/*删除节点*/
void deletNode(struct Node* Head, int nData)
{
	//遍历链表
	struct node* p = Head;
	//定义一个节点
	struct node* pPrev = NULL;
	while (NULL != p->Next)
	{
		pPrev = p;//节点指向遍历节点的前一个
		p = p->Next;

		//找节点,找到了
		if (p->data == nData)
		{
			//判断是不是尾节点
			if (NULL != p->Next)
			{
				pPrev->Next = NULL;
				free(p);
			}
			else
			{
				pPrev->Next = p->Next;
				free(p);
			}
            printf("删除完毕\n");
			return 0;
		}
	}
    printf("没有找到节点\n");
	return -1;
}


在这里插入图片描述

五、单链表的逆序输出

连表逆序输出步骤 1)判断连表是不是为空或则只有一个节点 2)遍历之前的链表 3)将之前的链表用头插法不停的插入一个新的链表中 4)遍历新的链表

在这里插入图片描述我们看代码

void reverseLink(struct node* Head)
{
	struct node* p = Head->Next; //第一个节点
	struct node* pBack;
	//如果只有一个节点或没有节点
	if ((NULL == p) || (NULL == p->Next))
		return;
	while (NULL != p->Next)
	{
		pBack = p->Next; //把原来的尾节点变为现在第一个节点
		if (p == Head->Next) 
		{
			p->Next = NULL;
		}
		else
		{
			p->Next = Head->Next;
		}
		Head->Next = p;
		p = pBack;
	}
	insert_hand(Head,p);
}

在这里插入图片描述

六、完整的代码

因为这篇文章写了好久,因为中间忙,面试加上自己工作,确实没时间写,所以代码可能有点前后不一,还有就是用的编译器不一样,导致出现了一些问题,这里附上完整的代码

#include <stdio.h>
#include <stdlib.h>
/*定义结构体*/
struct node
{
	int data;
	struct node* Next;
};
 /*构建节点,开辟空间*/
struct node *create_node(int data)
{
	struct node* p = malloc(sizeof(struct node));
	if (NULL == p)
	{
		printf("malloc error!\n");
		return NULL;
	}
	p->data = data;
	p->Next = NULL;
	return p;
}
 /*尾插法函数*/
void insert_tail(struct node *Head,struct node *New)
{
	
	struct node *p = Head;	
    /*遍历链表找到尾节点*/
    while(NULL != p->Next)
    {
		p = p->Next;
    }
    /*找到尾节点后,改变他指针域的指向*/
    p->Next = New;
    New->Next = NULL;
}
 /*头插法函数*/
void insert_hand(struct node *Head,struct node *New)
{
	 /*头插法不需要遍历直接找第一个节点就完事
     先把新节点的指针指向原来的第一个节点*/
     New->Next = Head->Next;
     /*原来的头结点指向现在的新节点*/
     Head->Next = New;
}
/*遍历链表*/
void traverseLink(struct node *Head)
{
	struct node* p = Head;
	printf("开始遍历\n");
	while (NULL != p->Next)
	{
		p = p->Next;
		printf("node data = %d\n",p->data);
	}
	printf("遍历完毕\n");
}
/*删除节点*/
int deletNode(struct node *Head, int nData)
{
	//遍历链表
	struct node* p = Head;
	//定义一个节点
	struct node* pPrev = NULL;
	while (NULL != p->Next)
	{
		pPrev = p;//节点指向遍历节点的前一个
		p = p->Next;

		//找节点,找到了
		if (p->data == nData)
		{
			//判断是不是尾节点
			if (NULL == p->Next)
			{
				pPrev->Next = NULL;
				free(p);
			}
			else
			{
				pPrev->Next = p->Next;
				free(p);
			}
            printf("**********删除完毕**********\n");
			return 0;
		}
	}
    printf("没有找到节点\n");
	return -1;
}

int  reverseLink(struct node* Head)
{
	struct node* p = Head->Next;
	struct node* pBack;
	//如果只有一个节点或没有节点
	if ((NULL == p) || (NULL == p->Next))
	{
		printf("链表不能反转\n");
		return -1;
	}
	while (NULL != p->Next)
	{
		pBack = p->Next; //把原来的尾节点变为现在第一个节点
		if (p == Head->Next)
		{
			p->Next = NULL;
		}
		else
		{
			p->Next = Head->Next;
		}
		Head->Next = p;
		p = pBack;
	}
	insert_hand(Head,p);
	printf("*********逆序后*********\n");
}
int main()
{
	struct node* Head = create_node(0); 		//头结点,方便我们找到链表的第一个节点
     /*尾插法通过头节点插入*/
	insert_hand(Head,create_node(1));
	insert_hand(Head,create_node(2));
	insert_hand(Head,create_node(3));
	insert_hand(Head,create_node(4));
	/*
	* traverseLink(Head);
	* reverseLink(Head);
	* traverseLink(Head);
	*/

	/*
	* traverseLink(Head);
	* deletNode(Head,?);
	* traverseLink(Head);
	*/
	return 0;
}