排序还是最基础的。
链表的排序总共知道三种,插入、快排、归并。。经过分析之后惊奇的发现链表的快排是稳定的🤣
快排的思想,partition的时候分为三种情况,左 中 右 三段新链表。新数据小于base,接在左边的后面,大于base,接在右边的后边,否则接在中间的后边。
注意选基准值为第一个,第一个作为mid的开头即为稳定排序。
插排的思想,遍历listNode,一个个重新插入即可,插入的时候插在大于自己的位置左边。即为稳定排序,插排适合基本有序或者数据较短的情况。
归并的思想,快慢结点找到中间位置,断开成两段,归并一下即可。
数据的排序就不发了,资料都比较全了我写的也不好。放在最后两个展开的吧,一个是归并不递归,一个是快排不递归,都是python写的。
主要排序应用场景:
冒泡:排行榜
插排:基本有序,小数据
希尔排序:就是间隔从大到小在变的插排。适合当base排序,适合各种情况。速度不行再换别的,算是工具人(排序)了。
归并:各种程度上的稳定排序,空间复杂度O(n)算是比较高了,注意最好不要递归里频繁的开辅助空间,最好事先开好一个O(n)的辅助空间。最好是不递归的更快一点。速度比快排慢。
快排:快,O(1)辅助空间,随机基准位置+三路排序优化,实际中C++的标准sort是快排+插排,所以是不稳定的,但速度比快排的qsort快。想稳定用stable_sort,stable_sort是归并排序。这里产生了疑问,为什么stable_sort用归并排序呢?稳定的快排需要额外O(n)空间和归并应该一样呀。。.(stable_sort是稳定排序的简单证明小程序在最后,C++)
注意快排比归并快一半左右,所以常数差一倍。为啥快一半呢?个人愚见,,可以假设最理想情况下,快排每次切中间。这样快排和归并排序都是一样的切法。
在部分排序中,快排扫描一次,归并扫描两次,归并需要左右扫描到辅助数据里再拷贝原位置所以扫描赋值了两遍,快排是N,那么归并就是2N。
所以二者总复杂度常数差一倍,快排1s归并就需要2s。但是快排最好才是切到中间,所以快排的时间复杂度是归并的一半多一点点。
因为快排的情况是绝大多数情况下接近时间复杂度下界,所以平均复杂度比理想下界慢一点点。(只要随机选择base平均复杂度就比较接近下届了,但很多博客比较排序时间的时候都不写。。。)
但是稳定排序的快排需要辅助数组帮忙,而且是扫描两遍,那么我们就知道了,理想情况下稳定快排和归并时间复杂度一样,但是稳定快排最理想在中间,归并本来就在中间,所以稳定快排复杂度比归并高那么一丁点点。
所以猜测这就是sort用快排打基础,stable_sort用归并原因?
实际时间这里我查阅了不少网上的资料,我真的服了,时间复杂度比较,比个锤子。
首先在很多人的代码里,快排写不对,写个一路快排就当快排也就算了。。然后归并也写不对,比时间?。。。写的时候不用非递归的写法也就算了,最后得出的结论要么是归并排序比快排快,甚至是堆排序比快排快。我整个人都傻了。另一份帖子里归并慢的一批,比堆排序还慢,是快排时间的3倍多?喵喵喵?
我这里找到的一份时间资料,但是他没有给代码。。。个人觉得理论平均时间快排是归并的时间一半朝上都是可以的,最起码代码没写错。
计数排序:用于辅助桶排序,工具人。对数据范围有限但数据很多的情况下非常实用,比如高考成绩全排名。不稳定,但速度是数据个数和数据范围之和的线性关系(O(m)+O(n))。
桶排序:数据切成桶,里面再排序。用于数据非常非常非常多的时候,因为数据特别特别多,需要存到多个电脑里,然后每个电脑当成一个桶或者多个桶,能够获得非常理想的时间复杂度。
C++链表快排、归并、插排
#include <iostream> #include <stack> using namespace std; struct ListNode { int val; ListNode *next; ListNode(const int&val0):val(val0),next(nullptr){}; void print() { ListNode* cur = this;//常指针不危险 while(cur->next) { cout<<cur->val<<"->"; cur = cur->next; } cout<<cur->val<<endl; } }; void delete_ListNode(ListNode*a) { stack<ListNode*>s; while(a) { s.push(a); a=a->next;} while(s.empty()==false) { ListNode* cur = s.top(); s.pop(); delete cur; } return; } //链表快排似乎是稳定的 ListNode* qSort_ListNode(ListNode* head) { //C++只能有一个返回值呀,所以把部分排序拆开了 //三路快排 if(head==nullptr || head->next==nullptr)return head; //前中后头结点 ListNode *L = new ListNode(-1); ListNode *cur_L = L; ListNode *M = head; ListNode *cur_M = M; ListNode *R = new ListNode(-1); ListNode *cur_R = R; //凡是递归里new出来的都要在递归里删掉 ListNode *cur = head->next; while(cur) { if(cur->val < head->val) { cur_L->next = cur; cur_L = cur_L->next; cur = cur->next; continue; } if(cur->val == head->val) { cur_M->next = cur; cur_M = cur_M->next; cur = cur->next; continue; } if(cur->val > head->val) { cur_R->next = cur; cur_R = cur_R->next; cur = cur->next; continue; } } //添加结尾标志符号 cur_L->next = nullptr; cur_R->next = nullptr; //数据被分成了三段,分别是左 中 右 //中间与右边接起来 cur_M->next = qSort_ListNode(R->next); //析构工具人 delete(R); R=nullptr; cur_L = qSort_ListNode(L->next); delete L; L = cur_L; if(L == nullptr)return M; //res代表最左边,不空,要遍历到结尾,导致链表的快排比归并慢一点。当然最根本的原因就是链表快排丧失了随机初始化的base选择和原地swap,而且稳定的快排并不比归并好多少,所以链表的快排并不如归并排序 while(cur_L->next)cur_L = cur_L->next; //接起来 cur_L->next = M; return L; } //两段链表merge连起来 ListNode* merge_ListNode(ListNode* left,ListNode* right) { ListNode* head = new ListNode(-1); ListNode* cur= head; while(left && right) { if(left->val<=right->val)//<=稳定排序 { cur->next = left; left = left->next; cur = cur->next; continue; }else { cur->next = right; right = right->next; cur = cur->next; continue; } } if(left)cur->next = left; if(right)cur->next = right; cur = head->next; delete head; return cur; } ListNode* mergeSort_ListNode(ListNode* head) { if(head==nullptr || head->next==nullptr)// C++会更倾向于nullptr==head的写法,因为如果不小心==写成=,会报错,nullptr不允许赋值,容易排查出来 { return head; } ListNode *fast = head,*slow = head; while(fast->next && fast->next->next) { fast = fast->next->next; slow = slow->next;//slow停在中间或中间靠左的位置 } //截成两半 fast = slow->next; slow->next = nullptr; head = mergeSort_ListNode(head); fast = mergeSort_ListNode(fast); return merge_ListNode(head,fast); } //插入 ListNode* insert(ListNode* head,ListNode* insert_ListNode) { if(head==nullptr)return insert_ListNode; if(head->val > insert_ListNode->val) { insert_ListNode->next = head; return insert_ListNode; } ListNode *cur = head; while(cur->next && cur->next->val <= insert_ListNode->val)cur = cur->next; insert_ListNode->next = cur->next; cur->next = insert_ListNode; return head; } //插入排序 ListNode *insertSort_ListNode(ListNode *head) { if(head==nullptr)return head; ListNode *L = new ListNode(-1); ListNode *cur = head; while(cur) { head = head->next; cur ->next = nullptr; L->next = insert(L->next,cur); cur = head; } cur = L->next; delete(L); return cur; } int main() { //List A; //A.print(); ListNode*B = new ListNode(1); B->next = new ListNode(5); B->next->next = new ListNode(2); B->next->next->next = new ListNode(4); B->next->next->next->next = new ListNode(3); B->print(); //B = qSort_ListNode(B); B = mergeSort_ListNode(B); //B = insertSort_ListNode(B); B->print(); delete_ListNode(B); return 0; }python:没写插排
#qsort_listNode class listNode: def __init__(self,val,next=None): self.val = val self.next = next def print(self): res = [self.val] cur = self.next while cur: res.append(cur.val) cur = cur.next print(res) a = listNode(1) a.next,a.next.next,a.next.next.next,a.next.next.next.next,a.next.next.next.next.next,a.next.next.next.next.next.next=listNode(6),listNode(2),listNode(5),listNode(3),listNode(4),listNode(0) a.print() def partition_listNode(st): if not st or st.next is None: return (None,st,None) base = st #三路切分 left = listNode(-1) mid = listNode(-1) right = listNode(-1) head_l,head_m,head_r=left,mid,right cur = st.next while cur: if cur.val<base.val: left.next = cur left = left.next cur = cur.next continue if cur.val>base.val: right.next = cur right = right.next cur = cur.next continue mid.next = cur mid = mid.next cur = cur.next continue #这一步使得排序变得不稳定了 mid.next = base mid = mid.next #修改方法很简单,把下面注释的加上 #mid.next = None #base.next = head_m.next #head_m.next = base ## left.next =None mid.next = None right.next =None #头结点的下一个才是真正的开头 return (head_l.next,head_m.next,head_r.next) #快排,可以稳定 def qsort_List(st): if not st or st.next is None: return st res = partition_listNode(st) left,mid,right = res[0],res[1],res[2] #右边不空 if right: mid.next = qsort_List(right) res = mid if not left: return res #左边不空 res = qsort_List(left) cur = res #sort的返回值是头结点,如果可以返回头结点和尾节点,这里就不需要走到最后了 #这一步复杂度增加了n,总体复杂度是2nlog(n),还是nlog(n)复杂度 while cur.next: cur = cur.next cur.next = mid return res qsort_List(a).print() #归并排序,稳定 #merge_sort_listNode def merge_listNode(left,right): new = listNode(-1) cur = new while left and right: if left.val<=right.val: cur.next = left cur = cur.next left = left.next else: cur.next = right cur = cur.next right = right.next if left: cur.next = left else: cur.next = right return new.next def merge_sort_listNode(st): if not st or not st.next: return st fast,slow = st.next,st while fast and fast.next: fast = fast.next.next slow = slow.next #slow是中间结点 left_end,right = slow,slow.next left_end.next = None left,right = merge_sort_listNode(st),merge_sort_listNode(right) return merge_listNode(left,right) a = listNode(1) a.next,a.next.next,a.next.next.next,a.next.next.next.next,a.next.next.next.next.next,a.next.next.next.next.next.next=listNode(6),listNode(2),listNode(5),listNode(3),listNode(4),listNode(0) a.print() merge_sort_listNode(a).print()C++:stable稳定肉眼判定
因为数据少了,sort用的是插排,所以我看的一些网上的资料说sort和stable_sort排序结果没区别,我真服了,就几个数据,都掉用插入排序,肯定没区别呀
感兴趣的可以把
for(int j=0;j<500;j++)改小一点,5-15吧,发现两个排序结果一样了,原因就是sort函数调用的是插入排序而不是快排了,插入排序是稳定滴
#include<iostream> #include<algorithm> #include<string> #include<vector> #include <iostream> #include <cstdlib> #include <ctime> #define random(x) rand()%(x) const int SIZE_CHAR = 6; //生成32 + 1位C Style字符串 using namespace std; bool less_len(const string &str1, const string &str2) { return str1.length() < str2.length(); } void print(vector<string>&data) { for(c:data)cout<<c<<"->"; cout<<endl; } int main() { string CCH = "_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"; srand(time(0)); vector<string>s1,s2; for(int j=0;j<500;j++) { string tmp; tmp=""; int size_s = rand()%SIZE_CHAR+1; for (int i = 0; i < size_s; ++i) { int x = rand()/(RAND_MAX /(sizeof(CCH)-1)); tmp.push_back(CCH[x]); } s1.push_back(tmp); s2.push_back(tmp); } print(s1); cout<<endl; cout<<endl; cout<<endl; cout<<endl; cout<<endl; sort(s1.begin(),s1.end(),less_len); cout<<endl; cout<<endl; cout<<endl; cout<<endl; cout<<endl; stable_sort(s2.begin(),s2.end(),less_len); print(s1); print(s2); }
快排非递归,两路快排:
def q_sort_bottom(data,st=None,en=None): if not data: return if st is None: st = 0 if en is None: en = len(data)-1 if st>=en: return import queue q = queue.Queue() q.put((st,en)) while(q.empty()==False): cur = q.get() st,en = cur[0],cur[1] import random index = random.randint(st,en) data[index],data[st]=data[st],data[index] l,r = st+1,en base = data[st] index = st while l<=r: #左端点比叫base,小值则交换base和当前左端点,大值则当前左端点和尾端交换 if data[l]<base: data[index],data[l],index,l=data[l],data[index],index+1,l+1 continue if data[l]>base: data[l],data[r],r=data[r],data[l],r-1 continue l+=1 if st<index-1: q.put((st,index-1)) if r+1<en: q.put((r+1,en)) return data print(q_sort(old_data[:])) print(q_sort_bottom(old_data[:]))归并非递归:
写的很丑。。。
#非递归,每次相邻合并一下即可,步长从1自增 def merge(data,l1,r1,l2,r2,data_help): cur_index = 0 t_l1,t_l2=l1,l2 while(l1<=r1 and l2<=r2): if data[l1]<=data[l2]: data_help[cur_index],cur_index,l1,=data[l1],cur_index+1,l1+1 else: data_help[cur_index],cur_index,l2=data[l2],cur_index+1,l2+1 while(l1<=r1): data_help[cur_index],cur_index,l1,=data[l1],cur_index+1,l1+1 while(l2<=r2): data_help[cur_index],cur_index,l2=data[l2],cur_index+1,l2+1 l1,l2=t_l1,t_l2 for index in range(r1-l1+1): data[l1+index]=data_help[index] for index in range(r2-l2+1): data[l2+index]=data_help[index+r1-l1+1] def merge_bottom(data,st = None,en = None): if not data: return None if st is None: st = 0 if en is None: en = len(data)-1 step = 1 data_help = [0]*(en-st+1) while step<=en-st+1: for left in range(st,en-step+1,2*step): l1,r1,l2,r2 = left,left+step-1,left+step,left+2*step-1 if r2>en: r2=en merge(data,l1,r1,l2,r2,data_help) step*=2 print(data) merge_sort(old_data[:]) merge_bottom(old_data[:])非常粗糙简单的桶排序:
#工具人排序 def nums_sort(data): if not data: return [] min_data = min(data) max_data = max(data) nums = [0]*(max_data-min_data+1) for value in data: nums[value-min_data]+=1 cur = min_data for index in range (len(data)): while cur<= max_data and nums[cur-min_data]<=0: cur+=1 nums[cur-min_data]-=1 data[index] = cur return data print(nums_sort(old_data[:])) def bucket_sort(data,K = 10): if not data: return #K桶的大小 桶的大小 = data的范围时,就一个桶,可以退化为计数排序 if K<=0: K = 10 min_data = min(data) max_data = max(data) #//K向下取整,需要多补一个,计数排序返回空[]才可以用extend函数 buckets = [[] for _ in range(1+(max_data-min_data)//K)] for value in data: buckets[(value-min_data)//K].append(value) res = [] for i in buckets: res.extend(nums_sort(i)) return res print(bucket_sort(old_data[:],K=2))