单链表(自制版)

#include<stdio.h>
#include<malloc.h>

//节点描述
struct Node
{
	int data;  //数据域
	struct Node* next;//指针域
};

//链表的增删改查都是用函数来实现的,需要注意函数之间的搭配

//1.创建表头  --->结构体变量
struct Node* createHrad()
{
	struct Node* headNode = (struct Node*)malloc(sizeof(struct Node));//给这个结构体指针变量headNode动态内存分配内存
	//headNode -> data =?  表头不存放数据 就称之为有头链表
	headNode->next = NULL;
	return headNode;
}

//2.创建节点--->为插入做准备---->需要把用户的数据变成一个节点(只有相同类型的结构才能插在一起)
struct Node* createNode(int data)  //建立一个指针函数
{
	struct Node* newNode= (struct Node*)malloc(sizeof(struct Node));//给这个结构体指针变量headNode动态内存分配内存 申请一个变量不用写*1
	newNode->next = NULL;
	newNode->data = data;
	return newNode;
 }

//3.插入
// 3.1 表头插入(有头链表的插入和无头链表的插入是不一样的)
//insertByHead(list,1);  headNode=list  data=1;
//insertByHead(list,2);  headNode=list  data=2;
void insertByHead(struct Node* headNode, int data)//headNode是头节点,int data是用户要插入的节点
{
	//newNode->data=1;
	//newNode->next=NULL;
	struct Node* newNode = createNode(data);//调用上面自己定义的函数,把用户的数据变成结构体变量

	//这段程序与下面这两句话不能换过来,顺序不能搞反,	一定是链接 后断开
	newNode->next = headNode->next; //新插入的节点的指针指向原来头节点的下一个节点,表示头节点原来的下一个节点是插入的新节点后面的节点了
	headNode->next = newNode; //再将头节点的下一个节点的位置放入要插入的新节点,代表现在头节点的下一个节点已经是插入的新节点了
}

// 3.2 表尾插入
void insetByTail(struct Node* headNode, int data)//headNode是头节点,int data是用户要插入的节点
{
	struct Node* pMove = headNode; //定义一个移动的结构体指针,指向表头节点  在这段程序的作用是 先走到最后一个节点的位置
	while (pMove->next != NULL) //遍历整个链表,到达表尾就停止循环,循环条件为节点不为空 
	{
		pMove = pMove->next;  //不等于空的时候就一直往下走
	}
	struct Node* newNode = createNode(data);  //再创建一个新节点
	pMove->next = newNode;  //pMove指向新的节点
}

// 3.3 指定位置插入
void insertByAppoin(struct Node* headNode, int data, int posData)//headNode是头节点,int data是要插入的数据,int posData是要用户要指定插                                                                  入节点的位置
{
	struct Node* posNode = headNode->next;  //新建一个节点,是头节点的下一个节点      第一个节点
	//posNode 是用来找指定节点的,因为有表头,所以要从第二个节点开始与 指定数据对比
	struct Node* posLeftNode = headNode;//再新建一个节点,将这个节点指向头节点
	//posLeftNode是用来记录指定位置的前面的那一个节点           第二个节点

	//空链表,以及没有找指定节点的状况,   while退出循环有两种状态,一种是找到用户指定的节点了,另一种就是当第一个节点后面的的节点为空(也就是没有节点)
	//head->3->2-1->NULL
	//posNode:3
	//posLeftNode:NULL
	//假设在1前面插入一个100 
	
	//struct Node* posLeftNode = NULL;的请况下,如果在 3节点 的前面插入的话,就会使循环一次都不会执行,因为头节点的下一个节点3就是我们要找的数据,所以不会执行。

	while (posNode != NULL&&posNode->data!=posData) //如果新建的第一个节点数据域为空 并且 的数据域不等于posData 才达到循环条件 而                                                       且这两个条件不能交换(头节点的下一个节点的数据域不为空并且头节点的数据域不是用户输                                                      入的数据)
		//posNode移动到2这个位置
	{//遍历链表,使链表全部走完
		posLeftNode = posNode;  //第二个节点移动到第一个节点的位置 
		posNode = posNode->next;//第一个节点移动到第一个节点的下一个节点的位置,(下一个节点的位置赋值给当前的指针,这样节点的位置就移动了)
//posNode = (*posNode).next; 因为每一个结构体变量里面都存了一个下一个节点的位置所以用结构体指针用"."访问成员的方式与上一句的方式是等价的
	}
	if (posNode != NULL)//如果第一个节点不为空 如果指定的数据不等于空 就是找到了指定的节点
	{
		struct Node* newNode = createNode(data);  //那么就新建一个新节点
		posLeftNode->next = newNode; //将第二个节点的指针域指向这个新建的节点 也就是上面新建的节点就是第二个节点的下一个节点
		newNode->next = posNode; // 将newNode新建的节点的指针指向第一个节点,第一个节点就成为了新建的节点 newNode 的下一个节点
	}
	else  //如果第一个节点为空 则新的节点无法插入
	{
		printf("未找到相关位置,无法插入!\n");
		return 0;
	}
 }
