数据结构与算法-单链表

通过上一章对于顺序表的学习,我们了解到顺序表的优点是:以物理位置相邻表示逻辑关系,任一元素均可随机存储。但同时它也存在着缺陷:进行插入和删除操作时,需要移动大量的元素,存储空间不灵活。这里,我们给大家引入链表,该结构不要求逻辑上相邻的数据元素物理上一定相邻,且插入和删除元素时不需要移动数据元素,只需要修改指针值

🎈1.链表的概念及结构

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

在链式存储结构中,为了能正确表示结点间的逻辑关系,每个存储结点不仅要包含数据元素本身的信息(称为数据域),还必须存储数据元素之间的逻辑关系的信息,即指示其后继结点的地址(或位置)信息,这个信息称为指针。一般的,每个结点有一个或多个指针域。若一个结点中的某个指针域不需要指向任何其他结点时,则它的值置为空,通常用常量NULL表示。

与链式存储有关的术语

  1. 结点:数据元素的存储映像,由数据域和指针域两部分组成。
  2. 链表:由n个结点由指针链组成一个链表
  3. 单链表、双链表、循环链表:
    (1). 结点只有一个指针域的链表称为单链表或者线性链表。
    (2). 结点由两个指针域的链表称为双链表
    (3). 首尾相接的链表称为循环链表
  4. 头指针、头结点和首元结点
    (1).头结点:是指向链表中第一个结点的指针。
    (2).首元结点:是指链表中存储第一个数据元素a1的结点。
    (3).头结点:是在链表的首元结点之前附设的一个结点。
    在这里插入图片描述

🔭1.1链表的分类

🏆单链表与双向链表:

如果每个结点除数据域外,仅设置一个指向后继的指针域,则这样构成的链表称为线性单向链表,称为单链表。在单链表中,由于每个结点只包含一个指向后继结点的指针,所以当访问一个结点后,只能依次访问其后继结点,但无法访问其前驱结点。
为此,引入了双向链表,该链表中每个结点除数据域外,设置两个指针域,一个指向前驱结点,一个指向后继结点,这样构成的链表称为线性双向链表,简称双向链表。该链表当访问一个结点后,既可以依次向后访问后继结点,也可以依次向前访问前驱结点。
🔎下面给出图示:单链表
双向链表

🏆对于单链表,又给出了两种分类标准:

📝带头结点与不带头结点:
在这里插入图片描述
📝循环与非循环:
在这里插入图片描述

🔭1.2数据结点的类型定义

在单链表中,假定每个结点的类型用LNode表示,它包括存储数据类型元素的数据域(data)和存储后继结点位置的指针域(next)。其数据类型定义如下:

typedef struct LNode//定义单链表结点类型
{
	ElemType data;//存放数据元素信息
	LNode* next;//存放后继结点的地址信息
}LNode;

在双向链表中,假定每个结点的类型用LNode表示,它包括存储数据类型元素的数据域(data)、存储前驱结点位置的指针域(prior)和存储后继结点位置的指针域(next)。其数据类型定义如下:

typedef struct DNode//定义双向链表结点类型
{
	ElemType data;//存放数据元素信息
	DNode* next;//存放后继结点的地址信息
	DNode* prior;//存放前驱结点的地址信息
}DNode;

🎈2.单链表

🔭2.1单链表的类定义

typedef int ElemType;
typedef struct LNode//定义单链表结点类型
{
	ElemType data;//存放数据元素信息
	LNode* next;//存放后继结点的地址信息
}LNode;
class LinkList
{
private:
	LNode* head;
public:
	LinkList();//构造函数,构造一个空表
	~LinkList();//析构函数,销毁函数
	void CreatList_h(int n);//头插法创建具有n个数据元素的线性链表
	void CreatList_t(int n);//尾插法创建具有n个数据元素的线性链表
	void InsertList(int i, ElemType e);//在表中第i个位置插入数据元素
	void DeleteList(int i, ElemType& e);//删除表中第i个元素的数据元素
	int GetElem(int i, ElemType& e);//获取第i个数据元素
	int LocateElem(ElemType e);//在链表中查找是否存在数据元素e,若存在,则返回1,否则返回0
	int ListLength();//计算表长
};

🔭2.2构造空链表

该操作由构造函数实现,申请一个头结点并置指针域为NULL

LinkList::LinkList()
{
	head = new LNode;
	head->next = NULL;
}

🔭2.3销毁链表

该操作由析构函数实现,主要工作是依次释放链表中结点的存储空间。

