数据结构学习总结 ——4.线性表一链式存储结构
4.1 线性表的链式存储结构
4.1.1 线性表链式存储结构的定义
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。这就意味着,这些数据元素可以存在内存为被占用的任意位置。
在顺序结构中,每个数据元素只需要存储数据元素信息就可以了。链式结构中,除了要存储数据元素信息外,还要存储它的后继元素的存储地址。
因此,为了表示每个数据 a1 与其直接后继数据元素 ai+1 之间的逻辑关系,对数据元素 a1 来说,除了存储其本身的信息之外,还需要存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据元素 ai 的 存储映像,称为结点(Node)。
n 个结点(ai 的存储映像)链结成一个链表,即为线性表(a1,a2,··· ,an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。
4.1.2 头指针与头结点的异同
我们把链表中第一个结点的存储位置叫做头指针。
为了更加方便对链表进行操作,会在单链表的第一个结点前附设一个结点,成为头结点。
头指针:
- 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针
- 头指针具有标识作用,所以常用头指针冠以链表的名字
- 无论链表是否为空,头指针均不为空。头指针是链表的必要元素
头指针: - 头结点是为了操作的统一和方便而设立的,房子啊第一元素的结点之前,其数据域一点无意义
- 有了头结点,对在第一结点,其操作与其他结点的操作就统一了
- 头结点不一定是链表必须要素
4.2 单链表的创建
结点由存放数据元素的数据域与存放后继结点地址的指针域组成。
假设 p 是指向线性表第 i 个元素的指针,则该结点 a1 的数据域我们可以用 p->data 来表示,p->data 的值是一个数据元素,结点 a1 的指针域可以用 p->next 来表示,p->next 的值是一个指针。
/**
* 创建单链表结点
*
* Class CreateListNode
*/
class CreateListNode
{
public $data;
public $next;
public function __construct()
{
$this->data = null;
$this->next = null;
}
}
/**
* 创建单链表
*
* Class SingleLinkedList
*/
class SingleLinkedList
{
protected $elem; //头结点数据
protected $next; //下一结点指针
protected $maxLength; //单链表长度
public function __construct()
{
$this->elem = null;
$this->next = null;
}
/**
* 头插法创建单链表
*
* @param $list
* @return bool
*/
public function getHeadCreateList(array $list = [])
{
//数组不能为空
if (empty($list)) {
return false;
}
foreach ($list as $key => $value)
{
$p = new CreateListNode();
$p->elem = $value;
$p->next = $this->next;
$this->next = $p;
self::$maxLength++;
}
return true;
}
/**
* 尾插法创建单链表
*
* @param $list
* @return bool
*/
public function getTailCreateList(array $list = [])
{
if (empty($list)) {
return false;
}
$q = $this;
foreach ($list as $key => $value)
{
$p = new CreateListNode();
$p->elem = $value;
$p->next = $q->next;
$q->next = $p;
$q = $p;
self::$maxLength++;
}
return true;
}
......
}
4.3 单链表的读取
在线性表的顺序存储结构中,我们要计算任意一个元素的存储位置是很容易的。但是在单链表中,由于第 i 个元素到底在哪,没有办法一开始就知道,必须要从头开始找。因此,对于单链表实现获取第 i 个元素的数据操作,在算法上,相对要麻烦一些。
获得单链表第 i 个元素的算法思路:
- 声明一个结点 p 指向链表第一个结点,初始化 j 从 1 开始;
- 当 j < i 时,就遍历链表,让 p 的指针向后移动,不断指向下一结点,j 累加 1;
- 若到链表末尾 p 为空,则说明第 i 个元素不存在;
- 否则查找成功,返回结点 p 的数据。
代码实现(PHP):
/**
* 创建单链表
*
* Class SingleLinkedList
*/
class SingleLinkedList
{
protected $elem; //头结点数据
protected $next; //下一结点指针
protected $maxLength; //单链表长度
public function __construct()
{
$this->elem = null;
$this->next = null;
}
......
/**
* 获得第 i 个数据元素
*
* Class CreateListNode
*/
public function getElemForPop(int $i = 0;)
{
if ($i <= 0 || $i > self::$maxLength) {
return null;
}
$p = $this->next;
for ($j = 1; $j < $i; $j++) {
$q = $p->next;
$p = $q;
}
return $p->elem;
}
}
4.4 单链表的插入与删除
4.4.1 单链表的插入
假设存储元素 e 的结点为 s , 要实现结点 p、 p->next 和 s 之间逻辑关系的变化,只需将结点 s 插入到结点 p 和 p->next 之间即可。
根本用不着惊动其他结点,只需要让 s->next 和 p->next 的指针做一点改变即可。
$s->next = $p->next; $p->next = $s;
单链表第 i 个数据插入结点的算法思路:
- 声明一结点 p 指向链表第一个结点,初始化 j 从 1 开始;
- 当 j < 1 时,就遍历链表,让 p 的指针向后移动,不断指向下一结点,j 累加 1;
- 若到链表末尾 p 为空,则说明第 i 个元素不存在;
- 否则查找成功,在系统中生成一个空结点 s;
- 将数据元素 e 赋值给 s->data;
- 单链表的标准插入语句 **s->next = p->next; p->next = s;
- 返回成功
/**
* 创建单链表
*
* Class SingleLinkedList
*/
class SingleLinkedList
{
protected $elem; //头结点数据
protected $next; //下一结点指针
protected $maxLength; //单链表长度
public function __construct()
{
$this->elem = null;
$this->next = null;
}
......
/**
* 单链表第 i 个数据插入
*
* @param $i
* @param $e
* @return bool
*/
public function insertList(int $i, $e)
{
if ($i < 0 || $i > self::$maxLength) {
return false;
}
$j = 1;
$p = $this;
while ($p->next != null && $j < $i)
{
$q->elem = $e;
$q->next = $p->next;
$p->next = $q;
$j++;
self::$maxLength++;
}
return true;
}
}
4.4.2 单链表的删除
设存储元素 a1 的结点为 q ,要实现将结点 q 删除单链表的操作,其实就是将它的前继结点的指针绕过,指向它的后继结点即可。我们所要做的实际上就一步,p->next = p->next->next,用 q 来取代 p->next。
$q = $p->next; $p->next = $q->next;
单链表第 i 个元素删除结点的算法思路:
- 声明一个指针 p 指向链表头指针,初始化 j 从 1 开始;
- 当 j < 1 时,就遍历链表,让 p 的指针向后移动,不断指向下一个结点, j 累加 1;
- 若链表末尾 p 为空,则说明第 i 个结点不存在;
- 否则查找成功,将欲删除结点 p->next 赋值给 q ;
- 将 q 结点的数据赋值给 e ;
- 释放 q 结点;
- 返回成功
/**
* 创建单链表
*
* Class SingleLinkedList
*/
class SingleLinkedList
{
protected $elem; //头结点数据
protected $next; //下一结点指针
protected $maxLength; //单链表长度
public function __construct()
{
$this->elem = null;
$this->next = null;
}
......
/**
* 单链表第 i 个数据删除
*
* @param $i
* @param $e
* @return bool
*/
public function insertList(int $i)
{
if ($i < 0 || $i > self::$maxLength) {
return false;
}
$j = 1;
$p = $this;
while ($p->next != null && $j < $i)
{
$q = $p->next;
++$j;
}
$p->next = $q->next;
$e = $q->elem;
unset($q);
self::$maxLength--;
return $e;
}
}
4.5 单链表结构与顺序存储结构优缺点
存储分配方式:
- 顺序存储结构用一段连续的存储单元依次存储线性表的数据元素
- 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素
时间性能:
- 查找
- 顺序存储结构 O(1)
- 单链表 O(n)
- 插入和删除
- 顺序存储结构需要平均移动表长一半的元素,时间为 O(n)
- 单链表在线出某位置的指针后,插入和删除时间仅为 O(1)
空间性能
- 顺序存储结构需要预分配存储空间,分大了浪费,分小了易发生上溢
- 单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制
通过上面的对比,我们可以得到一些经验性的结论:
- 若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构。若需要频繁插入和删除时,宜采用单链表结构。
- 当线性表中的元素个数变化较大或者根本不知道有多大时,最好用单链表结构,这样可以不用考虑存储空间的大小问题。而如果事先知道线性表的大致长度,比如一年 12 个月,一周就是 7 天,这种用顺序存储结构效率会高很多。