排序总结系列C++
1.插入排序
类似打扑克牌,拿牌阶段;
一个数组[2,3,4,5,3,2,2,1]

升序排序
类似打扑克拍抓牌阶段,让每次抓了牌以后插入手中已有的牌中,使得最后的牌序是升序的;
for(int i=1;i<Arr.size();i++)
{
int j=0;
flag_num = Arr[i];
for(j=i-1;j>=0 && Arr[j]>flag_num;j--)
{
Arr[j+1] = Arr[j];
}
Arr[j+1] = flag_num;
}
降序排序
for(int i=1;i<Arr.size();i++)
{
int j=0;
int flag_num = Arr[i];
for(j = i-1; j>=0 && Arr[j]<flag_num;j--)
{
Arr[j+1] = Arr[j];
}
Arr[j+1] = flag_num;
}
单向链表插入排序
力扣147:

/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* insertionSortList(ListNode* head) {
ListNode* dummyhead = new ListNode(0);
dummyhead->next = head;
ListNode* cur=head->next;
ListNode* per_cur=head;
while(cur!=NULL)
{
ListNode* per_ins=dummyhead;
if(cur->val < per_cur->val)
{
while(per_ins->next->val <= cur->val)
{
per_ins = per_ins->next;
}
per_cur->next = cur->next;
cur->next = per_ins->next;
per_ins->next = cur;
}
else
{
per_cur = per_cur->next;
}
cur = per_cur->next;
}
return dummyhead->next;
}
};
双向链表插入排序
2.选择排序

选择排序分为两步
- 选择i~N-1位置的数组上最小值所在的索引,i依次加1
- 交换i位置与最小值索引位置的数值
for(int i=0;i<N;i++)
{
int minidx = i;
for(int j=i+1;j<N;j++)
{
minidx = Arr[minidx]<Arr[j] ? minidx : j;
}
swap[Arr[i],Arr[minidx]];
}
3.冒泡排序
每次排出一个元素,放在数组头部或者尾部
- 在0~N-1的范围内,依次比较12位置,23位置,…,N-2 N-1位置,小数前移
- 在0~N-2的范围内,依次比较12位置,23位置,…,N-3 N-2位置,小数前移

