数据结构:链表

目录

一.为什么要使用链表存储数据?

二.链表的分类

单向或者双向链表:

带头或者不带头:

循环或者非循环:

三.链表的实现

3.1无头单向非循环链表的实现:

3.1.1单向无头非循环链表的声明

3.1.2动态申请一个节点

 3.1.3单链表打印

  3.1.4单链表尾插

   3.1.5单链表的头插

   3.1.6单链表的尾删

   3.1.7单链表头删

   3.1.8单链表查找

  3.1.9单链表在pos位置之前插入x

 3.1.10单链表在pos位置之后插入x

  3.1.11单链表删除pos之后的值

 3.1.12单链表删除pos位置的值

3.1.13销毁单链表

 头文件:

 测试文件:

3.2带头双向循环链表的实现

3.2.1带头双向循环链表的声明

3.2.2动态申请一个节点

 3.2.3哨兵位初始化(创建链表的头结点)

 3.2.4带头双向循环链表打印

 3.2.5双向链表尾插

  3.2.6双向链表头插

3.2.7双线链表尾删

3.2.8双线链表头删

3.2.9双向链表查找

 3.2.10双向链表在pos的前面进行插入

3.2.11双向链表删除pos位置的结点

 3.2.12双向链表销毁

头文件:

测试文件:

四.链表总结


一.为什么要使用链表存储数据?

内存空间是所有程序的公共资源,在一个复杂的系统运行环境下,空闲的内存空间可能散落在内存各处。我们知道,存储数组的内存空间必须是连续的,而当数组非常大时,内存可能无法提供如此大的连续空间。此时链表的灵活性优势就体现出来了。

让我们来看看链表的结构:

  •  可以得出:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
  • 上述图中的链表知识链表中的其中一种

二.链表的分类

单向或者双向链表:

带头或者不带头:

循环或者非循环:


虽然链表的种类很多,但我们主要使用的还是无头单向非循环链表(OJ题中最常见的链表)和带头循环双向链表(实践应用)

三.链表的实现

3.1无头单向非循环链表的实现:

对于项目我们需要区分测试文件和接口文件,这样做有利于培养良好的代码能力。

3.1.1单向无头非循环链表的声明

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


typedef int SLNDataType;

// 声明一个  Single Link List Node (单向无头链表)

typedef struct SLLN
{
	//节点值
	SLNDataType val;

	//指向下一个节点的指针
	struct SLLN* next;

}SLNode;

3.1.2动态申请一个节点

// 动态申请一个节点
SLNode* CreateNode(SLNDataType x)
{

	SLNode* newnode = (SLNode*)calloc(1, sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("calloc");
		//直接终止程序
		exit(-1);
	}

	newnode->val = x;
	newnode->next = NULL;
	return newnode;
}

 3.1.3单链表打印

// 单链表打印
void SLNodePrint(SLNode* plist)
{
	SLNode* cur = plist;
	if (cur != NULL)
	{
		while (cur)
		{
			printf("%d->", cur->val);
			cur = cur->next;
		}
		printf("NULL");
		printf("\n");
	}
	else
	{
		printf("链表为空无需打印\n");
	}
}

  3.1.4单链表尾插

