首页 > 数组作链表

数组作链表

一般传统链表的物理结构,是由指针把一个一个的节点相互连接而成:

struct node
{
DataType data;
node* previous;
node* next;
}

其特点是按需分配节点,灵活动态增长。

但是此外,还有另外一种方式是使用数组实现链表,这里所有的node都在预先分配好的数组中,不使用指针,而是用数组下标来指向前一个、下一个元素:

struct node
{
DataType data;
int previous;
int next;
}

其特点是预先分配节点,并且如果需要链表长度随需增加,需要reallocation ,和vector类似。

下面就我自己的一些了解,谈一下其优缺点与应用。

数组作链表有哪些优点

能要省些内存吗?不见得;速度要快吗?没看出来,那么为什么要使用这种不那么直观的方式来实现链表呢?

  • 数组的内存布局比较紧凑,能占些局部性原理的光
  • 在不提供指针的语言中实现链表结构,如vb等
  • 进程间通信,使用index比使用指针是要靠谱 - 一个进程中的指针地址在另外一个进程中是没有意义的
  • 对一些内存奇缺应用,当其值类型为整型,且值域与数组index相符时,可以将next指针与data复用,从而节省一些内存
  • 整存零取,防止内存碎片的产生(多谢Emacs补充)

实现与应用

Id allocator

这里第一个例子针对上面第四点展开,其主要的应用在于ID的分配与回收,比如数据库表中的每条记录都需要一个unique id,当你增增减减若干次之后,然后新建一个表项,你该分配给它哪个id呢?

  • 维持一个id,每增加一行就加1,删行不回收id --- 这样id会无限增加,太浪费了
  • 每次分配都遍历一遍,找到最小的那个还没被用过的id --- 这样太浪费时间了
一个比较合理的做法是维护一个“可用ID”的链表,每次增加就从链表中拿一个,每次删除就把被删的ID链接到链表中,但是,对于传统链表结构而言,其节点的定义类似于: 
struct idnode
{
int availableID;
idnode* next;
};
这里,因为链表的值的类型与值域都与数组的index相符,我们可以复用其值和next,从而只需一个int数组,而不是一个idnode数组,数组中某个元素的值代表的即是链表节点的值,也是链表的下一个节点下标。下面是一个idallocator的实现,主要提供allocate和free两个函数用来满足上述要求:
const static char LIST_END = -1;
template < typename integer>
class idallocator
{
public:
idallocator(int _num): num(_num)
{
array = new integer[num];
// Initialize the linked list. Initially, it is a full linked list with all elements linked one after another.
head = 0;
for (integer i = 0; i < num; i++)
{
array[i] = i + 1;
}
array[num-1] = LIST_END;
count = num;
}
~idallocator()
{
delete [] array;
}
integer allocate() // pop_front, remove a node from the front
{
int id = head;
if(id != LIST_END)
{
head = array[head];
count--;
}
return id;
}
// push_front, add a new node to the front
void free(const integer& id) 
{
array[id] = head;
count++;
head = id;
}
// push_front, add a new node to the front
bool free_s(const integer& id) 
{
// make sure this id is not in the list before we add it
if(exist(id)) return false; 
free(id);
return true;
}
size_t size()
{
return count;
}
private:
bool exist(const integer& id)
{
int i = head;
while (i != LIST_END)
{
if(i == id) return true;
i = array[i];
}
return false;
}
private:
integer* array;
int num;// totall number of the array
int count;// number in the linked list
int head;// index of the head of the linked list
};

Double linked list

用数组实现链表,大多数情况下,是与传统链表类似的,无非是在添加、删除节点后,调整previous,next域的指向。但是有一点,当我在添加一个新的节点时,如何从数组中拿到一个还未被使用过的节点呢?这里有两种方法:

  • 如果你看懂了上面的id allocator,你也许已经意识到,使用上面那个idallocator类就可以简单的实现这个需求
  • 另外一种方法,其实原理上也类似,就是在这个double linked list类中维护两个链表,一个是已使用的,一个是未使用的
