C语言-基础入门-学习笔记(16):单链表与双链表

C语言-基础入门-学习笔记(16):单链表与双链表

一、链表简介

我们都知道,数组虽然使用方便,但是有两个重要的缺陷:
(1)数组内的元素类型必须相同。
(2)数组的元素个数在初始化之后就不能被改变了。
那么对于这两种情况的解决方法分别是:
(1)通过结构体来实现元素类型的不同。
(2)通过链表方式来实现元素个数的改变。
几乎可以这样理解:链表就是一个元素个数可以实时变大/变小的数组

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

二、单链表

1. 单链表的简单实现

实现前需要注意的点:
(1)由于链表是由一个一个节点组成的,而每个节点包括有效数据和指针,即是两个不同类型的变量,所以对于节点node采用结构体的方式来实现。
(2)链表的内存要求比较灵活,不能用栈,也不能用data数据段。只能用堆内存
(3)头指针并不是节点,而是一个普通指针,只占4字节。头指针的类型是 struct node * 类型的,所以它才能指向链表的节点。
在这里插入图片描述

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

//构建链表节点,其中包括有效数据和指针
struct node{
	int data;	//有效数据
	struct node *pNext;		//指针
};


//构建链表节点函数
struct node* create_node(int data){
	//定义一个节点p
	struct node *p = (struct node *)malloc(sizeof(struct node));
	//判断是否申请到了堆内存
	if(NULL == p){
		printf("malloc error.\n");
		return NULL;
	}
	//给申请到的内存清零
	memset(p,0,sizeof(struct node));
	//向节点中写入数据
	p->data = data;
	p->pNext = NULL;
	return p;
}

int main(void){
	
	//构建头指针
	struct node *pHeader = NULL;
		
	/************构建第一个节点*************/
	//这里函数的返回值是创建好的节点1的首地址,然后用头指针指向
	pHeader = create_node(1);		//通过此语句将头指针和节点1相连
	
	/************构建第二个节点*************/
	//这里函数的返回值是创建好的节点2的首地址,然后节点1的next指针指向
	pHeader->pNext = create_node(2);		//通过此语句将节点1和节点2相连
	
	/************构建第三个节点*************/
	//这里函数的返回值是创建好的节点3的首地址,然后节点2的next指针指向
	pHeader->pNext->pNext = create_node(3);		//通过此语句将节点2和节点3相连
	
	printf("node1 data: %d\n",pHeader->data);
	printf("node2 data: %d\n",pHeader->pNext->data);
	printf("node3 data: %d\n",pHeader->pNext->pNext->data);
	
	return 0;
}
2. 单链表插入新节点

这里为了操作方便,引入了头结点。头节点的特点是:第一,它紧跟在头指针后面。第二,头节点的数据部分是空的(有时候不是空的,而是存储整个链表的节点数),指针部分指向下一个节点,也就是第一个节点。
在这里插入图片描述
节点插入思路:
(1)尾部插入:将最后一个节点的pNext指针指向新添加的节点首地址,然后将新节点的pNext指针指向NULL,成为尾结点。
(2)头部插入:先将新节点的pNext指针指向节点1的首地址(因为一旦首节点与节点1的链断开,将无法找到节点1的首地址),然后再将首节点的pNext指针指向新节点的首地址。

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

//构建链表节点,其中包括有效数据和指针
struct node{
	int data;	//有效数据
	struct node *pNext;		//指针
};


//构建链表节点函数(输入参数为节点内数据)
struct node* create_node(int data){
	//定义一个节点p
	struct node *p = (struct node *)malloc(sizeof(struct node));
	//判断是否申请到了堆内存
	if(NULL == p){
		printf("malloc error.\n");
		return NULL;
	}
	//给申请到的内存清零
	memset(p,0,sizeof(struct node));
	//向节点中写入数据
	p->data = data;
	p->pNext = NULL;
	return p;
}

