链表
链表通过“指针”将一组零散的内存块串联起来,并不需要一块连续的内存空间。
链表的插入删除操作时间复杂度是O(1),随机访问则需要O(n)的时间复杂度。
循环链表优点:适用于处理的数据具有环形结构特点。约瑟夫问题
删除操作
删除结点中“值等于某个给定值”的结点
总时间复杂度O(n):删除操作O(1),遍历查找O(n)。
删除给定指针指向的结点
单链表O(n)时间复杂度,双向链表O(1)
双向链表费内存,但是其插入、删除、查找操作都比单链表有优势。
对于执行较慢的程序,可以通过消耗更多的内存(空间换时间)来进行优化。
对于消耗内存过多的程序,可以通过消耗更多的时间(时间换空间)来降低内存的消耗。
数组与链表
借助CPU的缓存机制,预读数组中的数据,所以访问效率更高。
链表对CPU缓存不友好,没办法有效预读。
数组容易出现“内存不足(out of memory)”,链表天然支持动态扩容。
CPU在从内存读取数据的时候,会先把读取到的数据加载到CPU的缓存中,而CPU每次从内存读取数据并不是制度去那个特定要访问的地址,而是读取一个数据块,并保存到CPU缓存中。然后下次访问内存数据的时候就会先从CPU缓存开始查找。
基于链表实现LRU缓存淘汰算法
缓存淘汰策略有三种:
先进先出策略FIFO
最少使用策略LFU
最近最少使用策略LRU
思路:
维护一个有序单链表,越靠近链表尾部的结点是越早之前访问的,当有一个新的数据被访问时,从链表头开始顺序遍历链表。
1.如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据对应的结点,并将其从原来的位置删除,然后再插入到链表的头部。
2.如果此数据没有在缓存链表中,又可以分为两种情况:
如果此时缓存未满,则将此结点直接插入到链表头部;
如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表头部;
如果字符串通过单链表存储,如何判断一个回文串
1.快慢指针定位中间节点
2.从中间节点对后半部分逆序
3.前后半部分比较,判断是否为回文
4.后半部分逆序复原
- 检查链表代码是否正确的边界条件
- 如果链表为空时,代码是否能正常工作
- 如果链表只包含一个结点时,代码是否能正常工作
- 如果链表只包含两个结点时,代码是否能正常工作
- 代码逻辑在处理头结点和尾结点时,是否能正常工作
链表练习
单链表反转
#递归方式实现
按照我的理解,简单来说就是从要反转的链表最后两个开始,最后一个指向前一个,断开前一个指向最后一个的关系,再递归进行。
网上的解答,用C++实现,我搬过来了
#include <iostream>
using namespace std;
struct ListNode
{
int value;
ListNode *next;
};
void CreateList(ListNode* &head,int data)
{
ListNode *p =(ListNode*)malloc(sizeof(ListNode));
p->value=data;
p->next =NULL;
if (head == NULL)
{
head=p;
return;
}
p->next=head;
head=p;
}
void printList(ListNode* head)
{
ListNode* p = head;
while (p != NULL)
{
cout << p->value<<" ";
p=p->next;
}
cout << endl;
}
ListNode* ReverseList(ListNode* head)
{
if (head == NULL || head->next == NULL)
return head;
else
{
ListNode* newhead=ReverseList(head->next);
head->next->next=head;
head->next=NULL;
return newhead;
}
}
int main()
{
ListNode* head=NULL;
for(int i=0;i<9;i++)
CreateList(head,i);
printList(head);
head=ReverseList(head);
printList(head);
//system("pause");
return 0;
我反正用gcc,g++都没运行出来。
附参考原文:https://blog.csdn.net/geekmanong/article/details/51097196
链表中环的检测
PSListNode HasCycle(PSListNode pHead)
{
if ((NULL == pHead) || (NULL == pHead->pNextNode))
{
return NULL;
}
else
{
PSListNode pFast = pHead->pNextNode->pNextNode;
PSListNode pSlow = pHead->pNextNode;
//利用快慢指针,让快指针每次走两步,慢指针每次走一步,要是快指针没有走到NULL,且快指针与慢指针指向相同就说明是有环
while (pFast != pSlow)
{
//快指针要是没有指向为空,那么慢指针就不可能指向空(快指针走得快)
if (NULL == pFast)
{
return;
}
else
{
pFast = pFast->pNextNode;
pSlow = pSlow->pNextNode;
if (NULL == pFast)
{
return;
}
else
{
pFast = pFast->pNextNode;
}
}
}
return pFast;
}
}
int GetCyleLen(PSListNode pMeetNode)
{
//默认传的参数是HasCycle函数返回的环中的一个结点
if (NULL == pMeetNode)
{
return 0;
}
else
{
int nCount = 1;
PSListNode pNode = pMeetNode;
while (pMeetNode != pNode->pNextNode)
{
pNode = pNode->pNextNode;
nCount++;
}
return nCount;
}
}
//pMeetNode参数是用HasCycle函数求链表是否有环时pFast指针与pSlow指针的碰撞点
//定律:在链表头,pFast指针与pSlow指针的碰撞点分别设定一个指针,每次各走一步,两个指针必定相遇,则相遇第一点为环入口点
PSListNode FindEnterNode(PSListNode pHead, PSListNode pMeetNode)
{
PSListNode pNode = pHead;
if ((NULL == pHead) || (NULL == pMeetNode))
{
return NULL;
}
while (pNode != pMeetNode)
{
pNode = pNode->pNextNode;
pMeetNode = pMeetNode->pNextNode;
}
return pNode;
}
---------------------
作者:ljx_csdn
来源:CSDN
原文:https://blog.csdn.net/ljx_5489464/article/details/50934793
版权声明:本文为博主原创文章,转载请附上博文链接!
两个有序的链表合并
PSListNode MergeList(PSListNode pL1, PSListNode pL2)
{
PSListNode pNewNode = NULL;
PSListNode pListNode1 = pL1;
PSListNode pListNode2 = pL2;
PSListNode pNode = NULL;
if (NULL == pListNode1)
{
return pListNode2;
}
else if (NULL == pListNode2)
{
return pListNode1;
}
else
{
//先把新链表的头结点的指针找到,每次取两个链表中保存的数据较小的结点后插到新链表中
if (pListNode1->data > pListNode2->data)
{
pNode = pListNode2;
pListNode2 = pListNode2->pNextNode;
pNewNode = pNode;
}
else
{
pNode = pListNode1;
pListNode1 = pListNode1->pNextNode;
pNewNode = pNode;
}
while ((NULL != pListNode1) && (NULL != pListNode2))
{
if (pListNode1->data > pListNode2->data)
{
pNode->pNextNode = pListNode2;
pListNode2 = pListNode2->pNextNode;
pNode = pNode->pNextNode;
}
else
{
pNode->pNextNode = pListNode1;
pListNode1 = pListNode1->pNextNode;
pNode = pNode->pNextNode;
}
}
if (NULL == pListNode1)
{
pNode->pNextNode = pListNode2;
return pNewNode;
}
else
{
pNode->pNextNode = pListNode1;
return pNewNode;
}
}
}
---------------------
作者:ljx_csdn
来源:CSDN
原文:https://blog.csdn.net/ljx_5489464/article/details/50934793
版权声明:本文为博主原创文章,转载请附上博文链接!
删除链表倒数第n个结点
PSListNode FindLastKNode(PSListNode pHead, int K)
{
if ((NULL == pHead) || (K <= 0))
{
return NULL;
}
else
{
PSListNode pFast = pHead;
PSListNode pSlow = pHead;
//利用快慢指针,让快指针先走K-1步,然后两指针同时走,直到快指针指向的下一个结点为空为止
while (--K)
{
pFast = pFast->pNextNode;
if (NULL == pFast)
{
return NULL;
}
}
while (NULL != pFast->pNextNode)
{
pFast = pFast->pNextNode;
pSlow = pSlow->pNextNode;
}
return pSlow;
}
}
---------------------
作者:ljx_csdn
来源:CSDN
原文:https://blog.csdn.net/ljx_5489464/article/details/50934793
版权声明:本文为博主原创文章,转载请附上博文链接!
求链表的中间结点
PSListNode FindMidNode(PSListNode pHead)
{
if (NULL == pHead)
{
return NULL;
}
else
{
//快慢指针
PSListNode pSlow = pHead;
PSListNode pFast = pHead;
//注意结束条件得加上NULL != pFast->pNextNode,否则当NULL == pFast->pNextNode时,进入循环,
//执行pFast = pFast->pNextNode->pNextNode时会崩溃
while ((NULL != pFast) && (NULL != pFast->pNextNode))
{
pSlow = pSlow->pNextNode;
pFast = pFast->pNextNode->pNextNode;
}
return pSlow;
}
}
---------------------
作者:ljx_csdn
来源:CSDN
原文:https://blog.csdn.net/ljx_5489464/article/details/50934793
版权声明:本文为博主原创文章,转载请附上博文链接!