//4.删除
//  4.1 表头删除 
void deleteByHead(struct Node* headNode)
{
	struct Node* deleteNode = headNode->next; //先将头节点的下一个节点保存到deleteNode节点中去 也就是要删除的节点先保存起来 这个节点它是第二个                                          节点 因为链表的头删法是从第二个节点开始删(头节点里面是没有数据的所以先把下一个节点保存下来)

	if (deleteNode == NULL) //如果之这个节点为空的(也就是链表是空的)导致节点无法删除(空节点是没有的)
	{
		printf("无法删除,链表为空\n");
		return;
	}
	else //这个链表不为空 可以做删除
	{
		headNode->next = deleteNode->next;//把头节点的下一个指向要删除节点的下一个 这个要删除的节点要指向下一个节点代替删除节点的位置防止头节                                   点的下一个节点的位置没有节点,就是让整个链表不被断开,头节点要与删除节点的下一个节点连接起来保证链                                  表的连续性

		free(deleteNode);  //再释放一下删除节点的内存   因为传入的形参是结构体,形参的结构体之前就是做过动态内存申请的,动态释放
		deleteNode = NULL; //再将删除的节点指向空(习惯性操作)
	}
}

//  4.2 表尾删除
 
//  4.3 指定位置删除
//5.遍历(访问每个节点数据)
void printList(struct Node* headNode) //函数里面的形参是结构体指针变量,用于处理放入的结构体的
{
	//对于有头节点,应该是从第二个节点打印 第二个节点就是表头的下一个节点
	struct Node* pMove = headNode->next;//当前节点的下一个
	while (pMove != NULL) //如果当前节点的下一个节点为空,就说明链表走到了最尾端,循环也就结束了
	{
		printf("%d\t", pMove->data);//打印当前节点数据域
		
		pMove = pMove->next; //(功能是移动到下一个节点)将下一个的节点赋值给当前的节点 
	}
	printf("\n");
}

int main()
{
	struct Node* list = createHrad();//创建链表 表头为 list  这个表头非常重要,它主要的功能提供一个位置,其他节点根据这个位置来做增删改查的操                                  作
	
	for (int i = 1; i <=3; i++)//每循环一次就赋一个值给这个结构体(节点)每赋一个值就创建一个节点
	{
		//list->1
		//list->2->1;
		//list->3->2->1;
		insertByHead(list, i);  //因为是表头插入,所以新创建的节点会移到之前创建的节点位置上,之前的节点就会往后移动,所以打印出来的就是 3,2,1                         而不是 1,2,3
	}
	printList(list); //pirnfList函数打印结构体变量 list 的数据域成员 因为printList里面有while循环,所以list里面的数据域有多少printList函数都                  能打印出来 输出为3,2,1 因为 它是表头插入 for循环给它赋值 1,2,3 先赋值1 再赋值2,再赋值3, 表头插入就是后面后赋值的会排在                 前先赋值的前面,所以输出的就是321

	//insertByHead(list,1);  //新建一个结构体,结构体的数据域中增加一个int型的数据 1
	//insertByHead(list, 2); //新建一个结构体,结构体的数据域中增加一个int型的数据 2
	//insertByHead(list, 3); //新建一个结构体,结构体的数据域中增加一个int型的数据 3

	for (int i = 1; i <= 3; i++) 
	{
		insetByTail(list, i);//输出的是321123,因为 list 之前的数据
	}

	printList(list);  //pirnfList函数打印结构体变量 list 的数据域成员 因为printList里面有while循环,所以list里面的数据域有多少printList函数都                  能打印出来 因为是表尾插入,所以先赋值的会比后赋值的排再前面,所以输出123

	insertByAppoin(list, 100,3); //为什么指定的位置只能在1和2的前面,而 3 不能
	printList(list);

	deleteByHead(list);//  4.1 表头删除 

	printList(list);
	
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值