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 中学习数据结构与算法. 概念: 链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的.每个 元素由一个存储元素本身的节点和一个指向下一个元素的引 ...
随机推荐
- 手把手教你配置UltraEdit对Oracle的PLSQL着色
http://hi.baidu.com/kingbridge/blog/item/94e225ad5fad4b194b36d60d.html UltraEdit-32 12.1版本配置默认文件显示 ...
- linux 64位调用
linux系统中64位汇编和32位汇编的系统调用主要有以下不同:(1)系统调用号不同.比如x86中sys_write是4,sys_exit是1:而x86_64中sys_write是1, sys_exi ...
- unity对话代码
这个是根据网上unity GUI打字机教程修改的 原教程是JS,我给改成了C#,然后增加了许多功能 这个教程能实现一段文字对话,有打字机显示效果,能写许多对话,能快进对话,总之现在RPG游戏里有的功能 ...
- Beta版本发布说明
发布地址 https://github.com/LongWerLingShi/DataObtainingAndHandling/tree/beta 版本开发背景 首先,应软件工程课程要求,我们小组针对 ...
- 一些对新手有用的css技巧
一.表单部分 1.禁止textarea文本域的缩放 resize:none; 2.去除初始化textarea下拉条 overflow:auto; 3.如何让表单中的选项按钮,点击文字也能选中? < ...
- android之xmlpullparse解析器
Pull解析和Sax解析很相似,都是轻量级的解析,在Android的内核中已经嵌入了Pull,所以我们不需要再添加第三方jar包来支持Pull.Pull解析和Sax解析不一样的地方有(1)pull读取 ...
- 使用OpenLDAP构建基础账号系统
LDAP - Lightweight Directory Access Protocol,对该协议的具体应用,常见的是微软的Active Directory服务和Linux上的OpenLDAP组件. ...
- python 调用浏览器方法
每天都要登陆某网站,刷积分.为了节省时间,用了下python中的webbrowser模块.新建.py 文件 #!/usr/bin/python import webbrowser webbrowser ...
- hadoop streaming 多路输出 [转载]
转载 http://www.cnblogs.com/shapherd/archive/2012/12/21/2827860.html hadoop 支持reduce多路输出的功能,一个reduce可以 ...
- linux学习笔记--vi与vim编辑器
vi编辑器全名为Visual Interface,即为可视化接口,类似于Windows中的记事本 vim相当于是vi的一个升级版本,包含vi的一切操作命令,vim相对于vi做了哪些提升: 1.vim支 ...