关于链表的一些内容

目录

一、什么是链表

需要的一些基本准备

链表的基本格式

二、链表的基本操作

创建一个链表

遍历这个链表

从首节点开始正序遍历

从尾节点开始倒序遍历链表

摧毁链表

三、链表的进阶操作

在一个升序链表中插入新的节点

删除一个你想要删除的节点

连接两个链表

总代码


一、什么是链表

链表就像一辆小火车一样,每一个车厢之间相互连接,可以互相访问。

每个链结靠next 节点来连接下一个节点,靠prior来连接上一个节点

根据对链表的所需来决定是否最后需要使得首尾节点互相连接一下。

需要的一些基本准备

头文件

#include<stdio.h>

#include<stdlib.h>使用malloc函数的头文件 

链表的基本格式

struct Linkedlist {
	int date;//数据体,可根据需求修改
	struct Linkedlist* prior, * next;//上节点,下节点//尾节点tail
};

二、链表的基本操作

创建一个链表

函数格式

void add(struct Linkedlist** head, struct Linkedlist** last, int n);

这边往这个函数传入链表的首指针,尾指针,以及创建的长度

void add(struct Linkedlist** head, struct Linkedlist** last, int n) {
	//p为新建的节点,up用来存储首节点,down最后遍历到末尾,变成尾节点
	struct Linkedlist* p, * up = *head, * down = *head;
	p = (struct Linkedlist*)malloc(sizeof(struct Linkedlist));
	scanf("%d", &p->date);
	up = p;//因为到来之前首节点为NULL,所以将第一个申请的p作为首节点
	down = p;
	for (int i = n - 1; i > 0; i--) {
		p = (struct Linkedlist*)malloc(sizeof(struct Linkedlist));
		scanf("%d", &p->date);
		down->next = p;//尾接法
		p->prior = down;//上节点
		down = p;
	}
	//首尾节点的设置
	down->next = up;
	up->prior = down;
	*head = up;
	*last = down;
}

这边用到了尾插法创建链表,使用malloc函数每次生成新的节点,接在该链表的末尾,并且在最后设置一下首尾节点的连接。

遍历这个链表

从首节点开始正序遍历

函数格式

void display_head_next(struct Linkedlist* head)

将该链表的首指针传入函数,进行正序的遍历

void display_head_next(struct Linkedlist* head) {
	//用p来遍历链表
	struct Linkedlist* p = head;
	//因为尾节点的next节点是首节点
	//所以用do while是为了刚刚好遍历完整这个链表
	do {
		printf("%d ", p->date);
		p = p->next;
	} while (p != head);
	printf("正序输出\n");
}

从尾节点开始倒序遍历链表

函数格式

void display_last_prior(struct Linkedlist* last)

将函数的尾节点传入函数,进行倒序遍历

void display_last_prior(struct Linkedlist* last) {
	//用p来遍历链表
	struct Linkedlist* p = last;
	//因为尾节点的next节点是首节点
	//所以用do while是为了刚刚好遍历完整这个链表
	do {
		printf("%d ", p->date);
		p = p->prior;
	} while (p != last);
	printf("倒序输出\n");
}

摧毁链表

函数格式

void destory(struct Linkedlist* head)

将链表首节点传入函数

void destory(struct Linkedlist* head) {
	//用p来存储当前节点,然后pr是p的下一个节点
	struct Linkedlist* p = head, * pr;
	do {
		pr = p;
		p = p->next;
		//每次释放当前节点的内存,然后越位到下一个
		free(pr);
	} while (p != head);
}

三、链表的进阶操作

在一个升序链表中插入新的节点

函数格式

void ad(struct Linkedlist** head, struct Linkedlist** last);

将需要修改的链表的首尾节点传入函数,进行修改

函数主体

void ad(struct Linkedlist** head, struct Linkedlist** last) {
	//p为新建的节点,up,down最后遍历到末尾,up永远领先down一次,
	//这样使得我们可以用up来记录,新节点的prior该指向谁。
	//用down来记录新节点的next指向
	struct Linkedlist* p, * up = *head, * down = *head;
	p = (struct Linkedlist*)malloc(sizeof(struct Linkedlist));
	scanf("%d", &p->date);
	while (p->date >= down->date && down->next != *head) {
		up = down;
		down = down->next;
	}
	//三种情况的第一种,在头部插入,所以新的节点作为首节点
	if (up == *head) {
		//原来的up节点作为第二个节点,接在p后面
		p->next = up;
		up->prior = p;
		//首节点的prior需要接上尾节点
		p->prior = *last;
		//处理尾节点
		(*last)->next = p;
		*head = p;
	}
	else {
		//第二种是新的节点接在最末尾
		if (p->date >= down->date && down->next == *head) {
			//将生成的节点接在最后一个节点后面
			down->next = p;
			p->prior = down;
			//修改新的尾节点的next节点和首节点接上这个尾节点
			p->next = *head;
			(*head)->prior = p;
			//尾节点的更换
			*last = p;
		}
		else {//第三种是新的节点接在中间
			//找到插入点,就是在up,down中间插入新建的p节点
			up->next = p;
			p->next = down;
			
			p->prior = up;
			down->prior = p;
		}
	}
}