//尾部节点插入函数(输入参数为首节点和新节点)
void tail_insert(struct node *ph, struct node *new){
	int cnt = 0;	//节点计数
	struct node *p = ph;
	while(NULL != p->pNext){
		p = p->pNext;		//向后移一个节点
		cnt++;
	}
	p->pNext = new;		//最后一个节点的pNext指针指向新添加的节点首地址
	ph->data = cnt + 1;	//计数节点个数
}

//头部节点插入函数(输入参数为首节点和新节点)
void head_insert(struct node *ph, struct node *new){
	int cnt = 0;
	struct node *p = ph;
	while(NULL != p->pNext){
		p = p->pNext;		//向后移一个节点
		cnt++;
	}
	new->pNext = ph->pNext;	//先将节点1的首地址和新节点的pNext指针绑定
	ph->pNext = new;		//再将新节点的首地址和头节点的pNext指针绑定
	ph->data = cnt + 1;		//计数节点个数
}

int main(void){
	
	//构建头指针和首节点
	struct node *pHeader = create_node(0);	//这里首节点的值先初始化为0(后期里面的值还可以表示节点个数)
	
	//进行尾部插入: 1, 2, 3
	tail_insert(pHeader, create_node(1));
	tail_insert(pHeader, create_node(2));	
	tail_insert(pHeader, create_node(3));
	
	//进行头部插入: 4, 5, 6, 1, 2, 3
	head_insert(pHeader, create_node(6));
	head_insert(pHeader, create_node(5));	
	head_insert(pHeader, create_node(4));
	
	printf("The number of node is: %d\n",pHeader->data);
	printf("node1 data: %d\n",pHeader->pNext->data);
	printf("node2 data: %d\n",pHeader->pNext->pNext->data);
	printf("node3 data: %d\n",pHeader->pNext->pNext->pNext->data);
	printf("node4 data: %d\n",pHeader->pNext->pNext->pNext->pNext->data);
	
	return 0;
}
3. 单链表之链表遍历

单链表的特点就是由很多个节点组成,头指针+头节点为整个链表的起始最后一个节点的特征是它内部的pNext指针值为NULL从起始到结尾中间由各个节点内部的pNext指针来挂接。通过起始+移动+结束标志的方法即可实现遍历。
具体方法为:从头指针+头节点开始,顺着链表挂接指针依次访问链表的各个节点,取出这个节点的数据,然后再往下一个节点,直到最后一个节点,结束返回。

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

//构建链表节点,其中包括有效数据和指针
struct node{
	int data;	//有效数据
	struct node *pNext;		//指针
};


//构建链表节点函数(输入参数为节点内数据)
struct node* create_node(int data){
	//定义一个节点p
	struct node *p = (struct node *)malloc(sizeof(struct node));
	//判断是否申请到了堆内存
	if(NULL == p){
		printf("malloc error.\n");
		return NULL;
	}
	//给申请到的内存清零
	memset(p,0,sizeof(struct node));
	//向节点中写入数据
	p->data = data;
	p->pNext = NULL;
	return p;
}

//尾部节点插入函数(输入参数为首节点和新节点)
void tail_insert(struct node *ph, struct node *new){
	int cnt = 0;	//节点计数
	struct node *p = ph;
	while(NULL != p->pNext){
		p = p->pNext;		//向后移一个节点
		cnt++;
	}
	p->pNext = new;		//最后一个节点的pNext指针指向新添加的节点首地址
	ph->data = cnt + 1;	//计数节点个数
}

//头部节点插入函数(输入参数为首节点和新节点)
void head_insert(struct node *ph, struct node *new){
	int cnt = 0;
	struct node *p = ph;
	while(NULL != p->pNext){
		p = p->pNext;		//向后移一个节点
		cnt++;
	}
	new->pNext = ph->pNext;
	ph->pNext = new;
	ph->data = cnt + 1;	//计数节点个数
}

//链表遍历函数(输入参数为首节点)
void traversal(struct node *ph){
	struct node *p = ph;
	printf("开始遍历:\n");
	while(NULL != p->pNext){
		p = p->pNext;
		printf("node data: %d\n", p->data);
	}
	printf("结束遍历:\n");
	
}

