数据结构链表完整实现(负完整代码)


文章目录

  • 前言
  • 引入
  • 1、链表定义及结构
  • 链表的分类
  • 3、单向不带头链表实现
    • 实现
    • 完整代码
  • 4、带头双向循环链表实现
    • 实现
    • 完整代码


前言


引入

在上一篇文章中,我们认识了顺序表,但是在许多情况中,顺序表在处理一些事件时还存在许多问题,比如:

1.头插、头删或者在中部的插入或删除需要移动大量的元素,时间复杂度过高。

2.增容需要申请新空间,拷贝数据,释放旧空间,会有不小的消耗。

3.增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为50,满了以后增容到 200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了45个数据空间。

为了解决这些问题,我们提出了如下结构,链表。

1、链表定义及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

结构:列表中存在数据域与指针域,数据域用于存放该地区的值,指针域用于存放指向的下一个目标的地址。

typedef int SLTDataType;

//single list
typedef struct SListNode {
	//数据域与指针域
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

在这里插入图片描述

上面就是常见的单链表的结构:

1.链表在逻辑上连续,在物理上不连续

2.每一个新的区域都是动态申请出来的,申请出的区域可以连续也可以不连续

链表的分类

1.单向和双向链表

在这里插入图片描述

2.带头和不带头

在这里插入图片描述

3.循环或非循环

在这里插入图片描述

虽然链表的分类有很多,但在实际情况中,我们并不是都用的,比较常用的链表就是无头单向链表与带头双向循环链表。

无头单向链表

在这里插入图片描述

带头双向循环链表

在这里插入图片描述

以下,我们就来实现一下这两个链表

3、单向不带头链表实现

实现

1)结构定义:单向不带头链表分为指针域和数据域。其中指针域存放下一个位置的地址,数据域存放当前位置的值。

typedef int SLTDataType;

//single list
typedef struct SListNode {
	//数据域与指针域
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

2)尾插

//尾插
void SListPushBack(SLTNode** pphead,SLTDataType x)
{
	SLTNode* newNode = CreatNode(x);

	//指针未指向任何位置,表明链表中还没有值
	if (*pphead == NULL) {
		*pphead = newNode;
	}
	else {
		//新建一个临时节点,用于寻找最后一个节点
		SLTNode* cur = *pphead;
		//找最后一个节点(尾节点指向NULL位置)
		while (cur->next != NULL) {
			cur = cur->next;
		}
		//尾节点指针域存放新节点地址
		cur->next = newNode;
	}

}

3)头插

//头插
void SListPushFront(SLTNode** pphead, SLTDataType x) {
	SLTNode* newNode = CreatNode(x);

	//让新节点指针域指向当前首节点
	newNode->next = *pphead;
	//新插入的节点变为了首节点
	*pphead = newNode;
}

4)尾删

//尾删
void SListPopBack(SLTNode** pphead) {
	//表里面没有值
	assert(*pphead);

	//表中只有一个值
	if ((*pphead)->next == NULL) {
		free(*pphead);
		*pphead = NULL;
	}
	//表中有一个或者一个以上的值
	else {
		SLTNode* cur = *pphead;
		SLTNode* prev = NULL;

		//找到尾节点和前一个节点
		while (cur->next != NULL) {
			prev = cur;
			cur = cur->next;
		}

		free(cur);
		cur = NULL;

		prev->next = NULL;
	}
}

5)头删

//头删
void SListPopFront(SLTNode** pphead) {
	assert(*pphead);

	//建立一个临时节点存储当前首节点指针域指向的地址,即第二个节点的地址
	SLTNode* cur = (*pphead)->next;

	//释放当前首节点的值
	free(*pphead);
	*pphead = NULL;

	//为首节点赋上第二个节点的值
	*pphead = cur;

}

6)在位置前插入

//在pos前插入
void SListInsert(SLTNode** pphead,SLTNode* pos,SLTDataType x) {
	//pos位置就是首结点,就转换为头插
	if (pos == *pphead) {
		SListPushFront(pphead,x);
	}//pos位置是其他的节点
	else {
		SLTNode* newNode = CreatNode(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos) {
			prev = prev->next;
		}

		//将前一个的指针域指向插入的值的地址:newNode
		prev->next = newNode;
		//将新的值指针域指向pos位置
		newNode->next = pos;
	}

}

7)在位置后插入

