数据结构-链表

目录

1.链表的定义

2.单链表的代码实现

3.双向循环链表的代码实现

4.链表和顺序表的对比


1.链表的定义

数据结构-顺序表_七月不远.的博客-CSDN博客icon-default.png?t=M4ADhttps://blog.csdn.net/weixin_58165485/article/details/123381369在这篇文章中,笔者介绍了线性表的概念,线性表的逻辑结构——表示元素之间一一对应的相邻关系,满足这种线性有序的数据表示方式都可以称为线性表,对于数据元素在空间上的储存并不做要求

线性表的顺序储存 (空间储存方式)称为顺序表

线性表的链式存储 (空间储存方式) 称为链表

因此,链表是一种在逻辑结构线性有序,而物理存储结构非连续非顺序的存储结构 

物理存储结构上的非连续体现在链表中结点的物理地址并不连续,各结点可能位于内存空间的不同地址处,如何将这样一种结构实现逻辑上的联系呢?

在链表存储中,每个结点不仅包含所存元素的信息,还包含元素之间逻辑关系的信息,以单链表为例——具体做法是让每一个结点保存相邻的下一个结点的地址

一个单链表简要的结构如下图所示

 

其中,L 是指向单链表的指针,单链表每个结点由两个部分组成

  • 数据域——用来存放保存的数据
  • 指针域——用来存放下一个结点的地址

值得注意的是,由于最后一个结点的后面不存在结点,因此需要将最后结点的指针域置为空( NULL

 单链表的结点定义为

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

常用的链表一般有单链表双向循环链表,后者是更为完善的一种链表,为了弥补单链表只记录后续结点导致无法访问前面结点的问题,双向循环链表的每个结点由三部分组成——指向前结点的指针域,数据域以及指向后结点的指针域,简要结构如下图

注意:

  • 双向循环链表的第一个结点为头结点 ,头结点内不存储有效信息
  • 头结点的next指针指向链表中的第一个结点,并且头节点的prev指针指向链表中最后一个结点
  • 链表的最后一个结点指向头结点

双向循环链表的结点定义为 

typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	int data;
}LTNode;

 


2.单链表的代码实现

 常用的链表接口有:

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

typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

//单向+不带头+不循环

//单链表打印
void SListPrint(SLTNode* plist);

//单链表尾插
void SListPushBack(SLTNode** pplist, SLTDataType x);
//单链表头插
void SListPushFront(SLTNode** pplist, SLTDataType x);

//单链表尾删
void SListPopBack(SLTNode** pplist);
//单链表头删
void SListPopFront(SLTNode** pplist);

//单链表查找
SLTNode* SListFind(SLTNode* plist, SLTDataType x);

//单链表在pos之后插入
void SListInsertAfter(SLTNode* pos, SLTDataType x);

//在pos之前插入(很麻烦,不建议)
void SListInsertBefore(SLTNode** pplist, SLTNode* pos, SLTDataType x);

//单链表在pos之后删除
void SListEraseAfter(SLTNode* pos);

//单链表删除pos
void SListEraseCur(SLTNode** pplist, SLTNode* pos);

接口实现

#include "SList.h"

//打印
void SListPrint(SLTNode* plist)
{
	SLTNode* cur = plist;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

//创建一个新节点
SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
	node->data = x;
	node->next = NULL;

	return node;
}

//尾插
void SListPushBack(SLTNode** pplist, SLTDataType x)
{
	SLTNode* newnode = BuySLTNode(x);

	if (*pplist == NULL)
	{
		*pplist = newnode;
	}

	//如果非空,找到尾(最后一个节点,其next域为NULL)
	else
	{
		SLTNode* tail = *pplist;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

//头插
void SListPushFront(SLTNode** pplist, SLTDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = *pplist;          //先把当前结点的地址(存在plist里,这里就是*pplist)放到新结点的next
	*pplist = newnode;                //再将新节点的地址传给plist,这里也是*pplist
}

//尾删
void SListPopBack(SLTNode** pplist)
{
	//没有节点
	if (*pplist == NULL)
	{
		return;
	}
	//一个节点
	else if ((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}

	//多个节点
	else
	{
		SLTNode* prev = NULL;
		SLTNode* tail = *pplist;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		//tail = NULL;

		prev->next = NULL;
	}
}

//头删
void SListPopFront(SLTNode** pplist)
{
	if (*pplist == NULL)
	{
		return;
	}

	else
	{
		SLTNode* next = (*pplist)->next;
		free(*pplist);
		*pplist = next;
	}
}

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

		cur = cur->next;
	}
	return NULL;
}

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

	SLTNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

//在pos之前插入
void SListInsertBefore(SLTNode** pplist, SLTNode* pos, SLTDataType x)
{
	assert(pos);

	SLTNode* newnode = BuySLTNode(x);

	if (pos == *pplist)
	{
		newnode->next = pos;
		*pplist = newnode;

	}
	SLTNode* prev = NULL;
	SLTNode* cur = *pplist;
	while (cur != pos)
	{
		prev = cur;
		cur = cur->next;
	}

	prev->next = newnode;
	newnode->next = pos;
}

//单链表在pos之后删除
void SListEraseAfter(SLTNode* pos)
{
	assert(pos);

	if (pos->next == NULL)     //头删
	{
		return;
	}

	SLTNode* next = pos->next->next; 
	free(pos->next);
	pos->next = next;
}

//单链表删除pos
void SListEraseCur(SLTNode** pplist, SLTNode* pos)
{
	assert(pos);

	if (pos == *pplist)
	{
		SLTNode* next = pos->next;
		free(*pplist);
		*pplist = next;
		next = NULL;
	}

	SLTNode* prev = NULL;
	SLTNode* cur = *pplist;
	while (cur != pos)
	{
		prev = cur;
		cur = cur->next;
	}
	SLTNode* next = pos->next;
	free(pos);
	prev->next = next;
	next = NULL;
}

 


3.双向循环链表的代码实现

常用接口

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

typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	int data;
}LTNode;