int main(void){
	
	//构建头指针和首节点
	struct node *pHeader = create_node(0);	//这里首节点的值先初始化为0(后期里面的值还可以表示节点个数)
	
	//进行尾部插入: 1, 2, 3
	tail_insert(pHeader, create_node(1));
	tail_insert(pHeader, create_node(2));	
	tail_insert(pHeader, create_node(3));
	
	//进行头部插入: 4, 5, 6, 1, 2, 3
	head_insert(pHeader, create_node(6));
	head_insert(pHeader, create_node(5));	
	head_insert(pHeader, create_node(4));
	
	traversal(pHeader);
	
	return 0;
}
4. 单链表之节点删除

删除节点重点是要先找到这个节点,然后删除它。
在这里插入图片描述
删除思路:通过遍历来查找节点。从头指针+头节点开始,顺着链表依次将各个节点拿出来,按照一定的方法比对,找到我们要删除的那个节点
(1)待删除的不是尾节点:首先把待删除的节点的前一个节点的pNext指针指向待删除的节点的后一个节点的首地址(这样就把这个节点从链表中摘出来了),然后再将这个摘出来的节点free掉接口。
(2)待删除的是尾节点:首先把待删除的尾节点的前一个节点的pNext指针指向null(这时候就相当于原来尾节点前面的一个节点变成了新的尾节点),然后再将这个摘出来的节点free掉接口。
由于当遍历到待删除节点后,需要知道该节点的首地址,只能从它的前一个节点来获取,那么我们在移动到该节点之前就要先将待删除的前一个节点的pNext指针(待删除节点首地址)保存下来。用到了pPrev指针

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

//构建链表节点,其中包括有效数据和指针
struct node{
	int data;	//有效数据
	struct node *pNext;		//指针
};


//构建链表节点函数(输入参数为节点内数据)
struct node* create_node(int data){
	//定义一个节点p
	struct node *p = (struct node *)malloc(sizeof(struct node));
	//判断是否申请到了堆内存
	if(NULL == p){
		printf("malloc error.\n");
		return NULL;
	}
	//给申请到的内存清零
	memset(p,0,sizeof(struct node));
	//向节点中写入数据
	p->data = data;
	p->pNext = NULL;
	return p;
}

//尾部节点插入函数(输入参数为首节点和新节点)
void tail_insert(struct node *ph, struct node *new){
	int cnt = 0;	//节点计数
	struct node *p = ph;
	while(NULL != p->pNext){
		p = p->pNext;		//向后移一个节点
		cnt++;
	}
	p->pNext = new;		//最后一个节点的pNext指针指向新添加的节点首地址
	ph->data = cnt + 1;	//计数节点个数
}

//头部节点插入函数(输入参数为首节点和新节点)
void head_insert(struct node *ph, struct node *new){
	int cnt = 0;
	struct node *p = ph;
	while(NULL != p->pNext){
		p = p->pNext;		//向后移一个节点
		cnt++;
	}
	new->pNext = ph->pNext;
	ph->pNext = new;
	ph->data = cnt + 1;	//计数节点个数
}

//链表遍历函数(输入参数为首节点)
void traversal(struct node *ph){
	struct node *p = ph;
	printf("开始遍历:\n");
	while(NULL != p->pNext){
		p = p->pNext;
		printf("node data: %d\n", p->data);
	}
	printf("结束遍历:\n");
	
}

//链表删除节点函数(输入参数为首节点,要删除的节点数据)
int delate_node(struct node *ph, int data){
	struct node *p = ph;			//用来指向当前节点
	struct node *pPrev = NULL;		// 用来指向当前节点的前一个节点
	//循环检测直到NULL结束
	while(NULL != p->pNext){
		pPrev = p;					// 在p走向下一个节点前先将其保存
		p = p->pNext;				// 走到下一个节点,也就是循环增量
		//如果检测到待删除数据
		if(p->data == data){
			//待删除节点为尾结点时
			if(NULL == p->pNext){
				pPrev->pNext = NULL;	//待删除节点的的前一个节点的pNext指向NULL
				free(p);				//释放节点
			}
			//待删除节点不是尾结点时
			else{
				pPrev->pNext = p->pNext;//待删除节点的前一个节点的pNext指向下一个节点的首地址
				free(p);				//释放节点
			}
			return 0;
		}
	}
	printf("没找到这个节点.\n");
	return -1;
}