//在pos后插入
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
	//建立一个新的节点
	SLTNode* newNode = CreatNode(x);
	//建立一个临时变量存储pos后面的节点
	SLTNode* after = pos->next;
	//让pos指向新节点
	pos->next = newNode;
	//让新节点指向刚刚pos后面的节点
	newNode->next = after;

}

8)删除位置的值

//删除pos位置的值
void SListErase(SLTNode** pphead,SLTNode* pos) {
	//pos位置与首节点重合,转换为头删
	if (pos == *pphead) {
		SListPopFront(pphead);
	}
	else {
		SLTNode* prev = *pphead;

		//找pos的前一个位置
		while (prev->next != pos) {
			prev = prev->next;
		}
		//将前一个值指向pos的后一个值
		prev->next = pos->next;

		free(pos);
		pos = NULL;
	}

}

9)删除位置后的值

//删除pos后面的值
void SListEraseAfter(SLTNode** pphead,SLTNode* pos) {
	//建立一个临时变量存储pos后两位的位置
	SLTNode* after = pos->next->next;

	free(pos->next);
	pos->next = NULL;

	//让pos指向刚刚后两位的位置
	pos->next = after;

}

10)按值查找

//按值查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x) {
	SLTNode* cur = phead;
	while (cur) {
		if (cur->data == x) {
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}

11)修改

//修改
void SListModify(SLTNode** pphead, SLTNode* pos,SLTDataType x) {
	pos->data = x;
}

12)保存

//保存
void SListSave(SLTNode* pphead) {
	FILE* pf = fopen("SListNode.txt","wb");

	if (pphead == NULL) {
		fclose(pf);
		pf = NULL;
	}
	else {
		SLTNode* cur = pphead;
		while (cur != NULL) {
			fwrite(cur,sizeof(SLTDataType),1,pf);
			cur = cur->next;
		}
	}

}

13)打印

//打印
void SListPrint(SLTNode* phead) {
	SLTNode* cur = phead;

	if (cur == NULL) {
		//如果链表中无元素,则cur == NULL,不进入循环
		printf("NULL\n");
	}
	else {
		//一直遍历到最后一个位置:尾节点指向的NULL位置
		while (cur != NULL) {
			//打印数据
			printf("%d ", cur->data);
			//根据指针跳转到下一个位置
			cur = cur->next;
		}
		printf("\n");
	}
}

14)清空

//清空
void SListClear(SLTNode** pphead) {
	assert(*pphead);

	//清空链表,链表之后还要使用,所以我们只将首位置置为NULL,不释放
	//这里从第二个位置开始释放
	SLTNode* cur = (*pphead)->next;
	SLTNode* after = NULL;
	while (cur != NULL) {
		//先记录下一个节点的位置
		after = cur->next;
		//释放当前节点
		free(cur);
		cur = NULL;

		cur = after;
	}

	//清空链表,链表之后还要使用,所以我们只将首位置置为NULL,不释放
	*pphead = NULL;
}

15)销毁

//销毁
void SListDestroy(SLTNode** pphead) {
	assert(*pphead);

	//销毁链表,链表之后不能使用了,所以将首位置也一并释放
	SLTNode* cur = *pphead;
	SLTNode* after = NULL;
	while (cur != NULL) {
		//先记录下一个节点的位置
		after = cur->next;
		//释放当前节点
		free(cur);
		cur = NULL;

		cur = after;
	}
}

完整代码

1)SListNode.h

#pragma once

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

typedef int SLTDataType;

//single list
typedef struct SListNode {
	//数据域与指针域
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

//打印
void SListPrint(SLTNode* phead);

//头插
void SListPushFront(SLTNode** pphead, SLTDataType x);

//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x);

//头删
void SListPopFront(SLTNode** pphead);

//尾删
void SListPopBack(SLTNode** pphead);

//按值查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x);

//在pos前插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);

//在pos后插入
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x);

//删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos);

//删除pos后面的值
void SListEraseAfter(SLTNode** pphead, SLTNode* pos);

//修改
void SListModify(SLTNode **pphead, SLTNode* pos, SLTDataType x);

//保存
void SListSave(SLTNode* pphead);

//清空
void SListClear(SLTNode** pphead);

//销毁
void SListDestroy(SLTNode** pphead);

2)SListNode.c

#define _CRT_SECURE_NO_WARNINGS

#include"SListNode.h"

