通常使用的八大排序算法是:插入排序、选择排序、冒泡排序、希尔排序、归并排序、快速排序、堆排序、基数排序,每一个方法有其适合的使用场景,能够根据具体数据进行选择.算法
几个概念:shell
内部排序:排序期间元素所有存放在内存中的排序;api
外部排序:排序期间元素没法所有存放在内存中,必须在排序过程当中根据要求不断地进行内外存之间移动地排序;数组
(这八种排序算法中除了多路归并排序是外部排序,其余都是内部排序)微信
稳定性:指的是通过排序后,值相同的元素保持原来顺序中的相对位置不变.函数
各算法时间复杂度、空间复杂度、所需辅助空间与稳定性以下:
性能
冒泡排序思想很简单,就是对每一个下标i,取j从0到n-1-i(n是数组长度)进行遍历,若是两个相邻的元素s[j]>s[j+1],就交换,这样每次最大的元素已经移动到了后面正确的位置.ui
void bubbleSort(vector<int> &s){ for (int i = 0; i < s.size(); i++){ for (int j = 0; j < s.size() - 1 - i; j++){ if (s[j] > s[j + 1]) swap(s[j], s[j + 1]); } } }
冒泡排序的特色:稳定,每次排序后,后面的元素确定是已经排好序的,因此每次排序后能够肯定一个元素在其最终的位置上,冒泡排序比较次数:n(n-1)/2,移动次数:3n(n-1)/2.spa
插入排序又分为简单插入排序和折半插入排序;简单插入排序思想是每趟排序把元素插入到已排好序的数组中,折半插入排序是改进的插入排序,因为前半部分为已排好序的数列,这样咱们不用按顺序依次寻找插入点,能够采用折半查找的方法来加快寻找插入点的速度.code
简单插入排序:
void insertSort(vector<int> &s){ if (s.size() > 1){ for (int i = 1; i < s.size(); i++){ for (int j = i; j > 0 && s[j] < s[j - 1]; j--){ swap(s[j], s[j - 1]); } } } }
折半插入排序:
void binaryInsertSort(vector<int> &s){ int low, high, m, temp, i, j; for (i = 1; i < s.size(); i++){ //采用折半查找方法,寻找应该插入的位置 low = 0; high = i - 1; while (low <= high){ m = (low + high) / 2; if (s[m] > s[i]) high = m - 1; else low = m + 1; } //统一移动元素,将该元素插入到正确的位置 temp = s[i]; for (j = i; j > high + 1; j--){ s[j] = s[j - 1]; } s[high + 1] = temp; } }
插入排序的特色:稳定,最坏状况下比较n*(n-1)/2次,最好状况下比较n-1次
选择排序思想是对每一个下标i,从i后面的元素中选择最小的那个和s[i]交换.
void selectSort(vector<int> &s){ if(s.size()>1){ for(int i=0;i<s.size();i++){ int min=i; for(int j=i+1;j<s.size();j++){ if(s[j]<s[min]) min=j; } swap(s[i], s[min]); } } }
选择排序的特色:不稳定,每趟排序后前面的元素确定是已经排好序的了,每次排序后能够肯定一个元素会在其最终位置上.
快速排序是内排序中平均性能较好的排序,思想是每趟排序时选取一个数据(一般用数组的第一个数)做为关键数据,而后将全部比它小的数都放到它的左边,全部比它大的数都放到它的右边.
void quickSort(vector<int> &s, int low, int high){ if (low >= high) return; int l = low, r = high, val = s[low]; while (l < r){ while (l < r&&s[r] >= val) r--; if (l < r) s[l++] = s[r]; while (l < r&&s[l] <= val) l++; if (l < r) s[r--] = s[l]; } s[l] = val; quickSort(s, low, l - 1); quickSort(s, r + 1, high); }
快速排序的特色:
1.不稳定;
2.快速排序过程当中不会产生有序子序列,但每一趟排序后都有一个元素放在其最终位置上;
3.每次选择的关键值能够把数组分为两个子数组的时候,快速排序算法的速度最快,当数组已是正序或逆序时速度最慢;
4.递归次数与每次划分后获得的分区的处理顺序无关;
5.对n个关键字进行快速排序,最大递归深度为n,最小递归深度为log2n;
上面的快速排序算法是递归算法,非递归算法使用栈来实现:
//进行区域的划分 int partition(vector<int> &s, int low, int height){ int val = s[low]; while (low < height){ while (low < height&&s[height] >= val) height--; s[low] = s[height]; while (low < height&&s[low] <= val) low++; s[height] = s[low]; } s[low] = val; return low; } //排序 void quickSortNonRecursive(vector<int> &s, int low, int height){ stack<int> p; if (low < height){ int mid = partition(s, low, height); if (mid - 1 > low){ p.push(low); p.push(mid - 1); } if (mid + 1 < height){ p.push(mid + 1); p.push(height); } while (!p.empty()){ int qHeight = p.top(); p.pop(); int pLow = p.top(); p.pop(); int pqMid = partition(s, pLow, qHeight); if (pqMid - 1 > pLow){ p.push(pLow); p.push(pqMid - 1); } if (pqMid + 1 < qHeight){ p.push(pqMid + 1); p.push(qHeight); } } } }
希尔排序是基于插入排序的一种排序算法,思想是对长度为n的数组s,每趟排序基于间隔h分红几组,对每组数据使用插入排序方法进行排序,而后减少h的值,这样刚开始时候虽然分组比较多,但每组数据不多,h减少后每组数据多但基本有序,而插入排序对已经基本有序的数组排序效率较高.
void shellSort(vector<int> &s){ int n = s.size(), h = 1; while (h < n / 3) h = 3 * h + 1; while (h >= 1){ for (int i = h; i < n; i++){ for (int j = i; j >= h && s[j] < s[j - h]; j -= h){ swap(s[j], s[j - h]); } } h /= 3; } }
希尔排序的特色:不稳定,每次排序后不能保证有一个元素在最终位置上
归并排序的思想是将两个有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每一个子序列是有序的。而后再把有序子序列合并为总体有序序列。即先划分为两个部分,最后进行合并。
const int maxn=500000,INF=0x3f3f3f3f; int L[maxn/2+2],R[maxn/2+2]; void merge(vector<int> &a,int n,int left,int mid,int right){ int n1=mid-left,n2=right-mid; for(int i=0;i<n1;i++) L[i]=a[left+i]; for(int i=0;i<n2;i++) R[i]=a[mid+i]; L[n1]=R[n2]=INF; int i=0,j=0; for(int k=left;k<right;k++){ if(L[i]<=R[j]) a[k]=L[i++]; else a[k]=R[j++]; } } void mergesort(vector<int> &a,int n,int left,int right){ if(left+1<right){ int mid=(left+right)/2; mergesort(a,n,left,mid); mergesort(a,n,mid,right); merge(a,n,left,mid,right); } }
归并排序特色:稳定,能够用在顺序存储和链式存储的结构,时间复杂度在最好和最坏状况下都是O(nlogn)
堆排序是基于选择排序的一种排序算法,堆是一个近似彻底二叉树的结构,且知足子结点的键值或索引老是小于(或者大于)它的父节点。这里采用最大堆方式:位于堆顶的元素老是整棵树的最大值,每一个子节点的值都比父节点小,堆要时刻保持这样的结构,因此一旦堆里面的数据发生变化,要对堆从新进行一次构建。
void max_heapify(vector<int> &arr, int start, int end) { //创建父节点指标和子节点指标 int dad = start; int son = dad * 2 + 1; while (son <= end) { //若子节点指标在范围内才作比较 if (son + 1 <= end && arr[son] < arr[son + 1]) //先比较两个子节点大小,选择最大的 son++; if (arr[dad] > arr[son]) //若是父节点大於子节点表明调整完毕,直接跳出函数 return; else { //不然交换父子内容再继续子节点和孙节点比较 swap(arr[dad], arr[son]); dad = son; son = dad * 2 + 1; } } } void heap_sort(vector<int> &arr, int len) { //初始化,i从最後一个父节点开始调整 for (int i = len / 2 - 1; i >= 0; i--) max_heapify(arr, i, len - 1); //先将第一个元素和已经排好的元素前一位作交换,再重新调整(刚调整的元素以前的元素),直到排序完毕 for (int i = len - 1; i > 0; i--) { swap(arr[0], arr[i]); max_heapify(arr, 0, i - 1); } }
堆排序特色:不稳定,最坏,最好,平均时间复杂度均为O(nlogn)
基数排序是一种非比较型整数排序算法,其原理是将数据按位数切割成不一样的数字,而后按每一个位数分别比较,在相似对百万级的电话号码进行排序的问题上,使用基数排序效率较高
//寻找数组中最大数的位数做为基数排序循环次数 int KeySize(vector<int> &s, int n){ int key = 1; for (int i = 0; i < n; i++){ int temp = 1; int r = 10; while (s[i] / r > 0){ temp++; r *= 10; } key = (temp > key) ? temp : key; } return key; } //基数排序 void RadixSort(vector<int> &s, int n){ int key = KeySize(s, n); int bucket[10][10] = { 0 }; int order[10] = { 0 }; for (int r = 1; key > 0; key--, r *= 10){ for (int i = 0; i < n; i++){ int lsd = (s[i] / r) % 10; bucket[lsd][order[lsd]++] = s[i]; } int k = 0; for (int i = 0; i < 10; i++){ if (order[i] != 0){ for (int j = 0; j < order[i]; j++) s[k++] = bucket[i][j]; } order[i] = 0; } } }
基数排序特色:稳定,时间复杂度为O (nlog(r)m),其中r为所采起的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
部分参考:百度百科、微信公众号这或许是东半球分析十大排序算法最好的一篇文章https://mp.weixin.qq.com/s/Qf416rfT4pwURpW3aDHuCg