int main(void){
	
	//构建头指针和首节点
	struct node *pHeader = create_node(0);	//这里首节点的值先初始化为0(后期里面的值还可以表示节点个数)
	
	//进行尾部插入: 1, 2, 3
	tail_insert(pHeader, create_node(1));
	tail_insert(pHeader, create_node(2));	
	tail_insert(pHeader, create_node(3));
	
	//进行头部插入: 4, 5, 6, 1, 2, 3
	head_insert(pHeader, create_node(6));
	head_insert(pHeader, create_node(5));	
	head_insert(pHeader, create_node(4));
	
	traversal(pHeader);
	delate_node(pHeader,5);
	printf("节点删除后:\n");
	traversal(pHeader);
	return 0;
}
5. 单链表之逆序

在这里插入图片描述
逆序思路:首先遍历原链表,然后将原链表中的头指针和头节点作为新链表的头指针和头节点,原链表中的有效节点挨个依次取出来,采用头插入的方法插入新链表中即可。
链表逆序 = 遍历 + 头插入
第一步先将node1的pNext指向NULL,此时链被断开了,无法通过头指针进行链的遍历,所以在断开前要先将下一个节点的首地址进行pBack的保存,然后依次将剩余的节点进行头插,实现逆序。

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

//构建链表节点,其中包括有效数据和指针
struct node{
	int data;	//有效数据
	struct node *pNext;		//指针
};


//构建链表节点函数(输入参数为节点内数据)
struct node* create_node(int data){
	//定义一个节点p
	struct node *p = (struct node *)malloc(sizeof(struct node));
	//判断是否申请到了堆内存
	if(NULL == p){
		printf("malloc error.\n");
		return NULL;
	}
	//给申请到的内存清零
	memset(p,0,sizeof(struct node));
	//向节点中写入数据
	p->data = data;
	p->pNext = NULL;
	return p;
}

//尾部节点插入函数(输入参数为首节点和新节点)
void tail_insert(struct node *ph, struct node *new){
	int cnt = 0;	//节点计数
	struct node *p = ph;
	while(NULL != p->pNext){
		p = p->pNext;		//向后移一个节点
		cnt++;
	}
	p->pNext = new;		//最后一个节点的pNext指针指向新添加的节点首地址
	ph->data = cnt + 1;	//计数节点个数
}

//头部节点插入函数(输入参数为首节点和新节点)
void head_insert(struct node *ph, struct node *new){
	int cnt = 0;
	struct node *p = ph;
	while(NULL != p->pNext){
		p = p->pNext;		//向后移一个节点
		cnt++;
	}
	new->pNext = ph->pNext;
	ph->pNext = new;
	ph->data = cnt + 1;	//计数节点个数
}

//链表遍历函数(输入参数为首节点)
void traversal(struct node *ph){
	struct node *p = ph;
	printf("开始遍历:\n");
	while(NULL != p->pNext){
		p = p->pNext;
		printf("node data: %d\n", p->data);
	}
	printf("结束遍历:\n");
	
}