struct List
{
	LTNode* phead;
	int size;
};

// 初始化
LTNode* ListInit();

// 打印
void ListPrint(LTNode* phead);

// 尾插
void ListPushBack(LTNode* phead, LTDataType x);

// 头插
void ListPushFront(LTNode* phead, LTDataType x);

// 尾删
void ListPopBack(LTNode* phead);

// 头删
void ListPopFront(LTNode* phead);

// 在pos位置之前插入x
void ListInsert(LTNode* pos, LTDataType x);

// 使用ListInsert实现尾插
void ListPushBackByInsert(LTNode* phead, LTDataType x);

// 使用ListInsert实现头插
void ListPushFrontByInsert(LTNode* phead, LTDataType x);

// 删除pos位置的节点
void ListErase(LTNode* pos);

// 使用ListErase实现尾删
void ListPopBackByListErase(LTNode* phead);

// 使用ListErase实现头删
void ListPopFrontByListErase(LTNode* phead);

// 判空
bool ListEmpty(LTNode* phead);

// 链表长度
int ListSize(LTNode* phead);

// 销毁
void ListDestory(LTNode* phead);

 接口实现

#include "List.h"

// 新建一个结点的函数定义
LTNode* BuyListNode(LTDataType x)
{
	// malloc一个新结点
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));

	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	node->data = x;
	node->next = NULL;
	node->prev = NULL;
	return node;
}

// 初始化的函数定义
LTNode* ListInit()
{
	LTNode* phead = BuyListNode(-1);

	// 链表初始化只有一个头结点————prev和next都指向自己
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

// 打印的函数定义
void ListPrint(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;
	// 从头结点的下一个结点开始打印
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
}

// 尾插的函数定义
void ListPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* newnode = BuyListNode(x);
	// 1. 记录链表当前的最后结点的地址
	// 2. 将原来的尾的next指向新结点
	// 3. 将新结点链接起来
	// 4. 更新头结点的prev指向新尾(newnode)
	LTNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}


// 头插的函数定义
void ListPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* newnode = BuyListNode(x);
	// 1. 记录链表当前的第一个结点的地址
	// 2. 将新结点的地址给第一个结点的prev
	// 3. 将新结点链接起来
	// 4. 更新头结点的next指向新尾(newnode)
	LTNode* next = phead->next;
	next->prev = newnode;
	newnode->next = next;
	newnode->prev = phead;
	phead->next = newnode;
}

// 尾删的函数定义
void ListPopBack(LTNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));

	LTNode* tail = phead->prev;
	LTNode* tailprev = tail->prev;

	free(tail);

	tailprev->next = phead;
	phead->prev = tailprev;
}

// 头删的函数定义
void ListPopFront(LTNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));

	LTNode* next = phead->next;
	LTNode* nextaftre = next->next;

	free(next);

	nextaftre->prev = phead;
	phead->next = nextaftre;
}

// 在pos位置之前插入x函数定义
void ListInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);

	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

// 使用ListInsert实现尾插
void ListPushBackByInsert(LTNode* phead, LTDataType x)
{
	assert(phead);

	ListInsert(phead, x);
}

// 使用ListInsert实现头插
void ListPushFrontByInsert(LTNode* phead, LTDataType x)
{
	assert(phead);

	ListInsert(phead->next, x);
}

// 删除pos位置的节点函数定义
void ListErase(LTNode* pos)
{
	assert(pos);

	LTNode* prev = pos->prev;
	LTNode* next = pos->next;

	prev->next = next;
	next->prev = prev;

	free(pos);
}

// 使用ListErase实现尾删
void ListPopBackByListErase(LTNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));

	ListErase(phead->prev);
}

// 使用ListErase实现头删
void ListPopFrontByListErase(LTNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));

	ListErase(phead->next);
}


// 判空的函数定义
bool ListEmpty(LTNode* phead)
{
	assert(phead);

	return phead->next == phead;
}

// 求链表长度函数定义
int ListSize(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;
	int size = 0;
	while (cur != phead)
	{
		++size;
	}

	return size;
}

// 销毁链表的函数定义
void ListDestory(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		ListErase(cur);
		cur = next;
	}

	free(phead);
}

4.链表和顺序表的对比

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持:O(1)不支持:O(N)
任意位置插入或者删除元素可能需要搬移元素,效率低O(N)只需修改指针指向
插入动态顺序表,空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁
缓存利用率
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值