一直以来都有童鞋说链表难以理解,我也一直想写篇博文统一讲清楚一下,今天稍得空闲,下笔五千言(感觉超过了《道德经》的字数了),算是基本写完了,说是故事但又没有结局,其实只是闲话而已。有哪里说得不对的大家指正。

一、闲话链表的由来

1、链表的特点

链表,无论在哪种语言中,它必须满足两个特点,一、可游标(索引)访问(如无游标则必须存储在连续的内存中,否则无法找到一个有效的游标)。二,必须可动态增减(也可复杂到增删改查)。

2、链表的本质为动态数组

其实链表的本质就是动态数组。但遗憾的是我们使用的普通数组不可动态增加,而且数组中各个元素都是存储在连续的内存中。

(1)、数组的为何要声明为固定大小?

严格的说,不仅仅是数组要声明为固定的大小,任何类型的变量都要声明为固定的大小,否则会导致先来的变量不宣布你的“地盘”有多大,那后来的变量的“地盘”就可能会与先来变量的地盘交错甚至完全重叠。
数组就是一组同类型的变量集体,与其他类型变量没有任何差别,只不过声明数组时是集体声明-“团购”而已!为了便于分配,系统则为这个团购者则分配了一块连续的地皮,比如一个int类型数组int[8],系统一看8个元素,我要是分散这给你们找地皮那多麻烦啊,那就将你们分配到一起吧,每个int需要4个字节,那就分32个字节的地皮给你们好了。

所以说,给数组分配固定连续的地皮有两个好处,第一,好分配,第二,好访问。

(2)、为何访问数组的下标要比实际位置小1?

前面我们说明白了数组的声明必须为固定大小,而且是连续的。连续的内存中储存的同类型元素,也就是他们各个元素的地盘一样大,那么每个相邻的元素距离相等,欢聚话说,某个元素到上家和下家距离都一样,从下家到下下家距离都一样,我们估计称之相邻元素之间的距离为偏移距离,也就是说数组内的元素住在内存中,他们的偏移距离相等,而且偏移距离为一个元素的大小,比如int数组中的偏移距离就是4,char数组中的便宜距离就是1。
这样一来我们就可以用偏移距离来访问各个元素了,比如我们声明了一个int数组:

int arr[8]={4,5,6,7,8,9,10,11};

系统默认第一个元素为地址的起点,需要访问距离起点偏移距离多远的那个个元素就直接在数组名后加偏移量即可,如我们访问距离起点偏移量为5的元素,也就是第6个元素了:

cout<<arr[5];

那自然就会输出第六个元素9,因为我们是按照偏移量来进行访问的,这也就回到了本小节提出的问题了。之所以访问的元素下标总是比元素实际位置数小1,就是因为我们是按照偏移巨量量来访问的,而不是位置数!
这个问题,教材中和老师上课时是没有讲清楚的,这里算是给大家小小解惑吧。

这里需要强调的是,每种不同类型的数组其元素地址偏移量是不同的,int的是4,char的是1,前面说过了,如果你声明了一个你自己定义的类,搞不好是520也不一定哦。

不要问我系统怎么知道每个类型偏移多少?我说,每个类型都是系统生出来的,系统能不知道吗?

(3)、关于越界访问

其实,理解了数组的访问原理,那么访问越界就很容易理解,上一小节中将的偏移量,你偏移量大,走远了,走出了数组的“家族”地盘,到了别人的地盘(也许那里住着别的变量,也许是个无人看管的地盘),访问的是别人的“隐私”,这是不准许的,所以系统告诉你-“你过分了哦!!!!”,跑到别人的地盘去了还企图窥探他人隐私,万一是个“姑娘”呢?尴尬了吧!

但c++的指针就相当于一把万能钥匙,谁的地盘谁的家都可以进入,最要命的是任何时候都可以进入,就算别人在“洗澡”!
(指针是可以按照指定的偏移量来偏移的,所以数组也可以用指针来操作就是这个道理)
指针是有特权的,不存在越界,也许他是“城管”,也有他是所有房间的“房东”,也许他就是c++世界里面的神。

3、动态数组如何实现?

前面说了,固定大小的数组存储在连续的内存中,当根据索引访问内存中的元素只要按照元素的大小,偏移固定的内存地址既可以访问到指定的元素。

(1)、如果给数组事后增加一个元素会怎样?