for(int i=N-1;i>=0;i--)
{
for(int j=0;j<i;j++)
{
if(Arr[j]>Arr[j+1])
swap(Arr[j],Arr[j+1]);
}
}
4. 归并排序
时间复杂度O(N)*logN
- 思路过程:
a. 找到中点,对左右两部分进行排序
b. 合并排序以后的左右两部分数组 - code
void merge(vector<int>& Arr,int L,int mid,int R)
{
vector<int> res;
int i = L;
int j = mid + 1;
while (i <= mid && j <= R)
{
if (Arr[i] > Arr[j])
{
res.push_back(Arr[j]);
j++;
}
else
{
res.push_back(Arr[i]);
i++;
}
}
while (i <= mid)
{
res.push_back(Arr[i]);
i++;
}
while (j <= R)
{
res.push_back(Arr[j]);
j++;
}
//容易忽略这点
for (auto num : res)
{
Arr[L] = num;
L++;
}
}
void merge_sort(vector<int>& Arr, int L, int R)
{
if (L == R)
return;
int mid = (L + R) >> 1;
merge_sort(Arr, L, mid);
merge_sort(Arr, mid + 1, R);
merge(Arr,L,mid,R);
}
int main()
{
vector<int> Arr{ 1,2,3,7,5,3,9,3,5 };
int R = Arr.size()-1;
int L = 0;
merge_sort(Arr, L, R);
for (int i = 0; i < Arr.size(); i ++ )
cout << Arr[i] << " ";
return 0;
}
归并排序符合master公式求时间复杂度
子问题的规模等规模都是N/2,为2T(N/2)
决策过程主要是比较递归层是否返回和merge过程,时间复杂度为O(N);
所以归并排序的时间复杂度master计算公式为
T(N) = 2
×
\times
×T(
N
2
\frac{N}{2}
2N) + O(N)
根据master公式求时间复杂度的公式可以算出复杂度为O(NlogN)
master公式参考链接
还有合并两个升序数组的经典写法如下:
vector<int> merge(vector<int> left_Arr, vector<int>& right_Arr)
{
vector<int> res;
int idx1 = 0;
int idx2 = 0;
while (idx1 < left_Arr.size() && idx2 < right_Arr.size())
{
if (left_Arr[idx1] > right_Arr[idx2])
{
res.push_back(right_Arr[idx2]);
idx2++;
}
else
{
res.push_back(left_Arr[idx1]);
idx1++;
}
}
while (idx1 < left_Arr.size())
{
res.push_back(left_Arr[idx1]);
idx1++;
}
while (idx2 < right_Arr.size())
{
res.push_back(right_Arr[idx2]);
idx2++;
}
return res;
}
vector<int> merge_sort(vector<int>& Arr, int L, int R)
{
if (L == R)
{
vector<int> new_vec(Arr.begin() + L, Arr.begin() + R+1);
return new_vec;
}
int mid = (L + R) >> 1;
vector<int> left_vec = merge_sort(Arr, L, mid);
vector<int> right_vec = merge_sort(Arr, mid + 1, R);
return merge(left_vec,right_vec);
}
- 力扣归并排序的题目
【链表排序问题】148排序链表
【小和问题】315. 计算右侧小于当前元素的个数
【逆序对问题】剑指 Offer 51. 数组中的逆序对
5. 快速排序
快速排序视频讲解:毕站传送门
- 快排思想的由来,两个荷兰国旗题目介绍