//打印
void SListPrint(SLTNode* phead) {
	SLTNode* cur = phead;

	if (cur == NULL) {
		//如果链表中无元素,则cur == NULL,不进入循环
		printf("NULL\n");
	}
	else {
		//一直遍历到最后一个位置:尾节点指向的NULL位置
		while (cur != NULL) {
			//打印数据
			printf("%d ", cur->data);
			//根据指针跳转到下一个位置
			cur = cur->next;
		}
		printf("\n");
	}
}

//建立一个新的,可以长久储存的节点
SLTNode* CreatNode(SLTDataType x) {
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL) {
		perror("malloc");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

//尾插
void SListPushBack(SLTNode** pphead,SLTDataType x)
{
	SLTNode* newNode = CreatNode(x);

	//指针未指向任何位置,表明链表中还没有值
	if (*pphead == NULL) {
		*pphead = newNode;
	}
	else {
		//新建一个临时节点,用于寻找最后一个节点
		SLTNode* cur = *pphead;
		//找最后一个节点(尾节点指向NULL位置)
		while (cur->next != NULL) {
			cur = cur->next;
		}
		//尾节点指针域存放新节点地址
		cur->next = newNode;
	}

}

//头插
void SListPushFront(SLTNode** pphead, SLTDataType x) {
	SLTNode* newNode = CreatNode(x);

	//让新节点指针域指向当前首节点
	newNode->next = *pphead;
	//新插入的节点变为了首节点
	*pphead = newNode;
}

//头删
void SListPopFront(SLTNode** pphead) {
	assert(*pphead);

	//建立一个临时节点存储当前首节点指针域指向的地址,即第二个节点的地址
	SLTNode* cur = (*pphead)->next;

	//释放当前首节点的值
	free(*pphead);
	*pphead = NULL;

	//为首节点赋上第二个节点的值
	*pphead = cur;

}

//尾删
void SListPopBack(SLTNode** pphead) {
	//表里面没有值
	assert(*pphead);

	//表中只有一个值
	if ((*pphead)->next == NULL) {
		free(*pphead);
		*pphead = NULL;
	}
	//表中有一个或者一个以上的值
	else {
		SLTNode* cur = *pphead;
		SLTNode* prev = NULL;

		//找到尾节点和前一个节点
		while (cur->next != NULL) {
			prev = cur;
			cur = cur->next;
		}

		free(cur);
		cur = NULL;

		prev->next = NULL;
	}
}

//按值查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x) {
	SLTNode* cur = phead;
	while (cur) {
		if (cur->data == x) {
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}

//在pos前插入
void SListInsert(SLTNode** pphead,SLTNode* pos,SLTDataType x) {
	//pos位置就是首结点,就转换为头插
	if (pos == *pphead) {
		SListPushFront(pphead,x);
	}//pos位置是其他的节点
	else {
		SLTNode* newNode = CreatNode(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos) {
			prev = prev->next;
		}

		//将前一个的指针域指向插入的值的地址:newNode
		prev->next = newNode;
		//将新的值指针域指向pos位置
		newNode->next = pos;
	}

}

//在pos后插入
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
	//建立一个新的节点
	SLTNode* newNode = CreatNode(x);
	//建立一个临时变量存储pos后面的节点
	SLTNode* after = pos->next;
	//让pos指向新节点
	pos->next = newNode;
	//让新节点指向刚刚pos后面的节点
	newNode->next = after;

}

//删除pos位置的值
void SListErase(SLTNode** pphead,SLTNode* pos) {
	//pos位置与首节点重合,转换为头删
	if (pos == *pphead) {
		SListPopFront(pphead);
	}
	else {
		SLTNode* prev = *pphead;

		//找pos的前一个位置
		while (prev->next != pos) {
			prev = prev->next;
		}
		//将前一个值指向pos的后一个值
		prev->next = pos->next;

		free(pos);
		pos = NULL;
	}

}

//删除pos后面的值
void SListEraseAfter(SLTNode** pphead,SLTNode* pos) {
	//建立一个临时变量存储pos后两位的位置
	SLTNode* after = pos->next->next;

	free(pos->next);
	pos->next = NULL;

	//让pos指向刚刚后两位的位置
	pos->next = after;

}

//修改
void SListModify(SLTNode** pphead, SLTNode* pos,SLTDataType x) {
	pos->data = x;
}

//保存
void SListSave(SLTNode* pphead) {
	FILE* pf = fopen("SListNode.txt","wb");

	if (pphead == NULL) {
		fclose(pf);
		pf = NULL;
	}
	else {
		SLTNode* cur = pphead;
		while (cur != NULL) {
			fwrite(cur,sizeof(SLTDataType),1,pf);
			cur = cur->next;
		}
	}

}

//清空
void SListClear(SLTNode** pphead) {
	assert(*pphead);

	//清空链表,链表之后还要使用,所以我们只将首位置置为NULL,不释放
	//这里从第二个位置开始释放
	SLTNode* cur = (*pphead)->next;
	SLTNode* after = NULL;
	while (cur != NULL) {
		//先记录下一个节点的位置
		after = cur->next;
		//释放当前节点
		free(cur);
		cur = NULL;

		cur = after;
	}

	//清空链表,链表之后还要使用,所以我们只将首位置置为NULL,不释放
	*pphead = NULL;
}

//销毁
void SListDestroy(SLTNode** pphead) {
	assert(*pphead);

	//销毁链表,链表之后不能使用了,所以将首位置也一并释放
	SLTNode* cur = *pphead;
	SLTNode* after = NULL;
	while (cur != NULL) {
		//先记录下一个节点的位置
		after = cur->next;
		//释放当前节点
		free(cur);
		cur = NULL;

		cur = after;
	}
}


3)Test.c