删除一个你想要删除的节点

函数格式

void de(struct Linkedlist** head, struct Linkedlist** last) 

将需要修改的链表的首尾指针传入函数,进行操作

void de(struct Linkedlist** head, struct Linkedlist** last) {
	struct Linkedlist* up = *head, * down = *head; int n;
	scanf("%d", &n);
	while (n != down->date && down->next != *head) {
		//遍历这个链表,寻找你要删除的节点
		up = down;
		down = down->next;
	}
	//会出现四种情况,在首节点,尾节点,中间,没有找到
	if (down == *head) {
		//四种情况的第一种,在首节点,这个时候down和up是一样的
		//那么我们需要删除up节点,然后设置新的首节点,对尾节点的prior进行修改
		*head = up->next;
		(*head)->prior = *last;
		(*last)->next = *head;
		free(up);//释放删除节点的内存
	}
	else {//第二种情况,删除尾节点
		if (n == down->date && down->next == *head) {
			//那么我们需要删除down节点,然后设置新的尾节点,对首节点的prior进行修改
			*last = up;
			(*last)->next = *head;
			(*head)->prior = *last;
			free(down);//释放删除节点的内存
		}
		else if (n != down->date && down->next == *head) {
			//第三种情况,找不到删除节点
			printf("输入的数据不在链表之中\n");
		}
		else {
			//第四种情况,删除中间节点down
			//那么我们要将up接上down的下一个节点,然后在修改对应的prior节点
			up->next = down->next;
			down->next->prior = up;
			free(down);//释放删除节点的内存
		}
	}
}

连接两个链表

前情提要:假设现在有两个升序链表,分别为

1-3-5-7-9  2-4-6-8-10

则,接下来函数的功能就是实现将他们变成一个新的升序链表

即 1-2-3-4-5-6-7-8-9-10

函数格式

void connect(struct Linkedlist** head1, struct Linkedlist** head2, struct Linkedlist** last1, struct Linkedlist** last2)

在讲述这个连接函数之前,需要提前讲述两个其中用到的重复较多的语句,我把它写成了两个函数

分别是

这个函数是将新节点接在链表的尾部,并且进行prior和next的连接操作

void zhong(struct Linkedlist** up, struct Linkedlist** down, struct Linkedlist* temp) {
	(*down)->next = temp;//这一步是将temp节点接在down后面
	*up = *down;         //这一步是将down赋值给up,为了接下来可以接prior这个节点
	*down = (*down)->next;//这一步是down进行遍历,遍历到自己的下一个节点,也就是temp
	(*down)->prior = *up;//这一步是设置新节点的prior节点
}
//这个函数的功能是对首尾节点进行设置
void jie(struct Linkedlist* head, struct Linkedlist* last) {
	head->prior = last;//
	wo = head;//这里的wo是新链表的首节点,我用到了全局变量,具体参考下文总代码
	wei = last;//这里的wei是新链表的尾节点,也是全局变量
	wei->next = head;
}

我将需要拼接的两个链表的首尾指针们传入这个函数,进行操作

