链表性质
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。
单链表只有一个后驱节点next,双向链表还会有一个前驱节点pre。
链表的基本操作:
- 插入
tempnode = cur->next;
cur->next = insertNode;
insertNode->next = tempnode
- 删除
tempnode = precur;
precur->next = precur->next->next;
free(tempnode);
- 遍历
当前指针 = 头指针
while 当前节点不为空 {
print(当前节点)
当前指针 = 当前指针.next
}
leetcode中节点定义
结构中定义了构造函数,用于构造带有初始值的节点
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
链表操作(可作为轮子)
链表反转(单向链表)
方法一:前序遍历
ListNode* reverse(ListNode* head)
{
ListNode* pre = nullptr;//设定一个尾节点指向的空指针
while(ListNode* != nullptr)
{
ListNode* next = head->next;//记录此时节点的下一个节点的位置
head->next = pre;//将此节点的节点指向前一个节点
pre = head; //将前一个节点指向这时的节点
head = next; //将这时的节点指向下一个节点、也就是将两个几点都往后移动一位
}
return pre;//这里是因为head已经指向了空指针,只有pre指向的是链表的最后一个位置
}
方法二:后续遍历,递归调用
ListNode* function2(ListNode* head)//递归解法---后续遍历
{
if(head == nullptr || head->next == nullptr){return head;}//一直走到最后一个节点后返回,作为反转链表的头节点
ListNode* cur = function2(head->next);//cur一直指向的都是最后一个节点
head->next->next = head;//将原本顺序的下一节点反置
head->next = nullptr;//将这一节点指向下一节点的指针赋空
return cur;//返回反转的头节点
}
方法三:当有前后两个节点,头节点和尾节点的下一个节点(建议画图理解)
ListNode* reversePart(ListNode* head, ListNode* tail) {
//添加一个尾节点,即在最后一个节点的最后再添加一个节点
//head 为链表第一个节点,tail 为最后一个节点后面一个节点
if (head == tail || head->next == tail) {
return head;
}
ListNode* p, *q;
q = tail;
while (head != tail) {
p = head->next; //q节点是用来存储head的下一个节点位置
head->next = q; //q是head要指向的节点位置
q = head; //每次更新完需要把指向节点的位置一刀下一次要指向的位置
head = p; //将head往后移一次
}
return q;
}
链表拼接
合并了两个有序链表—结果还是有序
递归调用:
//时间复杂度和空间复杂度都是O(n+m)
ListNode* function1(ListNode* l1,ListNode *l2)//递归调用也需要到最后一个指针处,可以考虑承接节点的前一个节点
{
if(l1==nullptr) //如果有一个节点是空了,直接返回另一个节点就可以
{return l2;}
if(l2==nullptr)
{return l1;}
if(l1->val < l2->val) //如果两个值的大小不等的话,以小的节点为起始节点,并计算当前链表的下一个节点和另一个链表节点值
{
l1->next = function1(l1->next,l2); //接着找下一个节点
return l1;
}
if(l1->val > l2->val)
{
l2->next = function1(l1,l2->next);
return l2;
}
}
不使用递归调用,使用迭代:
ListNode* function2(ListNode* l1,ListNode* l2)
{
ListNode* prehead = new ListNode(-1);
ListNode* pre = prehead;
//如果两者由一个为空了,那么后面的直接从另一个链表获取
while(l1!=nullptr && l2!=nullptr)//起初假设两者都不为空
{
if(l1->val > l2->val) //比较值的大小,小的值放到节点后,每次只放进一个
{
pre->next = l1;
l1 = l1->next;
}
else
{
pre->next = l2;
l2= l2->next;
}
pre = pre->next; //记录当前拼接链表的最后一个节点
}
pre->next = l1 == nullptr ? l2 : l1;//如果后续链表有一个没有值了,就直接读取另一个链表的值(因为两者已经拍好序了)
return prehead->next;//返回头节点的下一个节点
}
链表排序
无序链表按照大小排序
红黑树,利用set容器实现:
multiset<int> slist;
ListNode* res = head;
ListNode* result = head;
while(head != nullptr)
{
slist.insert(head->val);
head = head->next;
}
for(auto it = slist.begin(); it!= slist.end();it++)
{
res->val = *it;
res = res->next;
}
return result;
归并排序—将链表二分,直至不能再划分,再通过子链表按大小拼接
if(head == nullptr || head->next == nullptr){return head;}
ListNode* slow = head,*fast = head;
while(!fast->next && !fast->next->next)//这里和判断是否是回文链表的条件还不一样,灵活运用
{
slow = slow->next;//slow到达中点的前面一个
fast = fast->next->next;
}
fast = slow->next;
slow->next = nullptr;
return merge(sortList(head),sortList(fast));//用到的子函数
ListNode* merge(ListNode* l1,ListNode* l2)
{
ListNode res(0);
ListNode* ptr = &res;
while(l1 && l2)
{
if(l1->val > l2->val)
{
ptr->next = l2;
l2 = l2->next;
}
else
{
ptr->next = l1;
l1 = l1->next;
}
ptr = ptr->next;
}
ptr->next = l1 == nullptr ? l2 : l1;
return res.next;
}
堆排序,利用stl中的优先队列,其底层是堆排序算法
priority_queue<int,vector<int>,greater<int>> worker;
auto sub = head;
while(sub)
{
worker.push(sub->val);//每次放进去都是把最小的放到最前面
sub=sub->next;
}
sub = head;
while(sub)
{
sub->val = worker.top();
worker.pop();
sub = sub->next;
}
return head;
链表操作技巧
快慢指针
设定一个快指针一个慢指针,每次快指针走的步数是慢指针的两倍,最后返回慢指针就到达中点。
考虑一下几种情况
到达中点指针
ListNode* head;
ListNode* fast = head,* slow = head;
while(fast != nullptr && fast->next != nullptr){
fast = fast->next->next;
slow = slow->next;
}
if(fast != nullptr){
slow = slow->next;
}
到达中心节点的前一个节点
while(fast->next != nullptr && fast->next->next != nullptr){
fast = fast->next->next;
slow = slow->next;//这时的slow始终会在中心节点的前一个节点
}
虚拟头节点
新加一个虚拟头节点方便操作
在反转子链表的时候用到了
ListNode* virhead = new ListNode(0);
virhead->next = head;
注意切断链表
在重排链表这一题需要把一个链表分割成两个链表,需要把中间节点断开,即先记录中心节点的位置,再将中心节点前一个节点的next置为nullptr,利用快慢指针到中间节点的前一个节点。