a) 题目1:题目:数组Arr=[3,5,3,7,4,6,5,8],pvoit=5;使得小于5的元素分布在数组的左侧,大于5的元素分布在数组的右侧
题目1思路从左往右遍历数组元素,如果元素小于等pvoit的,与小于区域的下一位元素做交换,然后小于等于区域右扩;如果元素大于pvoit,无操作,继续往右遍历数组;当遍历数组越界的时候,就停止返回;这个时候的数组就完成了元素pvoit的定位。
题目1 code
vector<int> Arr{3,5,3,7,4,6,5,8};
int pvoit = 5;
int part_L = -1;//小于等于区域的边界
for(int i=0;i<Arr.size();i++)
{
if(Arr[i]>pvoit)
continue;
else
{
swap(Arr[++part_L],Arr[i]);
}
}
return part_L;
b) 题目2:数组Arr=[3,0,5,3,4,5,2,6,9,6],pvoit为5。要求是数组左侧是小于5的元素,中间是等于5的元素(如果数组中国存在元素pvoit),右侧是大于5的元素
题目2思路:
从左往右遍历数组,遍历元素的索引记为i
有三个策略:
i) 当索引i的元素Arr[i]<pvoit。Arr[i]元素与小于区的下一个元素做交换,小于区右扩一位,遍历索引i++;
ii) 当索引i的元素Arr[i]==pvoit, i++
iii) 当索引i的元素Arr[i]>pvoit,Arr[i]与大于区的前一个元素做交换,大于区左扩一位,i不变
当大于区的左边界于遍历的索引i相遇的时候,停止
题目2 code
vector<int> Arr{3,0,5,3,4,5,2,6,9,6};
int pvoit = 5;
int part_L = -1;
int part_R = Arr.size();
for(int i = 0 ;i < Arr.size();i++)
{
if(i==part_R)
break;
if(Arr[i]==pvoit)
continue;
else if(Arr[i]<pvoit)
swap(Arr[++part_L],Arr[i]);
else
{
swap(Arr[--part_R],Arr[i]);
i--;
}
}
return Arr;
- 快速排序1.0版本思路讲解
a) 快速排序每次可以完成一个数字的排序,选定参考轴pvoit,然后让数组中小于pvoit的元素放到其左侧,让大于pvoit的元素放到其右侧;这样一次循环就完成了一个元素的位置确定;
b) 假设每次都选定数组的最左侧元素看作pvoit轴 - 快速排序code
int Paritition1(vector<int>& Arr, int low, int high) {
int pivot = Arr[low];
while (low < high) {
while (low < high && Arr[high] >= pivot) {
--high;
}
Arr[low] = Arr[high];
while (low < high && Arr[low] <= pivot) {
++low;
}
Arr[high] = Arr[low];
}
Arr[low] = pivot;
return low;
}
void Quick_Sort(vector<int>& Arr, int low, int high) //快排母函数
{
if (low < high) {
int pivot = Paritition1(Arr, low, high);
Quick_Sort(Arr, low, pivot - 1);
Quick_Sort(Arr, pivot + 1, high);
}
}
因为每次都选取最左侧元素为pvoit那么如果最差的时间复杂度为O(N2),版本1.0一次只能解决一个数的位置;下面的2.0版本会稍微提一点速
- 快速排序2.0版本思路讲解
根据荷兰国旗的题目2,用在快速排序里边的话,如果数组中有重复元素,那么一次就可可以解决多个数的位置; - 快速排序2.0版本code
#include<iostream>
#include<vector>
using namespace std;
vector<int> Arr{ 3,5,3,7,4,6,5,8 };
int part_L = -1;
int part_R = Arr.size();
void partition(vector<int>& Arr, int L, int R)
{
part_L = L-1;//小于区域的边界
part_R = R+1;;
int pvoit = Arr[L];
for (int i = L; i <= R; i++)
{
if (i == part_R)
break;
if (Arr[i] < pvoit)
{
swap(Arr[i], Arr[part_L + 1]);
part_L++;
}
else if (Arr[i] == pvoit)
continue;
else
{
swap(Arr[i], Arr[part_R-1]);
part_R--;
i--;
}
}
}
void Quick_sort(vector<int>& Arr, int L, int R)
{
if (L < R)
{
partition(Arr, L, R);
Quick_sort(Arr, L, part_L);
Quick_sort(Arr, part_R, R);
}
}
int main()
{
vector<int> Arr{ 3,5,3,7,4,6,5,8 };
int L = 0;
int R = Arr.size() - 1;
Quick_sort(Arr, L, R);
for (int i = 0; i < Arr.size(); i++)
cout << Arr[i] << " ";
return 0;
}
快速排序2.0版本,如果原始数据不友好,时间复杂度仍旧是O(N2);
因为每一次选定的pvoit都是最左侧的元素,这这就导致每一次都有可能取到效果最差的情况;可以每次选取数据作为pvoit时用随机选取数字的方式,这样每种情况出现的情况都是等概率的,最后所有情况求期望得到快速排序3.0的时间复杂度为O(n*logN)
- 快速排序3.0思路讲解
在1.0版本上进行修改,每一次不是选取最左侧元素作为pvoit,而是随机选取一个元素作为pvoit,然后于最左侧元素互换,其他思路完全一样 - 快速排序3.0code
#include<iostream>
#include<vector>
using namespace std;
int Paritition1(vector<int>& Arr, int low, int high) {
int rand_num = (rand() % (high - low + 1)) + low;
swap(Arr[low], Arr[rand_num]);
int pivot = Arr[low];
while (low < high) {
while (low < high && Arr[high] >= pivot) {
--high;
}
Arr[low] = Arr[high];
while (low < high && Arr[low] <= pivot) {
++low;
}
Arr[high] = Arr[low];
}
Arr[low] = pivot;
return low;
}
void Quick_Sort(vector<int>& Arr, int low, int high) //快排母函数
{
if (low < high) {
int pivot = Paritition1(Arr, low, high);//占一个额外空间复杂度
Quick_Sort(Arr, low, pivot - 1);
Quick_Sort(Arr, pivot + 1, high);
}
}
int main()
{
vector<int> Arr{ 3,5,3,7,4,6,5,8 };
int L = 0;
int R = Arr.size() - 1;
Quick_Sort(Arr, L, R);
for (int i = 0; i < Arr.size(); i++)
cout << Arr[i] << " ";
return 0;
}
-
快速排序额外度分析
每次递归都要储存一个pvoit的值,最差结果就是O(N);最好的结果就是每次的pvoit都差不多是中间位置,空间复杂度为O(logN) -
快速排序题目
912排序数组
6 堆排序
推荐一个数据结构堆的介绍:https://www.jianshu.com/p/6b526aa481b1
堆分为两种:最大堆和最小堆,两者的差别在于节点的排序方式。
在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立
堆最重要的操作是heap_in_sort和heapify;给定一个数组,可以将数组根据完全二叉树的规则看作一棵树;记结点的索引为i;那么结点i的子节点为2i+1和2i+2;父节点为(i-1)/2;
heap_in_sort的功能是比较自身与父节点的大小,如果大于其父节点,就交换;从下往上排
heapify的功能是,比较自身与左右子节点中的较大值,如果小于子节点的值就互换;从上往下排
- heap_in_sort
//节点与其父节点比较,若大于其父节点,则交换;一路换到顶点或者换不动为止
void heap_in_sort(vector<int>& Arr, int index)
{
while (Arr[index] > Arr[(index - 1) / 2])
{
swap(Arr[index], Arr[(index - 1) / 2]);
index = (index - 1) / 2;
}
}
- heapify
可以取出最大堆的堆顶,而且让剩下的元素仍旧保持最大堆;
//取出最大堆的堆顶剩下的仍旧是最大堆
//位于index位置的元素,从上往下移
int max_headify(vector<int>& Arr, int index, int heapsize)
{
int temp = Arr[index];
swap(Arr[index], Arr[heapsize]);
int dad = index;
int son = dad * 2 + 1;
while (son <= heapsize) {
if (son + 1 <= heapsize && Arr[son] < Arr[son + 1])
son++;
if (Arr[dad] > Arr[son])
return temp;
else {
swap(Arr[dad], Arr[son]);
dad = son;
son = dad * 2 + 1;
}
}
return temp;
}
最大堆排序代码(结合左神的讲解思路)
再次之前也会堆排序,但是代码思路不如左神讲的简单
a) 将原始数组变成最大堆
b) 堆顶元素与堆尾元素互换,最大值放在堆尾,heapsize–,让已经得到的最大值与原始的数组断绝联系
c) 将heapsize–之后的数组再一次变成最大堆,周而复始
#include<iostream>
#include<vector>
using namespace std;
class HEAP_SORT
{
private:
vector<int> noOrderNums;
public:
HEAP_SORT(vector<int>& noOrderNums_): noOrderNums(noOrderNums_) {};
void heapBottom2Top(int index)
{
while(index != 0)
{
if (noOrderNums[index] > noOrderNums[(index - 1) / 2])
{
swap(noOrderNums[index], noOrderNums[(index - 1) / 2]);
index = (index - 1) / 2;
}
else
{
return;
}
}
}
vector<int> getOrderNums() { return noOrderNums; }
void headTop2Bottom(int index, int capacity)
{
int temp = noOrderNums[index];
swap(noOrderNums[index], noOrderNums[capacity]);
int dad = index;
int son = dad * 2 + 1;
while (son < capacity)
{
if (son + 1 < capacity && noOrderNums[son] < noOrderNums[son + 1])
son++;
if (noOrderNums[dad] > noOrderNums[son])
break;
else
{
swap(noOrderNums[dad], noOrderNums[son]);
dad = son;
son = dad * 2 + 1;
}
}
}
};
int main()
{
vector<int> noOrderNums{ 1,3,2,5,3,6,8,2,4,5 };
HEAP_SORT heapObject(noOrderNums);
for (int i = 0; i < noOrderNums.size(); i++)
{
heapObject.heapBottom2Top(i);
}
//每次取出堆顶
for (int i = noOrderNums.size() - 1; i >= 0; --i)
{
heapObject.headTop2Bottom(0, i);
}
for (const int& num : heapObject.getOrderNums())
{
cout << num << " ";
}
return 0;
}
# output
3 2 2 1 3 4 5 5 6 8
- 改变i位置元素的值,仍旧使得堆为最大堆结构
根据改变以后的i位置的值,如果元素值大于父节点,就调用heap_in_sort方法;heap_in_sort(Arr,i);反之heapify(Arr,i,Arr.size()-1)
最大堆排序代码(自己写的,思路不清晰,建议不要看,留着就是为做个对比)
- 使用二叉树构建大根堆
#include <iostream>
#include <algorithm>
using namespace std;
void max_heapify(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(int arr[], int len) {
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);
}
}
int main() {
int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
int len = (int) sizeof(arr) / sizeof(*arr);
heap_sort(arr, len);
for (int i = 0; i < len; i++)
cout << arr[i] << ' ';
cout << endl;
return 0;
}
优先队列
c++中优先队列默认是大根堆构造,也可以假如参数改变构造规则
优先队列使用的例子
#include <iostream> // std::cout
#include <queue> // std::priority_queue
#include <vector> // std::vector
#include <functional> // std::greater
using namespace std;
int main()
{
int myints[] = { 10,60,50,20 };
priority_queue<int> first;//默认构造大顶堆,基础容器为vector,相当priority_queue<int, vector<int>, less<int> >
priority_queue<int> second(myints, myints + 4);
cout << second.top() << endl;//此时输出最大元素60
priority_queue<int, vector<int>, greater<int> >
third(myints, myints + 4);//构造小顶堆
cout << third.top() << endl;//输出最小元素10
return 0;
}
改变构造规则的优先队列
题目链接:347. 前 K 个高频元素
https://leetcode-cn.com/problems/top-k-frequent-elements/
class Solution {
public:
static bool cmp(pair<int,int>& P1,pair<int,int>& P2)
{
return P2.second < P1.second;
}
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int> radio_nums;
for(int n:nums)
{
radio_nums[n]++;
}
priority_queue<pair<int,int>,vector<pair<int,int>>,decltype(&cmp)> q(cmp);
for(auto [key,val]:radio_nums)
{
if(q.size()==k)
{
if(q.top().second < val)
{
q.pop();
q.emplace(key,val);
}
}
else
{
q.emplace(key,val);
}
}
vector<int> res;
for(int i=0;i<k;i++)
{
res.push_back(q.top().first);
q.pop();
}
return res;
}
};
最小堆
与最大堆同理
堆解决的问题种类
- 构建优先队列
- 支持堆排序
- 快速找出一个集合中的最小值(或者最大值)
- 在朋友面前装逼
番外
使用c++中的优先队列可以轻松的使用堆,但是使用黑盒的话,不能去内部改变他的结构,只能完成给黑盒一个数据,从黑盒得到一个数据;不能实现改变一个元素的值,然后重新变成最大堆
7 拓扑排序
拓扑排序依据有向图等图结构,在力扣题目中,课程表类的题目使用拓扑排序会非常方便;题目传送门:207课程表 210题课程表问题
拓扑排序通常用来“排序”具有依赖关系的任务。
比如,如果用一个DAG图来表示一个工程,其中每个顶点表示工程中的一个任务,用有向边 表示在做任务 B 之前必须先完成任务 A。故在这个工程中,任意两个任务要么具有确定的先后关系,要么是没有关系,绝对不存在互相矛盾的关系(即环路)。
拓扑排序,主要是维护一个入度为0的数组
图的存储方式有两种:邻接矩阵和邻接表,力扣207我使用邻接矩阵表示图

