这一篇笔记主要记录总结了线性表
数据结构中的链表
概念,以及和数组
的对比,数组和链表都是计算机中最基本的数据结构,是构建其余高级复杂数据的结构的基础。开发中应该根据具体的场景选择最合适的数据结构和算法。下一篇将会实现链表相关的算法。算法
上一篇数组讲了数组是一种是一种线性表
数据结构,是用一组连续的内存空间
,来存储一组具备相同数据类型
的数据。和数组相同的是,链表
也是一种线性表
数据结构,不一样的是链表是经过指针将一组零散的内存块
串联起来存储数据,能够存储不一样数据类型
的数据。数组
链表的种类五花八门,主要有如下几类:缓存
每一个内存块被称为链表的结点
,每一个结点包含了数据域
和指针域
,数据域存储数据,指针域的指针指向下一个结点的内存地址
,称为后继指针next
。数据结构
另外,一般把第一个结点称做头结点
,头结点用来记录链表的基地址
,有了头结点就能够遍历整个链表。最后一个结点称做尾节点
,尾结点的指针指向NUll
。app
循环链表
是一种特殊的单链表
。和单链表惟一的区别是循环链表的尾指针不是指向NUll,而是指向头结点
。像环同样首尾相连,因此叫作循环链表
。数据结构和算法
循环链表
的优势是在处理的数据具备环形结构特色时,特别适合使用,如经典的约瑟夫问题。post
双向链表
除了像单链表同样结点指针域有后继指针外,还有前驱指针prev
,指向上一个结点
。学习
缺点是须要额外开辟两个空间来存储后继结点和前驱结点的地址,若是存储一样的数据,双向链表要比单链表占用更多的空间
。spa
优势是支持双向遍历
,能够在 O(1)的复杂度
下找到前驱结点,因此在某些状况下的插入、删除等操做比单链表简单高效。设计
如删除指定指针指向的结点q,单链表须要先遍历找到到这个结点的前驱结点,直到 p->nex = q, 时间复杂度是O(n),而双向链表
的结点指针域中已经存储了前驱结点的指针,不须要遍历,时间复杂度是O(1)。同理,要在指定结点前面插入一个结点,使用双向链表
也是只要 O(1) 复杂度就能完成。
另外,对于有序链表,查找一个数据时,能够记录上次查找的数据的位置p,后面的查找的数据能够和 p 位置的数据比大小,决定是向前仍是向后查找,这样平均查找只须要找一半的数据。
双向链表
里面有个重要的设计思想就是空间换时间
思想,当须要很在乎运行时间时,能够选择空间复杂度较高而时间复杂度相对较低的算法或者数据结构,相反,若是内存空间比较重要,如在单片机上的程序,就要反过来使用时间换空间
思想。缓存
的思想就是利用了空间换时间,让常用到的数据存储到到高速的内存中,大大提升了数据读取的速度。
双向循环链表
是双向链表
和循环链表
的结合体,相比较双向链表
,尾结点的后继指针
指向了头结点,头结点的前驱指针
指向尾结点。
能够看到,再算法时间复杂度上,数组和链表在随机访问
和插入删除
的复杂度上正好相反
。
数组是用一组连续的内存空间来存储数据,优势是能够借助 CPU 缓存机制,预先读取数组中的数据,访问效率更高,而链表在内存中不连续,因此对 CPU 缓存不友好。
数组的缺点是大小固定,要占用整块连续的内存空间,若是声明的数组过大,系统可能没有足够连续的空间给分配,就会致使内存不足,若是申请的数组大小太小,出现不够用,就须要从新申请一块更大的连续内存空间,而后将以前的数据所有拷贝一份过来,这个过程很耗时,而链表自然就支持动态扩容
。
因此,若是代码对内存使用很苛刻,就使用用数组,由于链表存储相同的数据,须要更多的内存空间。在实际的开发中,须要根据不一样状况选用最合适的数据结构和算法。
最近最少使用策略 LRU
是一种常见的缓存策略
。经常使用缓存策略
的有下列几种:
维护一个有序的单链表
,越靠近链表头部的越是最近访问的,也靠近尾结点的是越早以前访问的。
维护一个有序的数组,下标越小越是最近访问的,下标越大越是越早以前访问的。
解答:
代码以下:
/* * @lc app=leetcode.cn id=234 lang=c * * [234] 回文链表 * * https://leetcode-cn.com/problems/palindrome-linked-list/description/ * * algorithms * Easy (34.93%) * Total Accepted: 22.3K * Total Submissions: 61.2K * Testcase Example: '[1,2]' * * 请判断一个链表是否为回文链表。 * * 示例 1: * * 输入: 1->2 * 输出: false * * 示例 2: * * 输入: 1->2->2->1 * 输出: true * * * 进阶: * 你可否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题? * */
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */
bool isPalindrome(struct ListNode* head){
if(head == NULL || head->next == NULL) {
return true;
}
// 快慢指针获取中间结点指针
// 快指针,每次走两步
struct ListNode *fast = head;
// 慢指针,每次走一步
struct ListNode *slow = head;
struct ListNode *previous = NULL;
while (slow && fast && fast->next) {
fast = fast->next->next;
// 将前半部分链表翻转
struct ListNode *temp = slow->next;
slow->next = previous;
previous = slow;
slow = temp;
}
// 链表长度是偶数时,fast 为空
if (fast) {
// 链表数是奇数,此时 slow 指向中间结点,provious 指向头结点,slow指针须要向前走一步,
slow = slow->next;
}
while (slow) {
if (slow->val != previous->val) {
return false;
}
slow = slow->next;
previous = previous->next;
}
return true;
}
复制代码
扩展阅读
分享我的技术学习记录和跑步马拉松训练比赛、读书笔记等内容,感兴趣的朋友能够关注个人公众号「青争哥哥」。