排序算法稳定性:两个相同的元素排序前后的相对位置关系不会发生改变。
复杂度比较
算法 | 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(N^2) | O(N^2) | O(N^2) | O(1) | 稳定 |
插入排序 | O(N^2) | O(N) | O(N^2) | O(1) | 稳定 |
选择排序 | O(N^2) | O(N^2) | O(N^2) | O(1) | 稳定 |
希尔排序 | O(N ^ 3/2 ) | O(N^2) | O(1) | 不稳定 | |
堆排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(1) | 不稳定 |
归并排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(N) | 稳定 |
快速排序 | O(NlogN) | O(NlogN) | O(N^2) | O(logN) | 不稳定 |
冒泡排序
排序过程
- 将第一个元素与第二个元素比较大小,如果第一个元素大于第二个元素则调换他们两的位置;
- 比较第二个元素和第三个元素的大小,如果第二个元素大于第三个元素则调换他们两的位置;
- 依次类推,进行两两元素的比较和交换,最终最大的元素排在了最后面;
- 重复1到3过程,直到所有元素都排序。
图片演示

//冒泡排序 //平均时间复杂度:O(N^2) //最坏情况复杂度:O(N^2) //空间复杂度:O(1) //稳定排序 void bubblesort(vector<int>& a) { int n = a.size(); for (int i = 0; i < n; i++) { for (int j = 0; j < n - 1 - i; j++) { if (a[j] > a[j+1]) swap(a[j], a[j+1]); } } }
//冒泡排序升级版1 //设置一个标记来标志一趟比较是否发生交换 //如果没有发生交换,则数组已经有序 void bubblesort2(vector<int>& a) { int n = a.size(); bool flag; for (int i = 0; i < n; i++) { flag = false; for (int j = 0; j < n - 1 - i; j++) { if (a[j] > a[j+1]) { swap(a[j], a[j+1]); flag = true; } } if (!flag) break; } }
//冒泡排序优化二 //用一个变量记录下最后一个发生交换的位置,后面没有发生交换的已经有序 //所以可以用这个值来作为下一次比较结束的位置 void bubblesort3(vector<int>& a) { int n = a.size(); int flag = n; int stop_pos; for (int i = 0; i < n; i++) { stop_pos = flag - 1; flag = 0; for (int j = 0; j < stop_pos; j++) { if (a[j] > a[j+1]) { swap(a[j], a[j+1]); flag = j + 1; } } } }
插入排序
- 对于第K个元素,将该元素的值存储在零时变量中,比较第前一个元素与该元素的大小,如果大于该元素就将前一个元素往后移动一步;
- 比较前面第二个元素与该元素的大小,如果大于该元素就将前第二个元素往后移动一步;
- 重复上述过程直到找到小于等于原来第K个元素(保存在零时变量中)的位置,并将第K个元素插入到这个元素的后面。或者找不到小于等于第K个元素的位置,就将原来第K个元素插入到数组的首地址。
图片演示

//插入排序 //平均时间复杂度:O(N^2) //最坏情况复杂度:O(N^2) //最好情况复杂度:O(N) //空间复杂度:O(1) //最多需要n(n−1)/2次比较 //最少需要n−1次比较 //稳定排序 void insertsort(vector<int>& a) { int n = a.size(); for (int i = 1; i < n; i++) { int insert_num = a[i], j; for (j = i - 1; j >= 0; j--) { if (a[j] > insert_num) a[j + 1] = a[j]; else break; } a[j + 1] = insert_num; } }
选择排序
1、每一次从后面选择出一个最小的值(swap_pos),替换到前面来(i)。
//选择排序 //平均时间复杂度 O(n^2) //最坏时间复杂度 O(n^2) //最好时间复杂度 O(n^2) //空间复杂度 O(1) //我这个写法 是稳定排序 void select_sort(vector<int>& vt) { for (int i = 0; i < vt.size() - 1; i ++) { int swap_pos = i; for (int j = i + 1; j < vt.size(); j++) { if (vt[swap_pos] > vt[j]) { swap_pos = j; } } if (swap_pos != i) { swap(vt[swap_pos], vt[i]); } } }
希尔排序
希尔排序是在插入排序的基础上进行发展的,通过一个希尔增量先排序一定间隔的数据。
排序过程
- 插入排序每次与前面一个比较,然后再往前一个,而希尔排序每次往前K个;
- 当增量为1的时候,希尔排序与插入排序就完全是一样的过程;
- 所以代码也很好实现,将插入排序中增1的地方改为增K就行。
图片演示
初始 | 81 | 94 | 11 | 96 | 12 | 35 | 17 | 95 | 28 | 58 |
---|---|---|---|---|---|---|---|---|---|---|
第一趟5排序后 | 35 | 17 | 11 | 28 | 12 | 81 | 94 | 95 | 96 | 58 |
第二趟3排序后 | 28 | 12 | 11 | 35 | 17 | 81 | 58 | 95 | 96 | 94 |
第三趟1排序后 | 11 | 12 | 17 | 28 | 35 | 58 | 81 | 94 | 95 | 96 |
复杂度分析
希尔排序的时间复杂度比较复杂,选用不同的希尔增量也会导致复杂度不同。
//希尔排序 //最坏情况复杂度:O(N^2) //不稳定排序 void shellsort(vector<int>& a) { int n = a.size(); for (int increment = n / 2; increment > 0; increment /= 2) { for (int i = increment; i < n; i++) { int insert_num = a[i], j; for (j = i - increment; j >= 0; j -= increment) { if (a[j] > insert_num) a[j + increment] = a[j]; else break; } a[j + increment] = insert_num; } } }
堆排序
排序过程
- 建立最大堆,建堆的过程是从N/2的位置开始,将父节点与子节点比较,如果子节点大于父节点则交换。为什么是N/2,是因为堆中树叶的个数是N/2。
- 从堆中删除堆顶元素,对于最大堆而言,堆顶元素也就是最大元素。每删除一个堆顶元素,就将堆顶元素放在数组的后面,因为每删除一个就出现一个空位,所以数组后面是有地方存放的。
- 进行N-1次的删除以后,整个数组就是排序的状态了。
图片演示

//堆排序 //建堆的平均时间是:O(N) //建堆的最坏情况是:O(NlogN) //删除元素的时间是:O(logN) //整个排序平均时间复杂度:O(N+NlogN)=O(NlogN) //最坏情况复杂度:O(NlogN) //不稳定排序 //建立一个大顶堆O(n),要求就是 把最大的元素 移动到堆顶 也就是a[0] void make_heap(vector<int>& a, int size) //size的当前堆的大小,也就是数组的前size个数 { for (int i = size - 1; i > 0; i--) { if (i % 2 && a[i] > a[(i - 1) / 2])//奇数 swap(a[i], a[(i - 1) / 2]); else if (i % 2 == 0 && a[i] > a[(i - 2) / 2])//偶数 swap(a[i], a[(i - 2) / 2]); } } void heapsort(vector<int>& a) { int n = a.size(); while (n) { make_heap(a, n); //每次把新的最大元素移到堆顶,也就是a[0] n--; swap(a[0], a[n]); //然后把当前最大移动到后面来作为排好序的元素 } }
归并排序
排序过程
- 将数组N从中间分成两个数组N1和N2;
- 将N1和N2分别递归用归并排序来排序。
- 归并N1与N2。
图片演示

//归并排序 //平均时间复杂度:O(NlogN) //稳定排序 vector<int> mergeHelper(vector<int> &a, int left, int right) { if (left == right) return vector<int> (1, a[left]); int mid = (right - left) / 2 + left; vector<int> l = mergeHelper(a, left, mid); vector<int> r = mergeHelper(a, mid + 1, right); //merge vector<int> ret; int ll = 0, rr = 0; while (ll < l.size() && rr < r.size()) { if (l[ll] <= r[rr]) ret.push_back(l[ll++]); else ret.push_back(r[rr++]); } while (ll < l.size()) ret.push_back(l[ll++]); while (rr < r.size()) ret.push_back(r[rr++]); return ret; } void mergesort(vector<int>& a) { a = mergeHelper(a, 0, a.size() - 1); }
快速排序
排序过程
- 选择一个枢纽元,可以选择首,尾,中三个数的中位数作为枢纽元;
- 将枢纽元的为止与数组的尾地址进行交换;
- 定义两个指针,P1指向数组首地址,P2指向数组倒数第二个位置,P1所指元素的值与枢纽元比较,如果小于枢纽元则后移一位,如果大于就停下来。P1所指元素的值与枢纽元比较,如果大于枢纽元则前移一位,如果小于就停下来;
- 交换P1和P2所指向的元素;
- 重复3和4直到P1大于P2;
- 对数组的分割过程同样采用递归的方法。
快排是必须背住的排序!
//快速排序 //平均时间复杂度:O(NlogN) //最坏情况复杂度:O(N^2) //不稳定排序 void quicksortHelper(vector<int>& a, int start, int end) { if (start >= end) return; int l = start, r = end; int pivot = a[(end - start) / 2 + start]; while (l <= r) { while (l <= r && a[r] > pivot) r--; while (l <= r && a[l] < pivot) l++; if (l <= r) swap(a[l++], a[r--]); } quicksortHelper(a, start, r); quicksortHelper(a, l, end); } void quicksort(vector<int>& a) { quicksortHelper(a, 0, a.size() - 1); }
//快排的最差时间复杂度为O(n²) //通常出现在选择的轴值(pivot)不能将数组划分为两个长度相等的子数组的时候 //一个较好的办法是“三数取中”,查看当前数组的第一个、中间一个和最后一个位置的数组,取其中位数,以此来降低轴值选择得不好的可能性。 int findmiddle(int a, int b, int c) { if (a >= b && a <= c) return a; else if (b >= a && b <= c) return b; else return c; } void quicksortHelper(vector<int>& a, int start, int end) { if (start >= end) return; int l = start, r = end; int pivot = findmiddle(a[start], a[end], a[(end - start) / 2 + start]); while (l <= r) { while (l <= r && a[r] > pivot) r--; while (l <= r && a[l] < pivot) l++; if (l <= r) swap(a[l++], a[r--]); } quicksortHelper(a, start, r); quicksortHelper(a, l, end); } void quicksort(vector<int>& a) { quicksortHelper(a, 0, a.size() - 1); }
来源:CSDN
作者:liqinzhe223
链接:https://blog.csdn.net/liqinzhe11/article/details/78743743