题目描述
Sort a linked list in O(n log n) time using constant space complexity.
思路:
冒泡排序、选择排序、插入排序,时间复杂度为O(n^2);
快速排序、归并排序、堆排序,时间复杂度为O(nlogn);
基数排序、计数排序,时间复杂度都是O(n)。
首先确定时间复杂度nlogn且空间复杂度是常数的的排序算法有:归并排序、快速排序、堆排序
快速排序:可以适用。
堆排序:堆排序在排序的过程中需要比较第i个元素和第2 * i + 1个元素的大小,需要随机访问各个元素,因此适用于数组,不适用于链表。
归并排序:需要将整个数组(链表)分成两部分分别归并:在比较的过程中只比较相邻的两个元素大小;在合并的过程中需要顺序访问数组(链表)。因此不需要随机访问元素,是适用于这里的场景的。
快排实现代码:
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 void swap(int *a,int *b) 12 { 13 int temp = *a; 14 *a = *b; 15 *b = temp; 16 } 17 ListNode* partition(ListNode* head,ListNode* end) 18 { 19 int key = head->val; 20 ListNode *p = head; 21 ListNode *q = head->next; 22 while(q!=end) 23 { 24 if(q->val<key)//小于的时候,当前指针后移,交换p、q 25 { 26 //将当前标准值下一位与q处的值交换 27 p = p->next; 28 swap(&p->val,&q->val); 29 } 30 q = q->next;//否则q后移,比较下一位 31 } 32 //交换标准值和最后一个小于它的值,此时左侧均小于标准值,右侧均大于标准值 33 swap(&head->val,&p->val); 34 return p; 35 } 36 ListNode* quickSort(ListNode *head,ListNode* end) 37 { 38 if(head != end) 39 { 40 ListNode* pivot = partition(head,end);//确定随机选择的数的位置 41 quickSort(head,pivot);//一定要注意是从l开始,不是0 42 quickSort(pivot->next,end); 43 } 44 return head; 45 } 46 ListNode *sortList(ListNode *head) { 47 quickSort(head,NULL); 48 return head; 49 } 50 };
归并排序实现代码:
归并排序的一般步骤为:
1)将待排序数组(链表)取中点并一分为二;
2)递归地对左半部分进行归并排序;
3)递归地对右半部分进行归并排序;
4)将两个半部分进行合并(merge),得到结果。
归并排序实现代码:
1 class Solution { 2 public: 3 ListNode *sortList(ListNode *head) 4 { 5 if(!head || !head->next) 6 { 7 return head; 8 } 9 ListNode *p = head; 10 ListNode *q = head; 11 while(q->next && q->next->next) 12 { 13 //快慢指针思路,快指针一次走两步,慢指针一次走一步,快指针在链表末尾时,慢指针恰好在链表中点 14 p = p->next; 15 q = q->next->next; 16 } 17 ListNode *r = sortList(p->next); 18 p->next = NULL; 19 ListNode *l = sortList(head); 20 return merge(l,r); 21 } 22 ListNode *merge(ListNode *left,ListNode *right) 23 { 24 25 //写出merge函数,即如何合并链表。 26 ListNode dummy(0); //创建结构体dummy(0)为l的辅助空间,因为可能在最开始插入 27 ListNode *p = &dummy; 28 ListNode *p2 = right; 29 dummy.next = left; 30 while(left && right) 31 { 32 if (left->val < right->val) 33 { 34 p->next = left; 35 left = left->next; 36 } 37 else 38 { 39 p->next = right; 40 right = right->next; 41 } 42 p = p->next; 43 } 44 if (left) p->next = left; 45 if (right) p->next = right; 46 47 return dummy.next;//结构体取属性 48 } 49 };
单链表相关操作介绍:
1 #include <iostream> 2 #include <malloc.h> 3 using namespace std; 4 struct ListNode { 5 int val; 6 ListNode *next; 7 ListNode(int x) : val(x), next(NULL) {} 8 }; 9 /*-----------------------------创建链表(带头结点)---------------------------------*/ 10 /*在链表的末端插入新的节点,建立链表*/ 11 ListNode *CreateList(int n) 12 { 13 ListNode *head;//指向头结点指针 14 ListNode *p,*pre; 15 int i; 16 head=(ListNode *)malloc(sizeof(ListNode));//为头节点分配内存空间 17 head->next=NULL;//将头结点的指针域清空 18 pre=head;//先将头结点首地址赋给中间变量pre 19 for(i=1;i<=n;i++)//通过for循环不断加入新的结点 20 { 21 p=(ListNode *)malloc(sizeof(ListNode));//为要插入的节点分配 22 //内存空间p指向新插入结点的首地址 23 cin>>p->val;//输入数值 24 pre->next=p;//将p指向新结点插入链表也就是头结点指针域指向 25 //下个结点 26 //第一个结点就是p指向的,因为头结点内容为空 27 pre=p;//这个起着指向下一个结点的作用 等价于pre=pre->next 28 } 29 p->next=NULL;//最后将最后一个结点的指针域清空了 30 return head;//带空的头结点的 31 //return head->next;//不带空的头结点 32 } 33 /*-------------------------输出链表-----------------------------------*/ 34 void PrintList(ListNode *h) 35 { 36 ListNode *p; 37 p=h->next;//带空的头结点的 38 //p=h;//不带空的头结点 39 while(p) 40 { 41 cout<<p->val<<" "; 42 p=p->next; 43 cout<<endl; 44 } 45 } 46 /*----------------------插入链表结点(带头结点)--------------------------*/ 47 /*-------------------------------------------------------------------- 48 函数名称:InsertList(ListNode *h,int i,int val,int n) 49 函数功能:插入链表结点 50 入口参数: h: 头结点地址 i:插入到第几个结点 val:插入结点的值 51 n:链表中结点的个数 52 --------------------------------------------------------------------*/ 53 void InsertList(ListNode *h,int i,int val,int n) 54 { 55 ListNode *q,*p;//先定义2个指向一个结点的指针 56 int j; 57 //if(i<1 || i>n+1)//带空的头结点的 58 if(i<1 || i>n+1)//不带空的头结点的 59 cout<<"Error! Please input again"<<endl; 60 else 61 { 62 j=0;//带空的头结点的 63 p=h;//将指针p指向要链表的头结点 64 while(j<i-1) 65 { 66 p=p->next; 67 j++; 68 } 69 q=(ListNode *)malloc(sizeof(ListNode));/*为要插入的结点分配内存空间*/ 70 71 //----赋值操作--------- 72 q->val=val; //将要插入的节点中分数赋值 73 74 //调整指针域 75 76 q->next = p->next; /*这个是将新插入的结点指针域指向上一个结点指针域指向的结点地址即为p->next*/ 77 78 p->next=q;/*将要插入结点位置前面的结点指针域指向现在插入的结点首地址*/ 79 } 80 } 81 82 /*-------------------------------------------------------------------- 83 函数名称:DeleteList(ListNode *h, int i, int n)(带头结点) 84 函数功能:删除链表结点 85 入口参数: h: 头结点地址 i:要删除的结点所在位置 86 n:链表中结点的个数除下头结点外的个数 87 --------------------------------------------------------------------*/ 88 void DeleteList(ListNode *h, int i, int n) 89 { 90 ListNode *p,*q;//首先定义2个指向结点型结构体的指针 91 int j; 92 int val; 93 if(i<1 || i>n)//如果位置超出了1和n的范围的话则打印出错误信息 94 cout<<"Error! Please input again. "<<endl; 95 else//没有超出除头结点外的1到n 的范围的话那么执行删除操作 96 { 97 j=0; 98 p=h;//将指针指向链表的头结点首地址 99 while(j<i-1) 100 { 101 p=p->next; 102 j++; 103 } 104 q=p->next; /*q指向要删除的位置之前的那个结点指针域指向的地址q指向的结点就是要删除的结点*/ 105 106 p->next=q->next;/*这个就是将要删除的结点的前面那个结点的指针域指向要删除的结点指针域中存放的下个结点的首地址从而实现了删除第i个结点的作用*/ 107 108 val = q->val; 109 110 free(q);//释放q指向的结点 111 } 112 } 113 114 int main() 115 { 116 ListNode *h;//h指向结构体NODE 117 int n, val; 118 119 cin>>n;//输入链表节点个数 120 h=CreateList(n);/*创建链表*/ 121 cout<<"list elements is : "<<endl; 122 PrintList(h); 123 InsertList(h,1,10,n); 124 cout<<"list elements is : "<<endl; 125 PrintList(h); 126 DeleteList(h,3,n); 127 cout<<"list elements is : "<<endl; 128 PrintList(h); 129 130 }
运行结果如图: