线性表顺序存储结构的特点是,在逻辑关系上相邻的两个元素在物理位置上也是相邻的,因此可以随机存取表中任一元素。但是,当经常需要做插入和删除操作时,则需要移动大量的元素,而采用链式存储结构就可以避免这些移动。由于链式存储结构存储线性表数据元素的存储空间可能是连续的,也可能是不连续的,所以链表的结构是不可以随机存取的。链式存储是最常用的存储方式之一,不仅可以用来表示线性表,也可以用来表示各种非线性的数据结构。
1、单链表
在使用链式存储结构表示每个数据元素ai时(i是元素的下角标),除了保存ai信息本身以外,还要 一个存储指示其后继元素a(i+1)存储位置的指针,由这两个部分组成元素ai的存储映像通常称之为结点。它包括两个域:存储数据元素的域称为数据域,存储直接后继存储地址的域称为指针域。 即一个结点的存储结构为(data+next)如图: n个结点链成一个链表,即为线性表的链式存储结构。由于每个结点只包含一个指针域,所以称之为单链表。 如图:头指针和头节点
头指针:指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。头节点分为两种:- 真实头节点:存储数据
- 虚拟头节点:不存储数据
尾指针
尾指针表示该链表最后一个结点的位置,存储的是最后一个结点的地址。尾指针便于从尾部插入数据的效率,有了尾指针可以直接获取最后一位元素的指针域,然后指向新插入的结点,就完成了数据的插入。线性表链式存储结构(LinkedList)类图
由下面的类图我们可以看出,单链表也是实现了List接口,LinkedList为实现类,并增加了一个Node类。 链表插入方法 判断指定角标位置,就分为头插,尾插,中间插三种情况。 头插,只需将头指针指向新结点,然后让新结点指向原先的第一个元素。 尾插,先让尾指针元素指向新结点,然后将尾指针向后移一位。 中间插,先通过指定的角标进行遍历,找到插入角标的前一个元素,然后让前一个元素指向新结点,新结点指向当前角标位置的元素。 头插法: 尾插法: 一般插入: 链表的删除方法 有三种情况 如果是表头位置,则简单,不需要遍历,让头指针指向第二个元素即可。 如果是表尾位置,则需要通过遍历获取倒数第二个元素,然后让这个元素指向空,并让尾指针向前移一位 如果是中间位置,则需要对链表进行循环遍历找到指向角标的前一个元素,然后让这个元素指向删除元素的下一个元素,并让被删除元素指向为空,值为空。 头删: 尾删: 一般删除:2、循环链表
循环链表是链式存储结构的另一种形式。它的特点是单链表最后一个结点(终端结点)的指针域不为空,而是指向链表的头结点,使整个链表构成一个环。 循环链表的结点类型与单链表完全相同,在操作上也基本一致,差别仅在于算法中循环的判断条件不再是p或者p->next为空,而是他们是否等于头指针。 如图:循环链表实现约瑟夫环
约瑟夫环问题,是一个经典的循环链表问题,题意是:已知 n 个人(分别用编号 1,2,3,…,n 表示)围坐在一张圆桌周围,从编号为 k 的人开始顺时针报数,数到 m 的那个人出列;他的下一个人又从 1 开始,还是顺时针开始报数,数到 m 的那个人又出列;依次重复下去,直到圆桌上剩余一个人。 如图 所示,假设此时圆周周围有 5 个人,要求从编号为 3 的人开始顺时针数数,数到 2 的那个人出列:出列顺序依次为:
编号为 3 的人开始数 1,然后 4 数 2,所以 4 先出列;
4 出列后,从 5 开始数 1,1 数 2,所以 1 出列;
1 出列后,从 2 开始数 1,3 数 2,所以 3 出列;
3 出列后,从 5 开始数 1,2 数 2,所以 2 出列;
最后只剩下 5 自己,所以 5 胜出。
约瑟夫环问题有多种变形,比如顺时针转改为逆时针等,虽然问题的细节有多种变数,但解决问题的中心思想是一样的,即使用循环链表。
3、双向链表
若是希望从表中快速确定一个结点的直接前驱,只需要在单链表的结点类型中增加一个指向其直接前趋的指针域prior即可,这样就形成链表中有两条不同方向的链,因此成为双向链表。
双向链表的结点表示:
双向链表: 双向链表中插入结点: 双向链表删除结点:与删除结点反之。 遍历链表,找到要删除的结点,然后利用该结点的两个指针域完成删除操作。4、双向循环链表
其实就是双向链表和循环链表的结合体,对于新的约瑟夫环问题,需要将循环链表和双向链表结合使用,组成:双向循环链表。