不知道你有没有注意过,在我们身边,每天都在发生着各种各样的“排序”。每年期末的考试成绩在被排序,我们的年龄在被排序,一点一点过去的时间也是一种排序。作为一个计算机系的大学狗,博主最近也研究了一下几种常见的排序算法。
1.插入排序
顾名思义,插入排序即找到一个位置将数据置入,而插入排序又分为以下两种
时间复杂度O(N^2)
a.直接插入排序
直接插入排序即我们每次规定一个数据作为key使用key和每一个位于其前面的数据做比较,当找到一个比它小的数据时就将其放在这个数据的后面,如果数据都大于key的值就将数据后移。key的起始是数组的第二个数据,每次循环一轮后将寻找范围增加一。
例如:第一次插入时key的值为第二个数据,第二次则变成第三个以此类推直到数组结束。这样每次我们在进行单轮插入时都可以保证数组是有序的。
代码如下:
void InsertSort(int *arr, int n)
{
int end = 0;
int tmp = 0;
for (int i = 0; i < n - 1; i++)
{
end = i;
tmp = arr[end + 1];
while (end >= 0 && tmp < arr[end])
{
arr[end + 1] = arr[end];
--end;
}
//此处由于已经减出范围所以需要++
if (tmp < arr[++end])
arr[end] = tmp;
}
}
b.希尔排序
希尔排序实际上是直接插入排序的一种优化,它的key值不是每次和每一个数据进行比较,而是设置了一个gap值,每次跳动gap个数据进行比较。这样做的好处是能够在预排序阶段让小的数据尽量到前面,让大的数据尽量到后面。最后一次我们将gap值缩小到一,相当于做了一次直接插入排序
时间复杂度约为O(N^1.25)
代码如下:
void ShellSort(int *arr, int n)
{
int end = 0;
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
end = i;
int tmp = arr[end + gap];
while (end >= 0 && tmp < arr[end])
{
arr[end + gap] = arr[end];
end -= gap;
}
arr[end + gap] = tmp;
}
}
}
2.选择排序
a.直接选择排序
选择排序,即每次选择最小的放在当前范围的第一个位置,或者每次选择一个最大的放在当前范围的最后一个位置。开始时,我们的范围是整个数组,每经过一个轮次之后就缩小一次数组的范围,在此我们为了稍稍优化一下,就可以每经过一个轮次同时选择最大的和最小的数据放在相应的位置。
时间复杂度O(N^2)
void SelectSort(int *arr, int n)
{
int left = 0;
int right = n - 1;
int max = 0;
int min = 0;
while (left < right)
{
for (int i = left; i <= right; i++)
{
if (arr[i] > arr[max])
max = i;
if (arr[i] < arr[min])
min = i;
}
swap(arr[left], arr[min]);
if (max == left)
max = min;
swap(arr[right], arr[max]);
left++;
right--;
}
}
3.堆排序
我们在排序之前构造一个小堆这样就可保证每次最小的数据位于顶端,每次拿出一个数据放入数组。拿出时想小堆的顶端和末尾交换后拿出,再次调整小堆,继续选择,直到全部拿出,数组既有序
时间复杂度O(NlogN)
void AdjustDown(int *arr,int parent, int n)
{
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && arr[child] < arr[child + 1])
child++;
if (arr[parent] < arr[child])
{
swap(arr[parent], arr[child]);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
void HeapSort(int *arr, int n)
{
int index = (n - 2) / 2;
for (int i = index; i >= 0; i--)
{
AdjustDown(arr, i, n);
}
int end = n - 1;
while (end > 0)
{
swap(arr[0], arr[end]);
AdjustDown(arr, 0, end);
--end;
}
}
4.交换排序
a.冒泡排序
比较相邻两个元素的大小,如果顺序错误就将他们的位置进行交换
时间复杂度O(N^2)
void BubbleSort(int *arr,int n)
{
//chage用于优化
bool chage = false;
int end = n - 1;
for (int i = 0; i < n; i++)
{
chage = false;
for (int j = 0; j < end-i; j++)
{
if (arr[j] > arr[j + 1])
{
swap(arr[j], arr[j + 1]);
chage = true;
}
}
if (chage = false)
break;
}
}
其中,chage用于优化,如果一次排序伦次中一次交换都没有发生即说明数组已经有序
b.快速排序
快速排序是一个值得我们注意的算法,他的基本思想即找一个基准位置,每次使得指针从选定范围的头尾开始寻找,从头开始的指针寻找大于基准的数,
从尾开始的指针找小于基准的数,找到之后将两个值得位置进行交换,这样保证每个排序轮次后基准的左边的数都小于他而右边的数都大于他,然后再递归。
时间复杂度O(NlogN)
1)标准快排
void SelectMid(int *arr,int left,int right)
{
int mid = (left + right) / 2;
if (left < right)
{
if (arr[left] < arr[mid] && arr[mid] < arr[right])
swap(arr[left], arr[left]);
else if (arr[right] < arr[mid])
swap(arr[left], arr[right]);
}
else
{
if (arr[mid] > arr[right] && arr[mid] <arr[left])
swap(arr[left], arr[mid]);
}
}
void QuikSort(int *arr, int left, int right)
{
SelectMid(arr, left, right);
if (right < left)
{
return;
}
int j = right;
int i = left;
//设置比较的基准值
int base = arr[left];
//一次交换必须直到相遇即停
while (i != j)
{
//找从右边开始小于key值得数
while (arr[j] >= base && i < j)
j--;
//从左起找第一个大于key值的数
while (arr[i] <= base && i < j)
i++;
if (i < j)
{
swap(arr[i], arr[j]);
}
}
//将key值归位
swap(arr[left], arr[i]);
QuikSort(arr, left, i - 1);
QuikSort(arr, i + 1, right);
}
2)挖坑法快排
挖坑法的思想即第一次将key的位置置成一个坑然后从前找一个大于key的数据将其抛入坑中并在该数据的位置形成一个新的坑,然后从后面向前面
找一个小于key的数据在将其抛入坑中,然后再形成新的坑,如此循环就可以将数据置于正确的位置。
代码如下:
void QuikSort_OP1(int *arr, int start, int end)
{
SelectMid(arr, start, end);
if (start >= end)
return;
int key = arr[end];
int hoop = end;
int left = start;
int right = end;
while (left < right)
{
while (right >left && arr[left] < key)
++left;
if (right >left)
{
arr[hoop] = arr[left];
hoop = left;
}
while (right > left && arr[right] > key)
--right;
if (right > left)
{
arr[hoop] = arr[right];
hoop = right;
}
}
arr[hoop] = key;
QuikSort_OP1(arr, start, hoop-1);
QuikSort_OP1(arr, hoop+1, end);
}
3)prev&cur快排
此种方法稍显难以理解,实际上它定义了两个指针cur和prev,每次让cur找比key小的数据,当数据连续的小于key值时prev紧跟cur,当其遇到大于
key时就会发现cur会连续的走若干步,prev总是指向了比key大的数的前一个。大家可以在纸上画一画
代码如下:
<span style="font-size: 14px;">void QuikSort_OP2(int *arr, int start, int end)
{
SelectMid(arr, start, end);
int prev = start -1;
int cur = start;
while (cur < end)
{
while (arr[cur] > arr[end])
cur++;
if (cur < end )
swap(arr[++prev], arr[cur++]);
}
swap(arr[end], arr[++prev]);
QuikSort(arr, start, prev);
QuikSort(arr, prev, end);
</span><span style="font-size:12px;">}</span>