004——双向链表和循环链表

目录

双向链表

双向链表的初始化(与单链表类似)

增:

Ⅰ)头插法

Ⅱ)尾插法

Ⅲ)中间插入

整体代码示例:

 循环链表

循环单链表

​编辑 循环双链表


双向链表

不同于单链表,双向链表不仅可以往后指向,还可以往前指向,则双向链表是在单链表的基础上,每个结点增加一个指针域,这个指针域保存上一个结点的地址

pre指针域

(保存前一个结点的地址)

  数据域

    data

next指针域

(保存后一个结点的地址)

//双向链表的结构体
typedef struct Node {
	struct Node* pre;//增加保存前一个地址的结点
	int data;
	struct Node* next;
}Node,*LinkList;

 由003——单链表可知,单链表分为带头结点的不带头结点的,双向链表也是同理(上图的是带头结点的)

双向链表的初始化
(与单链表类似)

LinkList InitLinkList() {
	Node* p = (Node*)malloc(sizeof(Node));//申请头结点
	if (p == NULL) {
		printf("空间分配失败\n");
	}
	else {
		//p->data	脏数据,不用管
		p->pre=p->next = NULL;
		//注意:该语句的执行方向是从右往左
		/*
		p->pre = NULL;
		p->next = NULL;
		*/
	}
	return p;
}
int main() {
	LinkList L = InitLinkList();
}

增:

双向链表中增加一个结点(数据)

Ⅰ)头插法

固定在头结点和首元结点之间插入一个结点(头结点之后)如下图的例子

为了方便分析,我们为示意图进行编号 

注意,在这里对数据进行处理时,我们不能使得这个线(单向的,无论是正向还是负向)断掉,比如像下面这种情况,就是错误

正确的顺序应该是先处理③和④,然后再处理①和②(顺序并不唯一)

 (下面代码并不完整)

//头插法:
LinkList HeadInsert(LinkList L, int k) {
	//先申请一个新的结点用来保存数据k
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("空间分配失败/n");
	}
	else {
		s->data = k;//将数据传给新申请的结点s
		
		s->pre = L;//3
		s->next = L->next;//4
		L->next= s;//1
		s->next->pre = s;//2

	}
	return L;
}

或者可以写成3421(还有其他写法)

        s->pre = L;
        s->next = L->next;
        L->next->pre = s;
        L->next = s;

此时还要考虑L为空的情况,因为这个时候的②是不存在的,所以最好将修改②这条线路更改放到最后

所以在我们更改②这条线之前,需要进行一个判断

//头插法:
LinkList HeadInsert(LinkList L, int k) {
	//先申请一个新的结点用来保存数据k
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("空间分配失败/n");
	}
	else {
		s->data = k;//将数据传给新申请的结点s
		s->pre = L;//3
		s->next=L->next;//4
		L->next = s;//1
		if (s->next != NULL) {
			s->next->pre = s;//2
		}
	}
	return L;
}

        s->data = k;//将数据传给新申请的结点s


        s->pre = L;//3


        s->next=L->next;//4


        L->next = s;//1


        if (s->next != NULL) {
            s->next->pre = s;//2
        }

Ⅱ)尾插法

从头结点开始遍历找到尾结点,在尾结点的后面插入新的结点(需要多维护一个pre)

//尾插法
LinkList RearInsert(LinkList L, int k) {
	Node* p = L;	//指针指p向头结点
	while (p->next != NULL) {
		p = p->next;
	}
	//循环结束后指针p指向最后一个结点
	//先申请一个新的结点用来保存数据k
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("空间分配失败\n");
		return L;
	}
	else {
		s->data = k;//将数据传给新申请的结点s
		s->next = p->next;
		s->pre = p;
		p->next = s;

	}
	return L;
}

Ⅲ)中间插入


//中间插入
//首先需要一个查找函数,并且返回该结点的地址
Node* find(LinkList L, int k) {
	Node* p = L->next;
	while (p != NULL && p->data != k) {
		//出现NULL->data!=k会报错,所以这两项的顺序不可以颠倒
		p = p->next;
	}
	return p;//要么为空,要么保存所要数据
}
//中间插入的函数
Node* MidInsert(LinkList L, int x, int k) {
	//在元素x后面插入数据k
	Node* p = find(L, x);
	if (p == NULL) {
		printf("数据%d不存在,无法找到插入位置,插入失败\n", x);
		return L;
	}
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("空间分配失败,插入失败\n");
		return L;
	}
	else {
		s->data = k;
		s->next = p->next;
		p->next = s;
		if (s->next != NULL) {
			s->next->pre = s;
		}
	}
	return L;
}

