1、直接插入排序
最好O(n),最坏时间O(n^2),较稳定
基本思想:在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。
#include <iostream> using namespace std; int main() { int a[]={5,2,6,3,9}; int len=sizeof(a)/sizeof(a[0]); for(int i=1;i<len;++i) { if(a[i]<a[i-1]) { int t=a[i],j; for(j=i-1;j>=0&&a[j]>t;--j) a[j+1]=a[j]; a[j+1]=t; } } for(int i=0;i<len;++i) cout<<a[i]<<" "; cout<<endl; return 0; }
2、希尔排序
时间复杂度:O(n*log(n)),不稳定
由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
直接插入排序的的步长是为1的,而希尔排序向前比较是以一定步长向前比较。
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> arr{5,2,6,3,9}; for(int gap=arr.size()/2;gap>0;gap/=2)//步长 { for(int i=gap;i<arr.size();++i)//和直接插入排序一样,只不过是每次向前移动k步比较而不是移动一步比较 { int tmp=arr[i]; int j=i-gap; while(j>=0&&arr[j]>tmp) { arr[j+1]=arr[j]; j-=gap; } arr[j+gap]=tmp; } } for_each(arr.begin(),arr.end(),[](int i)->void{cout<<i<<" ";}); cout<<endl; return 0; }
3、简单选择排序
最好:O(n^2),最坏:O(n^2),不稳定
基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> arr{5,2,6,3,9}; for(int i=0;i<arr.size()-1;++i) { int minIndex=i; for(int j=i+1;j<arr.size();++j) { if(arr[j]<arr[minIndex]) minIndex=j; } if(minIndex!=i) swap(arr[minIndex],arr[i]); } for_each(arr.begin(),arr.end(),[](int i)->void{cout<<i<<" ";}); cout<<endl; return 0; }
4、堆排序
最好:nlog(n),最坏:nlog(n),不稳定
堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了
初建堆:
将一个任意序列看成是对应的完全二叉树,由于叶结点可以视为单元素的堆,因而可以反复利用上述调整的算法,自底向上逐层把所有子树调整为堆,直到整个完全二叉树为堆。最后一个非叶节点位于n/2(向下取整)个位置,n为二叉树结点数目,因此,筛选从n/2(向下取整)个结点开始,逐层向上倒退,知道根节点。
调整堆:
这是为了保持堆的特性而做的一个操作。对某一个节点为根的子树做堆调整,其实就是将该根节点进行“下沉”操作(具体是通过和子节点交换完成的),一直下沉到合适的位置,使得刚才的子树满足堆的性质。
- 在对应的数组元素A[i], 左孩子A[LEFT(i)], 和右孩子A[RIGHT(i)]中找到最大的那一个,将其下标存储在largest中。
- 如果A[i]已经就是最大的元素,则程序直接结束。
- 否则,i的某个子结点为最大的元素,将A[largest]与A[i]交换。
- 再从交换的子节点开始,重复1,2,3步,直至叶子节点,算完成一次堆调整。
这里需要提一下的是,一般做一次堆调整的时间复杂度为log(n)。
堆排序:
数组储存成堆的形式之后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n-2]交换,再对A[0…n-3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。
#include <iostream> #include <vector> using namespace std; /* 对于具有n个节点的完全二叉树,如果按照从上(根节点)到下(叶节点)和从左到右的顺序对二叉树中的所有节点从0开始到n-1进行编号,则对于任意的下标为k的节点,有: 如果k=0,则它是根节点,它没有父节点;如果k>0,则它的父节点的下标为[(i-1)/2]; 如果2k+1 <= n-1,则下标为k的节点的左子结点的下标为2k+1;否则,下标为k的节点没有左子结点. 如果2k+2 <= n-1,则下标为k的节点的右子节点的下标为2k+2;否则,下标为k的节点没有右子节点 */ class HeapSort { private: void adjustHeap(vector<int>& arr,int start,int end) { //2.调整堆:已知arr[start...end]中除关键字arr[start]之外均满足堆的定义 //本函数调整arr[start]的关键字,使arr[start,end]成为一个大根堆 int res=arr[start]; for(int j=start*2+1;j<=end;j=j*2+1) { if(j<end&&arr[j]<arr[j+1])//找出左右子树中最大的 ++j; if(res>arr[j]) break; arr[start]=arr[j]; start=j;//因为j和start交换了,所以要更新start的值;向下调整 } arr[start]=res; } public: void heapSort(vector<int>& arr) { int len=arr.size(); //1.初建堆 for(int i=len/2-1;i>=0;--i)//可以把数组中len/2-1后的元素看做单根堆,均满足堆的定义;所以向上调整 adjustHeap(arr,i,len-1); //堆排序 for(int i=len-1;i>0;--i) { swap(arr[0],arr[i]); adjustHeap(arr,0,i-1); } return ; } }; int main() { vector<int> arr{54,35,48,36,27,12,44,44,8,14,26,17,28}; HeapSort hs; hs.heapSort(arr); for(auto i:arr) cout<<i<<" "; cout<<endl; return 0; }
#include <iostream> #include <vector> #include <algorithm> using namespace std; class Heap_sort { public: void build_heap(vector<int> &v); void adjust_heap(vector<int> &v,vector<int>::iterator i,vector<int>::iterator dis); void sort_heap(vector<int> &v); vector<int>::iterator left_child(vector<int> &v,vector<int>::iterator i); vector<int>::iterator right_child(vector<int> &v,vector<int>::iterator i); }; //获取左节点下标 vector<int>::iterator Heap_sort::left_child(vector<int> &v,vector<int>::iterator i) { advance(i,distance(v.begin(),i)); return i; } //获取右节点下标 vector<int>::iterator Heap_sort::right_child(vector<int> &v,vector<int>::iterator i) { advance(i,distance(v.begin(),i)+1); return i; } //调整堆 void Heap_sort::adjust_heap(vector<int> &v,vector<int>::iterator i,vector<int>::iterator dis) { vector<int>::iterator l=left_child(v,i); vector<int>::iterator r=right_child(v,i); vector<int>::iterator largest=v.begin(); if((*i)<(*l)&&(l<dis)) largest=l; else largest=i; if((*largest)<(*r)&&(r<dis)) largest=r; //建的是大顶锥 if(largest!=i) { swap(*largest,*i); adjust_heap(v,largest,dis); } } //初建堆 void Heap_sort::build_heap(vector<int> &v) { for(vector<int>::iterator i=v.begin()+v.size()/2-1;i!=--v.begin();--i) adjust_heap(v,i,v.end()); } //堆排序 void Heap_sort::sort_heap(vector<int> &v) { build_heap(v);//堆排序之前应该先初建堆 for(auto i=--v.end();i!=--v.begin();--i) { swap(*i,*(v.begin()));//交换第一个元素(最大的)与最后一个元素 adjust_heap(v,v.begin(),i);//交换完之后第一个元素不是最大的,要调整堆使第一个元素最大 } } int main() { vector<int> v{70,30,40,10,80,20,90,100,75,60,45};//看做完全二叉树 Heap_sort hs;//创建堆排序的对象 hs.sort_heap(v); for(auto i:v) cout<<i<<" "; cout<<endl; return 0; }