void connect(struct Linkedlist** head1, struct Linkedlist** head2, struct Linkedlist** last1, struct Linkedlist** last2) {
	//我用first和second来接收传进来的两个链表,用遍历它们来把所有节点接到新的链表上
	//用up,down最后遍历到末尾,up永远领先down一次
	//用fi来存储首节点
	struct Linkedlist* first = *head1, * second = *head2, * up = NULL, * down = NULL, * fi = NULL;
	//当first和second均未遍历到各自的尾节点的时候,
	while (first->next != *head1 && second->next != *head2) {
		//升序连接链表,所以小链结的在前
		if (first->date < second->date) {
			//倘若新的链表还没有首节点
			if (down == NULL) {
				//设置首节点加保存首节点
				down = first;
				fi = first;
			}
			else
				zhong(&up, &down, first);
				//一个链接函数
				//等价于 down->next = first;第一步将新节点接在down后面
				//       up = down;			第二步用up保存这个新节点的上一个节点
			    //		 down = down->next; 第三步让down遍历到自己的下一个节点
			    //		 down->prior = up;  第四布设置down的prior节点
				first = first->next;//这个first接完了,我们要遍历到下一个去
		}
		else if (first->date == second->date) {
			if (down == NULL) {
				down = first;
				fi = first;
			}
			else
				zhong(&up, &down, first);
				//和上面一样的道理
			//因为这是两个节点相同的情况,所以都要遍历到自己的下一位
			first = first->next;
			second = second->next;
		}
		else {
			if (down == NULL) {
				down = second;
				fi = second;
			}//和上面一样的道理
			//因为这是接second,所以second要遍历到自己的下一位
			else
				zhong(&up, &down, second);
			second = second->next;
		}
	}
	//倘若first遍历到最后一个,那么如果这最后一个first也被接进了新的链表,
	//那么剩下的second直接接在first的最后即可,然后在修改一下首尾节点的prior和next
	while (first->next == *head1) {
		//从这
		if (first->date < second->date) {
			zhong(&up, &down, first);
			first = first->next;
		}
		else if (first->date == second->date) {
			zhong(&up, &down, first);
			first = first->next;
			second = second->next;
		}
		else {
			zhong(&up, &down, second);
			second = second->next;
		}
		//到这和上文一样
		if (first == *head1) {
			//如果这一次接的是first的最后一个节点,那么执行个步骤
			//第一步,接上first最后一个节点
			zhong(&up, &down, second);
			//这时候新链表的尾节点是second的尾节点
			jie(fi, *last2);
			//jie函数的作用
			//第二部,讲所剩余的second接在  first最后一个节点后面 ,然后设置首尾节点
			// 在这边我用了全局变量,所以没有把新链表的头部wo和尾部wei传进来
			//fi->prior = *last2;     这一步是在设置首节点的prior
			//wo = fi;				  这一步是在给新链表的待定首节点wo赋值成fi,新链表的首节点
			//wei = *last2;			  这一步是在给新链表的待定尾节点wei赋值成*last2,新链表的尾节点
			//wei->next = fi;		  这一步是在设置尾节点的next
			
		}
	}
	while (second->next == *head2) {
		//同理
		if (first->date < second->date) {
			zhong(&up, &down, first);
			first = first->next;
		}
		else if (first->date == second->date) {
			zhong(&up, &down, first);
			first = first->next;
			second = second->next;
		}
		else {
			zhong(&up, &down, second);
			second = second->next;
		}
		if (second == *head2) {
			zhong(&up, &down, first);
			//因为这时候,新链表的尾节点是first的尾节点
			jie(fi, *last1);
		}
	}
}

总代码

注意,倘若使用connect函数,那么head1和head2会发生改变,不推荐在这时候查看head1和head2的情况