- code 力扣207题题解
private:
vector<int> in_num;
vector<vector<int>> G;
vector<int> res;
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
in_num.resize(numCourses);
G.resize(numCourses);
queue<int> que;
for(auto k:prerequisites)
{
G[k[1]].push_back(k[0]);
in_num[k[0]]++;
}
for(int i=0;i<numCourses;i++)
{
if(in_num[i]==0)
{
que.push(i);
}
}
while(!que.empty())
{
int m = que.front();
que.pop();
res.push_back(m);
for(int n:G[m])
{
in_num[n]--;
if(in_num[n]==0)
{
que.push(n);
}
}
}
if(res.size()!= numCourses)
return false;
return true;
}
};
8 桶排序
之前所有的排序方法,都和比较有关系,所以之前的排序都是基于比较的排序;接下来介绍不基于比较的排序
计数排序
遍历数据,对数据中出现的词记录词频;然后根据词频表还原;比如说数组Arr=[2,1,1,2,2,3,4,5,6,6,7,7,3,3,3],词频记录为[0,2,3,1,1,2,2];然后根词频数组还原为排好序的数组Arr1=[1,1,2,2,2,3,4,5,6,6,7,7];
但是如果遇到的待排元素不是数,或者是数组太长,这种方法就行不通了;所以计数排序只是针对不对的数据规模和数据特性来指定的专一性的算法,不具有基于比较的排序的通用性
基数排序
桶排序的适用场景非常明了,那就是在数据分布相对比较均匀或者数据跨度范围并不是很大时,排序的速度还是相当快且简单的。
桶排序,顾明思意就是构建很多的桶来进行排序;接下来的内容不好描述,我用图画代替吧

