0
点赞
收藏
分享

微信扫一扫

ios:Command PhaseScriptExecution failed with a nonzero exit code

左小米z 2024-06-01 阅读 11

目录

1. 前言:

2. 双向链表

2.1双向链表的结构

2.2 双向链表节点的定义

3. 双向链表的实现

3.1双向链表的初始化

3.2 双向链表的打印

3.3 双向链表的尾插

 3.4 双向链表的头插

3.5 双向链表的尾删

 3.6 双向链表的头删

 3.7 双向链表的查找

3.8 双向链表在指定位置之后插入数据

3.9 双向链表删除指定位置数据

 3.10 双向链表的销毁

4.  删除指定位置数据和双向链表销毁的问题

 5. 顺序表和单链表的优缺点

 6. 完整代码

6.1 ListNode.h

6.2 ListNode.c


1. 前言:

在前面我们提到了单链表是不带头单项不循环链表,在单链表中,其实是没有头节点这个概念的,但是为了方便理解,我们将单链表里面的第一个有效节点称之为头节点,这是一种错误的说法,接下来我们将学习双向链表,带头双向循环链表

2. 双向链表

2.1双向链表的结构

在这里我们可以看到双向链表是有两个指针的,一个指向前一个节点的前驱指针prev和一个指向下一个节点的后继指针next。

这里我们说的头节点,其实就是哨兵位,它是不存储任何元素,相当于是一个用来“放哨的”,

我们知道双向链表是循环的,如果我们需要遍历链表的话,容易进入死循环,这里哨兵位就可以用来避免死循环的发生。

2.2 双向链表节点的定义

这里我们知道了双向链表的结构,那我们就可以定义节点

typedef int LNDataType;

typedef struct ListNode
{
	LNDataType data;//数据
	struct ListNode* next;//指向下一个节点的指针
	struct ListNode* prev;//指向前一个节点的指针
}ln;

3. 双向链表的实现

ListNode.h

//包含之后可能用到的库函数头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int LNDataType;//类型重命名,方便后续替换

typedef struct ListNode//类型重命名,简化写法
{
	LNDataType data;//数据
	struct ListNode* next;//指向下一个节点的指针
	struct ListNode* prev;//指向前一个节点的指针
}ln;
//链表初始化
ln* Init();
//链表的打印
void* LNPrint(ln* phead);
//链表尾插
void* LNPushBack(ln* phead, LNDataType x);
//链表头插
void* LNPushFront(ln* phead, LNDataType x);
//链表尾删
void* LNPopBack(ln* phead);
//链表头删
void* LNPopFront(ln* phead);
//链表查找
void* LNFind(ln* phead, LNDataType x);
//链表在指定位置之后插入数据
void* LNInsert(ln* pos, LNDataType x);
//链表删除指定位置数据
void* LNErase(ln* pos);
//链表销毁
void* LNDestroy(ln* phead);

3.1双向链表的初始化

我们之前在单链表中初始化都是在main函数里面初始化的,因为单链表为空的时候,链表是空链表,但是我们双向链表为空的时候,此时链表只剩下一个头节点。

当想要创建一个双向链表的时候,那我们必须要给他初始化一个头节点

ListNode.c

