目录
- 0.排序
- 思维导图(总)
- 一、插入排序
- 1.直接插入排序
- 思路分析
- 代码实现
- 时间复杂度
- 2.希尔排序
- 思路分析
- 代码实现
- 时间复杂度
- 二、选择排序
- 1.选择排序
- 思路分析
- 代码实现
- 时间复杂度
- 2.堆排序
- 思路分析
- 代码实现
- 时间复杂度
- 三、交换排序
- 冒泡排序
- 思路分析
- 代码实现
- 时间复杂度
0.排序
// Sort.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <time.h>
#include "Stack.h"
// 插入排序
void InsertSort(int* a, int n);
// 希尔排序
void ShellSort(int* a, int n);
// 选择排序
void SelectSort(int* a, int n);
// 堆排序
void AdjustDwon(int* a, int n, int root);
void HeapSort(int* a, int n);
// 冒泡排序
void BubbleSort(int* a, int n);
思维导图(总)
一、插入排序
1.直接插入排序
思路分析
-
升序排序
-
一个数插入一个升序列,找到不比自己大的数,插入在这个数的后面
-
具体是如何插入的:
- 如下图,如果 a[end]>tmp 就往后挪动数据→a[end+1]=a[end],当找到 不比tmp大的数 之后,将tmp插入到这个数的后面→a[end+1]=tmp
- 如下图,如果 a[end]>tmp 就往后挪动数据→a[end+1]=a[end],当找到 不比tmp大的数 之后,将tmp插入到这个数的后面→a[end+1]=tmp
-
因此,对于一个无序列,就是依次插入数据
代码实现
// 插入排序
void InsertSort(int* a, int n)
{
assert(a);
for (int i = 1; i < n; i++)
{
int tmp = a[i];
int end = i - 1;
while (end >= 0)
{
if (a[end] <= tmp)
{
break;
}
a[end + 1] = a[end--];
}
a[end + 1] = tmp;
}
}
时间复杂度
- 最坏的情况:排升序,但原数列为降序
array[0]
挪动数据并插入的次数为:0;
array[1]
挪动数据并插入的次数为:1;
array[2]
挪动数据并插入的次数为:2;
array[3]
挪动数据并插入的次数为:3;
…………
array[n]
挪动数据并插入的次数为:n;
Σ = 0+1+2+3+……+n = (1+n)*n/2
∴ 时间复杂度O(N)=N²
- 最好的情况:时间复杂度O(N)=N
插入排序的适应性很强,对于有序、局部有序的情况,都能效率提升
2.希尔排序
思路分析
为了优化直接插入,我们选择进行预排序,让较大的位于前列的数先排到较后面的位置,以此减少挪动数据的次数
预排序:分组,每组进行插入排序
下图以gap==3为例
gap的变化:
while(gap)
{
int gap = n / 2;
gap /= 2;
}
//当gap==1时就是直接插入排序
//or
while(gap)
{
int gap = n / 3;
gap = (gap/3 + 1);
}
代码实现
// 希尔排序
void ShellSort(int* a, int n)
{
assert(a);
int gap = n / 2;
while (gap)
{
for (int i = 1; i < n; i++)
{
int tmp = a[i];
int end = i - gap;
while (end >= 0)
{
if (a[end] <= tmp)
{
break;
}
a[end + gap] = a[end];
end -= gap;
}
a[end + gap] = tmp;
}
gap /= 2;
}
}
时间复杂度
希尔排序的时间复杂度算起来很复杂,预排序之后无法确定数列的顺序情况。
这里直接给结果:O(N)=N^1.3
二、选择排序
1.选择排序
思路分析
遍历数组(以排升序为例)
找到最一小的数与数组第一个位置的数交换;
找到第二小的数与数组第二个位置的数交换;
……(依次类推)
优化:两头缩进
从数组的头和尾开始,在中间(区间为👉[begin,end])同时找最大和最小的数,找到分别与“两头的数”交换,依次类推,“两头缩进”
找到后 Swap ↓
Swap(&a[begin], &a[mini]);
Swap(&a[end], &a[maxi]);
++begin;
--end;
- 注意!:Swap之后:
begin → min ; mini → original-a[begin]
- 如果 original-a[begin] → max 则会影响 a[maxi] =? max
if (begin == maxi)
即如果原本 begin 指向的数是 max,则 Swap 之后这个数已经被交换到了 mini 所指向的位置 - end 指向的数是否改变不影响,因为,我们只是要把 max 放在最后面的位置(同时保持数据不丢失)
- 就是说 ,begin 代表头部的位置,end 代表尾部的位置;mini 是最小值的下标,maxi 是最大值的下标
- 如果 original-a[begin] → max 则会影响 a[maxi] =? max
代码实现
//优化后:
void Swap(int* x, int* y)
{
assert(x && y);
int tmp = *x;
*x = *y;
*y = tmp;
}
// 选择排序
void SelectSort(int* a, int n)
{
assert(a);
int begin = 0, end = n - 1;
while (begin < end)
{
int mini = begin, maxi = end;
//[begin+1,end-1]
for (int j = begin; j <= end; j++)
{
if (a[j] < a[mini])
mini = j;
if (a[j] > a[maxi])
maxi = j;
}
//swap之前 min==a[mini] max==a[maxi]
Swap(&a[begin], &a[mini]);
//swap之后:begin → min ; mini → original-a[begin]
//if(original-a[begin] → max)则会影响 a[maxi] =? max
if (begin == maxi)//如果原本begin指向的数是max,则swap之后这个数已经被交换到了mini所指向的位置
maxi = mini;
Swap(&a[end], &a[maxi]);
++begin;
--end;
}
}
时间复杂度
未优化的算法:
从头找到尾
找到最一小的数与数组第一个位置的数交换,查找的次数为:n - 1;
找到第二小的数与数组第二个位置的数交换,查找的次数为:n - 2;
…………
最后一次查找次数为:1( n -(n-1))
Σ = 1+2+3+……+(n-1)= n*(n-1)/2
∴ 时间复杂度为O(N²)
2.堆排序
详细分析过程见→二叉树-堆| [堆的实现【建堆算法图解+分析】]
思路分析
排升序:建大堆。把数组按大堆的方式排列,此时堆顶就是最大的数,让堆顶与数组最后一个数交换,最大的数就被放在了数组的尾部。对于前面的部分,调整使其依旧为大堆,此时堆顶的数就是第二大的数……重复这个操作👉向下调整建堆,不断取堆顶的数
排降序:建小堆。思路同上。
代码实现
// 堆排序
void AdjustDwon(int* a, int n, int root)
{
assert(a);
int parent = root;
int child = parent * 2 + 1;
while (child < n)
{
if ((child + 1) < n && a[child + 1] > a[child])
++child;
if (a[parent] < a[child])
Swap(&a[parent], &a[child]);
else
break;
parent = child;
child = parent * 2 + 1;
}
}
void HeapSort(int* a, int n)
{
assert(a);
//建大堆
for (int parent = (n - 1 - 1) / 2; parent >= 0; parent--)
{
AdjustDwon(a, n, parent);
}
//取堆顶的数据并向下调整
int end = n - 1;
while (end)
{
Swap(&a[0], &a[end]);
AdjustDwon(a, end, 0);
end--;
}
}
时间复杂度
向下调整建堆的时间复杂度:O(N)
取堆顶数据然后向下调整时间复杂度:O(logN) → 重复 N次 → O(N*logN)
∴ 时间复杂度 为 O(N*logN)
三、交换排序
冒泡排序
思路分析
前后比较交换
以下列这个数组为例,展示一趟冒泡排序:
代码实现
// 冒泡排序
void BubbleSort(int* a, int n)
{
assert(a);
for (int j = 0; j < n; j++)
{
for (int i = 0; i < n - j - 1; i++)
{
if (a[i] > a[i + 1])
Swap(&a[i], &a[i + 1]);
}
}
}
时间复杂度
第一趟冒泡排序:n-1次
第二趟冒泡排序:n-2次
……
第n趟冒泡排序:n-n次
Σ = 1 + 2 + 3 + …… + n-1 = n(n-1)/2
∴时间复杂度为O(N)=N²