//链表逆序函数(输入参数为首节点)
void reverse_list(struct node *ph){
	struct node *p = ph->pNext;		// pH指向头节点,p指向第1个有效节点
	struct node *pBack;				// 保存当前节点的后一个节点地址
	//如果为首节点或只有一个有效节点,那么不需要逆序
	if(NULL == p || NULL == p->pNext){
		return;
	}
	while(NULL != p->pNext){
		//在断开链之前,先将下一个节点的首地址保存到pBack指针内
		pBack = p->pNext;
		//如果是第一个节点的话,将其pNext指向NULL
		if(ph->pNext == p){
			p->pNext = NULL;
		}
		//将从第二个开始的节点进行前插
		else{
			head_insert(ph, p);
		}
		p = pBack;		//因为第一个节点时pNext被指向NULL,所以需要借助pBack找到下一个节点的首地址
	}
	//当最后一个节点时,pNext指向NULL,所以没有进入循环内进行前插,所以需要单独对最后一个节点进行前插
	head_insert(ph, p);
}
int main(void){
	
	//构建头指针和首节点
	struct node *pHeader = create_node(0);	//这里首节点的值先初始化为0(后期里面的值还可以表示节点个数)
	
	//进行尾部插入: 1, 2, 3
	tail_insert(pHeader, create_node(1));
	tail_insert(pHeader, create_node(2));	
	tail_insert(pHeader, create_node(3));
	
	//进行头部插入: 4, 5, 6, 1, 2, 3
	head_insert(pHeader, create_node(6));
	head_insert(pHeader, create_node(5));	
	head_insert(pHeader, create_node(4));
	
	traversal(pHeader);
	reverse_list(pHeader);
	printf("逆序后:\n");
	traversal(pHeader);
	return 0;
}

三、双链表

单链表各个节点之间只由一个指针单向链接,这样实现有一些局限性。局限性主要体现在单链表只能经由指针单向移动(一旦指针移动过某个节点就无法再回来,如果要再次操作这个节点除非从头指针开始再次遍历一次),因此单链表在某些算法上具有局限性。于是我们引出了双链表

单链表的节点 = 有效数据 + 指针(指针指向后一个节点)
双向链表的节点 = 有效数据 + 2个指针(一个指向后一个节点,另一个指向前一个节点)

1. 双链表的简单实现

在这里插入图片描述
双链表的特点是,每个节点包含2个指针和一个数据区,其中pPrev指针指向前一个节点的首地址,pNext指针指向后一个节点的首地址。头结点的pPrev指针指向NULL,尾结点的pNext指针指向NULL。

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

//构建链表节点,其中包含有效数据和两个指针
struct node{
	int data;				//有效数据
	struct node *pPrev;     //前向指针
	struct node *pNext;     //后向指针
};

struct node * create_node(int data){
	//定义一个节点p
	struct node *p = (struct node *)malloc(sizeof(struct node));
	//判断内存申请是否成功
	if(NULL == p){
		printf("malloc error.\n");
		return NULL;
	}
	//如果申请成功了后,给内存清零
	memset(p, 0, sizeof(struct node));
	//向节点写入数据
	p->data = data;
	p->pPrev = NULL;
	p->pNext = NULL;
	return p;
}

int main(void){
	
	//构建头指针和头节点
	struct node *pHeader = create_node(0);
	
	/************构建第一个节点*************/
	pHeader->pNext = create_node(11);
	
	/************构建第二个节点*************/
	pHeader->pNext->pNext = create_node(12);
	
	/************构建第三个节点*************/
	pHeader->pNext->pNext->pNext = create_node(13);
	
	//打印查看节点
	printf("node1 data: %d\n",pHeader->pNext->data);
	printf("node2 data: %d\n",pHeader->pNext->pNext->data);
	printf("node3 data: %d\n",pHeader->pNext->pNext->pNext->data);
	return 0;
}
2. 双链表之插入新节点

在这里插入图片描述
节点插入思路:
(1)尾部插入:将最后一个节点的pNext指针指向新添加的节点首地址;将新节点的pPrev指针指向上一个节点的首地址;将新节点的pNext指针指向NULL。
(2)头部插入:将头节点的pNext指针指向新节点的首地址;将第一个节点的pPrev指针指向新节点的首地址;将新节点的pPrev指针指向头节点的首地址;将新节点的pNext指针指向第一个节点的首地址。

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

//构建链表节点,其中包含有效数据和两个指针
struct node{
	int data;				//有效数据
	struct node *pPrev;     //前向指针
	struct node *pNext;     //后向指针
};

