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

news2024/9/21 22:48:06

文章目录

  • 前言
  • 引入
  • 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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

P1067 [NOIP2009 普及组] 多项式输出————C++

目录 [NOIP2009 普及组] 多项式输出题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样例输入 #2样例输出 #2 提示 解题思路Code运行结果 [NOIP2009 普及组] 多项式输出 题目描述 一元 n n n 次多项式可用如下的表达式表示&#xff1a; f ( x ) a n x n a …

现代 C++ 及 C++ 的演变

C 活跃在程序设计领域。该语言写入了许多新项目&#xff0c;而且据 TIOBE 排行榜数据显示&#xff0c;C 的受欢迎度和使用率位居第 4&#xff0c;仅次于 Python、Java 和 C。 尽管 C 在过去二十年里的 TIOBE 排名都位居前列&#xff08;2008 年 2 月排在第 5 名&#xff0c;到…

行为型设计模式——迭代器模式

迭代器模式 迭代器模式也是非常的简单&#xff0c;定义如下&#xff1a; 提供一个对象来顺序访问聚合对象中的一系列数据&#xff0c;而不暴露聚合对象的内部表示。 相信大家都使用过类似下面的迭代器&#xff1a; List<String> list new ArrayList<>(); Iterat…

Seata TC端协调全局事务

1、Seata server注册器 //来自RM分支事务注册 super.registerProcessor(MessageType.TYPE_BRANCH_REGISTER, onRequestProcessor, messageExecutor); //开启全局事务 super.registerProcessor(MessageType.TYPE_GLOBAL_BEGIN, onRequestProcessor, messageExecutor); //提交全…

MySQL夯实之路-事务详解

事务四大特性 事务需要通过严格的acid测试。Acid表示原子性&#xff0c;一致性&#xff0c;隔离性&#xff0c;持久性。 原子性&#xff08;atomicity&#xff09; 事务是不可分割的最小单元&#xff0c;对于整个事务的操作&#xff0c;要么全部提交成功&#xff0c;要么全部…

Linux———ps命令详解

目录 ps 命令&#xff08;"process status" 的缩写。&#xff09; 常用选项和参数&#xff1a; a&#xff1a;显示所有用户的进程&#xff0c;包括其他用户的进程。​ u&#xff1a;显示详细的进程信息&#xff0c;包括进程的所有者、CPU 使用率、内存使用量等。…

白嫖aws创建Joplin server服务器

网上有很多的Joplin服务器的搭建教程&#xff0c;但是基本都是抄来抄去&#xff0c;对初学者实在是太不友好了。 话不多说&#xff0c;说干就干&#xff0c;自己从头找资料搭了一个&#xff0c;这可能是全网最好的Joplin服务器搭建教程了。 aws服务器 aws的服务器还是很香的&…

MAVROS的进一步理解

一、Mavros简介 顾名思义&#xff0c; mavros就是mavlinkros。mavros是PX4官方提供的一个运行于ros下收发mavlink消息的工具&#xff0c;利用mavros可以发送mavlink消息给飞控(可以控制飞机)&#xff0c;并且可以从飞控中接受数据(例如&#xff1a;飞控的位置速度 IMU数据等等…

论文阅读_训练大模型用于角色扮演

英文名称: Character-LLM: A Trainable Agent for Role-Playing 中文名称: 角色-LLM&#xff1a;训练Agent用于角色扮演 文章: [https://arxiv.org/abs/2310.10158](https://arxiv.org/abs/2310.10158) 作者: Yunfan Shao, Linyang Li, Junqi Dai, Xipeng Qiu 机构: 复旦大学…

使用numpy处理图片——分离通道

大纲 读入图片分离通道堆叠法复制修改法 生成图片 在《使用numpy处理图片——滤镜》中&#xff0c;我们剥离了RGB中的一个颜色&#xff0c;达到一种滤镜的效果。 如果我们只保留一种元素&#xff0c;就可以做到PS中分离通道的效果。 读入图片 import numpy as np import PIL.…