ln* LNBuyNode(LNDataType x)
{
	ln* newnode = (ln*)malloc(sizeof(ln));
	if(newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = x;
	//双向链表是循环的,此时我们要创建一个头节点,就需要让他自己指向自己
	newnode->next = newnode->prev = newnode;
    return newnode;
}

ln* LNInit()
{
	ln* phead = LNBuyNode(-1);//这里初始化需要给它一个值,我们就设置具有辨识度的一个值给它
	return phead;
}

test.c

void test()
{
	ln* plist = NULL;
	plist = LNInit();

}
int main()
{
	test();
	return 0;
}

测试双向链表的初始化

3.2 双向链表的打印

思路:

ListNode.c

void* LNPrint(ln* phead)
{
	//这里我们创建一个指针变量指向头节点的下一个节点
	ln* pcur = phead->next;
	while (pcur != phead)//这里我们不需要打印头节点,也防止死循环
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

这里我们并没有插入数据,所以链表此时是空链表

3.3 双向链表的尾插

思路:

代码:

ListNode.c

//这里我们在单链表的时候都是传递二级指针,这里为什么传一级指针
//因为插入数据之前,链表必须初始化到只有一个节点的情况
//我们不需要改变哨兵位的地址,所以这里我们传一级指针即可
void* LNPushBack(ln* phead, LNDataType x)
{
	ln* newnode = LNBuyNode(x);//创建新节点
	newnode->next = phead;//将新节点指向下一个节点的指针指向头节点
	newnode->prev = phead->prev;//将新节点指向前一个节点的指针指向头节点的尾节点
	phead->prev->next = newnode;//将链表的尾节点的指向下一个节点的指针指向新节点
	phead->prev = newnode;//将头节点指向前一个节点的指针指向新节点
}

测试双向链表打印方法和双向链表尾插方法

 3.4 双向链表的头插

思路:

代码:

ListNode.c

void* LNPushFront(ln* phead, LNDataType x)
{
	ln* newnode = LNBuyNode(x);//创建新节点
	newnode->next = phead->next;//将新节点指向下一个节点的指针指向头节点的下一个节点
	newnode->prev = phead;//将新节点指向前一个节点的指针指向头节点
	phead->next->prev = newnode;//将头节点的下一个节点指向前一个节点的指针指向新节点
	phead->next = newnode;//将头节点指向下一个节点的指针指向新节点
}

测试双向链表的头插方法

3.5 双向链表的尾删

思路:

代码:

ListNode.c

void* LNPopBack(ln* phead)
{   
	assert(phead && phead->next != phead);//这里判断phead是否为空指针,并且链表是否为空链表
	//创建一个指针指向链表的尾节点
	ln* pcur = phead->prev;
	pcur->prev->next = phead;//将链表尾节点指向的前一个节点指向的下个节点的指针指向头节点
	phead->prev = pcur->prev;//将头节点指向前一个节点的指针指向尾节点指向的前一个节点
	free(pcur);//释放掉pcur
	pcur = NULL;//防止pcur变成野指针
}

测试双向链表的尾删

 3.6 双向链表的头删

思路:

代码:

ListNode.c

void* LNPopFront(ln* phead)
{
	assert(phead && phead->next!=phead);//这里判断phead是否为空指针,并且链表是否为空链表
	//创建一个指针指向头节点指向的下一个节点
	ln* pcur = phead->next;
	pcur->next->prev = phead;//将头节点的下一个节点指向的下一个节点指向前一个节点的指针指向头节点
	phead->next = pcur->next;//将头节点指向下一个节点的指针指向头节点下一个节点的下一个节点
	free(pcur);//释放掉pcur
	pcur = NULL;//防止pcur变成野指针
}

测试双向链表的头删

 3.7 双向链表的查找

思路:

代码:

ListNode.c

void* LNFind(ln* phead, LNDataType x)
{
	//创建一个指针变量指向头节点的下一个节点
	ln* pcur = phead->next;
	while (pcur != phead)//遍历查找
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}//没有找到返回空指针
	return NULL;
}

 测试双向链表查找

3.8 双向链表在指定位置之后插入数据

思路:

代码:

ListNode.c

void* LNInsert(ln* pos, LNDataType x)
{
	assert(pos);//判断pos是否是空指针
	//创建新节点
	ln* newnode = LNBuyNode(x);
	newnode->next = pos->next;//将新节点指向下一个节点的指针指向pos的下一个节点
	newnode->prev = pos;//将新节点指向前一个节点的指针指向pos
	pos->next->prev = newnode;//将pos指向的下一个节点指向前一个节点的指针指向新节点
	pos->next = newnode;//将pos指向下一个节点的指针指向新节点
}

测试双向链表在指定位置之后插入数据

3.9 双向链表删除指定位置数据

思路:

代码:

ListNode.c

void* LNErase(ln* pos)
{
	assert(pos);//判断pos是否为空指针
	pos->prev->next = pos->next;//将pos的前一个节点指向下一个节点的指针指向pos的下一个节点
	pos->next->prev = pos->prev;//将pos的下一个节点指向前一个节点的指针指向pos的前一个节点
	free(pos);//释放掉pos
	pos = NULL;//pos置为空
}

测试双向链表删除指定位置数据

 3.10 双向链表的销毁

思路:

 代码:

ListNode.c

void* LNDestroy(ln* phead)
{
	assert(phead && phead->next != phead);//这里判断phead是否为空指针,并且链表是否为空链表
	//创建一个指针变量指向头节点的下一个节点
	ln* next = phead->next;
	while (next != phead)
	{
		ln* pcur = next;
		free(pcur);
		next = next->next;
	}
	free(phead);//释放掉头节点
}

测试双向链表销毁

4.  删除指定位置数据和双向链表销毁的问题

 5. 顺序表和单链表的优缺点

 补充前面的单链表与顺序的对比:

不同点顺序表链表(单链表)
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持O(1)不支持O(N)
任意位置插入和删除数据可能需要移动元素,效率低O(N)只需要修改指针指向
空间动态顺序表,空间不够时需要扩容没有容量的概念
应用场景元素高校存储+频繁访问任意位置插入和删除频繁

 6. 完整代码

6.1 ListNode.h

//包含之后可能用到的库函数头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int LNDataType;//类型重命名,方便后续替换

typedef struct ListNode//类型重命名,简化写法
{
	LNDataType data;//数据
	struct ListNode* next;//指向下一个节点的指针
	struct ListNode* prev;//指向前一个节点的指针
}ln;
//链表初始化
ln* LNInit();
//链表的打印
void* LNPrint(ln* phead);
//链表尾插
void* LNPushBack(ln* phead, LNDataType x);
//链表头插
void* LNPushFront(ln* phead, LNDataType x);
//链表尾删
void* LNPopBack(ln* phead);
//链表头删
void* LNPopFront(ln* phead);
//链表查找
void* LNFind(ln* phead, LNDataType x);
//链表在指定位置之后插入数据
void* LNInsert(ln* pos, LNDataType x);
//链表删除指定位置数据
void* LNErase(ln* pos);
//链表销毁
void* LNDestroy(ln* phead);

6.2 ListNode.c

#include"ListNode.h"

ln*LNBuyNode(LNDataType x)
{
	ln* newnode = (ln*)malloc(sizeof(ln));
	if(newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = x;
	//双向链表是循环的,此时我们要创建一个头节点,就需要让他自己指向自己
	newnode->next = newnode->prev = newnode;
	return newnode;
}

ln* LNInit()
{
	ln* phead = LNBuyNode(-1);//这里初始化需要给它一个值,我们就设置具有辨识度的一个值给它
	return phead;
}

void* LNPrint(ln* phead)
{
	//这里我们创建一个指针变量指向头节点的下一个节点
	ln* pcur = phead->next;
	while (pcur != phead)//这里我们不需要打印头节点,也防止死循环
	{
		printf("%d->",pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

//这里我们在单链表的时候都是传递二级指针,这里为什么传一级指针
//因为插入数据之前,链表必须初始化到只有一个节点的情况
//我们不需要改变哨兵位的地址,所以这里我们传一级指针即可
void* LNPushBack(ln* phead, LNDataType x)
{
	ln* newnode = LNBuyNode(x);//创建新节点
	newnode->next = phead;//将新节点指向下一个节点的指针指向头节点
	newnode->prev = phead->prev;//将新节点指向前一个节点的指针指向头节点的尾节点
	phead->prev->next = newnode;//将链表的尾节点的指向下一个节点的指针指向新节点
	phead->prev = newnode;//将头节点指向前一个节点的指针指向新节点
}

void* LNPushFront(ln* phead, LNDataType x)
{
	ln* newnode = LNBuyNode(x);//创建新节点
	newnode->next = phead->next;//将新节点指向下一个节点的指针指向头节点的下一个节点
	newnode->prev = phead;//将新节点指向前一个节点的指针指向头节点
	phead->next->prev = newnode;//将头节点的下一个节点指向前一个节点的指针指向新节点
	phead->next = newnode;//将头节点指向下一个节点的指针指向新节点
}

void* LNPopBack(ln* phead)
{   
	assert(phead && phead->next!=phead);//这里判断phead是否为空指针,并且链表是否为空链表
	//创建一个指针指向链表的尾节点
	ln* pcur = phead->prev;
	pcur->prev->next = phead;//将链表尾节点指向的前一个节点指向的下个节点的指针指向头节点
	phead->prev = pcur->prev;//将头节点指向前一个节点的指针指向尾节点指向的前一个节点
	free(pcur);//释放掉pcur
	pcur = NULL;//防止pcur变成野指针
}

void* LNPopFront(ln* phead)
{
	assert(phead && phead->next!=phead);//这里判断phead是否为空指针,并且链表是否为空链表
	//创建一个指针指向头节点指向的下一个节点
	ln* pcur = phead->next;
	pcur->next->prev = phead;//将头节点的下一个节点指向的下一个节点指向前一个节点的指针指向头节点
	phead->next = pcur->next;//将头节点指向下一个节点的指针指向头节点下一个节点的下一个节点
	free(pcur);//释放掉pcur
	pcur = NULL;//防止pcur变成野指针
}

void* LNFind(ln* phead, LNDataType x)
{
	//创建一个指针变量指向头节点的下一个节点
	ln* pcur = phead->next;
	while (pcur != phead)//遍历查找
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}//没有找到返回空指针
	return NULL;
}

void* LNInsert(ln* pos, LNDataType x)
{
	assert(pos);//判断pos是否是空指针
	//创建新节点
	ln* newnode = LNBuyNode(x);
	newnode->next = pos->next;//将新节点指向下一个节点的指针指向pos的下一个节点
	newnode->prev = pos;//将新节点指向前一个节点的指针指向pos
	pos->next->prev = newnode;//将pos指向的下一个节点指向前一个节点的指针指向新节点
	pos->next = newnode;//将pos指向下一个节点的指针指向新节点
}

void* LNErase(ln* pos)
{
	assert(pos);//判断pos是否为空指针
	pos->prev->next = pos->next;//将pos的前一个节点指向下一个节点的指针指向pos的下一个节点
	pos->next->prev = pos->prev;//将pos的下一个节点指向前一个节点的指针指向pos的前一个节点
	free(pos);//释放掉pos
	pos = NULL;//pos置为空
}

void* LNDestroy(ln* phead)
{
	assert(phead && phead->next != phead);//这里判断phead是否为空指针,并且链表是否为空链表
	//创建一个指针变量指向头节点的下一个节点
	ln* next = phead->next;
	while (next != phead)
	{
		ln* pcur = next;
		free(pcur);
		next = next->next;
	}
	free(phead);//释放掉头节点
}
举报

相关推荐

0 条评论