删除如下面图示

修改后的结果

//删除
LinkList Delete(LinkList L, int k) {
	if (L->next == NULL) {
		printf("空链表,删除失败\n");
		return L;
	}
	//找到k所在的结点p
	Node* p = find(L, k);
	if (p == NULL) {
		printf("数据%d不存在,删除失败\n");
		return L;
	}
	//删除
	p->pre->next = p->next;
	if (p->next != NULL) {
		p->next->pre = p->pre;
	}
	//防止p成为空指针
	free(p);
	p = NULL;
	return L;
	
}

修改代码与单链表是相同的

//修改
LinkList Replace(LinkList L, int x, int k) {
	Node* p = find(L, x);
	if (p == NULL) {
		printf("数据%d不存在,无法找到修改位置,修改失败\n", x);
		return L;
	}
	else {
		p->data = k;
	}
	return L;
}

查找代码与单链表是相同的

Node* find(LinkList L, int k) {
	Node* p = L->next;
	while (p != NULL && p->data != k) {
		//出现NULL->data!=k会报错,所以这两项的顺序不可以颠倒
		p = p->next;
	}
	return p;//要么为空,要么保存所要数据
}

整体代码示例:

#include<stdio.h>
#include<stdlib.h>
//双向链表的结构体
typedef struct Node {
	struct Node* pre;
	int data;
	struct Node* next;
}Node,*LinkList;

LinkList InitLinkList() {
	Node* p = (Node*)malloc(sizeof(Node));//申请头结点
	if (p == NULL) {
		printf("空间分配失败\n");
	}
	else {
		//p->data	脏数据,不用管
		p->pre=p->next = NULL;
		//注意:该语句的执行方向是从右往左
		/*
		p->pre = NULL;
		p->next = NULL;
		*/
	}
	return p;
}

//头插法:
LinkList HeadInsert(LinkList L, int k) {
	//先申请一个新的结点用来保存数据k
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("空间分配失败/n");
	}
	else {
		s->data = k;//将数据传给新申请的结点s
		s->pre = L;//3
		s->next=L->next;//4
		L->next = s;//1
		if (s->next != NULL) {
			s->next->pre = s;//2
		}
	}
	return L;
}

//尾插法
LinkList RearInsert(LinkList L, int k) {
	Node* p = L;	//指针指p向头结点
	while (p->next != NULL) {
		p = p->next;
	}
	//循环结束后指针p指向最后一个结点
	//先申请一个新的结点用来保存数据k
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("空间分配失败\n");
		return L;
	}
	else {
		s->data = k;//将数据传给新申请的结点s
		s->pre = p;//3
		s->next = p->next;//4
		p->next = s;//1
		if (s->next != NULL) {
			s->next->pre = s;//2
		}

	}
	return L;
}


//中间插入
//首先需要一个查找函数,并且返回该结点的地址
Node* find(LinkList L, int k) {
	Node* p = L->next;
	while (p != NULL && p->data != k) {
		//出现NULL->data!=k会报错,所以这两项的顺序不可以颠倒
		p = p->next;
	}
	return p;//要么为空,要么保存所要数据
}
//中间插入的函数
Node* MidInsert(LinkList L, int x, int k) {
	//在元素x后面插入数据k
	Node* p = find(L, x);
	if (p == NULL) {
		printf("数据%d不存在,无法找到插入位置,插入失败\n", x);
		return L;
	}
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("空间分配失败,插入失败\n");
		return L;
	}
	else {
		s->data = k;
		s->next = p->next;
		p->next = s;
		if (s->next != NULL) {
			s->next->pre = s;
		}
	}
	return L;
}

//修改
LinkList Replace(LinkList L, int x, int k) {
	Node* p = find(L, x);
	if (p == NULL) {
		printf("数据%d不存在,无法找到修改位置,修改失败\n", x);
		return L;
	}
	else {
		p->data = k;
	}
	return L;
}