【UE Niagara学习笔记】04 - 火焰喷射时的黑烟效果

目录 效果 步骤 一、创建烟雾材质 二、添加新的发射器 三、设置新发射器 3.1 删除Color模块 3.2 减少生成的粒子数量 3.3 设置粒子初始颜色 3.4 设置烟雾的位置偏移 3.5 设置烟雾淡出 在上一篇博客&#xff08;【UE Niagara学习笔记】03 - 火焰喷射效果&#xf…

【开源】基于JAVA+Vue+SpringBoot的医院门诊预约挂号系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 功能性需求2.1.1 数据中心模块2.1.2 科室医生档案模块2.1.3 预约挂号模块2.1.4 医院时政模块 2.2 可行性分析2.2.1 可靠性2.2.2 易用性2.2.3 维护性 三、数据库设计3.1 用户表3.2 科室档案表3.3 医生档案表3.4 医生放号…

5文件操作

包含头文件<fstream> 操作文件三大类&#xff1a; ofstream : 写文件ifstream &#xff1a;读文件fstream : 读写文件 5.1文本文件 -文件以ascii的形式存储在计算机中 5.1.1写文件 步骤&#xff1a; 包含头文件 #include "fstream"创建流对象 ofs…

【Linux运维】LVM和RAID学习及实践

LVM和RAID学习及实践 背景LVM简介新加硬盘的操作RAID-磁盘阵列应用场景RAID0RAID1其他结构RAID制作RAID 小结 背景 某台服务器的磁盘管理需要自己动手处理&#xff0c;找了一些资料也踩了一些坑&#xff0c;在这里记录一下&#xff0c;先介绍一下LVM和RAID这两个东西。在计算机…

Java实现在线编辑预览office文档

文章目录 1 在线编辑1.1 PageOffice简介1.2 前端项目1.2.1 配置1.2.2 页面部分 1.3 后端项目1.3.1 pom.xml1.3.2 添加配置1.3.3 controller 2 在线预览2.1 引言2.2 市面上现有的文件预览服务2.2.1 微软2.2.2 Google Drive查看器2.2.3 阿里云 IMM2.2.4 XDOC 文档预览2.2.5 Offic…

逆变器3前级推免(高频变压器)

一节电池标压是在2.8V—4.2V之间&#xff0c;所以24V电压需要大概七节电池串联。七节电池电压大概在19.6V—29.4V之间。 从24V的电池逆变到到220V需要升压的过程。那么我们具体需要升压到多少&#xff1f; 市电AC220V是有效值电压&#xff0c;峰值电压是220V*1.414311V 如果…

ubuntu安装node

1 下载 node 官网下载 如果需要其他版本&#xff0c;点击上图的Other Downloads 这里下载的版本是20.11.0 Linux Binaries (x64)&#xff0c;下载下来后是node-v20.11.0-linux-x64.tar.xz这样的格式&#xff0c;直接右键解压得到如下目录&#xff1a; 直接拷贝该文件夹到指定目…

一些前端学习过程的自测练习题

目录 页面设计部分 1 设计一个简单的学院网站首页&#xff1b; 2.按照图示要求完成简单的登录页面 3.完成如下网站设计 4.完成如下网站设计&#xff08;练习页面布局&#xff09; 5 利用下面素材&#xff0c;设计一个满足H5规范的网页&#xff08;移动端页面练习&#xff…

有道云笔记编辑 Markdown 文件 - GitHub README.md

有道云笔记编辑 Markdown 文件 - GitHub README.md 1. 新建 -> Markdown2. GitHub README.mdReferences 1. 新建 -> Markdown ​ 2. GitHub README.md ​​​ References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

group by 查询慢的话,如何优化?

1、说明 根据一定的规则&#xff0c;进行分组。 group by可能会慢在哪里&#xff1f;因为它既用到临时表&#xff0c;又默认用到排序。有时候还可能用到磁盘临时表。 如果执行过程中&#xff0c;会发现内存临时表大小到达了上限&#xff08;控制这个上限的参数就是tmp_table…