//构建链表节点函数(输入参数为节点内数据)
struct node * create_node(int data){
	//定义一个节点p
	struct node *p = (struct node *)malloc(sizeof(struct node));
	//判断内存申请是否成功
	if(NULL == p){
		printf("malloc error.\n");
		return NULL;
	}
	//如果申请成功了后,给内存清零
	memset(p, 0, sizeof(struct node));
	//向节点写入数据
	p->data = data;
	p->pPrev = NULL;
	p->pNext = NULL;
	return p;
}

//尾部节点插入函数(输入参数为首节点和新节点)
void tail_insert(struct node *ph, struct node *new){
	int cnt = 0;	//节点计数
	struct node *p = ph;

	while(NULL != p->pNext){
		//第一个作用是走的尾部,第二个作用是直接从第一个有效节点开始
		p = p->pNext;
		cnt++;
	}
	p->pNext = new;	//将最后一个节点的pNext指针指向新添加的节点首地址
	new->pPrev = p;	//将新节点的pPrev指针指向最后一个节点的首地址
	ph->data = cnt + 1;
}

//头部节点插入函数(输入参数为首节点和新节点)
void head_insert(struct node *ph, struct node *new){
	int cnt = 0;
	struct node *p = ph;
	while(NULL != p->pNext){
		p = p->pNext;
		cnt++;
	}
	
	//(1)将新节点的pNext指针指向第一个节点的首地址
	new->pNext = ph->pNext;
	//(2)将第一个节点的pPrev指针指向新节点的首地址
	if(NULL != ph->pNext)
		ph->pNext->pPrev = new;
	//(3)将头节点的pNext指针指向新节点的首地址
	ph->pNext = new;
	//(4)将新节点的pPrev指针指向头节点的首地址
	new->pPrev = ph;
	//注意在(3)中ph->pNext被改变了,所以要放在(2)的后面,(4)要放在最后
	ph->data = cnt + 1;

}
int main(void){
	
	//构建头指针和头节点
	struct node *pHeader = create_node(0);
	
	//进行尾部插入: 1, 2, 3
	tail_insert(pHeader, create_node(1));
	tail_insert(pHeader, create_node(2));	
	tail_insert(pHeader, create_node(3));
	
	//进行头部插入: 4, 5, 6, 1, 2, 3
	head_insert(pHeader, create_node(6));
	head_insert(pHeader, create_node(5));	
	head_insert(pHeader, create_node(4));
	
	printf("The number of node is: %d\n",pHeader->data);
	printf("node1 data: %d\n",pHeader->pNext->data);
	printf("node2 data: %d\n",pHeader->pNext->pNext->data);
	printf("node3 data: %d\n",pHeader->pNext->pNext->pNext->data);
	printf("node4 data: %d\n",pHeader->pNext->pNext->pNext->pNext->data);
	return 0;
}
3. 双链表之遍历节点

具体方法为:
(1)正向遍历:从头指针+头节点开始,顺着链表挂接指针依次访问链表的各个节点,取出这个节点的数据,然后再往下一个节点,直到最后一个节点,结束返回。
(2)反向遍历:从尾结点开始,顺着链表的前向指针依次访问链表的各个节点,取出这个节点的数据,然后再往前一个节点,直到首节点,结束返回。(这里的先后顺序见代码内)

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

//构建链表节点,其中包含有效数据和两个指针
struct node{
	int data;				//有效数据
	struct node *pPrev;     //前向指针
	struct node *pNext;     //后向指针
};

//构建链表节点函数(输入参数为节点内数据)
struct node * create_node(int data){
	//定义一个节点p
	struct node *p = (struct node *)malloc(sizeof(struct node));
	//判断内存申请是否成功
	if(NULL == p){
		printf("malloc error.\n");
		return NULL;
	}
	//如果申请成功了后,给内存清零
	memset(p, 0, sizeof(struct node));
	//向节点写入数据
	p->data = data;
	p->pPrev = NULL;
	p->pNext = NULL;
	return p;
}

