数组Array
-
数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
-
线性表(Linear List)。顾名思义,线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。其实除了数组,链表、队列、栈等也是线性表结构。
-
是连续的内存空间和相同类型的数据,它才有了一个堪称“杀手锏”的特性:“随机访问”。但有利就有弊,这两个限制也让数组的很多操作变得非常低效,比如要想在数组中删除、插入一个数据,为了保证连续性,就需要做大量的数据搬移工作。
-
对于数组来说,存储空间是连续的,所以在加载某个下标的时候可以把以后的几个下标元素也加载到CPU缓存这样执行速度会快于存储空间不连续的链表存储。
<?php
class myClass
{
private $data;
private $length;
private $capacity;
public function __construct($capacity)
{
$capacity = intval($capacity);
if ($capacity <= 0) return null;
$this->data = array();
$this->capacity = $capacity;
$this->length = count($this->data);
}
private function checkIfFull($index)
{
if ($index>=$this->capacity) {
return true;
}
return false;
}
private function checkOutOfRange($index)
{
if ($index <= $this->capacity) {
return true;
}
return false;
}
public function insert($index, $value)
{
$index = intval($index);
$value = intval($value);
if ($index < 0) {
return 1;
}
if ($this->checkIfFull($index)) {
$this->resize(intval(1.5 * $index));
}
for ($i = $this->length - 1; $i > $index; $i--) {
$this->data[$i + 1] = $this->data[$i];
}
$this->data[$index] = $value;
$this->length++;
return 0;
}
public function delete($index)
{
$value = 0;
$index = intval($index);
if ($index < 0) {
$code = 1;
return [$code, $value];
}
if ($this->checkOutOfRange($index)) {
$code = 2;
return [$code, $value];
}
$value = $this->data[$index];
for ($i = $index; $i < $this->length - 1; $i++) {
$this->data[$i] = $this->data[$i + 1];
}
$this->length--;
return [0, $value];
}
public function find($index)
{
$value = 0;
$index = intval($index);
if ($index < 0) {
$code = 1;
return [$code, $value];
}
if ($this->checkOutOfRange($index)) {
$code = 2;
return [$code, $value];
}
return [0, $this->data[$index]];
}
private function resize($capacity)
{
$newData = new myClass($capacity);
for ($i = 0; $i < $this->length; $i++) {
$newData->data[$i] = $this->data[$i];
}
$this->data = $newData->data;
$this->capacity=$capacity;
}
}
链表
链表通过指针将一组零散的内存块串联在一起。其中,我们把内存块称为链表的“结点”。为了将所有的结点串起来,每个链表的结点除了存储数据之外,还需要记录链上的下一个结点的地址。如图所示,我们把这个记录下个结点地址的指针叫作后继指针 next。
- 前面的节点叫前驱节点 后面的节点叫后继节点
跳表
- 链表加多级索引的结构,就是跳表
- 跳表中查询任意数据的时间复杂度就是 O(logn)
- 跳表是通过随机函数来维护平衡性
今天我们讲了跳表这种数据结构。跳表使用空间换时间的设计思路,通过构建多级索引来提高查询的效率,实现了基于链表的“二分查找”。跳表是一种动态数据结构,支持快速地插入、删除、查找操作,时间复杂度都是 O(logn)。跳表的空间复杂度是 O(n)。不过,跳表的实现非常灵活,可以通过改变索引构建策略,有效平衡执行效率和内存消耗。虽然跳表的代码实现并不简单,但是作为一种动态数据结构,比起红黑树来说,实现要简单多了。所以很多时候,我们为了代码的简单、易读,比起红黑树,我们更倾向用跳表。
Trie树:如何实现搜索引擎的搜索关键词提示功能?
概念
- 字典树,专门处理字符串匹配的数据结构,
- 用来解决在一组字符串集合快速查找某个字符串的问题。
- 利用字符串之间的公共前缀,讲重复的前缀合并在一起。
- Trie 树的本质,就是利用字符串之间的公共前缀,将重复的前缀合并在一起,减少存储空间,提升查询效率。
- 构建时间复杂度是 O(n)(n 表示所有字符串的长度和) 查询时间复杂度是 O(k)(n 表示要查找的字符串的长度)
实现
- 构造的每一步,都相当于在Trie树种插入一个字符串
- 当所有字符串都插入完成,Trie树就构造完毕
适用场景
- Trie适合前缀查找,比如搜索关键词智能匹配场景、自动输入补全、搜索引擎中的关键词提示功能
- 精确匹配查找这种问题更适合用散列表或者红黑树来解决
class TrieNode{
public char data;
public TrieNode[] children = new TrieNode[26];
public boolean isEndingChar = false;
public Integer num = 0;
public TrieNode(char data) {
this.data = data;
}
}
class Trie{
private TrieNode root=new TrieNode('/');
public void insert(char[] text){
TrieNode p = root;
for (int i = 0; i <text.length ; i++) {
int index = text[i] - 'a';
if (p.children[index] == null) {
p.children[index]=new TrieNode(text[i]);
}
p=p.children[index];
p.num+=1;
}
p.isEndingChar=true;
}
public boolean find(char[] pattern) {
TrieNode p = root;
for (int i = 0; i < pattern.length; i++) {
int index = pattern[i] - 'a';
if (p.children[index] == null) {
return false;
}
p = p.children[index];
}
if (!p.isEndingChar) return false;
else return true;
}
}