如果数组的大小不固定也就意味着所需内存大小不固定,我们都知道,内存就如同我们的仓库,在存储东西的时候每空间都是要按从里到外的顺序来使用的,如果你不声明需要使用的大小,谁也不知道你要占用多少,所以都必须实现声明大小,如果事后要修改大小,那只能让你挪到其他“地皮”上去。
但挪到其他地皮上去了,就不能依靠数组的下标来访问了,因为内存地址偏移距离量不是固定的了。
比如,有个int数组,原来存储在0~32的位置,现在要增加一个元素,这个元素存储到了100的位置,那么这个元素就无法用下标访问了,而且也没有一种办法来记录这个遥远的“亲戚”。因为数组原来的元素本就群居在一起的,只要找到其中一个就可以找到其他,现在来了个远方“亲戚”,那可是无人记得了。

(2)、建立一个元素住址的通讯录

继续说那个新增的远房亲戚怎么办?没有人记得,那就成了孤魂野鬼了,有什么办法吗?其实也不是无解,也还是可以解决的。
办法就是:为各个元素建立一个住址通讯录,记下他们的地址,下次访问,我们也不按照什么偏移量了,就按照地址找人!

有了地址害怕找不到人吗?不是说跑得了和尚跑不了庙吗?
说起来容易做起来难啊!这个地址簿放在哪里呢?放到哪个元素家里呢?第一个还是最后一个,或者是中间那个?
可以肯定的是,放到那家都是负担,俗话说“少做少错,多做多错”,事情小责任大啊,万一丢了,这不那些散居的亲戚不就再也找不到了吗?

先别说没有人愿意揽下这个差事,就算有人愿意当这个保管员,这遇到保管员有“婚丧嫁娶生老病死”还得能顺利交接,别要用的时候找不到人啊!还有,这保管通讯录的“保管员”万一有个“意外”-先挂了,这也是个大问题啊。
(既然是动态数组,那么就会随时有元素被删除,有元素加入)

哎,怕了,怕了!好了,咱一不做二不休,人手一本,各写各的各保管各的好吧!所以,动态数组的元素就必须至少是一对数据,一个是数据本身,另一个就是这个“地址簿”了,这个地址簿就是由指针来充当的。

懂得了上面的道理,其实我们自己做一个动态数组出来是很容易实现的。

(3)、链表的登场

前面,我们把动态数组的问题解决了,但并不能就只止步。所谓欲豁难填,欲望是无穷的,我们又想到,既然都有了地址簿,不如让这个“集体”更加有组织性吧,将来说不定能干出一份惊天地泣鬼神的“大事业”来。

比如,让动态数组能够动态的增减,甚至修改,查询等等。

随着欲望的膨胀,一个问题凸显出来了,那就是集体越来越大,人数多了,队伍越难越难带了,地址簿的更新太费时间和精力了,每次最麻烦的就是更新地址簿,同步各个元素手中的地址簿,随着元素个数的增加,地址簿的更新任务量几何级增长。
最终考虑到既要保留地址簿,又要避免地址簿更新的任务量大的问题,采取了“各扫门前雪”的策略,最终决定让每个元素都精简地址簿,每个元素只需要记录相邻的元素的地址,用指针去指向邻居的地址(指向下一个或者上一个元素的指针)。

这时,我们说的C++链表呼之欲出,一开始也不知道叫什么名字,反正动态数组将各个元素通过地址簿(指针)“链接”到了一起,这个“链”字是逃不掉了,至于链什么呢?那就要看元素里面都装着些什么,如果我们元素里面的数据是一个有多个属性特征的类(相当于一条记录),那么很多元素在一起就相当于一张表,所以,“链表”这个名字当再好不过啦!

只有一个指向下一个邻居指针的叫做单向链表,有指向上一个邻居和下一个邻居指针的叫做双向链表,种种不一而足。

4、链表的要素

前面我们的链表已然登场,我们该把他设计为何等模样呢?如何与代码对应呢?无非两样东西,一、元素,二、数据链

(1)、链表的元素

链表元素,英文名称喜欢写为Node,也叫作节点。这个其实也是很形象的,一个节点两头是绳子,分别连接着另外两个元素节点,只是这个链接的绳子不是别的而是地址簿。
节点元素,其实就是前面我们说的动态数组里面的元素,我们前面不是说过吗,链表的本质是动态数组,一个带着地址簿的数组。

那么节点元素的特征必定是有一个记录元素数据的变量,还必须有个记录相邻元素的指针变量,这么一来,链表元素就可以被设计为一个结构体或者是一个类。
一般的代码表现为:

struct node
{object name;node* next;  //
};
class node
{object name;node* next;
};