//尾部节点插入函数(输入参数为首节点和新节点)
void tail_insert(struct node *ph, struct node *new){
	int cnt = 0;	//节点计数
	struct node *p = ph;

	while(NULL != p->pNext){
		//第一个作用是走的尾部,第二个作用是直接从第一个有效节点开始
		p = p->pNext;
		cnt++;
	}
	p->pNext = new;	//将最后一个节点的pNext指针指向新添加的节点首地址
	new->pPrev = p;	//将新节点的pPrev指针指向最后一个节点的首地址
	ph->data = cnt + 1;
}

//头部节点插入函数(输入参数为首节点和新节点)
void head_insert(struct node *ph, struct node *new){
	int cnt = 0;
	struct node *p = ph;
	while(NULL != p->pNext){
		p = p->pNext;
		cnt++;
	}
	//(1)将新节点的pNext指针指向第一个节点的首地址
	new->pNext = ph->pNext;
	//(2)将第一个节点的pPrev指针指向新节点的首地址
	if(NULL != ph->pNext)
		ph->pNext->pPrev = new;
	//(3)将头节点的pNext指针指向新节点的首地址
	ph->pNext = new;
	//(4)将新节点的pPrev指针指向头节点的首地址
	new->pPrev = ph;
	//注意在(3)中ph->pNext被改变了,所以要放在(2)的后面,(4)要放在最后
	ph->data = cnt + 1;

}

//通过pNext指针进行正向链表遍历(输入参数为首节点)
void forward_traversal(struct node *ph){
	struct node *p = ph;
	printf("开始遍历:\n");
	while(NULL != p->pNext){
		p = p->pNext;
		printf("node data: %d\n", p->data);
	}
	printf("结束遍历:\n");
}

//通过pPrev指针进行反向链表遍历(输入参数为尾节点)
void backword_traversal(struct node *pt){
	struct node *p = pt;
	printf("开始遍历:\n");
	while(NULL != p->pPrev){
		printf("node data: %d\n", p->data);
		p = p->pPrev;
	}
	printf("结束遍历:\n");
}

int main(void){
	
	//构建头指针和头节点
	struct node *pHeader = create_node(0);
	struct node *pTail = pHeader;
	//进行尾部插入: 1, 2, 3
	tail_insert(pHeader, create_node(1));
	tail_insert(pHeader, create_node(2));	
	tail_insert(pHeader, create_node(3));
	
	//进行头部插入: 4, 5, 6, 1, 2, 3
	head_insert(pHeader, create_node(6));
	head_insert(pHeader, create_node(5));	
	head_insert(pHeader, create_node(4));
	
	//找到尾结点(用于测试反向遍历)
	while(NULL != pTail->pNext){
		pTail = pTail->pNext;
	}
	forward_traversal(pHeader);
	backword_traversal(pTail);
	return 0;
}
4. 双链表之删除节点

在这里插入图片描述
(1)待删除的不是尾节点:首先把待删除的节点的前一个节点的pNext指针指向待删除的节点的后一个节点的首地址(这样就把这个节点从链表中摘出来了),然后再将待删除节点的后一个节点的pPrev指针指向待删除节点的前一个节点的首地址,最后将这个摘出来的节点free掉接口。
(2)待删除的是尾节点:首先把待删除的尾节点的前一个节点的pNext指针指向null(这时候就相当于原来尾节点前面的一个节点变成了新的尾节点),然后再将这个摘出来的节点free掉接口。

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

//构建链表节点,其中包含有效数据和两个指针
struct node{
	int data;				//有效数据
	struct node *pPrev;     //前向指针
	struct node *pNext;     //后向指针
};

//构建链表节点函数(输入参数为节点内数据)
struct node * create_node(int data){
	//定义一个节点p
	struct node *p = (struct node *)malloc(sizeof(struct node));
	//判断内存申请是否成功
	if(NULL == p){
		printf("malloc error.\n");
		return NULL;
	}
	//如果申请成功了后,给内存清零
	memset(p, 0, sizeof(struct node));
	//向节点写入数据
	p->data = data;
	p->pPrev = NULL;
	p->pNext = NULL;
	return p;
}

