【对单链表的归并排序】
大致思路:
单链表排序——归并排序!
首先要理解归并排序的“分治思想”,会写归并排序。
归并排序的一般步骤为:
1)将待排序数组(链表)取中点并一分为二;
2)递归地对左半部分进行归并排序;
3)递归地对右半部分进行归并排序;
4)将两个半部分进行合并(merge),得到结果。
所以对应此题目,可以划分为三个小问题:
1)找到链表中点 (快慢指针思路,快指针一次走两步,慢指针一次走一步,快指针在链表末尾时,慢指针恰好在链表中点);
2)写出merge函数,即如何合并链表。
3)写出mergesort函数,实现上述步骤。
AC代码:
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution {
public:
//单链表的归并排序
ListNode *sortList(ListNode *head) {
//先判空和一个结点的情况
if(head==NULL)
return NULL;
if(head->next==NULL)
return head;
ListNode* mid = findMiddle(head);
//将该链表拆成两半,递归归并
ListNode* right = sortList(mid->next);
mid->next=NULL;
ListNode* left = sortList(head);
return mergeList(left,right);
}
//找到链表中点
ListNode* findMiddle(ListNode *head)
{
ListNode* slow=head;
ListNode* fast=head;
if(head==NULL)
return NULL;
if(head->next==NULL)
return head;
while(fast->next!=NULL && fast->next->next!=NULL) //注意这个条件语句的设计!!!注意是while不是if
{
fast=fast->next->next;
slow=slow->next;
}
return slow; //之前出错了!!!粗心了,之前return成fast了,拜托大哥你在求中点
}
//两个链表的合并操作
ListNode* mergeList(ListNode* left,ListNode* right)
{
//首先注意判空
if(left==NULL)
return right;
if(right==NULL)
return left;
ListNode* newHead = new ListNode(0);
ListNode* p = newHead;
while(left!=NULL && right!=NULL)
{
if(left->val<right->val)
{
p->next=left;
p=p->next;
left=left->next;
}
else
{
p->next=right;
p=p->next;
right=right->next;
}
}
if(left==NULL)
p->next=right;
if(right==NULL)
p->next=left;
return newHead->next; //之前这里出错了!!!应该写->next。newHead是无意义的头结点。
}
};
注意点:
首先对链表进行判空、判一个结点的特殊处理。
在求链表中点的函数上找了很久bug,最后发现是while(fast->next!=NULL && fast->next->next!=NULL) 这个语句没写好,注意不仅fast->next要判断,由于fast一次走两步,fast->next->next也要判断!
终于真正理解了“归并排序”,其实核心在于“合并”,归并的设计只是“分治”,递归自己去处理(包括合并),处理好了让我来直接“合并”。如图:
【对单链表的插入排序】
大致思路:
我刚开始的想法:基于插入排序,两层循环,外层是顺序的将序列中的每个数作为“要去前面插入的元素”,内层是对前面元素的逆序遍历比较+向后平移挪位置。这样的话就需要定义几个指针,不过链表这里不能平移,只能插入+删除操作。插入是插在前面,所以插入和删除都需要“上一个结点”才方便实现。而且,还要考虑头结点没有上一个节点,就把自己绕晕了。
后来看了题解,其实思路是正确的,毕竟就是插入排序的思路,但是人家能够简便解决的方法在于:建新链表存储结果!!!
这样的话,相当于内部循环就拿新链表中的元素与原链表中的那个元素进行比较,再进行插入操作。这里就省去了麻烦的删除操作,不过插在前面的插入操作还是需要上一个节点的哈。
AC代码:
要注意的地方挺多的,详见注释。
学习到两个很重要的点:①不一定要在原链表上操作啊,多想想能不能建新链表。②先创副本先迭代,避免影响循环正常进行。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *insertionSortList(ListNode *head) {
//先判空或一个结点
if(head==NULL)
return NULL;
if(head->next==NULL)
return head;
//插入排序
ListNode* p = head;
ListNode* new_Head = new ListNode(0); //新链表的头 这样就不用去删除结点了
//因为插入涉及上一个结点
ListNode* a = new_Head;
ListNode* b = a->next; //刚开始为空
while(p!=NULL)
{
ListNode* c = p;
p = p->next;
//这里很必要!!:必须先创建副本a,将p迭代一步。
//因为p是迭代变量,不要拿去做修改而打破循环逻辑了。
//又因为就算创建了副本c指向p,对a指向的内容做修改就是在修改p指向的内容,所以赶紧让p迭代一步指向下一个,这样c变化就无关p了。
b = new_Head->next; //联想“插入排序”,每趟比较,都要从头开始比
a = new_Head;
while(b && b->val < c->val)
{
b = b->next;
a = a->next; //因为a要一直作为b的上一个节点(方便插入操作),所以要跟着走
}
//如果b为空,或者b->val大于a->val了,处理都一样:让a插入到b之前
c->next=b;
a->next=c;
}
return new_Head->next; //注意返回Next,new_Head这个无意义头结点是为了让开始结点能够有“上一个结点”,以便插入操作。
}
};