delphi.数据结构.链表
链表作为一种基础的数据结构,用途甚广,估计大家都用过。
链表有几种,常用的是:单链表及双链表,还有N链表,本文着重单/双链表,至于N链表。。。不经常用,没法说出一二三来。
在D里面,可能会用Contnrs.pas.TStack/TQueue相关类,进行操作,不过里面的实现,并非使用的是链表实现,只是用TList,然后。。。实现的。
呵,TList怎么实现那些不是重点,本文着重是说一下自己使用链表的一些心得。
一:单链表:
单链表的用途,主要一个场景:队列(stack/queue),两者区别在于:queue: 先进先出(fifo), stack:后进先出(lifo)
在单链表操作中,我的建议是:只做:进队(push), 出队(pop) 操作,不做delete操作,原因就一个:
删除需要循环,如果需要删除操作,请使用双链表的实现。
我不建议使用删除操作,所以,就不贴出delete代码了。
(个人理解:在不同场景,选择更合适的数据结构来处理,我是理解是不用这操作,所以就不说明。)
下面给出一个基础的,简单的单链表操作:
type
PSingleEntry = ^TSingleEntry;
TSingleEntry = record
next: PSingleEntry;
data: Pointer;
end; //
// 定义单链表队列:
// : head: 第一个结点;
// : tail: 最后一个结点(为fifo准备)
PSingleLink = ^TSingleLink;
TSingleLink = record
head, tail: PSingleEntry;
end; // 初始化
procedure slink_init(var link: TSingleLink);
begin
link.head := nil;
link.tail := nil;
end; // 反初始化,或清空代码也类似,请自行写single_clear
procedure slink_uninit(var link: TSingleLink);
var
entry, next_entry: PSingleEntry;
begin
entry := link.head;
while entry <> nil do
begin
next_entry := entry.next;
FreeMem(entry);
entry := next_entry;
end;
link.head := nil;
link.tail := nil;
end; // 出队操作: result = false,表示无数据,否则出队成功,且data值正确
function slink_pop(link: PSingleLink; var data: Pointer): Boolean;
var
entry: PSingleEntry;
begin
result := link.head <> nil;
if result then
begin
entry := link.head;
data := entry.data;
link.head := entry.next;
FreeMem(entry);
end;
end; // fifo入队:
procedure slink_push_fifo(link: PSingleLink; data: Pointer);
var
entry: PSingleEntry;
begin
GetMem(entry, sizeof(entry^)); entry.next := nil;
entry.data := data;
if link.head <> nil then
link.tail.next := entry
else
link.head := entry;
link.tail := entry;
end; // lifo入队:
procedure slink_push_lifo(link: PSingleLink; data: Pointer);
var
entry: PSingleEntry;
begin
GetMem(entry, sizeof(entry^)); entry.data := data;
entry.next := link.head;
link.head := entry;
end;
上面,只是一个简单的示例。不过,上面的基本操作,基本不会有变动,欢迎各位提出更简化的版本。
一般说来,上面的示例已经足够使用了。
不过,有些场景,需要更多的减少GetMem/FreeMem的使用,所以,会将这种操作,集成在所需要的对象中,如上例中的data: Pointer,它可能是TObject,又或是某数据类型的指针。。。不可避免的是它需要GetMem+FreeMem,所以,有时单链表的处理,又会如下:
type
PMyDataEntry = ^TMyDataEntry;
PMyDataEntry = record
next: PSingleEntry; field1: Integer;
field2: string;
timestamp: Cardinal;
...
end;
使用PMyDataEntry, 即自己所需要的数据类型,也可以是TMyObject = class,形式不重要,重要的是上面的push&pop方法。
估计写完PMyDataEntry,用了后,又会发现,如果我有N多这类需要,MyDataEntry1, MyDataEntry2....
如果这种写法,那不得写N次,就不能弄个通用型的法子?
回答是:可以的。
在指针应用篇中,曾提到过偏移的作法,在push&pop中,根据data: Pointer(不管是pop&push),都进行指针偏移,然后得到PDataEntry类型指针,然后,再进行pop&push操作。
当时,这种法子在data.create时,必须得先申请sizeof(TDataEntry) + sizeof(data)长度,再偏移sizeof(TDataEntry),释放时反操作。
是不是有点麻烦?是的,麻烦到死,不过写一次,全部通用,还是值的花时间的。
不过,单链表的这种方式不写了,因为下面的双链表方式,得用这法子写,所以省略。
单链表大概如此,其它附加操作,如线程保护,自行解决吧。建议是直接在push&pop内部代码处理,而不是在外面(减少锁定的代码行操作)。
个人友情提示:单链表只用在队列,只有push&pop的操作的场景,而不是有delete或循环操作的场合。
二:双链表。
上面的单链表,没有删除delete操作,因为,是发现在实际使用过程中,如果带有循环操作,一般都会慢。
慢的原因当然是数量多,所以慢。也许,看者可能会说:我这边的场合,就不可能有大量数据的可能,写个循环多简单。不过我真心建议不使用,因为写通用型算法的时候,A场景不慢,也量少,不代表B场景,C场景,且测试期快,软件实际运行上线后,量的可能性不是刚开始开发时能考虑的。所以,在开发阶段,就尽量使用不会因为量大的情况下形成瓶颈的算法。这是一个习惯问题。要让我们的脑子,习惯于用更快,更优的的解决方法去解决问题的做法。
言归正传。还是队列,下面给出的是通用+集成型的双链表队列实现。
type
// 双链表每项定义,与单链表相比,多了prev指针
// 请注意:必须要用packet record,否则计算偏移会有误。
PDoubleEntry = ^TDoubleEntry;
TDoubleEntry = packed record
next, prev: PDoubleEntry;
data: array [..] of Byte;
end; //
// 定义链表:
// head: 第一个结点
// tail: 最后一个结点(为fifo准备)
//
PDoubleLink = ^TDoubleLink;
TDoubleLink = record
head, tail: PDoubleEntry;
end; const
// 双链表结点的偏移字节数
DLINK_OFFSET_SIZE = sizeof(TDoubleEntry) - ; // 初始化
procedure dlink_init(var link: TDoubleLink);
begin
link.head := nil;
link.tail := nil;
end; // 反初始化,或清空代码也类似,请自行编写dlink_clear
procedure dlink_uninit(var link: TDoubleLink);
var
entry, next_entry: PDoubleEntry;
begin
entry := link.head;
while entry <> nil do
begin
next_entry := entry.next;
FreeMem(entry);
entry := next_entry;
end;
link.head := nil;
link.tail := nil;
end; function dlink_entry_alloc(size: Integer): Pointer;
var
entry: PDoubleEntry;
begin
entry := AllocMem(DLINK_OFFSET_SIZE + size);
result := @entry.data[];
end; procedure dlink_entry_free(data: Pointer);
begin
FreeMem(PAnsiChar(data) - DLINK_OFFSET_SIZE);
end; // 出队操作: result = false,表示无数据,否则出队成功,且data值正确
function dlink_pop(link: PDoubleLink; var data: Pointer): Boolean;
var
entry: PDoubleEntry;
begin
result := link.head <> nil;
if result then
begin
entry := link.head;
data := @entry.data[];
link.head := entry.next;
end;
end; // fifo入队
procedure dlink_push_fifo(link: PDoubleLink; data: Pointer);
var
entry: PDoubleEntry;
begin
entry := Pointer(PAnsiChar(data) - DLINK_OFFSET_SIZE);
entry.next := nil;
if link.head <> nil then
begin
link.tail.next := entry;
entry.prev := link.tail;
end else
begin
link.head := entry;
entry.prev := nil;
end;
link.tail := entry;
end; // lifo入队:
procedure dlink_push_lifo(link: PDoubleLink; data: Pointer);
var
entry: PDoubleEntry;
begin
entry := Pointer(PAnsiChar(data) - DLINK_OFFSET_SIZE);
entry.next := link.head;
entry.prev := nil;
if link.head <> nil then
link.head.prev := entry;
link.head := entry;
end; //
// 双链表.delete结点操作
// 标准几步操作,然后没了。
//
procedure dlink_delete(link: PDoubleLink; data: Pointer);
var
entry: PDoubleEntry;
begin
entry := Pointer(PAnsiChar(data) - DLINK_OFFSET_SIZE);
if entry.prev <> nil then
begin
entry.prev.next := entry.next;
if entry.next <> nil then
entry.next.prev := entry.prev;
end else
begin
link.head := entry.next;
if entry.next <> nil then
entry.next.prev := nil;
end;
FreeMem(entry);
end; type
// 这是调用: 自定义数据类型,以上双链表,可以自定义数据类型,如下:
PMyTestRec = ^TMyTestRec;
TMyTestRec = record
v: Integer;
s: string;
end; procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
data, temp: PMyTestRec;
link: TDoubleLink;
pop_count, push_count, error_count: Integer;
begin
dlink_init(link); // 测试1
pop_count := ;
push_count := ;
error_count := ; // 入队1
for i := to do
begin
data := dlink_entry_alloc(sizeof(TMyTestRec));
data.v := i;
data.s := IntToStr(i);
dlink_push_fifo(@link, data);
inc(push_count);
end; // 出队1
while dlink_pop(@link, Pointer(data)) do
begin
inc(pop_count);
if data.v <> StrToIntDef(data.s, -) then
inc(error_count);
dlink_entry_free(data);
end;
ShowMessageFmt('test1: push: %d, pop: %d, error: %d', [push_count, pop_count, error_count]); // 测试2
pop_count := ;
push_count := ;
error_count := ;
temp := nil; // 入队2
for i := to do
begin
data := dlink_entry_alloc(sizeof(TMyTestRec)); // 从中间找个entry赋于temp,用于测试dlink_delete
if i = then
temp := data; data.v := i;
data.s := IntToStr(i); dlink_push_lifo(@link, data);
inc(push_count);
end; // 测试:删除中间的结点。
dlink_delete(@link, temp); // 出队2
while dlink_pop(@link, Pointer(data)) do
begin
inc(pop_count);
if data.v <> StrToIntDef(data.s, -) then
inc(error_count);
dlink_entry_free(data);
end;
ShowMessageFmt('test2: push: %d, pop: %d, error: %d', [push_count, pop_count, error_count]); dlink_uninit(link);
end;
请看测试代码Button1Click,里面中的测试1是fifo,然后出队,测试2是lifo,将中间的某结点记录,进行删除中间某结点。
上述双链表队列,适合push&pop&delete操作,可独立运作,也可与其它数据结构一块进行,比如hash什么的。
与单链表不同的是,多了一个dlink_delete函数,因为双链表,所以删除就几行,不进行循环。
与单链表实现不同的是:它在通用的基础上,将结点的分配与释放集成在一块,而单链表那个实现,只是一个通用的情况,结点的分配与释放得另外处理,这点要注意,当然,你可以自己去写集成在一块的写法,这里只是一个举例。
双链表使用场合甚广,写成这种集成模式,不好阅读不说,且不知如何调用。
因为本人用的比较多这种模式,比如:
1:hash中,根据key找到一个entry,然后删除就调用dlink_delete,操作多快
2:更多的情况下,上面的示例,只是一个示例,我会更多的扩展它里的头信息,而不只是next,prev几个字段,
增删改查时,进行相应处理。dlink_delete只是一个示例。:)
目地,还是想给大家一个抛砖的作用。
没了。
水平有限,如有雷同,就是盗链,:D
2014.10.25 by qsl
delphi.数据结构.链表的更多相关文章
- Python—数据结构——链表
数据结构——链表 一.简介 链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构.由一系列节点组成的元素集合.每个节点包含两部分,数据域item和指向下一个节 ...
- (js描述的)数据结构[链表](4)
(js描述的)数据结构 [链表](4) 一.基本结构 二.想比于数组,链表的一些优点 1.内存空间不是必须连续的,可以充分利用计算机的内存,事项灵活的内存动态管理. 2.链表不必再创建时就确定大小,并 ...
- 数据结构和算法(Golang实现)(12)常见数据结构-链表
链表 讲数据结构就离不开讲链表.因为数据结构是用来组织数据的,如何将一个数据关联到另外一个数据呢?链表可以将数据和数据之间关联起来,从一个数据指向另外一个数据. 一.链表 定义: 链表由一个个数据节点 ...
- Redis数据结构—链表与字典的结构
目录 Redis数据结构-链表与字典的结构 链表 Redis链表节点的结构 Redis链表的表示 Redis链表用在哪 字典 Redis字典结构总览 Redis字典结构分解 Redis字典的使用 Re ...
- Redis数据结构—链表与字典
目录 Redis数据结构-链表与字典 链表 Redis链表节点的结构 Redis链表的表示 Redis链表用在哪 字典 Redis字典结构总览 Redis字典结构分解 哈希算法 解决键冲突 rehas ...
- [数据结构]——链表(list)、队列(queue)和栈(stack)
在前面几篇博文中曾经提到链表(list).队列(queue)和(stack),为了更加系统化,这里统一介绍着三种数据结构及相应实现. 1)链表 首先回想一下基本的数据类型,当需要存储多个相同类型的数据 ...
- JavaScript数据结构——链表
链表:存储有序的元素集合,但不同于数组,链表中的元素在内存中不是连续放置的.每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成. 好处:可以添加或移除任意项,它会按需扩容 ...
- JavaScript数据结构——链表的实现
前面楼主分别讨论了数据结构栈与队列的实现,当时所用的数据结构都是用的数组来进行实现,但是数组有的时候并不是最佳的数据结构,比如在数组中新增删除元素的时候需要将其他元素进行移动,而在javascript ...
- js 实现数据结构 -- 链表
原文: 在 Javascript 中学习数据结构与算法. 概念: 链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的.每个 元素由一个存储元素本身的节点和一个指向下一个元素的引 ...
随机推荐
- IOS开发网络数据---- AFNetworking的使用
http网络库是集XML解析,Json解析,网络图片下载,plist解析,数据流请求操作,上传,下载,缓存等网络众多功能于一身的强大的类库.最新版本支持session,xctool单元测试.网络获取数 ...
- printf函数
printf函数的格式及含义 d 以十进制带符号的形式输出整数(对正数不输出符号) o 以八进制无符号的形式输出整数(不输出 ...
- 如何在Mac OS系统下配置Java服务器开发环境
1.http://www.oracle.com/technetwork/Java/javase/downloads/index-jsp-138363.html 安装JDK(可通过java -versi ...
- cocos2d-x 帧循环不严谨造成场景切换卡顿
最近在用cocos2d-x做引导界面,2dx版本是2.2.3,场景切换加上了效果,所有资源都已经使用texturepacker打包预加载,但是在实际运行调试中,场景切换相当卡顿. 各种纠结后,无意中将 ...
- Base64加密与MD5的区别?
MD5是一种不可逆的摘要算法.而Base64是一种编码方式,主要用于将二进制数据转换为文本数据,方便使用HTTP协议等,是可逆的.无论多少二进制数据,在MD5算法一定的情况下,都会变成一个定长的数据, ...
- C语言字符串处理函数
函数名: strcpy 功 能: 拷贝一个字符串到另一个 用 法: char *stpcpy(char *destin, char *source); 程序例: #include < ...
- JSBinding / FAQ & Trouble Shooting
Q: Why javascript file extension is .javascript?A: Because Unity treats .js files as Unity script an ...
- c# List去重
1 list如果数据是值类型,比如list<int> 这种,添加linq之后就可以使用list = list.Distinct().ToList(); 2 如果是数据是引用类型,比如中间是 ...
- Perl/Nagios – Can’t locate utils.pm in @INC
While trying to use a Nagios plugin I got an error saying that “Can’t locate utils.pm in @INC”. Foll ...
- nginx 反向代理TCP mysql
stream {upstream mysql { hash $remote_addr consistent; server 10.26.112.12:3306 max_fails=3 fail_tim ...