链表之排序(插入、选择、归并、快速、冒泡)
2015-08-21 15:49
393 查看
已知链表节点结构如下:
插入排序算法交换节点,思想是先构造一个fakeHead让其指向head,以便于接下来搜索待插入节点应该插入的地方。cur指针指向待插入节点,pre为cur指针的前驱,next为cur指针的后继。时间复杂度为O(N^2),空间复杂度为O(1)。更详细的算法思想,见代码注释:
选择排序算法交换节点的值,算法复杂度为O(N^2),空间复杂度为O(1),思想是先构造一个伪头结点,让其指向head,以便于操作。另外,用sortedTail指针指向有序部分的末尾,剩下的工作就是在无序部分找数值最小的节点minNode,若minNode不等于sortedTail->next,则交换sortedTail->next节点和minNode节点的数值。更详细的思想,见代码注释:
归并排序算法交换链表节点,时间复杂度为O(NlogN),不考虑递归栈空间的话空间复杂度是O(1)) ,算法思想是首先用快慢指针的方法找到链表中间节点,然后递归地对两个子链表进行排序,把两个排好序的子链表合并成一条有序的链表。归并排序算是链表排序中的最好选择,保证了最好和最坏时间复杂度都是NlogN,而且它在数组排序中广受诟病的空间复杂度在链表排序中也从O(N)降到了O(1)。
快速排序1(算法只交换节点的val值,平均时间复杂度O(nlogn),不考虑递归栈空间的话空间复杂度是O(1))
这里的partition我们参考数组快排partition的第二种写法(选取第一个元素作为枢纽元的版本,因为链表选择最后一元素需要遍历一遍),具体可以参考here。这里我们还需要注意的一点是数组的partition两个参数分别代表数组的起始位置,两边都是闭区间,这样在排序的主函数中:
快速排序2(算法交换链表节点,平均时间复杂度O(nlogn),不考虑递归栈空间的话空间复杂度是O(1))
这里的partition,我们选取第一个节点作为枢纽元,然后把小于枢纽的节点放到一个链中,把不小于枢纽的及节点放到另一个链中,最后把两条链以及枢纽连接成一条链。
这里我们需要注意的是,1.在对一条子链进行partition时,由于节点的顺序都打乱了,所以得保正重新组合成一条新链表时,要和该子链表的前后部分连接起来,因此我们的partition传入三个参数,除了子链表的范围(也是前闭后开区间),还要传入子链表头结点的前驱;2.partition后链表的头结点可能已经改变
冒泡排序(算法交换链表节点val值,时间复杂度O(n^2),空间复杂度O(1))
对于希尔排序,因为排序过程中经常涉及到arr[i+increment]操作,其中increment为希尔排序的当前步长,这种操作不适合链表。
对于堆排序,一般是用数组来实现二叉堆,当然可以用二叉树来实现,但是这么做太麻烦,还得花费额外的空间构建二叉树
struct ListNode { int val; //数值 ListNode *next; //后继指针 ListNode(int x) : val(x), next(NULL) {} };
插入排序算法交换节点,思想是先构造一个fakeHead让其指向head,以便于接下来搜索待插入节点应该插入的地方。cur指针指向待插入节点,pre为cur指针的前驱,next为cur指针的后继。时间复杂度为O(N^2),空间复杂度为O(1)。更详细的算法思想,见代码注释:
class Solution { public: ListNode* insertionSortList(ListNode* head) { if(head==NULL||head->next==NULL) return head; //cur初始化为指向第二个元素,pre初始化为指向head, next初始化为NULL ListNode* fakeHead=new ListNode(0), *p, *cur=head->next, *next=NULL, *pre=head; fakeHead->next=head; //使fakeHead指向head,以便于接下来搜索待插入节点应该插入的位置 while (cur) { next=cur->next; //保存cur的后继next以便于接下来的操作 if (cur->val>=pre->val) { //如果cur节点的数值>=pre节点的数值,则说明cur节点,不需要移动。 pre=cur; //更新pre指针 } else { pre->next=next; //移动cur节点之前,让pre和next连接起来 p = fakeHead; while (p->next->val<cur->val) { //搜索cur节点插入的位置,cur应该插入p节点之后 p = p->next; } cur->next=p->next; //连接cur和p指针后面的节点 p->next=cur; //连接p和cur指针 } cur=next; //更新cur指针 } p = fakeHead->next; delete fakeHead; //delete fakeHead return p; } };
选择排序算法交换节点的值,算法复杂度为O(N^2),空间复杂度为O(1),思想是先构造一个伪头结点,让其指向head,以便于操作。另外,用sortedTail指针指向有序部分的末尾,剩下的工作就是在无序部分找数值最小的节点minNode,若minNode不等于sortedTail->next,则交换sortedTail->next节点和minNode节点的数值。更详细的思想,见代码注释:
ListNode* selectSortList(ListNode *head) { if(head==NULL) return head; ListNode* fakeHead = new ListNode(0); fakeHead->next = head; //为了操作方便,添加伪头节点 ListNode* sortedTail = fakeHead; //sortedTail指向已排序部分的尾部,注意在链表中的这种使用方法 while(sortedTail->next != NULL) { //minNode记录最小节点 ListNode* minNode = sortedTail->next, *p = sortedTail->next->next; //寻找未排序部分的最小节点 while(p != NULL) { if(p->val < minNode->val) minNode = p; p = p->next; } if(minNode != sortedTail->next) swap(minNode->val, sortedTail->next->val); //交换节点数值 sortedTail = sortedTail->next; } head = fakeHead->next; delete fakeHead; return head; }
归并排序算法交换链表节点,时间复杂度为O(NlogN),不考虑递归栈空间的话空间复杂度是O(1)) ,算法思想是首先用快慢指针的方法找到链表中间节点,然后递归地对两个子链表进行排序,把两个排好序的子链表合并成一条有序的链表。归并排序算是链表排序中的最好选择,保证了最好和最坏时间复杂度都是NlogN,而且它在数组排序中广受诟病的空间复杂度在链表排序中也从O(N)降到了O(1)。
ListNode* merge(ListNode* left, ListNode* right) { if(left==NULL) { //如果一个为空,则直接返回另外一个 return right; } else if (right==NULL) { return left; } ListNode* res, *tmpPos; if (left->val<right->val) { //确定头指针 res=left; left=left->next; } else { res = right; right=right->next; } tmpPos=res; while (left!=NULL&&right!=NULL) { if(left->val<right->val) { tmpPos->next=left; left=left->next; } else { tmpPos->next=right; right=right->next; } tmpPos=tmpPos->next; } if (left!=NULL) { tmpPos->next=left; } if (right!=NULL) { tmpPos->next=right; } return res; //返回已经合并好的链表的头指针 } ListNode* mergeSortList(ListNode *head) { if (head==NULL||head->next==NULL) { //递归终止条件 return head; } ListNode* fast=head, *slow=head; //通过快慢指针,寻找链表中点 while (fast->next!=NULL&&fast->next->next!=NULL) { fast=fast->next->next; slow=slow->next; } fast=slow->next; //fast为右半部分链表的起始 slow->next=NULL; //slow为为左半部分的结尾 slow=mergeSortList(head); //对左半部分排序 fast=mergeSortList(fast); //对右半部分排序 return merge(slow, fast); //合并左右部分 }
快速排序1(算法只交换节点的val值,平均时间复杂度O(nlogn),不考虑递归栈空间的话空间复杂度是O(1))
这里的partition我们参考数组快排partition的第二种写法(选取第一个元素作为枢纽元的版本,因为链表选择最后一元素需要遍历一遍),具体可以参考here。这里我们还需要注意的一点是数组的partition两个参数分别代表数组的起始位置,两边都是闭区间,这样在排序的主函数中:
void quicksort(vector<int>&arr, int low, int high) { if(low < high) { int middle = mypartition(arr, low, high); quicksort(arr, low, middle-1); quicksort(arr, middle+1, high); } }对左边子数组排序时,子数组右边界是middle-1,如果链表也按这种两边都是闭区间的话,找到分割后枢纽元middle,找到middle-1还得再次遍历数组,因此链表的partition采用前闭后开的区间(这样排序主函数也需要前闭后开区间),这样就可以避免上述问题。
ListNode* partition(ListNode* low, ListNode* high) { int key = low->val; ListNode* loc = low; //loc为小于key节点序列的最后一个节点 for (ListNode* i=low->next; i!=high; i=i->next) { if (i->val<key) { loc=loc->next; swap(i->val, loc->val); } } swap(loc->val, low->val); return loc; //loc就是枢纽节点 } void qSortList(ListNode* head, ListNode* tail) { if(head!=tail&&head->next!=tail) { //如果子链表中至少有两个元素 ListNode* mid = partition(head, tail); //得到枢纽节点 qSortList(head, mid); qSortList(mid->next, tail); } } ListNode* quickSortList(ListNode* head) { if (head==NULL||head->next==NULL) { return head; } qSortList(head, NULL); return head; }
快速排序2(算法交换链表节点,平均时间复杂度O(nlogn),不考虑递归栈空间的话空间复杂度是O(1))
这里的partition,我们选取第一个节点作为枢纽元,然后把小于枢纽的节点放到一个链中,把不小于枢纽的及节点放到另一个链中,最后把两条链以及枢纽连接成一条链。
这里我们需要注意的是,1.在对一条子链进行partition时,由于节点的顺序都打乱了,所以得保正重新组合成一条新链表时,要和该子链表的前后部分连接起来,因此我们的partition传入三个参数,除了子链表的范围(也是前闭后开区间),还要传入子链表头结点的前驱;2.partition后链表的头结点可能已经改变
ListNode* partition(ListNode* lowPre, ListNode* low, ListNode* high) { int key = low->val; ListNode node0(0), node1(0); ListNode* little=&node0, *big=&node1; for (ListNode* i=low->next; i!=high; i=i->next) { if (i->val<key) { little->next=i; little=little->next; } else { big->next=i; big=big->next; } } big->next=high; //保证子链表[low, high)和后面的部分连接 little->next=low; low->next=node1.next; lowPre->next=node0.next; //保证子链表[low, high)和前面的部分连接 return low; } void qSortList(ListNode* headPre, ListNode* head, ListNode* tail) { if(head!=tail&&head->next!=tail) { //如果子链表中至少有两个元素 ListNode* mid = partition(headPre, head, tail); //注意这里的head可能不再指向表头了 qSortList(headPre, headPre->next, mid); qSortList(mid, mid->next, tail); } } ListNode* quickSortList(ListNode* head) { if(head==NULL||head->next==NULL) return head; ListNode* headPre = new ListNode(0); headPre->next=head; qSortList(headPre, head, NULL); return headPre->next; }
冒泡排序(算法交换链表节点val值,时间复杂度O(n^2),空间复杂度O(1))
class Solution { public: ListNode *bubbleSortList(ListNode *head) { // IMPORTANT: Please reset any member data you declared, as // the same Solution instance will be reused for each test case. //链表快速排序 if(head == NULL || head->next == NULL)return head; ListNode *p = NULL; bool isChange = true; while(p != head->next && isChange) { ListNode *q = head; isChange = false;//标志当前这一轮中又没有发生元素交换,如果没有则表示数组已经有序 for(; q->next && q->next != p; q = q->next) { if(q->val > q->next->val) { swap(q->val, q->next->val); isChange = true; } } p = q; } return head; } };
对于希尔排序,因为排序过程中经常涉及到arr[i+increment]操作,其中increment为希尔排序的当前步长,这种操作不适合链表。
对于堆排序,一般是用数组来实现二叉堆,当然可以用二叉树来实现,但是这么做太麻烦,还得花费额外的空间构建二叉树
相关文章推荐
- 集群redis创建
- CSS3:绘制图形
- 织梦cms如何调用含有某一关键词的文章?
- 咖啡斑治疗的治疗方法
- MVC中导航菜单,选中项的高亮问题。。
- 设计角色状态权限控制问题
- Oil Deposits
- 权限验证原理篇
- 谁让APP工程师产生了泡沫?
- AngularJS自定义Echarts标签 — 雷达图Radar
- android 组件CheckBox实例
- 8.2.1(1-2) 优化查询语句
- Codeforces 546E - Soldier and Traveling (网络流输出流量)
- sql 集合
- 大话Linux内核中锁机制之原子操作、自旋锁
- VS项目属性的一些配置项的总结
- Android大图加载内存优化(如何防止OutOfMemory)
- 简单的网页布局效果html5+CSS3
- api接口
- 大数据时代我们都是透明人