//删除
LinkList Delete(LinkList L, int k) {
	if (L->next == NULL) {
		printf("空链表,删除失败\n");
		return L;
	}
	//找到k所在的结点p
	Node* p = find(L, k);
	if (p == NULL) {
		printf("数据%d不存在,删除失败\n");
		return L;
	}
	//删除
	p->pre->next = p->next;
	if (p->next != NULL) {
		p->next->pre = p->pre;
	}
	//防止p成为空指针
	free(p);
	p = NULL;
	return L;
	
}


void show(LinkList L) {
	Node* p = L->next;
	while (p != NULL)
	{
		printf("%d\t", p->data);
		p = p->next;
	}
}

int main() {
	LinkList L = InitLinkList();
	L = HeadInsert(L, 10);
	L = HeadInsert(L, 22);
	L = HeadInsert(L, 16);
	L = HeadInsert(L, 45);
	L = RearInsert(L, 77);
	L = MidInsert(L, 77,99);
	L = Delete(L, 10);
	show(L);
	return 0;

}

运行结果

 循环链表

循环单链表

循环链表只需要让最后一个结点的指针域指向头结点

 那么循环链表和单链表几乎没有太大差异,只是在为空的一些位置改成头结点

#include<stdio.h>
#include<stdlib.h>
typedef struct Node {
	int data;		//该节点的数据
	struct Node* next;		
}Node,*LinkList;


//初始化一个带头结点的空的循环链表
LinkList InitLinkList() {
	Node* s = (Node*)malloc(sizeof(Node));//申请头结点
	if (s == NULL) {
		printf("空间分配失败\n");
	}
	else {
		//s->data	脏数据,不用管
		s->next = s;//改变。。。。。。。。。。。。
	}
	return s;
}


//头插法:
LinkList HeadInsert(LinkList L, int k) {
	//先申请一个新的结点用来保存数据k
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("空间分配失败/n");
	}
	else {
		s->data = k;//将数据传给新申请的结点s
		s->next = L->next;
		L->next = s;
	}
	return L;
}

//尾插法
LinkList RearInsert(LinkList L, int k) {
	Node* p = L;	//指针指p向头结点
	while (p->next != L) {//改变。。。。。。。。。。。。
		p = p->next;
	}
	//循环结束后指针p指向最后一个结点
	//先申请一个新的结点用来保存数据k
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("空间分配失败\n");
		return L;
	}
	else {
		s->data = k;//将数据传给新申请的结点s
		s->next = p->next;//改变。。。。。。。。。。。。
		p->next = s;
	}
	return L;
}

//中间插入
//首先需要一个查找函数,并且返回该结点的地址
Node* find(LinkList L, int k) {
	Node* p = L->next;
	while (p!=L && p->data != k) {//改变。。。。。。。。。。。。
//出现NULL->data!=k会报错,所以这两项的顺序不可以颠倒
		p = p->next;
	}
	return p;//要么为空,要么保存所要数据
}
//中间插入的函数
Node* MidInsert(LinkList L, int x, int k) {
	//在元素x后面插入数据k
	Node* p = find(L, x);
	if (p == L) {//改变。。。。。。。。。。。。
		printf("数据%d不存在,无法找到插入位置,插入失败\n",x);
		return L;
	}
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("空间分配失败,插入失败\n");
		return L;
	}
	else {
		s->data = k;
		s->next = p->next;
		p->next = s;
	}
	return L;
}

//修改
LinkList Replace(LinkList L, int x, int k) {
	Node* p = find(L, x);
	if (p == L) {//改变。。。。。。。。。。。。
		printf("数据%d不存在,无法找到修改位置,修改失败\n", x);
		return L;
	}
	else {
		p->data = k;
	}
	return L;
}

//删除
LinkList Delete(LinkList L, int k) {
	if (L->next == L) {//改变。。。。。。。。。。。。
		printf("数据%d不存在,删除失败\n", k);
		return L;
	}
	//找到k所在的结点p和上一个结点
	Node* pre = L;
	Node* p = L->next;
	while (p!=L&&p->data!=k)//改变。。。。。。。。。。。。
	{
		pre = p;
		p = p->next;
	}
	if (p == L) {//改变。。。。。。。。。。。。
		printf("数据%d不存在,删除失败\n", k);
		return L;
	}
	pre->next = p->next;
	free(p);
	p = NULL;//防止p成为野指针
	return L;
}

