怎样提高链表的随机访问效率?
单链表的访问改进
我们知道单链表的插入和删除的时间复杂度是 O(1) ,但是其访问的时间复杂度是 O(N),不能实现随机访问。而顺序表是随机访问的,插入和删除的时间复杂度是 O(N)针对单链表的访问弊端,如何改进单链表数据结构,使得访问的效率有所提升?每种数据结构都有各自的优劣以及适用情况。这里有几种方案,其实不能算在方案吧,而是采用其他数据结构替换的策略。
第一种方案:
采用平衡二叉树,插入、删除、访问的复杂度都是 O(logN)
或者红黑树,插入、删除、访问的时间复杂度都是 O(logN)
STL 中的 set、map 可以完成该功能。
第二种方案:
采用分段的策略
针对每个节点的值,根据值进行分段,段数视具体情况而定。 插入和删除的时间复杂度保持不变,还是 O(1) ,访问的时间复杂度变为 O(N / 段的数目) ,这种方式访问的时间复杂度得到一定的改进,但是是常数级的。
这种策略实质上是哈希。 哈希函数为除法函数。
例如有 0 1 2 3 4 5 6 7 8 9 十个数,可以分为两段,0 - 4 为第一段,5 - 9 为第二段。
访问一个数时,首先计算其所在的段,m / 5,得到所在段的首地址,然后去遍历访问。
第三种方案:
采用线索二叉树
线索二叉树将二叉树线索化,二叉树可以想链表那样操作。插入和删除的时间复杂度都是 O(1)。
访问按照二叉树的方式,这时二叉树是平衡二叉树,访问的时间复杂度是 O(logN)。
几种方案的比较
插入和删除 访问
单链表 O(1) O(N)
平衡二叉树 O(logN) O(logN)
分段 O(1) O(N / 段的数目)
线索二叉树 O(1) O(logN)
总结
这几种方案,与其说是改进,不如说是更换另一种数据结构。另外哈希方式,最好在存在大量数据的情况下使用,否则会浪费空间,因为哈希表很大。针对单链表访问效率的改进,另一个角度是采用辅助性数据结构,记录一些信息,以方便快速地访问。
以下是一种实现方案:
一、问题的描述
链表由于各个元素之间是通过指针方式连接在一起,所以增加删除都非常方便,但是在随机访问却远不如数组。数组的下标是可以通过算法直接定位的,但链表却不行。
二、问题的方案
我们定义一种组织方式,以链表为基础,但是将相连的若干元素组成一个簇,和一个节点相关联。这个节点只负责管理链表元素的指针头部,和这个簇的所有元素的数量。当链表增加删除元素时,可以通过改变节点所记录的簇的头部和簇的数量来重新管理簇。如果这个节点没有簇元素,那么将这个节点删除,如果这个节点记录的簇元素数量超出最高限度,那么将进行分裂。当簇中元素作为链表的元素删除时,只需要减少节点记录簇元素的数量。
我们通过树来管理这些节点,将这些节点作为树的叶子。树的节点可以支持N个分支,对于每个分支,只记录分支下所有的簇元素数量。
我们这个方案的核心方案是通过树的方式来组织每个元素的下标,利用树的节点访问速度来拉近链表和数组之间的下标访问效率,同时保持链表增删元素的灵活性。我们打个比方,1个1K个节点的链表,如果要访问第500个节点,必须遍历500次,如果有个中间节点记录第500个节点的位置,那么我们只要遍历一次。
三、问题的分析
现在我们来计算添加树管理下标所需要的节点访问次数。包括树高度和叶子节点的数量,如果叶子节点只有一个,那这个树就退化成二叉树了,所以叶子节点包含的链表节点数是个很关键的问题。实际访问次数等于树的层高加上叶子的链表节点数。
一、链表元素结构
typedef struct _elem_st elem_t
struct _elem_st{
elem_t *prev ;
elem_t *next;
int len ;
char *buf ;
} ;
这是个双向链表,在增加删除时要方便多了。
二、索引结构
1、叶子结构
typedef struct _leaf_st leaf_t ;
struct _leaf_st{
elem_t *header ;
int count ;
} ;
2、节点结构
typedef struct _node_st node_t
struct _node_st{
leaf_t *leafs[1024] ;
int count ;
} ;
3、树结构
typedef struct _tree_st tree_t ;
struct _tree_st{
node_t *nodes[1024] ;
int count ;
} ;
上面定义的结构很笨,但是很容易说明思路。我们现在来模拟增加删除一个元素,看这个过程。
一、定位元素
elem_t *find_elem(int index) ;
1、顺序遍历tree->nodes数组,直到count
2、累计node->count,直到index在两个node之间
3、找到对应的leaf_t,很容易返回。
二、删除元素
1、判断下标小于数的总节点数
2、定位元素下标
3、从树的顶层开始,到该元素的每个路经的节点,计数器减1。
4、删除链表中该元素。
三、增加元素
2、定位元素下标
3、从树的顶层开始,到该元素的每个路经的节点,计数器加1。
4、在链表中添加该元素。
我们访问的方式比数组多很多,但是链表要少很多。
注:本文转自
http://blog.csdn.net/romandion/article/details/2421370
http://www.cppblog.com/unixfy/archive/2011/09/13/155696.html