#define _CRT_SECURE_NO_WARNINGS
//c基本的库
#include<stdio.h>
//用到了里面的malloc函数
#include<stdlib.h>
struct meizi {
	int date;//数据体,可根据需求修改
	struct meizi* prior, * next;//上节点,下节点//尾节点tail
};
//双向链表的初步建立以及添加,删减,拼接。
//函数功能分别为:创建,添加,删除,连接,连接函数*2,展示函数,摧毁函数
//关于二级指针的使用
//因为我们在对地址进行修改,那么我们就要使用到地址的地址,及二级指针,通过二级指针的使用来修改一级指针的值
//就像假设我们要用自定义函数来交换数值
//    int a = 10, b = 5;
//    void  swap (int *a,int *b)
//	  {
//        int t;
//		  t = *a;*a = *b;*b = t
//     }
// 通过指针改变a,b所对应地址上放的整数来完成交换数据,同理通过修改二级地址来交换一级地址
//这边观看下文,因为我们修改的是一级指针(地址),所以我们需要使用二级指针(地址)来进行修改	
void add(struct meizi** head, struct meizi** last, int n);
void ad(struct meizi** head, struct meizi** last);
void de(struct meizi** head, struct meizi** last);
void connect(struct meizi** head1, struct meizi** head2, struct meizi** last1, struct meizi** last2);
void zhong(struct meizi** up, struct meizi** down, struct meizi* temp);
void jie(struct meizi* head, struct meizi* last);
void display_head_next(struct meizi* head);
void display_last_prior(struct meizi* last);
void destory(struct meizi* head);
struct meizi* wo = NULL, * wei = NULL;
int main() {
	struct meizi* head1 = NULL, * last1 = NULL;
	struct meizi* head2 = NULL, * last2 = NULL;
	add(&head1, &last1, 3);
	add(&head2, &last2, 3);
	//display_head_next(head1);
	//display_last_prior(last1);
	//display_head_next(head2);
	//display_last_prior(last2);
	connect(&head1, &head2, &last1, &last2);
	display_head_next(wo);
	display_last_prior(wei);
	//ad(&head1, &last1);
	//display_head_next(head2);
	//display_last_prior(last1);
	//de(&head1,&last1);
	//display_head_next(head1);
	//display_last_prior(last1);
	//destory(head1);
	//destory(head2);
	destory(wo);
	return 0;
}
//创建新链表
void add(struct meizi** head, struct meizi** last, int n) {
	//p为新建的节点,up用来存储首节点,down最后遍历到末尾,变成尾节点
	struct meizi* p, * up = *head, * down = *head;
	p = (struct meizi*)malloc(sizeof(struct meizi));
	scanf("%d", &p->date);
	up = p;//因为到来之前首节点为NULL,所以将第一个申请的p作为首节点
	down = p;
	for (int i = n - 1; i > 0; i--) {
		p = (struct meizi*)malloc(sizeof(struct meizi));
		scanf("%d", &p->date);
		down->next = p;//尾接法
		p->prior = down;//上节点
		down = p;
	}
	//首尾节点的设置
	down->next = up;
	up->prior = down;
	*head = up;
	*last = down;
}
//在升序链表中插入新结点
void ad(struct meizi** head, struct meizi** last) {
	//p为新建的节点,up,down最后遍历到末尾,up永远领先down一次,
	//这样使得我们可以用up来记录,新节点的prior该指向谁。
	//用down来记录新节点的next指向
	struct meizi* p, * up = *head, * down = *head;
	p = (struct meizi*)malloc(sizeof(struct meizi));
	scanf("%d", &p->date);
	while (p->date >= down->date && down->next != *head) {
		up = down;
		down = down->next;
	}
	//三种情况的第一种,在头部插入,所以新的节点作为首节点
	if (up == *head) {
		//原来的up节点作为第二个节点,接在p后面
		p->next = up;
		up->prior = p;
		//首节点的prior需要接上尾节点
		p->prior = *last;
		//处理尾节点
		(*last)->next = p;
		*head = p;
	}
	else {
		//第二种是新的节点接在最末尾
		if (p->date >= down->date && down->next == *head) {
			//将生成的节点接在最后一个节点后面
			down->next = p;
			p->prior = down;
			//修改新的尾节点的next节点和首节点接上这个尾节点
			p->next = *head;
			(*head)->prior = p;
			//尾节点的更换
			*last = p;
		}
		else {//第三种是新的节点接在中间
			//找到插入点,就是在up,down中间插入新建的p节点
			up->next = p;
			p->next = down;
			
			p->prior = up;
			down->prior = p;
		}
	}
}
//删除你想要删除的节点
void de(struct meizi** head, struct meizi** last) {
	struct meizi* up = *head, * down = *head; int n;
	scanf("%d", &n);
	while (n != down->date && down->next != *head) {
		//遍历这个链表,寻找你要删除的节点
		up = down;
		down = down->next;
	}
	//会出现四种情况,在首节点,尾节点,中间,没有找到
	if (down == *head) {
		//四种情况的第一种,在首节点,这个时候down和up是一样的
		//那么我们需要删除up节点,然后设置新的首节点,对尾节点的prior进行修改
		*head = up->next;
		(*head)->prior = *last;
		(*last)->next = *head;
		free(up);//释放删除节点的内存
	}
	else {//第二种情况,删除尾节点
		if (n == down->date && down->next == *head) {
			//那么我们需要删除down节点,然后设置新的尾节点,对首节点的prior进行修改
			*last = up;
			(*last)->next = *head;
			(*head)->prior = *last;
			free(down);//释放删除节点的内存
		}
		else if (n != down->date && down->next == *head) {
			//第三种情况,找不到删除节点
			printf("输入的数据不在链表之中\n");
		}
		else {
			//第四种情况,删除中间节点down
			//那么我们要将up接上down的下一个节点,然后在修改对应的prior节点
			up->next = down->next;
			down->next->prior = up;
			free(down);//释放删除节点的内存
		}
	}
}
//按照升序将两个链表拼接起来
void connect(struct meizi** head1, struct meizi** head2, struct meizi** last1, struct meizi** last2){
 
	//我用first和second来接收传进来的两个链表,用遍历它们来把所有节点接到新的链表上
	//用up,down最后遍历到末尾,up永远领先down一次
	//用fi来存储首节点
	struct meizi* first = *head1, * second = *head2, * up = NULL, * down = NULL, * fi = NULL;
	//当first和second均未遍历到各自的尾节点的时候,
	while (first->next != *head1 && second->next != *head2) {
		//升序连接链表,所以小链结的在前
		if (first->date < second->date) {
			//倘若新的链表还没有首节点
			if (down == NULL) {
				//设置首节点加保存首节点
				down = first;
				fi = first;
			}
			else
				zhong(&up, &down, first);
				//一个链接函数
				//等价于 down->next = first;第一步将新节点接在down后面
				//       up = down;			第二步用up保存这个新节点的上一个节点
			    //		 down = down->next; 第三步让down遍历到自己的下一个节点
			    //		 down->prior = up;  第四布设置down的prior节点
				first = first->next;//这个first接完了,我们要遍历到下一个去
		}
		else if (first->date == second->date) {
			if (down == NULL) {
				down = first;
				fi = first;
			}
			else
				zhong(&up, &down, first);
				//和上面一样的道理
			//因为这是两个节点相同的情况,所以都要遍历到自己的下一位
			first = first->next;
			second = second->next;
		}
		else {
			if (down == NULL) {
				down = second;
				fi = second;
			}//和上面一样的道理
			//因为这是接second,所以second要遍历到自己的下一位
			else
				zhong(&up, &down, second);
			second = second->next;
		}
	}
	//倘若first遍历到最后一个,那么如果这最后一个first也被接进了新的链表,
	//那么剩下的second直接接在first的最后即可,然后在修改一下首尾节点的prior和next
	while (first->next == *head1) {
		//从这
		if (first->date < second->date) {
			zhong(&up, &down, first);
			first = first->next;
		}
		else if (first->date == second->date) {
			zhong(&up, &down, first);
			first = first->next;
			second = second->next;
		}
		else {
			zhong(&up, &down, second);
			second = second->next;
		}
		//到这和上文一样
		if (first == *head1) {
			//如果这一次接的是first的最后一个节点,那么执行个步骤
			//第一步,接上first最后一个节点
			zhong(&up, &down, second);
			//这时候新链表的尾节点是second的尾节点
			jie(fi, *last2);
			//jie函数的作用
			//第二部,讲所剩余的second接在  first最后一个节点后面 ,然后设置首尾节点
			// 在这边我用了全局变量,所以没有把新链表的头部wo和尾部wei传进来
			//fi->prior = *last2;     这一步是在设置首节点的prior
			//wo = fi;				  这一步是在给新链表的待定首节点wo赋值成fi,新链表的首节点
			//wei = *last2;			  这一步是在给新链表的待定尾节点wei赋值成*last2,新链表的尾节点
			//wei->next = fi;		  这一步是在设置尾节点的next
			
		}
	}
	while (second->next == *head2) {
		//同理
		if (first->date < second->date) {
			zhong(&up, &down, first);
			first = first->next;
		}
		else if (first->date == second->date) {
			zhong(&up, &down, first);
			first = first->next;
			second = second->next;
		}
		else {
			zhong(&up, &down, second);
			second = second->next;
		}
		if (second == *head2) {
			zhong(&up, &down, first);
			//因为这时候,新链表的尾节点是first的尾节点
			jie(fi, *last1);
		}
	}
}
//一个链接函数
void zhong(struct meizi** up, struct meizi** down, struct meizi* temp) {
	(*down)->next = temp;
	*up = *down;
	*down = (*down)->next;
	(*down)->prior = *up;
}
//一个设置新首尾的函数
void jie(struct meizi* head, struct meizi* last) {
	head->prior = last;
	wo = head;
	wei = last;
	wei->next = head;
}
//正序输出链表的函数
void display_head_next(struct meizi* head) {
	//用p来遍历链表
	struct meizi* p = head;
	//因为尾节点的next节点是首节点
	//所以用do while是为了刚刚好遍历完整这个链表
	do {
		printf("%d ", p->date);
		p = p->next;
	} while (p != head);
	printf("正序输出\n");
}
//倒序输出链表的函数
void display_last_prior(struct meizi* last) {
	//用p来遍历链表
	struct meizi* p = last;
	//因为尾节点的next节点是首节点
	//所以用do while是为了刚刚好遍历完整这个链表
	do {
		printf("%d ", p->date);
		p = p->prior;
	} while (p != last);
	printf("倒序输出\n");
}
//释放内存的函数
void destory(struct meizi* head) {
	//用p来存储当前节点,然后pr是p的下一个节点
	struct meizi* p = head, * pr;
	do {
		pr = p;
		p = p->next;
		//每次释放当前节点的内存,然后越位到下一个
		free(pr);
	} while (p != head);
}

                                                                                                                                2302班 翁雨翔

                                                                                                                                  --湖师计信小组

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值