void show(LinkList L) {
	Node* p = L->next;
	while (p!= L)//改变。。。。。。。。。。。。
	{
		printf("%d\t", p->data);
		p = p->next;
	}
}
int main() {
	LinkList L = NULL;
	L = InitLinkList();
	L=HeadInsert(L,10);
	L = HeadInsert(L, 8);
	L = RearInsert(L, 15);
	L = MidInsert(L, 5, 55);
	L=Replace(L, 8, 88);
	L = Delete(L, 8);
	show(L);
	return 0;
}

运行结果:

 循环双链表

循环双链表与之同理


#include<stdio.h>
#include<stdlib.h>
//双向链表的结构体
typedef struct Node {
	struct Node* pre;
	int data;
	struct Node* next;
}Node, * LinkList;

LinkList InitLinkList() {
	Node* p = (Node*)malloc(sizeof(Node));//申请头结点
	if (p == NULL) {
		printf("空间分配失败\n");
	}
	else {
		//p->data	脏数据,不用管
		//	p->next=p->pre=p;
		p->next = p;
		p->pre = p;
	}
	return p;
}

//头插法:
LinkList HeadInsert(LinkList L, int k) {
	//先申请一个新的结点用来保存数据k
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("空间分配失败/n");
	}
	else {
		s->data = k;//将数据传给新申请的结点s
		s->pre = L;//3
		s->next = L->next;//4
		L->next = s;//1
		s->next->pre = s;//2
	}
	return L;
}

//尾插法
LinkList RearInsert(LinkList L, int k) {
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL)
	{
		printf("空间分配失败\n");
		return L;
	}
	s->data = k;

	//找尾节点
	Node* p = L;
	while (p->next != L)
	{
		p = p->next;
	}
	s->next = p->next;
	s->pre = p;
	p->next = s;
	s->next->pre = s;
	return L;
}


//中间插入
//首先需要一个查找函数,并且返回该结点的地址
Node* find(LinkList L, int k) {
	//查找数据k所在的节点,并且返回该节点的地址 
	Node* p = L->next;
	while (p != L && p->data != k)
	{
		p = p->next;
	}
	return p;
}
//中间插入的函数
Node* MidInsert(LinkList L, int x, int k) {
	//数据x后插入数据k 
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL)
	{
		printf("空间分配失败\n");
		return L;
	}
	s->data = k;
	//找x所在节点 
	Node* p = find(L, x);
	if (p == L)
	{
		printf("数据%d不存在,插入失败\n", x);
		return L;
	}
	s->pre = p;//3
	s->next = p->next;//4
	p->next = s;//1
	s->next->pre = s;//2
	return L;

}

//修改
LinkList Replace(LinkList L, int x, int k) {
	Node* p = find(L, x);
	if (p == L) {
		printf("数据%d不存在,无法找到修改位置,修改失败\n", x);
		return L;
	}
	else {
		p->data = k;
	}
	return L;
}

//删除
LinkList Delete(LinkList L, int k) {
	if (L->next == L)
	{
		printf("空链表,删除失败\n");
		return L;
	}
	//找k所在的节点p
	Node* p = find(L, k);
	if (p == L)
	{
		printf("数据%d不存在,删除失败\n", k);
		return L;
	}
	//删除:
	p->pre->next = p->next;
	p->next->pre = p->pre;

	free(p);
	p = NULL;
	return L;
}


void show(LinkList L) {
	Node* p = L->next;
	while (p != L)
	{
		printf("%d\t", p->data);
		p = p->next;
	}
}

int main() {
	LinkList L = InitLinkList();
	L = HeadInsert(L, 10);
	L = HeadInsert(L, 22);
	L = HeadInsert(L, 16);
	L = HeadInsert(L, 45);
	L = RearInsert(L, 77);
	L = MidInsert(L, 77, 99);
	L = Delete(L, 10);
	show(L);
	return 0;

}

 运行结果:

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值