//尾部节点插入函数(输入参数为首节点和新节点)
void tail_insert(struct node *ph, struct node *new){
	int cnt = 0;	//节点计数
	struct node *p = ph;

	while(NULL != p->pNext){
		//第一个作用是走的尾部,第二个作用是直接从第一个有效节点开始
		p = p->pNext;
		cnt++;
	}
	p->pNext = new;	//将最后一个节点的pNext指针指向新添加的节点首地址
	new->pPrev = p;	//将新节点的pPrev指针指向最后一个节点的首地址
	ph->data = cnt + 1;
}

//头部节点插入函数(输入参数为首节点和新节点)
void head_insert(struct node *ph, struct node *new){
	int cnt = 0;
	struct node *p = ph;
	while(NULL != p->pNext){
		p = p->pNext;
		cnt++;
	}
	//(1)将新节点的pNext指针指向第一个节点的首地址
	new->pNext = ph->pNext;
	//(2)将第一个节点的pPrev指针指向新节点的首地址
	if(NULL != ph->pNext)
		ph->pNext->pPrev = new;
	//(3)将头节点的pNext指针指向新节点的首地址
	ph->pNext = new;
	//(4)将新节点的pPrev指针指向头节点的首地址
	new->pPrev = ph;
	//注意在(3)中ph->pNext被改变了,所以要放在(2)的后面,(4)要放在最后
	ph->data = cnt + 1;

}

//通过pNext指针进行正向链表遍历(输入参数为首节点)
void forward_traversal(struct node *ph){
	struct node *p = ph;
	printf("开始遍历:\n");
	while(NULL != p->pNext){
		p = p->pNext;
		printf("node data: %d\n", p->data);
	}
	printf("结束遍历:\n");
}

//通过pPrev指针进行反向链表遍历(输入参数为尾节点)
void backword_traversal(struct node *pt){
	struct node *p = pt;
	printf("开始遍历:\n");
	while(NULL != p->pPrev){
		printf("node data: %d\n", p->data);
		p = p->pPrev;
	}
	printf("结束遍历:\n");
}

//链表删除节点函数(输入参数为首节点,要删除的节点数据)
int delate_node(struct node *ph, int data){
	struct node *p = ph;			//用来指向当前节点
	
	//除去只有头指针的情况
	if(NULL == p){
		return -1;
	}
	while(NULL != p->pNext){
		p = p->pNext;		//走到下一个节点
		//如果检测到待删除数据
		if(p->data == data){
			//当待删除数据为尾结点时
			if(NULL == p->pNext){
				//把待删除的尾节点的前一个节点的pNext指针指向null
				p->pPrev->pNext = NULL;
			}
			//当待删除数据不是尾结点时
			else{
				//待删除的节点的前一个节点的pNext指针指向待删除的节点的后一个节点的首地址
				p->pPrev->pNext = p->pNext;
				//待删除节点的后一个节点的pPrev指针指向待删除节点的前一个节点的首地址
				p->pNext->pPrev = p->pPrev;
			}
			free(p);
			return 0;
		}
	}
	printf("未找到目标节点.\n");
	return -1;
}

int main(void){
	
	//构建头指针和头节点
	struct node *pHeader = create_node(0);
	struct node *pTail = pHeader;
	//进行尾部插入: 1, 2, 3
	tail_insert(pHeader, create_node(1));
	tail_insert(pHeader, create_node(2));	
	tail_insert(pHeader, create_node(3));
	
	//进行头部插入: 4, 5, 6, 1, 2, 3
	head_insert(pHeader, create_node(6));
	head_insert(pHeader, create_node(5));	
	head_insert(pHeader, create_node(4));
	
	forward_traversal(pHeader);
	delate_node(pHeader,3);
	forward_traversal(pHeader);
	return 0;
}
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值