#define _CRT_SECURE_NO_WARNINGS
#include"SListNode.h"

void menu() {
	printf("*******************************\n");
	printf("***1、头插     2、尾插      ***\n");
	printf("***3、头删     4、尾删      ***\n");
	printf("***5、打印     6、按值查找  ***\n");
	printf("***7、前插     8、后插      ***\n");
	printf("***9、删除     10、后删     ***\n");
	printf("***11、修改    12、保存     ***\n");
	printf("***13、清空    14、销毁     ***\n");
	printf("***-1、退出                 ***\n");
	printf("*******************************\n");
}

enum {
	PushFront = 1,
	PushBack,
	PopFront,
	PopBack,
	Print,
	FindByValue,
	Insert,
	InsertAfter,
	Erase,
	EraseAfter,
	Modify,
	Save,
	Clear,
	Destroy,
	Exit = -1
};


int main() {
	SLTNode* s = NULL;

	SLTDataType x;
	SLTNode* pos;

	int input = 0;

	do {
		menu();
		printf("请输入你想进行的操作:");
		scanf("%d", &input);
		switch (input) {
		case PushFront:
			printf("请输入你要插入的数据,以-1结束\n");
			do {
				scanf("%d", &x);
				if (x != -1)
				{
					SListPushFront(&s,x);
				}
			} while (x != -1);
			break;
		case PushBack:
			printf("请输入你要插入的数据,以-1结束\n");
			do {
				scanf("%d", &x);
				if (x != -1)
				{
					SListPushBack(&s,x);
				}
			} while (x != -1);
			break;
		case PopFront:
			SListPopFront(&s);
			break;
		case PopBack:
			SListPopBack(&s);
			break;
		case Print:
			SListPrint(s);
			break;
		case FindByValue:
			printf("请输入你想要查找的值:");
			scanf("%d", &x);
			pos = SListFind(s,x);
			if (pos == NULL) {
				printf("链表中没有这个值\n");
			}
			else {
				printf("找到了\n");
			}
			break;
		case Insert:
			printf("请输入你想要在哪个值前插入:");
			scanf("%d", &x);
			pos = SListFind(s,x);
			printf("请输入你想要插入的值:");
			scanf("%d", &x);
			if (pos == NULL) {
				printf("链表中没有这个值,请检查你输入的值是否正确\n");
			}
			else {
				SListInsert(&s, pos, x);
			}
			break;
		case InsertAfter:
			printf("请输入你想要在哪个值后插入:");
			scanf("%d", &x);
			pos = SListFind(s, x);
			printf("请输入你想要插入的值:");
			scanf("%d", &x);
			if (pos == NULL) {
				printf("链表中没有这个值,请检查你输入的值是否正确\n");
			}
			else {
				SListInsertAfter(&s, pos, x);
			}
			break;
		case Erase:
			printf("请输入你想要删除的值:");
			scanf("%d", &x);
			pos = SListFind(s, x);
			if (pos == NULL) {
				printf("链表中没有这个值,请检查你输入的值是否正确\n");
			}
			else {
				SListErase(&s, pos);
			}
			break;
		case EraseAfter:
			printf("请输入你想要删除哪个值之后的值:");
			scanf("%d", &x);
			pos = SListFind(s, x);
			if (pos == NULL) {
				printf("链表中没有这个值,请检查你输入的值是否正确\n");
			}
			else if (pos->next == NULL) {
				printf("这个值后已经没有值了,无法进行删除,请检查你输入的值是否正确\n");
			}
			else {
				SListEraseAfter(&s, pos);
			}
			break;
		case Modify:
			printf("请输入你想要修改的值:");
			scanf("%d", &x);
			pos = SListFind(s, x);
			printf("请输入修改后的值:");
			scanf("%d", &x);
			if (pos == NULL) {
				printf("链表中没有这个值,请检查你输入的值是否正确\n");
			}
			else {
				SListModify(&s, pos, x);
			}
			break;
		case Save:
			SListSave(s);
			break;
		case Clear:
			SListClear(&s);
			break;
		case Destroy:
			SListDestroy(&s);
			break;
		case Exit:
			break;
		default:
			printf("输入值错误,请重新输入\n");
		}
	} while (input != Exit);

	return 0;
}

