数据结构-线性表的顺序存储和链式存储结构php实现
大话数据结构
数据结构:是相互之间存在一种或多种特定关系的数据元素的集合
绪论
逻辑结构
逻辑结构:是指数据对象中数据元素之间的相互关系。其实这也是我们今后最需要关注的问题。 逻辑结构分为以下四种
- 集合结构:集合结构中的数据元素除了同属于一个集合外,他们之间没有其他关系
- 线性结构:线性结构中的数据元素之间是一对一的关系
- 树形结构:树形结构中的数据元素之间存在一种一对多的层次关系
- 图形结构:图形结构的数据元素是多对多的关系
物理结构
也叫作存储结构
物理结构:是指数据的逻辑结构在计算机中的存储形式。
存储结构有两种:
- 顺序存储:是把数据元素放在地址连续的存储单元里,其数据间的逻辑关系和屋里关系是一致的
- 链式存储:是把数据元素放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的。
算法
算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
算法定义
比如求1+2+3+。。。+100等于多少,一般用程序写出来是这样的
let sum =0
let n = 100
foreach (let i = 1;i<=n;i++){
sum += i;
}
但是还有别的方法可以求出来,比如高斯
小时候用的方法,1+100、2+99、。。。50+51,那么我们用程序可以这样写
let sum =0
let n = 100
sum = (1+n)*n/2
这样我们就不会像上面那样循环100次才能算出结果。
算法的特性
- 输入
- 输出
- 有穷性
- 确定性
- 可行性
函数的渐进增长
给定两个函数f(n) 和 g(n),如果存在一个整数N,是的对于所有的n > N,f(n)总是比g(n)大,那么,我们说f(n)的增长渐近快于g(n)
算法时间复杂度
在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间度量,记作:T(n)=O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。
推导大O阶方法
推导大O阶:
- 用常数1取代运行时间中的所有加法常数。
- 在修改后的运行次数函数中,只保留最高阶项。
- 如果最高阶项存在且不是1,则去除与这个项相乘的常数。
- 得到的结果就是大O阶
算法复杂度
常数阶O(1) < 对数阶O(logn) < 线性阶O(n) < O(n logn) < 平方阶O(n 2) < 立方阶O(n 3) < 指数阶O(2 n) < 阶乘O(n!) < O(n n)
最坏情况和平均情况
最坏情况运行时间是一种保证,那就是运行时间将不会再坏了。在应用中,这是一种最重要的需求,通常,除非特别指定,我们提到的运行时间都是最坏情况的运行时间。
而平均运行时间也就是从概率的角度看,这个数字在每一个位置的可能性是相同的,所以平均的查找时间为n /2次后发现这个目标元素。
平均运行时间是所有情况中最有意义的,因为它是期望的运行时间。
一般在没有特殊说明的情况下,都是指最坏时间复杂度
算法空间复杂度
算法的空间复杂度通过计算算法所需的存储空间时间,算法空间复杂度的计算公式记作: S(n) = O(f(n)), 其中, n为问题的规模,f(n)为语句关于n所占存储空间的函数
线性表
零个或多个数据元素的有限序列
线性表的顺序存储结构
假设占用的是c个存储单元,那么线性表中第i+1个数据元素的存储位置和第i个数据元素的存储位置满足下列关系(LOC表示获得存储位置的函数)
LOC(ai+1) = LOC(ai) + c
所以对于第i个数据元素ai的存储位置可以由a1推算得出:
LOC(ai) = LOC(a1) + (i -1) * c
比如i = 5在数组中(数组下标从0开始):
ai = 0 + (5 - 1) * 1 = 4
那么我们对每个线性表位置的存入或者取出数据,对于计算机来说都是相等的时间,也就是一个常数,因此它的存取时间性能为O(1)。我们通常把具有这一特点的存储结构称为随机存取结构。
线性表顺序存储结构php版本实现
插入操作
- 判断下标
- 把从最后一个元素到要插入的位置的元素全部向后移动一位,把位置空出来让新元素插入
- 表长加一
/**
* 在线性表指定位置添加元素
*/
function insert($index, $value) {
if ($this->checkLen() == false) {
return '表满了';
}
if ($index < 1 || $index > $this->dataLen) {
return '出错啦';
}
/**
* 如果$data = [1,2,3];
* 要在2号位置添加4, $index = 2, $value=4, $this->len = 3
* 把从最后一个元素到要插入的$index - 1的位置的元素全部向后移动一位,把$index - 1的位置空出来让新元素插入
*/
if ($index <= $this->len) {
for ($i = $this->len - 1; $i >= $index - 1; $i--) { //$i = 2; $i >= 1; $i--
/**
* 第一遍 $this->data = [1,2,3,3]; $i = 2;
* 第二遍 $this->data = [1,2,2,3];$i = 1;
*/
$this->data[$i + 1] = $this->data[$i];
}
}
$this->data[$index - 1] = $value; // $this->data = [1,4,2,3]
$this->len++;
return true;
}
删除操作
- 判断下标
- 把要删除的位置的元素取出来
- 把从要删除的位置到最后一个位置的元素全部向前移动一位,把最后的元素删除
- 表长减一
/**
* 删除线性表指定位置的元素
*/
function remove($index) {
if ($this->len == 0) {
return '空表';
}
if ($this->checkLen() == false) {
return '表满了';
}
if ($index < 1 || $index > $this->len) {
return '出错啦';
}
/**
* 如果$data = [1,4,2,3];
* 要把2号位置4删除, $index = 2, $value=4, $this->len = 4
* 把从要删除的$index - 1的位置到最后一个位置的元素全部向前移动一位,把最后的元素删除
*/
$elem = $this->data[$index - 1]; //取出要删除的元素
if ($index <= $this->len) {
for ($i = $index - 1; $i < $this->len - 1; $i++) { //$i = 1; $i < 3; $i--
/**
* 第一遍 $this->data = [1,2,2,3]; $i = 1;
* 第二遍 $this->data = [1,2,3,3];$i = 2;
*/
$this->data[$i] = $this->data[$i + 1];
}
//删除最后一个元素 $this->data = [1,2,3];
unset($this->data[$this->len - 1]);
}
$this->len--;
return $elem;
}
完整代码
<?php
namespace App\Http\Models;
/**
* 顺序存储
* 线性表
*/
class ListTable {
private $data = [];
private $len = 0; //数据大小
private $dataLen = 0; //线性表大小
function __construct($dataLen, $data = [])
{
$this->dataLen = $dataLen;
$this->data = $data;
$this->len = count($data);
}
/**
* 在线性表末尾添加元素
*/
function add($value) {
if ($this->checkLen() == false) {
return '表满了';
}
$this->data[] = $value;
$this->len++;
}
/**
* 在线性表指定位置添加元素
*/
function insert($index, $value) {
if ($this->checkLen() == false) {
return '表满了';
}
if ($index < 1 || $index > $this->dataLen) {
return '出错啦';
}
/**
* 如果$data = [1,2,3];
* 要在2号位置添加4, $index = 2, $value=4, $this->len = 3
* 把从最后一个元素到要插入的$index - 1的位置的元素全部向后移动一位,把$index - 1的位置空出来让新元素插入
*/
if ($index <= $this->len) {
for ($i = $this->len - 1; $i >= $index - 1; $i--) { //$i = 2; $i >= 1; $i--
/**
* 第一遍 $this->data = [1,2,3,3]; $i = 2;
* 第二遍 $this->data = [1,2,2,3];$i = 1;
*/
$this->data[$i + 1] = $this->data[$i];
}
}
$this->data[$index - 1] = $value; // $this->data = [1,4,2,3]
$this->len++;
return true;
}
private function checkLen() {
if ($this->len >= $this->dataLen) {
return false;
}
return true;
}
function list() {
return $this->data;
}
/**
* 删除线性表指定位置的元素
*/
function remove($index) {
if ($this->len == 0) {
return '空表';
}
if ($this->checkLen() == false) {
return '表满了';
}
if ($index < 1 || $index > $this->len) {
return '出错啦';
}
/**
* 如果$data = [1,4,2,3];
* 要把2号位置4删除, $index = 2, $value=4, $this->len = 4
* 把从要删除的$index - 1的位置到最后一个位置的元素全部向前移动一位,把最后的元素删除
*/
$elem = $this->data[$index - 1]; //取出要删除的元素
if ($index <= $this->len) {
for ($i = $index - 1; $i < $this->len - 1; $i++) { //$i = 1; $i < 3; $i--
/**
* 第一遍 $this->data = [1,2,2,3]; $i = 1;
* 第二遍 $this->data = [1,2,3,3];$i = 2;
*/
$this->data[$i] = $this->data[$i + 1];
}
//删除最后一个元素 $this->data = [1,2,3];
unset($this->data[$this->len - 1]);
}
$this->len--;
return $elem;
}
function getIndex($value) {
if (in_array($value, $this->data)) {
return array_search($value, $this->data) + 1;
} else {
return 0;
}
}
function getElem($index) {
if (empty($this->data) || $index < 1 || $index > $this->dataLen) {
return '出错啦';
}
return $this->data[$index - 1];
}
}
从上面可以看出,线性表顺序存储结构的插入和删除的时间复杂度都是O(n),读数据的时间复杂度则是O(1)
线性表顺序存储结构优缺点
优点
- 无须为表示表中元素之间的逻辑关系而增加额外的存储空间
- 可以快速地存取表中任一位置的元素
缺点
- 插入和删除操作需要移动大量元素
- 当线性表长度变化较大时,难以确定存储空间的容量
- 造成存储空间的
碎片
线性表的链式存储结构
为了表示每个数据元素ai 与其直接后继数据元素ai + 1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息。我们把存储数据元素信息的域称为数据域,把存直接后继位置的域称为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据元素ai的存储映像,称为节点。
n个节点链接成一个链表,即为线性表的链式存储结构,因为此链表的每个节点中只包含一个指针域,所以叫做单链表。
链表中第一个节点的存储位置叫做头指针
线性链表的最后一个节点指针为空
头指针与头结点的异同
头指针
- 头指针是指链表指向第一个节点的指针,若链表有头结点,则是指向头结点的指针
- 头指针具有标识作用,所以常用头指针冠以链表的名字
- 无论链表是否为空,头指针均不为空。头指针是链表的必要元素
头结点
- 头结点是为了操作的统一和方便而设立的,放在第一元素的节点之前,其数据域一般无意义(也可以存放链表的长度)
- 有了头结点,对在第一元素节点前插入节点和删除第一节点,其操作与其它节点的操作就统一了
- 头结点不一定是链表必须要素
单链表节点
<?php
namespace App\Http\Models\LinkList;
/**
*
* 单链表节点
*/
class Node {
public $data;
public $next; //指向下个元素的指针
function __construct($data = null)
{
$this->data = $data;
$this->next = null;
}
function __toString()
{
dump($this->data);
}
}
单链表的读取
- 声明一个节点p指向链表第一个节点,初始化j从1开始;
- 当j < i 时,就遍历链表,让p的指针向后移动,不断指向下一节点,j累加1
- 若到链表末尾p为空,则说明第i个元素不存在;
- 否则查找成功,返回节点p的数据
/**
* 查找元素
*/
function getElem($index) {
$current = $this->head;
$i = 1;
//找到要查找位置的节点
while($i < $index) {
$current = $current->next;
$i++;
}
return $current;
}
单链表的插入
/**
* 在线性表末尾添加元素
*/
function add($data) {
$current = $this->head;
//如果有后继节点就接着往后遍历,直到查找到最后一个节点
while(!empty($current->next)) {
$current = $current->next;
}
$current->next = new Node($data);
$this->len++;
}
单链表的删除
/**
* 删除线性表指定位置的元素
*/
function remove($index) {
if ($this->isEmpty()) {
return '空表';
}
$current = $this->getElem($index);
$elem = $current->next;
$current->next = $elem->next;
$this->len--;
return $elem;
}
单链表php完整实现
<?php
namespace App\Http\Models\LinkList;
/**
* 链式存储
* 线性表
* 单链表
*/
class LinkTable {
private $head; //头结点
private $len = 0; //链表长度
function __construct()
{
$this->head = new Node();
}
/**
* 在线性表末尾添加元素
*/
function add($data) {
$current = $this->head;
//如果有后继节点就接着往后遍历,直到查找到最后一个节点
while(!empty($current->next)) {
$current = $current->next;
}
$current->next = new Node($data);
$this->len++;
}
/**
* 在线性表指定位置添加元素
*/
function insert($index, $data) {
$current = $this->getElem($index);
$node = new Node($data); //新建节点
$node->next = $current->next; //把新节点的后继节点设置为之前这个位置节点的后继节点
$current->next = $node; //把之前这个位置的后继节点设置成新节点
$this->len++;
return true;
}
function isEmpty() {
if ($this->len == 0) {
return true;
}
return false;
}
/**
* 删除线性表指定位置的元素
*/
function remove($index) {
if ($this->isEmpty()) {
return '空表';
}
$current = $this->getElem($index);
$elem = $current->next;
$current->next = $elem->next;
$this->len--;
return $elem;
}
/**
* 查找元素
*/
function getElem($index) {
$current = $this->head;
$i = 1;
//找到要查找位置的节点
while($i < $index) {
$current = $current->next;
$i++;
}
return $current;
}
/**
* 用头插法插入$count个节点创建一个单链表
*/
function createListHead($count) {
for ($i = 0; $i < $count; $i++) {
$this->insert(1, rand(0, 100));
}
$this->len = $count;
}
/**
* 用尾插法插入$count个节点创建一个单链表
*/
function createListTail($count) {
$current = $this->head;
for ($i = 0; $i < $count; $i++) {
$current->next = new Node(rand(0, 100));
$current = $current->next;
}
$this->len = $count;
}
/**
* 清空整个单链表
*/
function clearList() {
if ($this->isEmpty()) {
return '空表';
}
$current = $this->head->next;
while($current) {
$temp = $current->next;
unset($current);
$current = $temp;
}
$this->head->next = null;
$this->len = 0;
return true;
}
}
单链表和顺序存储结构优缺点
存储分配方式
- 顺序存储结构用一段连续的存储单元依次存储线性表的数据元素
- 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素
时间性能
- 查找:顺序存储O(1), 单链表O(n)
- 插入和删除:顺序存储O(n),单链表在找到某位置的指针后,为O(1)
空间性能
- 顺序存储结构需要预先分配存储空间,分大了,浪费,分小了容易溢出
- 单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制
静态链表
静态链表就是用二维组来实现链表,在数组里面存上data和next
循环链表
将单链表中终端节点的指针由空指针改为指向头结点,就使得整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表
其实循环链表和单链表的主要差异就在于循环的判断条件上,原来是判断p->next是否为空,现在则是p->next不等于头结点,则循环未结束。
双向链表
为了克服单向性这一缺点,设计出了双向链表。双向链表是在单链表的每个节点中,在设置一个指向其前驱节点的指针域。所以在双向链表中的节点都有两个指针域,一个指向直接后继,另一个指向直接前驱。
转载自:https://juejin.cn/post/7146013973320564772