当然,你可以让他有更多的属性特征,如:

struct node
{string name;int score1;int score2;int score3;int score4;node* next;
};

(2)、链表的数据链

既然是链表,那就是个数据组织,那就有排头兵,也就是firstNode,然后就游标p,一开始我们要让指向标指向头节点(p=head),next就是地址簿了,然后就是新的节点q,next=q.

 node* head = new node; // new了一个头节点指针,便于被别的元素来记录地址node* p= new node; // 同样的方式,创建指向标P,你也可以成为游标Pp=head; // p指向head,表示当前p->next=NULL; //将游标p的地址簿next指向下一个节点,一开始指针next指向空.防止指向了不该指向的地方,犯错误就不好了

node* head 头节点以指针方式声明便于被别的元素来记录地址,同样的方式,创建指向标P,你也可以成为游标p,然后让 p指向head,表示当前节点 p->next=NULL,这里将游标p的地址簿next指向下一个节点,一开始指针next指向空.防止指向了不该指向的地方,犯错误就不好了。

这样的链表是死链表,写几个元素就接元素,其实我们可以让链表用于更多功能,为其增加增删改查的功能。

(3)、为何要有这个游标元素p?

很多初学链表的童鞋都或许有过这样的疑问,为何要整个游标p(pointer指向标的缩写,也许或者说不定是picker的缩写,不用好奇为何每个人都这么写),每个元素生产出来直接让先前的元素的next指向它不就可以了吗?

其实,如果没有p这个元素,就会面临节点元素的next指针无从指向的问题,这也是头节点为什么指向了一个Null指针的原因,我们来看看具体情形,某个节点比如位于第n个位置,那么n+1个节点产生之前节点n的next是无从指向的,只能被设置为null,等待下一个节点n+1产生后才有地址让n节点的next指,这时候问题来了,我们必须从头结点一个个以接力的方式指向下一个,一直到指向n节点我们才有机会操作这个n节点(一般节点创建完了,我们没有为其取特定的专用的变量名,我们想回头去操作它已经是不可能了,这叫做“过了这个村就没有这个店了”),所以,我们用一个有名称的变量先暂存着它,便于我们在下一个新节点产生时能够操作它的next指针,令其指向新的元素。

所以,为什么要有这个游标元素,那就是因为要暂存上一个节点元素的“操作权”,不知道我说清楚了没有?

搞懂了这个了,就不再会对p为什么会有喜新厌旧特点有任何疑问了。

二、链表的设计和使用

其实链表的实际和使用本不是我这里要重点讲的,因为讲这个的文章多了去,而且使用起来特别简单,教程特别多。但为了文章的完整性,我这里还是拿出来简单说说。

1、C#中的链表

链表对于学习过C++的童鞋一点都不陌生,而且C#中实际已经实现了单向的链表,也就是实现了接口IList的类,基本都可以认为是单向链表了。
C#中没有指针,那怎么办呢?其实C#中已经有完整的线程的链表LinkedList,它的完美搭档LinkedListNode,也是可以很好的配合完成链表的几乎任何任务。

 LinkedList<string> list = new LinkedList<string>();

也许,这些都满足不了你,那你就来亲自实现一个自己的链表。

2、C++中的链表

最典型的链表就是c++中的链表了,我们来看看一个典型的C++中的链表的代码,思路是先创建头节点 并设为当前节点p,然后创建邻居节点,并让当前节点的next指向新建的邻居节点,最后把新创建的节点设置为当前节点p, 所以我们可以发现,这个p就是一个游标,始终喜新厌旧,看代码:

struct node
{string name;node* next;
};
int main()
{node* head = new node; node* p= new node; p=head; p->next=NULL; while (true) {cout<<"Please enter  name: ";cin>>p->name; node*q =new node; p->next=q; q->next=NULL; p=q; //喜新厌旧的p有了新欢q,进入下一个轮回}return head;
}

这里了返回头结点head就好比数组告诉你了首地址,这样默认你访问起来就可以以第一个元素作为开始。
这里我们只是贴出了最简代码,如何生成一个链表,其余的功能没有去实现,在讲游戏制作的时候会带大家一起实现,有兴趣的可以继续关注。
链表在做游戏的时候非常常用,有喜欢游戏编程的需要好好理解。

闲话链表的诞生以及它与数组亲缘关系的故事相关推荐

  1. 程序员编程艺术:第九章、闲话链表追赶问题

    程序员编程艺术:第九章.闲话链表追赶问题 作者:July.狂想曲创作组. 出处:http://blog.csdn.net/v_JULY_v . 前奏     有这样一个问题:在一条左右水平放置的直线轨 ...

  2. 图解数据结构:数组背后的故事

    今天,我们一起来了解数据类型--数组背后的故事吧~~ ps,想学python或者java的小伙伴,不要错过后面有彩蛋哟. 数组(array)概念: 是数据的一种组织形式.是有限个相同类型变量所组成的有 ...

  3. 《剑指offer》--二维数组中的查找、从头到尾打印链表、重建二叉树、旋转数组的最小数字

    一.二维数值中的查找: 1.题目: 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数 ...

  4. Java 数据结构(链表LinkedList增删改查、数组Vector、获取Vector最大值、交换Vector两成员位置、栈的实现、压栈出栈实现反转、队列Queue)

    在链表(LinkedList)的开头和结尾添加元素 import java.util.LinkedList;public class Main {public static void main(Str ...

  5. 【终极完美高效】C语言实用算法系列之学生管理系统_单向链表外排序_堆内数组存储链表节点指针_函数指针数组

    代码 #define _CRT_SECURE_NO_WARNINGS#include <stdio.h> #include <string.h> #include <st ...

  6. 数据结构算法动图识记_【数据结构与算法】用动图解说数组、链表、跳表原理与实现...

    「初」前言 在学习数据结构与算法的过程中,感觉真的是一入算法深似海,但是越学越觉得有趣.不过我们会发现在终身学习的过程中,我们都是越学越多,不知的也越来越多,但是更渴望认知更多的知识,越是对知识感兴趣 ...

  7. 数据结构各结构特点(数组、链表、栈、队列、树)

    目录 一.数组 二.链表 三.栈 四.队列 五.树 1.二叉树 2.二叉查找树 3.平衡二叉树(AVL树) 4.红黑树 六.总结: 1.红黑树和平衡二叉树的区别: 2.为什么有了数组和链表还要引入二叉 ...

  8. 数组、链表、Hash(转)

    在程序中,存放指定的数据最常用的数据结构有两种:数组和链表. 数组和链表的区别: 1.数组是将元素在内存中连续存放. 链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起. 2.数组 ...

  9. 数据结构之数组、链表、栈和队列

    1.数组 1.1:概念 数组是一种线性表数据结构,它用一组连续的内存空间,来存储一组具有相同类型的数据.这里我们要抽取出三个跟数组相关的关键词:线性表,连续内存空间,相同数据类型:数组具有连续的内存空 ...

最新文章

  1. [我的1024开源程序]30元写的广义误差分布函数
  2. scala-wordcount
  3. 一个柱状图里两种数据_分享一些数据分析常用的统计图图表
  4. python中如何获取类的属性,python – 获取类的属性
  5. SQL SERVER 2014 安装图解(含 SQL SERVER 2014 安装程序共享)
  6. Apache 如何手动安装为服务并启动运行?
  7. 命令前加./ ,在后台运行程序 linux批处理 linux自动运行程序
  8. lnmp mysql 命令_LNMP状态管理命令
  9. OpenCV_霍夫变换_直线检测_HougLines
  10. 【Caffe代码解析】compute_image_mean
  11. seqkit根据基因id_AgriSeq 靶向测序法基因分型技术
  12. nero express如何刻录DVD数据光盘?详细介绍刻录教程
  13. 阿里王坚:万物互联网=云计算+大数据
  14. nodejs 牛刀小试
  15. Windows C编程中Win7隐藏任务栏图标方法
  16. Python中文社区开源项目计划:ImagePy
  17. 【IXDC 2014】小米、BroadLink对垒智能Wi-Fi模块
  18. thinkphp6+swoole websocket使用教程自研路线不建议使用
  19. 错排公式的理解与推导(转载)
  20. 完美日记母公司逸仙电商发布Q4财报净亏损同比收窄42.5%

热门文章

  1. 革命性新疗法后 第三例艾滋病治愈病例可能出现了!
  2. nutz dao基本应用
  3. WDCP是什么 关于WDCP的详细介绍
  4. 使用JavaScript DOM制作简单留言板
  5. 【四轴飞行器】【电机部分】PWM驱动空心杯转速
  6. VScode神仙插件,程序员必备
  7. 如何进行邮件群发营销?外贸邮件怎么进行邮件群发推广?
  8. Node.js系列-----数据库MySQL
  9. 2014年网络犯罪与社会安全(中国)论坛
  10. 一文读懂,WMS仓储管理系统与ERP有什么区别