4、带头双向循环链表实现

实现

1)结构定义:带头双向循环链表分为数据域、前指针域和后指针域。其中前指针域存放前一个位置的地址,后指针域存放后一个位置的地址。

typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType val;
}ListNode;

2)初始化头结点:创建一个头节点。前后指针域都指向自己,数据域不做处理。

//初始化创建头结点
ListNode* ListInit()
{
	ListNode* phead =ListCreate(0);
	//前指针域
	phead->next = phead;
	//后指针域
	phead->prev = phead;

	return phead;
}

3)插入

//创建新节点
ListNode* ListCreate(LTDataType x)
{
	//动态申请内存
	ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
	if (newNode == NULL) {
		perror("malloc");
	}
	//赋值
	newNode->val = x;

	return newNode;
}

//按位置插入
void ListInsert(ListNode* pos, LTDataType x){
	assert(pos);

	//建立新节点
	ListNode* newNode = ListCreate(x);

	//临时节点存储插入位置的前一个位置地址
	ListNode* prev = pos->prev;
	//将新节点后指针域存储插入位置地址
	newNode->next = pos;
	//将插入位置前指针域存储新节点位置
	pos->prev = newNode;
	//插入位置前一个位置的后指针域存储新节点位置
	prev->next = newNode;
	//将新节点前指针域存储插入位置前一个位置地址
	newNode->prev = prev;
}

4)删除

//按位置删除
void ListErase(ListNode* pos) {
	assert(pos);

	//创建临时节点存储插入位置前后节点地址
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	//将前节点的后指针指向后节点
	prev->next = next;
	//将后节点的前指针指向前节点
	next->prev = prev;

	free(pos);
	pos = NULL;
}

5)头插

// 头插
void ListPushFront(ListNode* pHead, LTDataType x) {
	assert(pHead);

	ListInsert(pHead->next,x);
}

6)尾插

// 尾插
void ListPushBack(ListNode* pHead, LTDataType x) {
	assert(pHead);

	ListInsert(pHead,x);
}

7)头删

// 头删
void ListPopFront(ListNode* pHead) {
	assert(pHead);

	ListErase(pHead->next);
}

8)尾删

// 尾删
void ListPopBack(ListNode* pHead) {
	assert(pHead);

	ListErase(pHead->prev);
}

9)查找

//查找
ListNode* ListFind(ListNode* pHead, LTDataType x){
	assert(pHead);
	//新建临时节点作为首元素节点
	ListNode* tail = pHead->next;
	while (tail != pHead) {
		if (tail->val == x) {
			return tail;
		}
		tail = tail->next;
	}
	return NULL;
}

10)打印

//打印
void ListPrint(ListNode* pHead) {
	assert(pHead);

	if (pHead->next == pHead) {
		printf("表中无元素\n");
		return;
	}
	ListNode* tail = pHead->next;
	while (tail != pHead) {
		printf("%d ",tail->val);
		tail = tail->next;
	}
	printf("\n");
}

11)清空

