“从0开始做LeetCode”之常用排序算法总结
1.冒泡排序
Note:先排最大的
时间复杂度: O ( n 2 ) O(n^2) O(n2) 空间复杂度: O ( 1 ) O(1) O(1)
void bubble_sort(vector<int> &nums, int n){ //这里可以传入n,也可以不用
if(nums.empty() || nums.size()<2){
return;
}
for(int end = nums.size()- 1; end > 0; end--){
for(int i = 0; i < end; i++){
if(nums[i] > nums[i+1]){
swap(nums, i, i+1);
}
}
}
}
void swap(vector<int> &nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
2.选择排序
Note:先排最小的
时间复杂度: O ( n 2 ) O(n^2) O(n2) 空间复杂度: O ( 1 ) O(1) O(1)
void selection_sort(vector<int> &nums, int n){
if(nums.empty() || nums.size() < 2){
return;
}
for(int i = 0; i<nums.size()-1; i++){
int minIndex = i;
for(int j = i + 1; j < nums.size() - 1; j++){
//minIndex = nums[j] < nums[minIndex] ? j : minIndex;
if(nums[j] < nums[minIndex]){
minIndex = j;
}
}
swap(nums, i, minIndex);
}
}
void swap(vector<int> &nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
3.插入排序
Note:先排前面的,有点类似插牌
时间复杂度: O ( n 2 ) O(n^2) O(n2) 空间复杂度: O ( 1 ) O(1) O(1)
时间复杂度按照最差情况排序
和数据状态有关系,故:
最好:
O
(
n
)
O(n)
O(n)
最坏:
O
(
n
2
)
O(n^2)
O(n2)
平均:
void insertion_sort(vector<int> &nums, int n){ //这里可以传入n,也可以不用
if(nums.empty() || nums.size()<2){
return;
}
for(int i = 1; i < nums.size(); i++){
for(int j = i - 1; j >= 0 && nums[j] > nums[j+1]; j--){
swap(nums, j, j + 1);
}
}
}
void swap(vector<int> &nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
4.归并排序
Note:分治思想,实践(小和问题和逆序对问题)
时间复杂度: O ( N l o g N ) O(NlogN) O(NlogN) 空间复杂度: O ( N ) O(N) O(N) *“内部缓存法”*可以改善
master公式求时间复杂度,递归思想
class Sol {
public:
void merge_sort(vector<int> &nums) {
if (nums.empty() || nums.size() < 2) {
return;
}
sortProcess(nums, 0, nums.size() - 1);
}
void sortProcess(vector<int> &nums, int L, int R) {
if (L == R) {
return;
}
int mid = L + ((R - L) >> 1); // L和R中间的位置(L + R) / 2
sortProcess(nums, L, mid); // T(n/2)
sortProcess(nums, mid + 1, R); // T(N/2)
merge(nums, L, mid, R); // O(N)
// T(N) = 2 T(N/2) + O(N)
}
void merge(vector<int> &nums, int L, int mid, int R) {
vector<int> help(R - L + 1);
int i = 0;
int p1 = L;
int p2 = mid + 1;
while (p1 <= mid && p2 <= R) {
help[i++] = nums[p1] < nums[p2] ? nums[p1++] : nums[p2++];
}
//两个必有一个越界
while (p1 <= mid) {
help[i++] = nums[p1++];
}
while (p2 <= R) {
help[i++] = nums[p2++];
}
for (i = 0; i < help.size(); i++) {
nums[L + i] = help[i];
}
return;//这里应该有return
}
};
引出快排——荷兰国旗
vector<int> partition(vector<int> &nums, int L, int R, int num){
int less = L - 1;
int more = R + 1;
int cur= L;
vector<int> res;
while(cur < more){
if(nums[cur] < num){
swap(nums, ++less, cur++);
}else if(nums[cur] > num){
swap(nums, --more, cur);
}else{ // == num
L++;
}
}
return vector<int> {less + 1, more - 1};
}
void swap(vector<int> &nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
5.快速排序
Note:分治思想,实践(荷兰国旗),partition,工程上必须改成非递归形式,自己压栈
随机快排更好,避开数据状况,可以分堆更好的概率更高
时间复杂度: O ( N l o g N ) O(NlogN) O(NlogN) 空间复杂度: O ( l o g N ) O(logN) O(logN) // 随机快排的,记录断点的空间
随机快排是用到最多的地方,常数项少,导致更快,比merge快
// 经典快排
void quick_sort(vector<int> &nums, int L, int R){
if(L < R){
srand((unsigned)time(NULL));//随机种子
swap(nums, L + rand() % (R - L + 1), R)// 随机快排
vector<int> p = partition(nums, L, R);
quick_sort(nums, L, p[0] - 1);
quick_sort(nums, p[1] + 1, R);
}
}
// 划分,时间O(N),空间O(1)
vector<int> partition(vector<int> &nums, int L, int R){
int less = L - 1;
//int more = R + 1;
int more = R; // 和上面的不一样的原因是,最后一个nums[R]是不能改变的,直接先把x扩进大于的区域,炫技——可以省去一个变量
int cur = L;
while(cur < more){
if(nums[cur] < arr[R]){
swap(nums, ++less, cur++);
}else if(nums[cur] > arr[R]){
swap(nums, --more, cur);
}else{
cur++;
}
}
swap(nums, more, R); // 这一句很重要,和上面的不太一样
return vector<int> {less + 1, more};
}
void swap(vector<int> &nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
6.堆排序
Note:堆,完全二叉树,满二叉树,大根堆,小根堆
抽象——数组->树
左子 2 ∗ i + 1 2*i+1 2∗i+1, 右子 2 ∗ i + 2 2*i+2 2∗i+2, 父 ( i − 1 ) / 2 (i-1)/2 (i−1)/2
完全二叉树 节点和高度公式: h = l o g N h=logN h=logN,单个加进来 l o g i logi logi,所有节点加进来 l o g 1 + l o g 2 + . . . = O ( N ) log1+log2+...=O(N) log1+log2+...=O(N)
时间复杂度: O ( N l o g N ) O(NlogN) O(NlogN) 空间复杂度: O ( 1 ) O(1) O(1)
void heap_sort(vector<int> &nums){
if(nums.empty() || nums.size() < 2){
return;
}
//建立大根堆 复杂度:O(N)
for(int i = 0; i < nums.size(); i++){
heapInsert(nums, i);
}
int heapSize = nums.size();
swap(nums, 0, --heapSize);
while(heapSize > 0){
heapify(nums, 0, heapSize);
swap(nums, 0, --heapSize);
}
}
// 变成大根堆 复杂度:O(logN)
void heapInsert(vector<int> &nums, int Index){
while(nums[index] > nums[(index - 1) / 2]){
swap(nums, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
// 下沉
void heapify(vector<int> nums, int index, int heapSize){
int left = index * 2 + 1;
while(left < heapSize){
int largest = left + 1 < heapSize && nums[left + 1] > nums[left] ? left + 1 : left;
largest = nums[largest] > nums[index] ? largest : index;
if(largest == index){
break;
}
swap(nums, largest, index);
index = largest;
left = index * 2 + 1;
}
}
void swap(vector<int> &nums, int i; int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
排序稳定性
保证原始的相同顺序不变
具有稳定性:冒泡,插入,归并
不具有稳定性:选择排序(merge),快排(partition),堆排(建立大根堆)
综合排序
短(< 60):插排
基础类型:快排
自定义类型:堆排
比较器
桶排序:计数排序,基数排序:不重要
时间复杂度: O ( N ) O(N) O(N) 空间复杂度: O ( N ) O(N) O(N)
稳定
非基于比较的排序
计数排序:词频
基数排序:按位
例:排序后,相邻数组最大值