第一种因为借助于一个工具类,实现看起来会简洁很多,但是idallocator会消耗额外的内存,因此第二种方法相对来讲会比较好。

这里有个粗略的实现:arraylist. 

参考

  • 算法导论10.3
  • 另类的链表数据结构以及算法
  • 链表结构原理 与 数组模拟链表 的应用

转载于:https://www.cnblogs.com/baiyanhuang/archive/2010/08/22/1805644.html

更多相关:

  •     先吐为敬!   最近心血来潮研究nodejs如何完成微信支付功能,结果网上一搜索,一大堆“代码拷贝党”、“留一手”、“缺斤少两”、“不说人话”、“自己都没跑通还出来发blog”、“各种缺少依赖包”、“各种注释都没有”、“自己都不知道在写什么”的程序大神纷纷为了增加自己博客一个帖子的名额而发布了各种千奇百�...

  • 阅读ceph源码过程中需要明确当前操作是由哪个线程发出,此时需要根据线程id来确认线程名称 C++获取线程id是通过系统调用来直接获取 函数描述 头文件: 函数名称:syscall(SYS_gettid) 该函数直接返回了一个pid_t int类型的数字,即为当前线程id 此外函数pthread_s...

  • 面试题 分库分表之后,id 主键如何处理? 面试官心理分析 其实这是分库分表之后你必然要面对的一个问题,就是 id 咋生成?因为要是分成多个表之后,每个表都是从 1 开始累加,那肯定不对啊,需要一个全局唯一的 id 来支持。所以这都是你实际生产环境中必须考虑的问题。 面试题剖析 基于数据库的实现方案 数据库自增 id 这个就是说你的...

  • ORM操作    单表、一对多表操作 1 from django.db import models 2 3 4 class UserGroup(models.Model): 5 title = models.CharField(max_length=32) 6 7 8 class UserInfo(m...

  • 建立如下表: 建表语句: class表创建语句 create table class(cid int not null auto_increment primary key, caption varchar(32) not null)engine=innodb default charset=utf8;student表创建语句 c...

  • 原文出处: 韩昊    1 2 3 4 5 6 7 8 9 10 作 者:韩 昊 知 乎:Heinrich 微 博:@花生油工人 知乎专栏:与时间无关的故事   谨以此文献给大连海事大学的吴楠老师,柳晓鸣老师,王新年老师以及张晶泊老师。   转载的同学请保留上面这句话,谢谢。如果还能保留文章来源就更感激不尽了。 我保证这篇文章...

  • 原文出处: 韩昊   我保证这篇文章和你以前看过的所有文章都不同,这是 2012 年还在果壳的时候写的,但是当时没有来得及写完就出国了……于是拖了两年,嗯,我是拖延症患者…… 这篇文章的核心思想就是: 要让读者在不看任何数学公式的情况下理解傅里叶分析。 傅里叶分析不仅仅是一个数学工具,更是一种可以彻底颠覆一个人以前世界观的思维...

  • 很多Linux高手都喜欢使用screen命令,screen命令可以使你轻松地使用一个终端控制其他终端。尽管screen本身是一个非常有用的工具,byobu作为screen的增强版本,比screen更加好用而且美观,并且提供有用的信息和快捷的热键。 想象一下这样一个场景:你通过Secure Shell(ssh)链接到一个服务器,并...

  • NarrowbandPrimary Synchronization Signal时域位置每1个SFN存在一个NPSSSFNSubframeSymbol长度每个SFN5最后11个symbol11个symbols频域位置NB-IOT下行带宽固定180kHz,一个PRB,12个子载波。...

  •  [h1]反斜杠只能够阻止一个字符  [h2]位于键盘的左上角,和~公用一个键。...

  • 1.1 题目:反转链表:输入一个链表,反转链表后,输出新链表的表头。 1.2 思路:这道题,我们要做到的是反转链表,我们的思路是将前一个节点与后一个节点断开,然后让后一个节点指向前一个节点,这个过程就需要节点引用(可以理解为指针)来确定记录当前操作节点的前一个节点和后一个节点。 1.3 代码: 1 # -*- coding:utf...