//清空
void ListClear(ListNode* pHead) {
	assert(pHead);

	ListNode* tail = pHead->next;
	//依次对各个空间进行释放
	while (tail != pHead) {
		ListNode* next = tail->next;
		free(tail);
		tail = NULL;
		tail = next;
	}
	//修改头结点前后指针域
	pHead->next = tail;
	pHead->prev = tail;
}

12)销毁

//销毁
void ListDestory(ListNode* pHead) {
	assert(pHead);

	ListNode* tail = pHead->next;
	//依次对各个空间进行释放
	while (tail != pHead) {
		ListNode* next = tail->next;
		free(tail);
		tail = NULL;
		tail = next;
	}
	//释放头结点
	free(pHead);
	pHead = NULL;
}

完整代码

1)ListNode.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType val;
}ListNode;


// 创建返回链表的头结点.
ListNode* ListInit();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
//清空
void ListClear(ListNode* pHead);
//打印
void ListPrint(ListNode* pHead);

2)ListNode.c

#define _CRT_SECURE_NO_WARNINGS

#include"ListNode.h"


//创建新节点
ListNode* ListCreate(LTDataType x)
{
	//动态申请内存
	ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
	if (newNode == NULL) {
		perror("malloc");
	}
	//赋值
	newNode->val = x;

	return newNode;
}

//初始化创建头结点
ListNode* ListInit()
{
	ListNode* phead =ListCreate(0);
	//前指针域
	phead->next = phead;
	//后指针域
	phead->prev = phead;

	return phead;
}

//查找
ListNode* ListFind(ListNode* pHead, LTDataType x){
	assert(pHead);
	//新建临时节点作为首元素节点
	ListNode* tail = pHead->next;
	while (tail != pHead) {
		if (tail->val == x) {
			return tail;
		}
		tail = tail->next;
	}
	return NULL;
}

//按位置插入
void ListInsert(ListNode* pos, LTDataType x){
	assert(pos);

	//建立新节点
	ListNode* newNode = ListCreate(x);

	//临时节点存储插入位置的前一个位置地址
	ListNode* prev = pos->prev;
	//将新节点后指针域存储插入位置地址
	newNode->next = pos;
	//将插入位置前指针域存储新节点位置
	pos->prev = newNode;
	//插入位置前一个位置的后指针域存储新节点位置
	prev->next = newNode;
	//将新节点前指针域存储插入位置前一个位置地址
	newNode->prev = prev;
}

//按位置删除
void ListErase(ListNode* pos) {
	assert(pos);

	//创建临时节点存储插入位置前后节点地址
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	//将前节点的后指针指向后节点
	prev->next = next;
	//将后节点的前指针指向前节点
	next->prev = prev;

	free(pos);
	pos = NULL;
}

//打印
void ListPrint(ListNode* pHead) {
	assert(pHead);

	if (pHead->next == pHead) {
		printf("表中无元素\n");
		return;
	}
	ListNode* tail = pHead->next;
	while (tail != pHead) {
		printf("%d ",tail->val);
		tail = tail->next;
	}
	printf("\n");
}

//清空
void ListClear(ListNode* pHead) {
	assert(pHead);

	ListNode* tail = pHead->next;
	//依次对各个空间进行释放
	while (tail != pHead) {
		ListNode* next = tail->next;
		free(tail);
		tail = NULL;
		tail = next;
	}
	//修改头结点前后指针域
	pHead->next = tail;
	pHead->prev = tail;
}

//销毁
void ListDestory(ListNode* pHead) {
	assert(pHead);

	ListNode* tail = pHead->next;
	//依次对各个空间进行释放
	while (tail != pHead) {
		ListNode* next = tail->next;
		free(tail);
		tail = NULL;
		tail = next;
	}
	//释放头结点
	free(pHead);
	pHead = NULL;
}

// 尾插
void ListPushBack(ListNode* pHead, LTDataType x) {
	assert(pHead);

	ListInsert(pHead,x);
}
// 尾删
void ListPopBack(ListNode* pHead) {
	assert(pHead);

	ListErase(pHead->prev);
}
// 头插
void ListPushFront(ListNode* pHead, LTDataType x) {
	assert(pHead);

	ListInsert(pHead->next,x);
}
// 头删
void ListPopFront(ListNode* pHead) {
	assert(pHead);

	ListErase(pHead->next);
}

3)Test.c

#define _CRT_SECURE_NO_WARNINGS

#include"ListNode.h"


