简单易懂排序算法【总结】
相关概念
稳定:如果原本在前面,而,排序之后仍然在的前面。
不稳定:如果的前面,而,排序之后 a 可能会出现在 的后面。
时间复杂度:对排序数据的总的操作次数。反映当变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模的函数。
排序分类
以下对之前介绍的七种排序进行一个简单的分类
各种性能指标对比
排序方法 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | 稳定 | ||||
简单选择排序 | 稳定 | ||||
直接插入排序 | 稳定 | ||||
希尔排序 | ~ | 不稳定 | |||
堆排序 | 不稳定 | ||||
归并排序 | 稳定 | ||||
快速排序 | ~ | 不稳定 |
动图详解
1.冒泡排序:
代码:
void BubbleSort(vector<int>& vec)
{
int n = vec.size();
for (int i = 0; i < n - 1; ++i)
{
for (int j = i + 1; j < n; ++j)
{
if (vec[i] > vec[j])
swap(vec[i], vec[j]);
}
}
}
2.简单选择排序:
代码:
void SelectSort(vector<int>& vec)
{
int n = vec.size();
int minIndex;
for (int i = 0; i < n - 1; ++i)
{
minIndex = i;
for (int j = i + 1; j < vec.size(); ++j)
{
if (vec[j] < vec[minIndex])
minIndex = j;
}
if (i != minIndex)
swap(vec[i], vec[minIndex]);
}
}
3.直接插入排序
代码:
void InsertSort(vector<int>& vec)
{
int n = vec.size();
int i, j, tmp;
for (i = 1; i < vec.size(); ++i)
{
tmp = vec[i]; //将当前待插入的元素提出
for (j = i - 1; j >= 0 && vec[j] > tmp; --j)
vec[j + 1] = vec[j]; //将已经排好序的序列往后移一位,找到插入位置则停止
vec[j + 1] = tmp; //将元素插入其中
}
}
4.希尔排序:
代码:
void ShellSort(vector<int>& vec)
{
int i,j;
int tmp;
int increment = vec.size();
do
{
increment = increment/3 + 1; //初始跳跃间隔
for (i = increment + 1; i <= vec.size(); ++i)
{
if (vec[i - 1] < vec[i - increment - 1])
{
//对每一个小序列执行直接插入排序
tmp = vec[i - 1];
for (j = i - increment - 1; j >= 0 && vec[j] > tmp; j -= increment)
vec[j + increment] = vec[j];
vec[j + increment] = tmp;
}
}
}while (increment > 1); //当跳跃间隔大于1继续
}
5.堆排序:
代码:
void HeapSort(vector<int>& vec)
{
int i;
//这里vec.size() - 1是在数组前面加了一个0,方便下标进行操作
//除以2是因为i 小于 n/2的节点**才有子节点**,这里用到了上述提到过的节点关系的思想
for (i = (vec.size() - 1)/2;i > 0; --i)
{
HeapAdjust(vec, i, vec.size() - 1); //将元素构造成大顶堆
}
for (i = vec.size() - 1;i > 0;--i)
{
swap(vec[i], vec[1]); //交换堆顶元素与当前未经排序的子序列的最后一个记录
HeapAdjust(vec, 1, i - 1); //将vec[1..i-1]重新调整为大顶堆
}
}
void HeapAdjust(vector<int>& vec, int max_index, int length)
{
int tmp, j;
tmp = vec[max_index]; //记录下堆顶元素
//以下沿元素值较大的孩子节点往下找
for (j = 2 * max_index; j <= length; j *= 2) //这里j *= 2,之所以递增2倍也是节点关系讨论的结果
{
//找到当前节点的较大孩子节点的下标,即为j
if (vec[j] < vec[j+1] && j < length)
++j;
//若当前父节点比其两个孩子节点都大,则不用调整
if (tmp >= vec[j])
break;
//否则将将较大值与当前父节点交换,并记录下重新出发的点
vec[max_index] = vec[j];
max_index = j;
}
vec[max_index] = tmp;
}
6.归并排序:
代码:
void MergeSort(vector<int>& vec)
{
MSort(vec, vec, 0, vec.size() - 1);
}
//TR2定义为全局变量
#define MAXSIZE 9
vector<int> TR2(MAXSIZE, 0);
void MSort(vector<int> SR, vector<int>& TR1, int start, int end)
{
int m;
if (start == end) //单个序列直接赋值到目标序列中
TR1[start] = SR[start];
else
{
m = (start + end)/2; //将SR[s..t]平分为SR[sstart..m]、SR[m+1..end]
MSort(SR, TR2, start, m); //递归将SR[start..m]归并为有序的TR2[start..m]
MSort(SR, TR2, m + 1, end);//递归将SR[m+1..end]归并为有序的TR2[m+1..end]
Merge(TR2, TR1,start, m, end);//将TR2[start..m]、TR2[m+1..end]归并到TR1中
}
}
void Merge(vector<int> SR, vector<int>& TR, int i, int m, int n)
{
int j,k,l;
for (j = m + 1,k = i;i <= m && j <= n; ++k)
{
if (SR[i] < SR[j]) //判断两个子序列中对应位置元素哪个更大,将大的加入到目标序列TR中
TR[k] = SR[i++];
else
TR[k] = SR[j++];
}
//若前序列有余,则加入到目标序列尾后
if (i <= m)
{
for (l = 0; l <= m - i; ++l)
TR[k] = SR[i]; //将剩余的SR[i..m]复制到TR
}
//若后序列有余,则加入到目标序列尾后
if (j <= n)
{
for (l = 0;l <= n - j; ++l)
TR[k] = SR[j]; //将剩余的SR[j..n]复制到TR
}
}
7.快速排序:
代码:
void QuickSort(vector<int>& vec)
{
QSort(vec, 0, vec.size() - 1);
}
void QSort(vector<int>& vec, int low, int high)
{
int pivot; //记录枢轴值pivot
if (low < high)
{
pivot = Partition(vec, low, high); //将序列分割为符合要求的两部分
//算出枢轴值pivot
QSort(vec, low, pivot - 1); //对低子表递归排序
QSort(vec, pivot + 1, high); //对高子表递归排序
}
}
int Partition(vector<int>& vec, int low, int high)
{
int pivotkey;
pivotkey = vec[low]; //对子表的第一个作枢轴记录
while (low < high) //从表的两端进行扫描
{
while (low < high && vec[high] >= pivotkey) //从后找到第一个比枢轴小的值
high--;
swap(vec[low], vec[high]); //交换该值与枢轴值
while (low < high && vec[low] <= pivotkey) //从前找到第一个比枢轴大的值
low++;
swap(vec[low], vec[high]); //交换该值与枢轴值
}
return low; //返回枢轴下标
}