另类的链表数据结构以及算法
2009-08-13 19:51
190 查看
一般情况下,我们使用链表无非就是在链表结点中保存该链表中下一个元素的指针.如果为了删除方便,可能需要保存前一个元素的指针,也就是双向链表,这样在删除一个结点的时候就可以很快的定位到前面和后面的结点,并且改变它们相应的指向.在这些操作里面,指向链表元素的指针无疑是最关键的数据.
考虑这样一个问题,如果两个进程进行通信,A进程负责管理链表,B进程向A进程发出分配或者删除链表元素的请求.这种情况下,像上面所描述的那样A进程直接向B进程返回链表元素的指针是不能做到的了,很自然的,可以想到返回另一个key标示该链表元素.但是,当需要查找或者删除该链表元素的时候,就不能像上面那样马上定位到链表元素的位置了,需要遍历整个链表.原来常量级时间复杂度的算法,在使用情形变换了之后变成了O(n)级别的复杂度.
可以找到别的办法来解决这个问题.第一可以返回一个key标示该链表元素,第二保证了时间的复杂度,在这里需要定义一种新的数据结构和算法来解决这个问题.
首先,我们使用一个数组,数组中的元素是指向链表元素的指针,而指针的索引则是每个链表元素的id,这样,通过id就可以马上定位到对应的链表元素.
但是这里又会出现另一个问题,该id是从零开始的,假如在一段时间之后,需要分配一个新的链表元素,如何知道数组中的哪个位置是可以分配的?在这里,使用了一个整型数据的数组,数组中的每个元素是该id对应的链表元素在链表中下一个结点的id(有点拗口).我们使用两个链表头,一个将已经使用的链表元素id连接起来,另一个则将未使用的链表元素id连接起来.于是,在程序初始化的时候,未使用的链表中保存了所有的id,而已经使用的链表为空.每次分配了一个新的链表元素,将它的id放在使用链表的最开始;而每次释放一个链表元素,将它的id放到未使用的链表头部.
同时,改变原先链表元素的定义,在该结构体中,保存的不再是指针,而是链表中前一个元素的数组索引id.而它的下一个元素id则保存在上面的那个数组中.
如果上面我的解释还不够明白,可以看看下面的代码:
<!--
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->
#include <stdio.h>
#define LIST_NODE_NULL -1
#define ARRAY_SIZE 200
/* 链表元素定义 */
typedef struct list_node
{
int prev; /* 下一个链表元素在list_array中的id */
}list_node;
/* 存放链表元素指针的数组 */
list_node* list_array[ARRAY_SIZE];
/* 未使用链表的头结点id */
int top_of_free;
/* 已使用链表的头结点id */
int top_of_used;
/* 保存链表下一个元素结点id的链表 */
int next_list[ARRAY_SIZE];
void init_list()
{
int i;
for (i = 0; i < ARRAY_SIZE; ++i)
{
list_array[i] = NULL;
/* 初始时,next_list中每个结点的值都是下一个id */
next_list[i] = i + 1;
}
/* 最后一个结点是空 */
next_list[i - 1] = LIST_NODE_NULL;
top_of_free = 0;
top_of_used = LIST_NODE_NULL;
}
int alloc_list_node()
{
int id;
/* 从未使用链表头部取出一个id */
id = top_of_free;
if (LIST_NODE_NULL == id)
{
return LIST_NODE_NULL;
}
/* 未使用链表头结点往下走一步 */
top_of_free = next_list[top_of_free];
if (NULL == list_array[id])
{
list_array[id] = (list_node*)malloc(sizeof(list_node));
if (NULL == list_array[id])
{
return LIST_NODE_NULL;
}
}
if (LIST_NODE_NULL == top_of_used)
{
next_list[id] = top_of_used;
top_of_used = id;
}
else
{
/* 修改prev和next */
list_array[top_of_used]->prev = id;
list_array[id]->prev = LIST_NODE_NULL;
next_list[id] = top_of_used;
top_of_used = id;
}
return id;
}
void free_list_node(int id)
{
int prev, next;
prev = list_array[id]->prev;
next = next_list[id];
/* 修改next和prev */
if (LIST_NODE_NULL != prev)
{
next_list[prev] = next_list[id];
}
if (LIST_NODE_NULL != next && NULL != list_array[next])
{
list_array[next]->prev = prev;
}
if (id == top_of_used)
{
top_of_used = next_list[id];
}
/* 将链表id返回到free链表头部并且修改free链表头结点 */
next_list[id] = top_of_free;
top_of_free = id;
}
int main()
{
int id;
init_list();
id = alloc_list_node();
free_list_node(id);
return 0;
}
这个想法很巧妙,有效的避免了查找和删除数组元素带来的开销.我不知道具体的出处在哪里,如果您知道,劳烦告诉我一声:)
考虑这样一个问题,如果两个进程进行通信,A进程负责管理链表,B进程向A进程发出分配或者删除链表元素的请求.这种情况下,像上面所描述的那样A进程直接向B进程返回链表元素的指针是不能做到的了,很自然的,可以想到返回另一个key标示该链表元素.但是,当需要查找或者删除该链表元素的时候,就不能像上面那样马上定位到链表元素的位置了,需要遍历整个链表.原来常量级时间复杂度的算法,在使用情形变换了之后变成了O(n)级别的复杂度.
可以找到别的办法来解决这个问题.第一可以返回一个key标示该链表元素,第二保证了时间的复杂度,在这里需要定义一种新的数据结构和算法来解决这个问题.
首先,我们使用一个数组,数组中的元素是指向链表元素的指针,而指针的索引则是每个链表元素的id,这样,通过id就可以马上定位到对应的链表元素.
但是这里又会出现另一个问题,该id是从零开始的,假如在一段时间之后,需要分配一个新的链表元素,如何知道数组中的哪个位置是可以分配的?在这里,使用了一个整型数据的数组,数组中的每个元素是该id对应的链表元素在链表中下一个结点的id(有点拗口).我们使用两个链表头,一个将已经使用的链表元素id连接起来,另一个则将未使用的链表元素id连接起来.于是,在程序初始化的时候,未使用的链表中保存了所有的id,而已经使用的链表为空.每次分配了一个新的链表元素,将它的id放在使用链表的最开始;而每次释放一个链表元素,将它的id放到未使用的链表头部.
同时,改变原先链表元素的定义,在该结构体中,保存的不再是指针,而是链表中前一个元素的数组索引id.而它的下一个元素id则保存在上面的那个数组中.
如果上面我的解释还不够明白,可以看看下面的代码:
<!--
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->
#include <stdio.h>
#define LIST_NODE_NULL -1
#define ARRAY_SIZE 200
/* 链表元素定义 */
typedef struct list_node
{
int prev; /* 下一个链表元素在list_array中的id */
}list_node;
/* 存放链表元素指针的数组 */
list_node* list_array[ARRAY_SIZE];
/* 未使用链表的头结点id */
int top_of_free;
/* 已使用链表的头结点id */
int top_of_used;
/* 保存链表下一个元素结点id的链表 */
int next_list[ARRAY_SIZE];
void init_list()
{
int i;
for (i = 0; i < ARRAY_SIZE; ++i)
{
list_array[i] = NULL;
/* 初始时,next_list中每个结点的值都是下一个id */
next_list[i] = i + 1;
}
/* 最后一个结点是空 */
next_list[i - 1] = LIST_NODE_NULL;
top_of_free = 0;
top_of_used = LIST_NODE_NULL;
}
int alloc_list_node()
{
int id;
/* 从未使用链表头部取出一个id */
id = top_of_free;
if (LIST_NODE_NULL == id)
{
return LIST_NODE_NULL;
}
/* 未使用链表头结点往下走一步 */
top_of_free = next_list[top_of_free];
if (NULL == list_array[id])
{
list_array[id] = (list_node*)malloc(sizeof(list_node));
if (NULL == list_array[id])
{
return LIST_NODE_NULL;
}
}
if (LIST_NODE_NULL == top_of_used)
{
next_list[id] = top_of_used;
top_of_used = id;
}
else
{
/* 修改prev和next */
list_array[top_of_used]->prev = id;
list_array[id]->prev = LIST_NODE_NULL;
next_list[id] = top_of_used;
top_of_used = id;
}
return id;
}
void free_list_node(int id)
{
int prev, next;
prev = list_array[id]->prev;
next = next_list[id];
/* 修改next和prev */
if (LIST_NODE_NULL != prev)
{
next_list[prev] = next_list[id];
}
if (LIST_NODE_NULL != next && NULL != list_array[next])
{
list_array[next]->prev = prev;
}
if (id == top_of_used)
{
top_of_used = next_list[id];
}
/* 将链表id返回到free链表头部并且修改free链表头结点 */
next_list[id] = top_of_free;
top_of_free = id;
}
int main()
{
int id;
init_list();
id = alloc_list_node();
free_list_node(id);
return 0;
}
这个想法很巧妙,有效的避免了查找和删除数组元素带来的开销.我不知道具体的出处在哪里,如果您知道,劳烦告诉我一声:)
相关文章推荐
- 数据结构和算法设计专题之---单链表中在指定的节点前面插入以及删除一个节点
- 数据结构 求链表的长度以及用冒泡排序的算法对链表中的值进行排序
- 数据结构和算法设计专题之---单链表中在指定的节点前面插入以及删除一个节点
- 数据结构和算法设计专题之---单链表中在指定的节点前面插入以及删除一个节点
- 数据结构和算法设计专题之---单链表中在指定的节点前面插入以及删除一个节点
- 【郝斌数据结构自学笔记】26_通过链表排序算法的演示再次详细讨论到底什么是算法以及到底什么是泛型【重点】
- 数据结构和算法设计专题之---单链表中在指定的节点前面插入以及删除一个节点
- 数据结构与算法:单链表(利用万能指针实现对任意类型数据进行操作)
- 【算法之链表(三)】单链表中,在仅允许使用一个指针的情况下,在指定的节点前面插入以及删除一个节点
- 左程云_算法与数据结构 — 链表问题 — Node、DoubleNode、RandomNode类
- 算法与数据结构面试题(7)-链表“香蕉”问题
- 数据结构和算法-009 双向链表
- 数据结构与算法之六 双向链表和循环链表
- 【郝斌数据结构自学笔记】27-29_链表插入和删除算法的演示_复习
- 经典算法与数据结构的c++实现——带头结点的单链表
- (二叉树)谈一谈各类算法和数据结构的c++实现以及相关操作的复杂度(二)
- 算法与数据结构面试题(13)-求链表倒数第K个节点
- 数据结构入门学习系列-5(链表的基本操作算法)
- 常用的数据结构以及算法
- 数据结构-单向链表相关算法