void TestList1()
{
	ListNode* plist = ListInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPrint(plist);

	ListPushFront(plist, 0);
	ListPushFront(plist, -1);
	ListPrint(plist);

	ListPopFront(plist);
	ListPopFront(plist);
	ListPopFront(plist);
	ListPrint(plist);

	ListPopBack(plist);
	ListPrint(plist);

	ListNode* pos = ListFind(plist, 3);
	if (pos)
	{
		// 查找,附带着修改的作用
		pos->val *= 10;
		printf("找到了,并且节点的值乘以10\n");
	}
	else
	{
		printf("没有找到\n");
	}

	ListPrint(plist);

	ListInsert(pos, 300);
	ListPrint(plist);

	ListErase(pos);
	ListPrint(plist);

	ListClear(plist);
	ListPrint(plist);

	ListDestory(plist);
}




int main() {
	TestList1();


	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/317762.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机缺失msvcr100.dll如何修复?分享五种实测靠谱的方法

在计算机系统的日常运行与维护过程中&#xff0c;我们可能会遇到一种特定的故障场景&#xff0c;即系统中关键性动态链接库文件msvcr100.dll的丢失。msvcr100.dll是Microsoft Visual C Redistributable Package的一部分&#xff0c;对于许多基于Windows的应用程序来说&#xff…

增广路算法 DFS求解 最大网络流问题

最大网络流问题 最大网络流问题是这样的&#xff0c;有一个有向图&#xff0c;假定有一个源点&#xff0c;有一个汇点&#xff0c;源点有流量出来&#xff0c;汇点有流量进入&#xff0c;有向图上的边的权重为该条边可通过的最大流量(方向为边的方向)&#xff0c;问从源点到汇…

Servlet-Request

一、预览 在上一篇Servlet体系结构中&#xff0c;我们初步了解了怎么快速本篇将介绍Servlet中请求Request的相关内容&#xff0c;包括Request的体系结构&#xff0c;Request常用API。 二、Request体系结构 我们注意到我们定义的Servlet类若实现Servlet接口时&#xff0c;请求…

leetcode14. 最长公共前缀

题目 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 “”。 解题方法&#xff1a; 1.首先找到数组中长度最短的数据&#xff0c;与数组第一个数进行交换&#xff08;公共前缀的长度肯定不会大于列表中长度最短的字符串&#x…

MYSQL的操作

1.库的操作 1.1创建数据库 语法&#xff1a; CREATE DATABASE [IF NOT EXISTS] db_name [create_specification [, create_specification] ...] create_specification: [DEFAULT] CHARACTER SET charset_name [DEFAULT] COLLATE collation_name 说明&#xff1a; #…

windows11通过虚拟机安装Ubuntu20.04

VMware 分为 VMware Workstation Pro 和 VMware Workstation Player, Pro体验期后收费&#xff0c;Player则免费。player 早期不能创建虚拟机&#xff0c;只能Pro创建好后给Player运行&#xff0c;而现在player早已加入创建虚拟机功能&#xff0c;所以使用体验上两者相差不大&a…

计算机体系结构----存储系统

本文严禁转载&#xff0c;仅供学习使用。参考资料来自中国科学院大学计算机体系结构课程PPT以及《Digital Design and Computer Architecture》、《超标量处理器设计》、同济大学张晨曦教授资料。如有侵权&#xff0c;联系本人修改。 1.1 引言 1.1.1虚拟和物理内存 程序员看到…

学习Qt笔记

前言&#xff1a; 学习笔记的内容来自B站up主阿西拜编程 《Qt6 C开发指南 》2023&#xff08;上册&#xff0c;完整版&#xff09;_哔哩哔哩_bilibili《Qt6 C开发指南 》2023&#xff08;上册&#xff0c;完整版&#xff09;共计84条视频&#xff0c;包括&#xff1a;00书籍介…

Java初学习

Java代码示例&#xff1a; public class helloworld {public static void main(String[] args){System.out.println("hello world");} } Java程序的名字需要和文件名字一致&#xff0c;就是那个helloworld Java程序需要对类有深度的认识&#xff1a; 对象是类的…

让java程序就像脚本一样去写工具

背景&#xff1a; 接触了各种语言之后发现&#xff0c;java还是比go&#xff0c;.netcore之类的简单&#xff0c;成熟&#xff0c;我最终选择了jenkinsshelljava去部署我们的代码&#xff0c;此时很多人可能去使用js或者python之类的去写部署逻辑&#xff0c;毕竟java每次打包…

回归预测 | Matlab实现SSA-CNN-LSTM-Attention麻雀优化卷积长短期记忆神经网络注意力机制多变量回归预测(SE注意力机制)

回归预测 | Matlab实现SSA-CNN-LSTM-Attention麻雀优化卷积长短期记忆神经网络注意力机制多变量回归预测&#xff08;SE注意力机制&#xff09; 目录 回归预测 | Matlab实现SSA-CNN-LSTM-Attention麻雀优化卷积长短期记忆神经网络注意力机制多变量回归预测&#xff08;SE注意力…

【.NET Core】Lazy<T> 实现延迟加载详解

【.NET Core】Lazy 实现延迟加载详解 文章目录 【.NET Core】Lazy<T> 实现延迟加载详解一、概述二、Lazy<T>是什么三、Lazy基本用法3.1 构造时使用默认的初始化方式3.2 构造时使用指定的委托初始化 四、Lazy.Value使用五、Lazy扩展用法5.1 实现延迟属性5.2 Lazy实现…

我们做了个写论文解读的agent

已经2024年了&#xff0c;该出现一个论文解读AI Agent了。 尽管我们公司的主营业务不是做这块的&#xff0c;但&#xff0c;我们还是顺手做了这样一个agent&#xff0c;因为——我们公司的算法同学也需要刷论文啊喂&#xff0c; 而且我们也经常人工写论文解读嘛&#xff0c;所…

深入分析 Spring 中 Bean 名称的加载机制

目录 前言 通过前文&#xff1a;《深入分析-Spring BeanDefinition构造元信息》一文我们可以了解到&#xff1a;Spring Framework共有三种方式可以定义Bean&#xff0c;分别为&#xff1a;XML配置文件、注解、Java配置类&#xff0c; 从Spring Framework 3.0&#xff08;2019年…

node-sass@4.7.2 postinstall: `node scripts/build.js`

Can‘t find Python executable “D:\Python36\python.EXE“, you can set the PYTHON env variable.-CSDN博客 gyp ERR! build error gyp ERR! stack Error: C:\Windows\Microsoft.NET\Framework\v4.0.30319\msbuild.exe failed with exit code: 1 gyp ERR! stack at Chil…

解密Mybatis-Plus:优雅简化你的数据访问层!

目录 1、引言 2、什么是Mybatis-Plus 3、Mybatis-Plus的特点和优势 4、安装和配置Mybatis-Plus 5、使用Mybatis-Plus进行数据库操作 6、Mybatis-Plus的高级功能 7、Mybatis-Plus的扩展和插件 8、与Spring Boot集成 9、结语 1、引言 Mybatis-Plus是一个强大而优雅的Jav…

阿里云ingress配置时间超时的参数

一、背景 在使用阿里云k8s集群的时候&#xff0c;内网API网关&#xff0c;刚开始是用的是Nginx&#xff0c;后面又搭建了ingress。 区别于nginx配置&#xff0c;ingress又该怎么设置参数呢&#xff1f;比如http超时时间等等。 本文会先梳理nginx是如何配置&#xff0c;再对比…

Elasticsearch:是时候离开了! - 在 Elasticsearch 文档上使用 TTL

作者&#xff1a;来自 Elastic David Pilato 想象一下&#xff0c;圣诞老人必须向世界上所有的孩子们分发礼物。 他有很多工作要做&#xff0c;他需要保持高效。 他有一份所有孩子的名单&#xff0c;并且知道他们住在哪里。 他很可能会将礼物按区域分组&#xff0c;然后再交付。…

Qt QSQlite数据库插入字符串中存在单个双引号或单个单引号解决方案

1. 前言 当进行数据库写入或更新时&#xff0c;有时会遇到存在字符串中包含单个双引号或者单引号。 2. 单引号和双引号""作用 在数据库中&#xff0c;字符串常量时需要用一对英文单引号或英文双引号""将字符串常量括起来。 比如&#xff1a; select * …

stable-diffusion 学习笔记

从效果看Stable Diffusion中的采样方法 参考&#xff1a;Ai 绘图日常 篇二&#xff1a;从效果看Stable Diffusion中的采样方法_软件应用_什么值得买 大概示例&#xff1a;