链表排序(冒泡选择插入快排归并希尔堆排序)

Posted Java学习小记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了链表排序(冒泡选择插入快排归并希尔堆排序)相关的知识,希望对你有一定的参考价值。

以下排序算法的正确性都可以在LeetCode的链表排序这一题检测。本文用到的链表结构如下(排序算法都是传入链表头指针作为参数,返回排序后的头指针)

struct ListNode {

  int val;

  ListNode *next;

  ListNode(int x) : val(x), next(NULL) {}

  };

插入排序(算法中是直接交换节点,时间复杂度O(n^2),空间复杂度O(1)


  
    
    
  
  1. class Solution {

  2. public:

  3. ListNode *insertionSortList(ListNode *head) {

  4. // IMPORTANT: Please reset any member data you declared, as

  5. // the same Solution instance will be reused for each test case.

  6. if(head == NULL || head->next == NULL)return head;

  7. ListNode *p = head->next, *pstart = new ListNode(0), *pend = head;

  8. pstart->next = head; //为了操作方便,添加一个头结点

  9. while(p != NULL)

  10. {

  11. ListNode *tmp = pstart->next, *pre = pstart;

  12. while(tmp != p && p->val >= tmp->val) //找到插入位置

  13. {tmp = tmp->next; pre = pre->next;}

  14. if(tmp == p)pend = p;

  15. else

  16. {

  17. pend->next = p->next;

  18. p->next = tmp;

  19. pre->next = p;

  20. }

  21. p = pend->next;

  22. }

  23. head = pstart->next;

  24. delete pstart;

  25. return head;

  26. }

  27. };

 选择排序(算法中只是交换节点的val值,时间复杂度O(n^2),空间复杂度O(1)


  
    
    
  
  1. class Solution {

  2. public:

  3. ListNode *selectSortList(ListNode *head) {

  4. // IMPORTANT: Please reset any member data you declared, as

  5. // the same Solution instance will be reused for each test case.

  6. //选择排序

  7. if(head == NULL || head->next == NULL)return head;

  8. ListNode *pstart = new ListNode(0);

  9. pstart->next = head; //为了操作方便,添加一个头结点

  10. ListNode*sortedTail = pstart;//指向已排好序的部分的尾部


  11. while(sortedTail->next != NULL)

  12. {

  13. ListNode*minNode = sortedTail->next, *p = sortedTail->next->next;

  14. //寻找未排序部分的最小节点

  15. while(p != NULL)

  16. {

  17. if(p->val < minNode->val)

  18. minNode = p;

  19. p = p->next;

  20. }

  21. swap(minNode->val, sortedTail->next->val);

  22. sortedTail = sortedTail->next;

  23. }


  24. head = pstart->next;

  25. delete pstart;

  26. return head;

  27. }

  28. };

快速排序1(算法只交换节点的val值,平均时间复杂度O(nlogn),不考虑递归栈空间的话空间复杂度是O(1))

       这里的partition我们参考数组快排partition的第二种写法(选取第一个元素作为枢纽元的版本,因为链表选择最后一元素需要遍历一遍),具体可以参考here

       这里我们还需要注意的一点是数组的partition两个参数分别代表数组的起始位置,两边都是闭区间,这样在排序的主函数中:


  
    
    
  
  1. void quicksort(vector<int>&arr, int low, int high)


  2. {


  3. if(low < high)


  4. {


  5. int middle = mypartition(arr, low, high);


  6. quicksort(arr, low, middle-1);


  7. quicksort(arr, middle+1, high);


  8. }


  9. }

        对左边子数组排序时,子数组右边界是middle-1,如果链表也按这种两边都是闭区间的话,找到分割后枢纽元middle,找到middle-1还得再次遍历数组,因此链表的partition采用前闭后开的区间(这样排序主函数也需要前闭后开区间),这样就可以避免上述问题。


  
    
    
  
  1. class Solution {

  2. public:

  3. ListNode *quickSortList(ListNode *head) {

  4. // IMPORTANT: Please reset any member data you declared, as

  5. // the same Solution instance will be reused for each test case.

  6. //链表快速排序

  7. if(head == NULL || head->next == NULL)return head;

  8. qsortList(head, NULL);

  9. return head;

  10. }

  11. void qsortList(ListNode*head, ListNode*tail)

  12. {

  13. //链表范围是[low, high)

  14. if(head != tail && head->next != tail)

  15. {

  16. ListNode* mid = partitionList(head, tail);

  17. qsortList(head, mid);

  18. qsortList(mid->next, tail);

  19. }

  20. }

  21. ListNode* partitionList(ListNode*low, ListNode*high)

  22. {

  23. //链表范围是[low, high)

  24. int key = low->val;

  25. ListNode* loc = low;

  26. for(ListNode*i = low->next; i != high; i = i->next)

  27. if(i->val < key)

  28. {

  29. loc = loc->next;

  30. swap(i->val, loc->val);

  31. }

  32. swap(loc->val, low->val);

  33. return loc;

  34. }

  35. };

快速排序2(算法交换链表节点,平均时间复杂度O(nlogn),不考虑递归栈空间的话空间复杂度是O(1))

       这里的partition,我们选取第一个节点作为枢纽元,然后把小于枢纽的节点放到一个链中,把不小于枢纽的及节点放到另一个链中,最后把两条链以及枢纽连接成一条链。

       这里我们需要注意的是:1.在对一条子链进行partition时,由于节点的顺序都打乱了,所以得保正重新组合成一条新链表时,要和该子链表的前后部分连接起来,因此我们的partition传入三个参数,除了子链表的范围(也是前闭后开区间),还要传入子链表头结点的前驱;2.partition后链表的头结点可能已经改变。


  
    
    
  
  1. class Solution {

  2. public:

  3. ListNode *quickSortList(ListNode *head) {

  4. // IMPORTANT: Please reset any member data you declared, as

  5. // the same Solution instance will be reused for each test case.

  6. //链表快速排序

  7. if(head == NULL || head->next == NULL)return head;

  8. ListNode tmpHead(0); tmpHead.next = head;

  9. qsortList(&tmpHead, head, NULL);

  10. return tmpHead.next;

  11. }

  12. void qsortList(ListNode *headPre, ListNode*head, ListNode*tail)

  13. {

  14. //链表范围是[low, high)

  15. if(head != tail && head->next != tail)

  16. {

  17. ListNode* mid = partitionList(headPre, head, tail);//注意这里head可能不再指向链表头了

  18. qsortList(headPre, headPre->next, mid);

  19. qsortList(mid, mid->next, tail);

  20. }

  21. }

  22. ListNode* partitionList(ListNode* lowPre, ListNode* low, ListNode* high)

  23. {

  24. //链表范围是[low, high)

  25. int key = low->val;

  26. ListNode node1(0), node2(0);//比key小的链的头结点,比key大的链的头结点

  27. ListNode* little = &node1, *big = &node2;

  28. for(ListNode*i = low->next; i != high; i = i->next)

  29. if(i->val < key)

  30. {

  31. little->next = i;

  32. little = i;

  33. }

  34. else

  35. {

  36. big->next = i;

  37. big = i;

  38. }

  39. big->next = high;//保证子链表[low,high)和后面的部分连接

  40. little->next = low;

  41. low->next = node2.next;

  42. lowPre->next = node1.next;//为了保证子链表[low,high)和前面的部分连接

  43. return low;

  44. }

  45. };

归并排序(算法交换链表节点,时间复杂度O(nlogn),不考虑递归栈空间的话空间复杂度是O(1))                     

       首先用快慢指针的方法找到链表中间节点,然后递归的对两个子链表排序,把两个排好序的子链表合并成一条有序的链表。归并排序应该算是链表排序最佳的选择了,保证了最好和最坏时间复杂度都是nlogn,而且它在数组排序中广受诟病的空间复杂度在链表排序中也从O(n)降到了O(1)。


  
    
    
  
  1. class Solution {

  2. public:

  3. ListNode *mergeSortList(ListNode *head) {

  4. // IMPORTANT: Please reset any member data you declared, as

  5. // the same Solution instance will be reused for each test case.

  6. //链表归并排序

  7. if(head == NULL || head->next == NULL)return head;

  8. else

  9. {

  10. //快慢指针找到中间节点

  11. ListNode *fast = head,*slow = head;

  12. while(fast->next != NULL && fast->next->next != NULL)

  13. {

  14. fast = fast->next->next;

  15. slow = slow->next;

  16. }

  17. fast = slow;

  18. slow = slow->next;

  19. fast->next = NULL;

  20. fast = sortList(head);//前半段排序

  21. slow = sortList(slow);//后半段排序

  22. return merge(fast,slow);

  23. }


  24. }

  25. // merge two sorted list to one

  26. ListNode *merge(ListNode *head1, ListNode *head2)

  27. {

  28. if(head1 == NULL)return head2;

  29. if(head2 == NULL)return head1;

  30. ListNode *res , *p ;

  31. if(head1->val < head2->val)

  32. {res = head1; head1 = head1->next;}

  33. else{res = head2; head2 = head2->next;}

  34. p = res;


  35. while(head1 != NULL && head2 != NULL)

  36. {

  37. if(head1->val < head2->val)

  38. {

  39. p->next = head1;

  40. head1 = head1->next;

  41. }

  42. else

  43. {

  44. p->next = head2;

  45. head2 = head2->next;

  46. }

  47. p = p->next;

  48. }

  49. if(head1 != NULL)p->next = head1;

  50. else if(head2 != NULL)p->next = head2;

  51. return res;

  52. }

  53. };

冒泡排序(算法交换链表节点val值,时间复杂度O(n^2),空间复杂度O(1))


  
    
    
  
  1. class Solution {

  2. public:

  3. ListNode *bubbleSortList(ListNode *head) {

  4. // IMPORTANT: Please reset any member data you declared, as

  5. // the same Solution instance will be reused for each test case.

  6. //链表快速排序

  7. if(head == NULL || head->next == NULL)return head;

  8. ListNode *p = NULL;

  9. bool isChange = true;

  10. while(p != head->next && isChange)

  11. {

  12. ListNode *q = head;

  13. isChange = false;//标志当前这一轮中又没有发生元素交换,如果没有则表示数组已经有序

  14. for(; q->next && q->next != p; q = q->next)

  15. {

  16. if(q->val > q->next->val)

  17. {

  18. swap(q->val, q->next->val);

  19. isChange = true;

  20. }

  21. }

  22. p = q;

  23. }

  24. return head;

  25. }

  26. };

对于希尔排序,因为排序过程中经常涉及到arr[i+gap]操作,其中gap为希尔排序的当前步长,这种操作不适合链表。

对于堆排序,一般是用数组来实现二叉堆,当然可以用二叉树来实现,但是这么做太麻烦,还得花费额外的空间构建二叉树。


以上是关于链表排序(冒泡选择插入快排归并希尔堆排序)的主要内容,如果未能解决你的问题,请参考以下文章

插入.希尔.选择.堆排.冒泡.快排.归并.计数_8排序

编程-链表之希尔排序堆排序归并排序快速排序

八大排序算法C语言过程图解+代码实现(插入,希尔,选择,堆排,冒泡,快排,归并,计数)

你所知道的十大排序算法的总结(冒泡,选择,插入,希尔,归并,快排,堆排序,计数排序,桶排序,基数排序)

七大排序算法(插排,希尔,选择排序,堆排,冒泡,快排,归并)--图文详解

8种面试经典!排序详解--选择,插入,希尔,冒泡,堆排,3种快排,快排非递归,归并,归并非递归,计数(图+C语言代码+时间复杂度)