LinkList::~LinkList()
{
	LNode* p = head;
	while (p)
	{
		head = head->next;
		delete p;
		p = head;
	}
}

在这里插入图片描述

🔭2.4头插法创建链表

该函数是从一个空表开始(初始化),依次读取数据,生成新结点,将读入的数据元素存放到新结点的数据域中,然后将新结点插入到当前链表的头结点后,直至读入所有数据为止。头插法建立单链表的示意图如下:
在这里插入图片描述

void LinkList::CreatList_h(int n)
{
	LNode* s;
	int i = 0;
	for (i = 0; i < n; i++)
	{
		s = new LNode;//建立新结点
		cin >> s->data;//读入数据元素
		s->next = head->next;//新结点插入头结点之后
		head->next = s;
	}
}

该算法的时间复杂度为O(n),其中n为单链表中数据结点的个数。

🔭2.5尾插法创建链表

尾插法每次将新生成的结点插入到当前链表的表尾上。为此,需要增加一个尾指针rear,rear始终指向当前链表的尾结点。尾插法建立单链表的示意图如下:
在这里插入图片描述

void LinkList::CreatList_t(int n)
{
	LNode* rear, * s;
	int i = 0;
	for (i = 0; i < n; i++)
	{
		s = new LNode;//建立新结点
		cin >> s->data;//读入数据
		rear->next = s;//新结点插在表尾
		rear = s;//尾指针指向新结点
	}
	s->next = NULL;//置最后结点的指针域为NULL
}

该算法的时间复杂度为O(n),其中n为单链表中数据结点的个数。

🔭2.6插入操作

该操作是在第i个结点前插入值为e的新结点。操作过程如下图所示:
在这里插入图片描述

void LinkList::InsertList(int i, ElemType e)
{
	LNode* p, * s;
	p = head;
	int j = 0;
	while (p && j < i - 1)//查找第i-1个结点
	{
		p = p->next; 
		j++;
	}
	if (!p || j > i - 1)
		return;
	else//插入新结点
	{
		s = new LNode;
		s->data = e;
		s->next = p->next;
		p->next = s;
	}
}

🔭2.7删除操作

该操作是将单链表的第i个结点删去,也可以先找到第i-1个结点,然后删去它的后继结点,操作过程如下图所示:
在这里插入图片描述

void LinkList::DeleteList(int i, ElemType& e)
{
	LNode* p = head;
	int j = 0;
	while (p && j < i - 1)//查找第i-1个结点 
	{
		p = p->next;
		j++;
	}
	if (!(p->next) || j > i - 1)
		return;
	else
	{
		LNode* q = p->next;
		e = q->data;//保存被删除结点的数据元素
		p->next = q->next;//删除被删结点
		delete q;//释放空间
	}
}

🔭2.8取第i个元素

int LinkList::GetElem(int i, ElemType& e)
{
	LNode* p = head;
	int j = 0;
	while (j < i && p)
	{
		p = p->next;
		j++;
	}
	if (!p || j > i)//不存在第i个数据元素,返回0
		return 0;
	else
	{//存在第i个元素,返回1
		e = p->data;
		return 1;
	}
}

🔭2.9查找操作

该操作是在单链表中查找第一个值为e的结点,若存在这样的结点,则返回1,否则返回0.

int LinkList::LocateElem(ElemType e)
{
	LNode* p = head->next;
	while (p && p->data != e)
	{
		p = p->next;
	}
	if (p)//存在元素值为e的结点,返回1
		return 1;
	else//不存在,返回0
		return 0;
}

✅如果我们这里添加一条,如果找到了返回元素的位置,该怎么改这段代码呢?

int LinkList::LocateElem(ElemType e)
{
	LNode* p = head->next;
	int j = 1;
	while (p && p->data != e)
	{
		p = p->next;
		j++;
	}
	if (p)//存在元素值为e的结点,返回位置
		return j;
	else//不存在,返回0
		return 0;
}

🔭2.10计算表长

ListLength()函数从单链表的第一个结点开始依次遍历整个链表,每遍历一个结点,计数器+1,最后返回结点数量。

int LinkList::ListLength()
{
	LNode* p = head->next;
	int count = 0;
	while (p)
	{
		count++;//计数器+1
		p = p->next;
	}
	return count;//返回结点个数
}

🔭2.11打印单链表

void LinkList::print()
{
	LNode* p = head->next;
	while (p)
	{
		cout << p->data << " ";
		p = p->next;
	}
}