// 单链表尾插
void SLNodePushBack(SLNode** pplist, SLNDataType x)
{
	//先创造一个新节点
	SLNode* newnode = CreateNode(x);

	SLNode* tail = *pplist;//将plist赋值给tail

	//找尾
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}
	else
	{
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

   3.1.5单链表的头插

// 单链表的头插
void SLNodePushFront(SLNode** pplist, SLNDataType x)
{
	//先创建一个新节点
	SLNode* newnode = CreateNode(x);
	newnode->next = *pplist;
	*pplist = newnode;
}

   3.1.6单链表的尾删

// 单链表的尾删
void SLNodePopBack(SLNode** pplist)
{
	//如果链表为空则不能删除,报错
	assert(*pplist);
	//找尾
	SLNode* tail = *pplist;
	//tail移动时我们还需要有一个前驱指针 prev 跟在tail后面
	SLNode* prev = NULL;

	//单链表只有一个节点情况下尾删
	if (tail->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	//单链表有多个节点情况下尾删
	else
	{
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;
	}

}

   3.1.7单链表头删

// 单链表头删
void SLNodePopFront(SLNode** pplist)
{
	//当链表为空时不能删除,报错
	assert(*pplist);
	SLNode* cur = *pplist;
	SLNode* newplist = (*pplist)->next;
	free(cur);
	cur = NULL;
	*pplist = newplist;

}

   3.1.8单链表查找

// 单链表查找(配合在pos位置插入或者删除使用)
SLNode* SLNodeFind(SLNode* plist, SLNDataType x)
{
	while (plist != NULL)
	{
		if (plist->val == x)
		{
			return plist;
		}
		else
		{
			plist = plist->next;
		}
	}
	return NULL;
}

  3.1.9单链表在pos位置之前插入x

//单链表在pos位置之前插入x
void SLNodeInsertBefore(SLNode** pplist, SLNode* pos, SLNDataType x)//此处传入二级指针pplist是为了在头插时改变plist的值,传址调用
{
	//此处需要对哪个指针进行断言检查呢?
	assert(pos && *pplist);//防止人为乱传空

	SLNode* cur = *pplist;
	SLNode* prev = NULL;//前驱指针prev保存cur前一个节点的地址
	if (pos == *pplist)
	{
		//在头节点位置前插入x实质上就是头插,我们调用之前写的头插函数即可
		SLNodePushFront(pplist, x);
	}
	else
	{
		SLNode* newnode = CreateNode(x);
		while (cur != pos)
		{
			prev = cur;
			cur = cur->next;
		}
		prev->next = newnode;
		newnode->next = cur;
	}
}

 3.1.10单链表在pos位置之后插入x

// 单链表在pos位置之后插入x
void SLNodeInsertAfter(SLNode* pos, SLNDataType x)
{
	//链表为空无法在pos位置之后插入x
	assert(pos);

	SLNode* newnode = CreateNode(x);
	if (newnode == NULL)
	{
		perror(calloc);
		return;
	}

	SLNode* next = pos->next;

	pos->next = newnode;

	newnode->next = next;

}

  3.1.11单链表删除pos之后的值

//单链表删除pos之后的值
void SLNodeEraseAfter(SLNode* pos)
{
	//链表为空无法删除
	assert(pos);

	//当链表只剩下一个节点或者pos后面无节点时也无法删除
	assert(pos->next);

	SLNode* del = pos->next;
	pos->next = pos->next->next;

	free(del);
	del == NULL;
}

 3.1.12单链表删除pos位置的值

//单链表删除pos位置的值
void SLNodeErasepos(SLNode** pplist, SLNode* pos)
{
	//链表为空不能删除
	assert(*pplist);

	SLNode* cur = *pplist;
	SLNode* prev = NULL;//前驱指针prev保存cur前一个节点的地址

	if (pos != *pplist)
	{
		while (cur != pos)
		{
			prev = cur;
			cur = cur->next;
		}
		SLNode* next = cur->next;
		free(cur);
		cur = NULL;
		//链接两个节点
		prev->next = next;
	}
	else
	{
		SLNode* next = cur->next;
		free(cur);
		cur = NULL;
		//更新头节点
		*pplist = next;
	}
}

3.1.13销毁单链表

//销毁链表
void SLNodeDestory(SLNode** pplist)
{
	assert(pplist);
	assert(*pplist);

	SLNode* cur = *pplist;
	while (cur)
	{
		SLNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pplist = NULL;
}

以上代码为单链表所实现的所有功能(接口)


 头文件:

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


typedef int SLNDataType;

// 声明一个  Single Link List Node (单向无头链表)

typedef struct SLLN
{
	//节点值
	SLNDataType val;

	//指向下一个节点的指针
	struct SLLN* next;

}SLNode;


// 动态申请一个节点
SLNode* CreateNode(SLNDataType x);

// 单链表打印
void SLNodePrint(SLNode* plist);

// 单链表尾插
void SLNodePushBack(SLNode** pplist, SLNDataType x);

// 单链表的头插
void SLNodePushFront(SLNode** pplist, SLNDataType x);

// 单链表的尾删
void SLNodePopBack(SLNode** pplist);

// 单链表头删
void SLNodePopFront(SLNode** pplist);

// 单链表查找
SLNode* SLNodeFind(SLNode* plist, SLNDataType x);

//单链表在pos位置之前插入x
void SLNodeInsertBefore(SLNode** pplist, SLNode* pos, SLNDataType x);

//单链表在pos位置之后插入x
void SLNodeInsertAfter(SLNode* pos, SLNDataType x);

//单链表删除pos位置之后的值
void SLNodeEraseAfter(SLNode* pos);

//单链表删除pos位置的值
void SLNodeErasepos(SLNode** pplist, SLNode* pos);

//销毁单链表
void SLNodeDestory(SLNode** pplist);

 测试文件:

#define  _CRT_SECURE_NO_WARNINGS 1
#include"Single_linked_lists.h"


//单链表尾插测试
void text1()
{
	SLNode* plist = NULL;
	SLNodePushBack(&plist, 1);
	SLNodePushBack(&plist, 2);
	SLNodePushBack(&plist, 3);
	SLNodePushBack(&plist, 4);

	SLNodePrint(plist);
}
//单向链表头插测试
void text2()
{
	SLNode* plist = NULL;
	SLNodePushFront(&plist, 1);
	SLNodePushFront(&plist, 2);
	SLNodePushFront(&plist, 3);
	SLNodePushFront(&plist, 4);

	SLNodePrint(plist);

}
//单向链表尾删测试
void text3()
{
	//SLNode* plist = NULL;
	//SLNodePushBack(&plist, 1);
	//SLNodePushBack(&plist, 2);
	//SLNodePushBack(&plist, 3);
	//SLNodePushBack(&plist, 4);

	多节点尾删测试
	//SLNodePopBack(&plist);


	SLNode* plist = NULL;
	SLNodePushBack(&plist, 1);
	//单节点尾删测试
	SLNodePopBack(&plist);


	SLNodePrint(plist);
}
//单向链表头删测试
void text4()
{
	SLNode* plist = NULL;
	SLNodePushBack(&plist, 1);
	SLNodePushBack(&plist, 2);
	SLNodePushBack(&plist, 3);
	SLNodePushBack(&plist, 4);

	//头删测试
	SLNodePopFront(&plist);

	SLNodePrint(plist);
}

//单向链表查找 val 测试
void text5()
{
	SLNode* plist = NULL;
	SLNodePushBack(&plist, 1);
	SLNodePushBack(&plist, 2);
	SLNodePushBack(&plist, 3);
	SLNodePushBack(&plist, 4);

	//查找val测试
	SLNode* pos = SLNodeFind(plist, 4);
	printf("%p\n", pos);
}

// 单链表在pos位置之后插入 x 测试
void text6()
{
	SLNode* plist = NULL;
	SLNodePushBack(&plist, 1);
	SLNodePushBack(&plist, 2);
	SLNodePushBack(&plist, 3);
	SLNodePushBack(&plist, 4);

	//pos之后插入x

	SLNode* pos = SLNodeFind(plist, 3);
	SLNodeInsertAfter(pos, 0);
	SLNodePrint(plist);
}

//单链表删除pos之后的值测试
void text7()
{
	SLNode* plist = NULL;
	SLNodePushBack(&plist, 1);
	SLNodePushBack(&plist, 2);
	SLNodePushBack(&plist, 3);
	SLNodePushBack(&plist, 4);

	//单链表删除pos之后的值测试
	SLNode* pos = SLNodeFind(plist, 3);
	SLNodeEraseAfter(pos);

	SLNodePrint(plist);

}
//单链表在pos前一个位置插入 x 测试
void text8()
{
	SLNode* plist = NULL;
	SLNodePushBack(&plist, 1);
	SLNodePushBack(&plist, 2);
	SLNodePushBack(&plist, 3);
	SLNodePushBack(&plist, 4);

	SLNodePrint(plist);
	SLNode* posbefore = SLNodeFind(plist, 3);
	//单链表在pos前一个位置插入 x 测试
	SLNodeInsertBefore(&plist, posbefore, 0);
	SLNodePrint(plist);

}
//单链表删除pos位置的值
void text9()
{
	SLNode* plist = NULL;
	SLNodePushBack(&plist, 1);
	SLNodePushBack(&plist, 2);
	SLNodePushBack(&plist, 3);
	SLNodePushBack(&plist, 4);

	SLNodePrint(plist);

	//单链表删除pos位置的值
	SLNode* pos1 = SLNodeFind(plist, 1);
	SLNode* pos2 = SLNodeFind(plist, 3);

	SLNodeErasepos(&plist, pos1);
	SLNodeErasepos(&plist, pos2);

	SLNodePrint(plist);

}
int main()
{
	//text1();//单链表尾插测试
	//text2();//单向链表头插测试
	//text3();//单向链表尾删测试
	//text4();//单向链表头删测试
	//text5();//单向链表查找 val 测试
	//text6();// 单链表在pos位置之后插入 x 测试
	//text7();//单链表删除pos之后的值测试
	//text8();//单链表在pos前一个位置插入 x 测试
	//text9();//单链表删除pos位置的值测试
	return 0;
}

3.2带头双向循环链表的实现

3.2.1带头双向循环链表的声明

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

typedef int DataType;

typedef struct ListNode
{
	DataType val;
	struct LTNode* next;
	struct LTNode* prev;
}LTNode;

3.2.2动态申请一个节点

//Create one newnode
LTNode* Createnode(DataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));

	if (newnode == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	newnode->val = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}

 3.2.3哨兵位初始化(创建链表的头结点)

//哨兵位初始化(创建链表的头结点)
LTNode* LTInit()
{
	LTNode* plist = Createnode(-1);
	plist->next = plist;
	plist->prev = plist;

	return plist;
}

 3.2.4带头双向循环链表打印

//带头双向循环链表打印
void LTPrint(LTNode* plist)
{
	assert(plist);
	LTNode* cur = plist->next;
	printf("哨兵位<=>");
	while (cur != plist)
	{
		printf("%d<=>", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

 3.2.5双向链表尾插

// 双向链表尾插
void LTNodePushBack(LTNode* plist, DataType x)
{
	LTNode* newnode = Createnode(x);
	LTNode* tail = plist->prev;

	tail->next = newnode;
	newnode->prev = tail;
	plist->prev = newnode;
	newnode->next = plist;

}

  3.2.6双向链表头插

// 双向链表头插
void LTNodePushFront(LTNode* plist, DataType x)
{
	assert(plist);

	LTNode* first = plist->next;
	LTNode* newnode = Createnode(x);

	newnode->next = first;
	first->prev = newnode;
	plist->next = newnode;
	newnode->prev = plist;

}

3.2.7双线链表尾删

// 双向链表尾删
void LTNodePopBack(LTNode* plist)
{
	//防止链表不存在
	assert(plist);
	//防止链表为空
	assert(plist->next);

	LTNode* tail = plist->prev;
	plist->prev = tail->prev;
	LTNode* tailprev = tail->prev;
	tailprev->next = plist;
}

3.2.8双线链表头删

// 双向链表头删
void LTNodePopFront(LTNode* plist)
{
	//防止链表不存在
	assert(plist);
	//防止链表为空
	assert(plist->next);

	LTNode* first = plist->next;
	LTNode* second = first->next;

	plist->next = second;
	second->prev = plist;
	free(first);
	first = NULL;

}

3.2.9双向链表查找

// 双向链表查找
LTNode* LTNodeFind(LTNode* plist, DataType x)
{
	//防止链表不存在
	assert(plist);
	//防止链表为空
	assert(plist->next);

	LTNode* cur = plist->next;
	while (cur != plist)
	{
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

 3.2.10双向链表在pos的前面进行插入

// 双向链表在pos的前面进行插入
void LTNodeInsert(LTNode* pos, DataType x)
{
	assert(pos);

	LTNode* newnode = Createnode(x);

	LTNode* posprev = pos->prev;
	posprev->next = newnode;
	newnode->next = pos;
	pos->prev = newnode;
	newnode->prev = posprev;
}

3.2.11双向链表删除pos位置的结点

// 双向链表删除pos位置的结点
void LTNodeErase(LTNode* pos)
{
	//防止传空
	assert(pos);
	LTNode* posnext = pos->next;
	LTNode* posprev = pos->prev;
	posprev->next = posnext;
	posnext->prev = posprev;

	free(pos);
}

 3.2.12双向链表销毁

// 双向链表销毁
void LTNodeDestory(LTNode* plist)
{
	//防止链表不存在
	assert(plist);

	LTNode* cur = plist->next;
	while (cur != plist)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(plist);
	plist = NULL;
}

以上代码为单链表所实现的所有功能(接口)


头文件:

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

typedef int DataType;

typedef struct ListNode
{
	DataType val;
	struct LTNode* next;
	struct LTNode* prev;
}LTNode;

//生成新节点
LTNode* Createnode(DataType x);

//哨兵位初始化
LTNode* LTInit();

//带头双向循环链表打印
void LTPrint(LTNode* plist);


// 双向链表销毁
void LTNodeDestory(LTNode* plist);

// 双向链表尾插
void LTNodePushBack(LTNode* plist, DataType x);

// 双向链表尾删
void LTNodePopBack(LTNode* plist);

// 双向链表头插
void LTNodePushFront(LTNode* plist, DataType x);

// 双向链表头删
void LTNodePopFront(LTNode* plist);

// 双向链表查找
LTNode* LTNodeFind(LTNode* plist, DataType x);

// 双向链表在pos的前面进行插入
void LTNodeInsert(LTNode* pos, DataType x);

// 双向链表删除pos位置的结点
void LTNodeErase(LTNode* pos);

测试文件:

#define  _CRT_SECURE_NO_WARNINGS 1
#include"list.h"
// 初步测试
void test1()
{
	// 创建一个哨兵位头节点
	LTNode* plist = LTInit();
	LTPrint(plist);
}

// 尾插尾删测试
void test2()
{
	LTNode* plist = LTInit();
	LTNodePushBack(plist, 1);
	LTNodePushBack(plist, 2);
	LTNodePushBack(plist, 3);
	LTNodePushBack(plist, 4);
	LTNodePushBack(plist, 5);

	LTPrint(plist);
	LTNodePopBack(plist);
	LTPrint(plist);
}
// 头插头删测试
void test3()
{
	LTNode* plist = LTInit();
	LTNodePushFront(plist, 1);
	LTNodePushFront(plist, 2);
	LTNodePushFront(plist, 3);
	LTNodePushFront(plist, 4);
	LTNodePushFront(plist, 5);


	LTPrint(plist);
	LTNodePopFront(plist);
	LTPrint(plist);
}
// 链表查找测试
void test4()
{
	LTNode* plist = LTInit();
	LTNodePushBack(plist, 1);
	LTNodePushBack(plist, 2);
	LTNodePushBack(plist, 3);
	LTNodePushBack(plist, 4);
	LTNodePushBack(plist, 5);

	LTPrint(plist);
	LTNode* pos = LTNodeFind(plist, 3);
	printf("%d\n", pos->val);

}
// 双向链表在pos的前面进行插入测试
void test5()
{
	LTNode* plist = LTInit();
	LTNodePushBack(plist, 1);
	LTNodePushBack(plist, 2);
	LTNodePushBack(plist, 3);
	LTNodePushBack(plist, 4);
	LTNodePushBack(plist, 5);

	LTPrint(plist);
	LTNode* pos = LTNodeFind(plist, 3);
	LTNodeInsert(pos, 100);
	LTNodeInsert(pos, 200);

	LTPrint(plist);

}
// 双向链表删除pos位置的结点测试
void test6()
{
	LTNode* plist = LTInit();
	LTNodePushBack(plist, 1);
	LTNodePushBack(plist, 2);
	LTNodePushBack(plist, 3);
	LTNodePushBack(plist, 4);
	LTNodePushBack(plist, 5);

	LTPrint(plist);
	LTNode* pos = LTNodeFind(plist, 3);
	LTNodeErase(pos);

	LTPrint(plist);
}
// 链表销毁测试
void test7()
{
	LTNode* plist = LTInit();
	LTNodePushBack(plist, 1);
	LTNodePushBack(plist, 2);
	LTNodePushBack(plist, 3);
	LTNodePushBack(plist, 4);
	LTNodePushBack(plist, 5);

	LTNodeDestory(plist);
	plist = NULL;
	LTPrint(plist);
}
int main()
{


	// 初步测试
	test1();
	// 尾插尾删测试
	test2();
	// 头插头删测试
	test3();
	// 链表查找测试
	test4();
	// 双向链表在pos的前面进行插入测试
	test5();
	// 双向链表删除pos位置的结点测试
	test6();
	// 链表销毁测试
	//test7();
	return 0;
}

四.链表总结

这是一个我个人做的思维导图,对于学习链表的一些总结,希望对你有所帮助:

  • 14
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 24
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jamo@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值