数据结构----链表

将从下面4部分进行介绍

首先介绍链表是什么,然后介绍为什么定义链表,接着是链表的分类,最后简单介绍一下链表结点的插入与删除方法


1.链表是什么

首先,在介绍链表之前,我们先介绍一下什么是顺序存储结构。我们知道数据在计算机中的存储就像货物在仓库中的存储一样,不但占用一定的空间,还要有一个标示存储位置的地址。计算机通过该地址找到这个数据。 

那么顺序存储结构,就是在存储空间中,以一块连续的内存地址开辟的空间来存储内容的结构。 

那什么是链式存储结构呢?就是在存储空间中,以一块不连续的内存地址开辟的空间来存储内容的结构。

我们看一下链式存储结构示意图,它就像一条链子一样,其中每一个元素就相当于这条链子的一个小突起,看起来就像在绳子上的结一样,我们称这个结叫做结点。

我们放大其中一个结点看看,其中包括了数值域和指针域,数值域用来存储数据,指针域用来存放指向下一个结点的地址。

我们所说的链表,就是一个典型的链式数据存储结构。


2.为什么定义链表

我们为什么要定义链表这样的链式存储结构呢?

通常我们存放同种类型的数据时,可能会定义数组来进行存放,数组的优点在于便于对数据进行随机访问,因为数组是属于顺序存储结构,访问其中的每个数据所需要的时间与这个数据所在的位置无关。

但是它也同样存在弊端,就是数组的大小在定义时要事先规定,不能在程序中进行调整,这样一来,在程序设计中针对不同问题,有时需要3 0个大小的数组,有时需要5 0个数组的大小,难于统一。我们只能够根据可能的最大需求来定义数组,常常会造成一定存储空间的浪费。

链表就很好的解决的这个问题。它的特点是结构的长度十分灵活,都在在运行的时候现用现申请而不是像数组那样有固定的长度限制。而且它比顺序存储结构另一个更方便的地方,是在数据插入或者删除上,不用像数组一类的顺序存储结构那样进行很多次的数据移动,它只需要插入或者删除其中的一个节点就可以了。


3.链表的分类

链表简单可以分三类,分别是单向链表、想象循环链表和双向链表,他们的结构如图所示,下面我对三种链表分别进行介绍。

1.单向链表:

首先是单向链表,单向链表是最简单最基础的链表,它的指针域里只有一个指针变量指向下一个结点,也就是说它只能从头走到尾,进行单向遍历。

我们看一下它的结构,最前面那个结点叫做表头结点,它后面的这个叫做第一个结点,表头结点中数据域不存放内容,只是使用它的“next指针”指向真正的第一个结点。

计算机从一个节点中取出一个数据后,根据该节点中存放的指针,找到下一个节点的位置,如此循环下去。

单向链表中最后一个结点称为表尾结点,它存储的指针为“NULL”,代表单向链表的结束。 

接下来我们举个例子看一下,如图一个线性表A/B/C/D/E/F/G,如果是一个单向链表存储结构,那么看一下它的头结点中的指针是31,应该指向的是第一个节点的地址,我们就找到了A,A结点的指针域中存储的指针是7,就指向下一个结点,也就是B,如此类推,一直到最后一个结点G,它的指针域是空,代表着链表的结束。

我们可以把上述逻辑顺序画成用箭头连接成的结点序列,就成了如图所示的结构。

但是单向链表有一个显著的缺点,就是从一个已知节点出发,只能访问该结点以后的结点,无法找到某个结点以前的结点。

为了解决这个问题,我们就引入了单向循环链表。

2.单向循环链表

单向循环链表与单向链表在结构上极为相似,不同的是,单向循环链表的尾结点的next指向的不是NULL,而是链表的第一个结点。

由此可见,单向循环链表是一种首尾相连的链表。那么在单向循环链表中,从任一结点出发,都可以访问到链表中所有结点。

接下来,再举一个例子,就是经典的约瑟夫问题。

约瑟夫和另外40个犹太人被罗马军队包围在山洞中。犹太人决定宁愿自杀也不要被俘。

       他们商量一个自杀的方法:41个人围坐一圈,从第一个人开始报数,每报数到3的人就自杀,下一个人重新开始报数,如此循环下去,直到所有人死光。

      约瑟夫不想自杀,那么一开始他要站在什么位置才能避免自杀?

我们看一下使用单向循环链表来解决这个问题的主要程序。

void main()
{
	CriList<int> jos; //新建单向循环链表,模拟约瑟夫问题

	for (int i = 1; i < 41; i++) //向链表中加入1-41,代表41个编号的人
	{
		jos.AddTail(i);
	}
	jos.SetBegin();  //开始模拟约瑟夫问题

	int length = jos.GetCount(); //原始人数
	for (i = 1; i < length; i++) 
	{
		for (int j = 1; j < 3; j++)
		 jos.GetNext();
		 jos.RemoveThis(); //指针每往后推3次,删除一个人
	}

	cout << jos.GetNext() << endle;    //输出最后剩下的人编号
}

单向链表和单向循环链表在寻找已知结点的前驱结点都是十分不容易的,必须从链表头开始进行顺序遍历链表,直到某一结点的后继结点为我们寻找的目标位置,当链表的数目很多时,这种寻找前驱结点的方法就会效率很低,于是又引入了双向循环链表。

3.双向循环链表

双向循环链表中,每个结点中都存储两个指针,一个指向该结点的前驱结点,另一个指向该结点的后驱结点。这样的话,就可以沿着两个方向对整个链表进行遍历。

双向循环链表就是通过这种双指针形式实现了链表的双向循环。


4.链表的插入与删除

最后,再简单的说一下链表结点的插入语删除。

这里我们以最简单的单向链表为例。

如图所示,如果想要在这个位置插入一个结点F,就只需要将B中的指针域改为指向F的地址,将F的指针域填写为C的地址就可以了。

如果想删除一个结点,例如将结点B删除,就把A结点的指针域改为指向的地址即可。 

对于单向循环链表和双向链表的结点如果插入或者删除的话,也是类似的情况,但要考虑所删除的结点是最后一个结点或者第一个结点的情况。

猜你喜欢

转载自blog.csdn.net/zzu_seu/article/details/83315524