桶排序更多地被用于一些特定的环境,比如数据范围较为局限或者有一些特定的要求,比如需要通过哈希映射快速获取某些值、需要统计每个数的数量。但是这一切都需要确认数据的范围,如果范围太大,就需要巧妙地解决这个问题或者使用其他算法了
- 基数排序code
#include<vector>
#include<iostream>
#include<math.h>
using namespace std;
void radix_Sort(vector<int>&, int, int, int);
int maxbits(vector<int>& Arr)
{
int max = INT_MIN;
for (int i = 0; i < Arr.size(); i++)
{
max = max > Arr[i] ? max : Arr[i];
}
int res = 0;
while (max != 0)
{
res++;
max /= 10;
}
return res;
}
void radixSort(vector<int>& Arr)
{
if (Arr.size() < 2)
return;
radix_Sort(Arr, 0, Arr.size() - 1, maxbits(Arr));
}
int getDigit(int x, int d)
{
return (x / (int)pow(10, d - 1)) % 10;
}
void radix_Sort(vector<int>& Arr, int L, int R, int digit)
{
int radix = 10;
int i = 0;
int j = 0;
vector<int> help(R - L + 1);
for (int k = 1; k <= digit; k++)
{
vector<int> Count(10);
for (i = L; i <= R; i++)
{
j = getDigit(Arr[i], k);
Count[j]++;
}
for (i = 1; i < radix; i++)
{
Count[i] = Count[i] + Count[i - 1];
}
for (i = R; i >= L; i--)
{
j = getDigit(Arr[i], k);
help[Count[j] - 1] = Arr[i];
Count[j]--;
}
for (i = L,j=0; i <= R; i++, j++)
{
Arr[i] = help[i];
}
}
}
int main()
{
vector<int> Arr{ 17,23,43,25,56,13,24,45,67,100,43,81 };
radixSort(Arr);
for (auto n : Arr)
cout << n << " ";
return 0;
}
代码解析:请看视频讲解,最后20分钟
排序性能总结
左神视频截图


19万+

被折叠的 条评论
为什么被折叠?