🔭2.12全部代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
typedef int ElemType;
typedef struct LNode//定义单链表结点类型
{
	ElemType data;//存放数据元素信息
	LNode* next;//存放后继结点的地址信息
}LNode;
class LinkList
{
private:
	LNode* head;
public:
	LinkList();//构造函数,构造一个空表
	~LinkList();//析构函数,销毁函数
	void CreatList_h(int n);//头插法创建具有n个数据元素的线性链表
	void CreatList_t(int n);//尾插法创建具有n个数据元素的线性链表
	void InsertList(int i, ElemType e);//在表中第i个位置插入数据元素
	void DeleteList(int i);//删除表中第i个元素的数据元素
	void GetElem(int i);//获取第i个数据元素
	void LocateElem(ElemType e);//在链表中查找是否存在数据元素e,若存在,则返回1,否则返回0
	void ListLength();//计算表长
	void print();//打印链表
};
LinkList::LinkList()
{
	head = new LNode;
	head->next = NULL;
}
LinkList::~LinkList()
{
	LNode* p = head;
	while (p)
	{
		head = head->next;
		delete p;
		p = head;
	}
}
void LinkList::CreatList_h(int n)
{
	LNode* s;
	int i = 0;
	for (i = 0; i < n; i++)
	{
		s = new LNode;//建立新结点
		cin >> s->data;//读入数据元素
		s->next = head->next;//新结点插入头结点之后
		head->next = s;
	}
}
void LinkList::CreatList_t(int n)
{
	LNode* rear = NULL, * s = NULL;
	int i = 0;
	for (i = 0; i < n; i++)
	{
		s = new LNode;//建立新结点
		cin >> s->data;//读入数据
		rear->next = s;//新结点插在表尾
		rear = s;//尾指针指向新结点
	}
	s->next = NULL;//置最后结点的指针域为NULL
}
void LinkList::InsertList(int i, ElemType e)
{
	LNode* p, * s;
	p = head;
	int j = 0;
	while (p && j < i - 1)//查找第i-1个结点
	{
		p = p->next; 
		j++;
	}
	if (!p || j > i - 1)
		return;
	else//插入新结点
	{
		s = new LNode;
		s->data = e;
		s->next = p->next;
		p->next = s;
	}
}
void LinkList::DeleteList(int i)
{
	ElemType e;
	LNode* p = head;
	int j = 0;
	while (p && j < i - 1)//查找第i-1个结点 
	{
		p = p->next;
		j++;
	}
	if (!(p->next) || j > i - 1)
		return;
	else
	{
		LNode* q = p->next;
		e = q->data;//保存被删除结点的数据元素
		p->next = q->next;//删除被删结点
		delete q;//释放空间
	}
	cout << "删除了单链表上的第" << i << "个元素,值为" << e << endl;
}
void LinkList::GetElem(int i)
{
	ElemType e;
	LNode* p = head;
	int j = 0;
	while (j < i && p)
	{
		p = p->next;
		j++;
	}
	if (!p || j > i)//不存在第i个元素
		cout << "不存在第i个数据元素"<<endl;
	else
	{//存在第i个元素
		e = p->data;
		cout << "第" << i << "个元素的值为" << e << endl;
	}
}
void LinkList::LocateElem(ElemType e)
{
	LNode* p = head->next;
	int j = 1;
	while (p && p->data != e)
	{
		p = p->next;
		j++;
	}
	if (p)//存在元素值为e的结点,返回位置
		cout << e << "所在的位置为:" << j << endl;
	else//不存在,返回0
		cout << "不存在该数据元素" << endl;
}
void LinkList::ListLength()
{
	LNode* p = head->next;
	int count = 0;
	while (p)
	{
		count++;//计数器+1
		p = p->next;
	}
	cout << "表的长度为:" << count << endl;//返回结点个数
}
void LinkList::print()
{
	LNode* p = head->next;
	while (p)
	{
		cout << p->data << " ";
		p = p->next;
	}
	cout << endl;
}
int main()
{
	LinkList L;
	L.CreatList_h(10);
	L.print();
	L.InsertList(3, 6);
	cout << "在第3个位置插入6后单链表为:";
	L.print();
	L.LocateElem(5);
	L.DeleteList(8);
	L.print();
	L.GetElem(3);
	L.ListLength();
	return 0;
}

✅示例运行:
在这里插入图片描述

好啦,关于单链表的知识点到这里就结束啦,后期会继续更新数据结构与算法的相关知识,欢迎大家持续关注、点赞和评论!❤️❤️❤️

  • 65
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 111
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一口⁵个团子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值