数组与链表(暂时只有单链表)
个人代码实现+概念理解参考自知乎 https://zhuanlan.zhihu.com/p/46435778
首先先摆明区别
数组 VS 链表
- 插入、删除元素时时间复杂度,数组为
,链表为
。
- 随机访问时间复杂度,数组为
,链表为
。
- 数组简单易用,还可以借助 CPU 的缓存机制,预读数据,访问效率更高,但一经申请就大小固定。
- 链表本身没有大小的限制,支持动态扩容,但每个结点都需要额外的空间存放指针,频繁的删除和插入操作还会造成内存碎片。
1. 数组
1.1 数组为什么从零编号?
数组名代表数组的首地址,数组的下标其实代表数组中某个元素相对首地址的偏移量,数组的第一个元素是零偏移,因此从 0 开始。
上面其实也只是一个解释, C 语言设计者用零开始编号,后来的各种语言也便纷纷效仿,因此就形成了这个习惯。
1.2 数组的特点?
- 数组是一种线性结构,在内存上按顺序存放,支持随机访问,但需要足够的连续内存空间。在数组中增加或者删除元素时需要大量的移动操作,效率较低。
- 警惕数组越界,对数组进行越界访问时就相当于在其他未知内存上进行了操作,这些区域可能原本存放着重要的系统信息或者其他数据,错误操作会对程序带来无法估计的结果。
- 容器支持动态扩容,且一般封装了许多操作的方法,但如果事先知道数组大小,或者表示多维数据,还是可以考虑使用数组这种数据结构。
2. 链表
2.1 链表的特点?
- 不需要连续的内存,通过指针的关联可以任意存放在零散的空间上。在链表中插入或者删除数据比较方便,只需要更改相邻两个节点即可,但查找某一特定元素效率较低,只能从头指针开始一个一个地按照顺序访问。
- 比较贴切的比喻 链表就是一列火车,每个节点都是一截车厢,车厢里面装的是值域,车尾挂钩连接着下一节车厢的首地址,如此循环,直到最后一截车厢.车尾挂钩为NULL
2.2 链表的一些注意事项
- 指针和引用都可以看作是变量的地址,通过地址可以访问到变量。
- 警惕指针丢失和内存泄露。链表中丢失了某一结点的指针,其后所有的数据都会丢失,另外,删除节点时一定记得手动释放内存。
- 利用哨兵简化实现难度。没有哨兵,在头结点插入新的结点和在中间位置插入新的节点操作可能会不一样。但引入哨兵后就只有一种情况,可以简化实现难度。
- 留意边界条件,重点考虑链表为空、链表只有一个结点和链表头尾结点的处理情况。
2.3 常见链表分类
- 单链表
单链表只支持一个方向的访问,第一个节点称为头结点,最后一个节点称为尾结点。
2.4 代码实现
100次循环
链表值域赋值是循环数变量i的两倍
#include<iostream> using namespace std; struct List { int data; //信息域,存放数据 List *pNext; //指针域,指向下一个链表元素结点 }; List *CreateList() { List *new_node = NULL; // 临时新建的链表节点 List *last_node = NULL; // 上一个链表节点 List *head = NULL; // 链表首地址 for (int i=0; i<100; i++){ new_node = new List; // 为新的链表节点 划分List数据类型的内存空间 new_node->data = i * 2; //数据设定为i*2 if (head == NULL) { head = new_node; //判断head首地址为空,则为第一次循环,生成第一个结点 } else { last_node->pNext = new_node; // head不为空,则非第一个节点,执行赋值,将当前新生成的new_node地址赋值给上一个节点的指针域 (这里可以理解为 下一节火车车厢 牵挂在上一节车厢的尾部) } last_node = new_node; // 这轮循环的新建链表操作结束了, 当前新建的链表节点设定为下一轮的last_node,以便下一轮循环操作 } if (head != NULL){ last_node->pNext = NULL; // 前面设定的链表结构生成循环完成后,若至少插入了一个结点,则将最后一个结点的指针域置为NULL,以此作为单链表结束标识 } return head; //返回的是链表首地址 如果要查找具体每个节点的位置的话 ,通过 首地址和偏移量 } void displayList(const List *head) { while( head != NULL){ cout << head->data; //输出数据 head = head->pNext; //使head指向下一结点 if ( head != NULL ){ cout << "->"; // head 这里是下一个链表节点的指针值,若为空,则当前链表节点为最后一个节点,退出循环 ,若不为空则打印 "->" } } cout<< endl; } int main(){ List* head; head = CreateList(); displayList(head); return 0; }