一、选择排序
1.1排序原理
选择排序的实现思路是遍历数组找出最大/最小的值所对应的下标,然后把0下标位置的值与他互换,依此类推,下一次是1位置下标交换,最后实现整个数组的有序
1.2代码实现
依照1.1中原理推理,我们可以得到如下代码(Swap是交换函数,详见补充说明)
void SelectSort1(int* a, int n)
{
int i = 0;
for (i = 0; i < n; i++)
{
int gapi = i;
for (int j = i; j < n; j++)
{
if (a[j] < a[gapi])
{
gapi = j;
}
}
Swap(&a[i], &a[gapi]);
}
}
1.3改善代码
自行实现的代码还是有很多缺陷的,比如时间复杂度相对较高,还有缩短空间,可以用mini和maxi代替gapi同时来进行,加快效率。
(过程实现有坑:maxi和begin重叠问题要注意)
void SelectSort(int* a, int n)
{
int begin = 0;
int end = n - 1;
while (begin < end)
{
int mini = begin, maxi = begin;
for (int i = begin+1; i <= end; i++)
{
if (a[i] < a[mini])
mini = i;
if (a[i] > a[maxi])
maxi = i;
}
Swap(&a[begin], &a[mini]);
if (maxi == begin)
maxi = mini;
Swap(&a[end], &a[maxi]);
++begin;
--end;
}
}
二、堆排序
2.1排序原理
堆排序需要先建堆(物理上是数组存储的堆)
注意升序建大堆,降序建小堆。
建堆完成之后利用先取堆顶数据,然后交换堆顶和堆的末尾,下一次调整减少堆的总数据个数来保存最后一个数据,依此类推实现整个堆的有序。
2.2堆排序前置:向下调整算法
向下调整算法,本质上就是把一个堆顶的数据一层层往下调,升序建大堆,降序建小堆
要建立大堆,最上面的数据要大,所以找到左右孩子里比较大的,如果根节点比大孩子要小
那么换了他们,根节点就同时比左右孩子都大了大了,然后从这个换过的孩子往下接着找
void AdjustDwon(int* a, int n, int root)
{
int child = root * 2 + 1;
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child])
{
child++;
}
if (a[root] < a[child])
{
Swap(&a[root], &a[child]);
root = child;
child = child * 2 + 1;
}
else
{
break;
}
}
}
2.3代码实现
void HeapSort(int* a, int n)
{
//先建堆,再完成依次交换
//建堆,树的最后一层不用调,上去就找最后一个结点的父节点,从父节点对应位置调到根节点
for (int i=(n-1-1)/2;i>=0;i--)
{
AdjustDwon(a, n, i);
}
//建堆之后才可以调整算法规范位置,此时从最后一个数据开始调,调到最开始的数据,期间
//配合换位置,总数减一的思路。
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDwon(a, end, 0);
end--;
}
}
三、冒泡排序
3.1排序原理
冒泡排序是从第一个数据开始,第一个比第二个,第二个比第三个.......假设我们要排升序,那么过程中,如果我们发现第二个数据比第一个小,那么就需要交换他们的位置,这样以后我们就可以把数组中最大的数据换到数组末尾了。依此类推,下一次的末尾是这一次的前一个,会得到次大的数,循环往复即可使数组有序。
3.2代码实现
void BubbleSort(int* a, int n)
{
//添加exchange避免已经排好序还在一直走
for (int i = 0; i < n-1; i++)
{
int exchange = 0;
for (int j = 0; j < n - 1 - i; j++)
{
if (a[j] > a[j + 1])
{
Swap(&a[j], &a[j + 1]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
四、快速排序(Hoare提出方案)
4.1排序原理
由Hoare提出的快速排序运用了递归的思想,
我们首先说一趟的过程:
4.1.1设置需要的标志
首先传入的使数组左下标left和右下标right,我们要设置一个下标keyi=left,
(设置keyi其实可以是别的,比如keyi=right,但这种情况我们就需要左下标先走,而其他情况会更复杂,此处不做过多讨论)
4.1.2单趟过程
有了这些以后就可以实现大概思路了:用循环找数据,此处假设要排升序,那么
左边排完了是小,右边排完了是大,所以我们要在左边找出大的,右边找出小的把他们换了,
循环往复可以达到目的。
while(left<right)
{
while (left < right && a[right] >= a[keyi])
right--;
while (left < right && a[left] <= a[keyi])
left++;
Swap(&a[left], &a[right]);
}
4.1.3完成一轮更换后
经过单趟调整,left和right已经到了同一个位置,也就是keyi应该去的新位置,我们只需要交换他们
Swap(&a[keyi], &a[left]);
keyi = left;//更新keyi的值用来递归
4.1.4递归过程
此时我们的数组其实已经成为了以keyi为界限,左侧小于等于它,右侧大于等于他的新数组,
对于新数组我们只要通过递归,再让他们走一遍之前的路即可
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
注意添加递归结束条件:
if (left >= right)
return;
4.2代码实现
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int begin = left;
int end = right;
//设置keyi的位置
int keyi = left;
//排升序,左侧找大,右侧找小
while (left < right)
{
//此处必须再限制一遍,避免左向右全是小的,刹不住车,并且要先走右边再走左边
while (left < right && a[right] >= a[keyi])
right--;
while (left < right && a[left] <= a[keyi])
left++;
Swap(&a[left], &a[right]);
}
//走完了,左和右指向同一位置,就是keyi要去的位置(右侧因为刚换过去,一定会挪动一步)
Swap(&a[keyi], &a[left]);
keyi = left;//更新keyi的值用来递归
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
五、插入排序
5.1排序原理
插入排序是将第0下标位置的数字视为有序,比如要排升序,只要看它的下一个数应该在这个有序序列的哪一个位置,位置不对就换,对就停下,依次向下走就可以获得一个完全的有序序列
5.2代码实现
void InsertSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end = i;
int tmp = a[end + 1];
while (end >= 0)
{
if (a[end] > tmp)
{
Swap(&a[end], &a[end + 1]);
end--;
}
//如果没有tmp比a[end]小这一情况,就停下
else {
break;
}
}
//放入这一数据
a[end + 1] = tmp;
}
}
六、希尔排序(插入排序的一种改善)
6.1排序原理
希尔排序作为对插入排序的一种改善,本质上是在分析了插入排序影响效率的因素之后,对这一因素进行的理想型修正,在超多数据排序时会有意想不到的效果。
6.1.1预排序
分组进行插入排序,目的是为了让数组更接近有序,分组的多少受到数组长度n和gap值的影响,gap值即分组标准,比如gap=2,就是说数组第一个值与第三个,第五个...构成同一组,
当gap=1时,就是数组排到有序的时候。
分组完成后对每一组进行插入排序,就是预排序过程。
6.1.2预排序基础上的插入排序
本质上为gap==1时的预排序。
6.2代码实现
void ShellSort(int* a, int n)
{
int gap = n;
//对于gap也有说法,只有gap为1的时候才彻底排好,所以循环gap>1
while (gap > 1)
{
gap = gap / 2;
//起初有两层循环,把第一层和第二层融合为一个
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (a[end] > tmp)
{
Swap(&a[end], &a[end + gap]);
end -= gap;
}
//如果没有tmp比a[end]小这一情况,就停下
else {
break;
}
}
a[end + gap] = tmp;
}
}
}
补充说明:
①swap函数是用来交换两个值的,传入两个地址
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
②快速排序中,a[keyi]位置的值一定比left和right相遇位置的值大(只要保证right先走)
可以分left遇到right和right遇到left这两种情况讨论。