一、直接插入排序
把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含1个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,将它插入到有序表中的适当位置,使之成为新的有序表,重复n-1次可完成排序过程.
void InsertSort(int *a, int size)
{
assert(a);
for (int i = 0; i < size - 1; i++)
{
int end = i;
int key = a[end + 1];
while (end >= 0 && key < a[end])
{
a[end + 1] = a[end];
end--;
}
a[end + 1] = key;
}
}
时间复杂度:
- 最好情况:
如果待排序的元素本身有序,那么在进行插入排序时,每一个元素直接在前面有序表末尾处进行插入,整个过程下来,时间复杂度为O(N)
- 最坏情况:
如果待排序的元素无序,那么在进行插入排序时,每一个元素都需要在前面的有序表中找到其合适的插入位置,整个过程下来,时间复杂度为O(N^2)
- 平均情况:O(N^2)
空间复杂度:O(1)
稳定性:稳定
说明:设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面,在排序之后,a[i]仍然在a[j]前面,则这个排序算法是稳定的。
二、希尔排序
(1)预排序:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成),分别进行直接插入排序,然后依次缩减增量再进行排序,使整个序列接近有序
(2)当整个序列中的元素基本有序时,再对全体元素进行一次直接插入排序
因为直接插入排序在元素基本有序的情况下效率是很高的,因此希尔排序在时间效率上相对于直接插入排序有较大提高。
void ShellSort(int *a, int size)
{
assert(a);
int gap = size;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < size - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0 && tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
a[end + gap] = tmp;
}
}
}
时间复杂度:
- 最好情况:O(N)
- 最坏情况:O(N^2)
- 平均情况:O(N^1.3)
空间复杂度:O(1)
稳定性:不稳定
例如:待排序列3 2 2* 4,当gap为2时进行希尔排序,经过排序后变为2* 2 3 4,此时2和2*之间的相对位置发生变化。
三、直接选择排序
在元素序列a[i] ~ a[n-1]中选择关键码最大(最小)的数据元素,将它与这组元素中的最后一个(第一个)元素进行交换,接着在剩余的元素序列a[i] ~ a[n-2](a[i+1] ~ a[n-1])中重复上述步骤,直到剩余1个元素时完成排序。
void SelectSort(int *a, int size)
{
assert(a);
int left = 0;
int right = size - 1;
while (left < right)
{
int min = left;
int max = left;
for (int i = left; i <= right; i++)
{
if (a[i] < a[min])
{
min = i;
}
if (a[i] > a[max])
{
max = i;
}
}
Swap(&a[left], &a[min]);
if (max == left)
{
max = min;
}
Swap(&a[right], &a[max]);
left++;
right--;
}
}
时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:不稳定
例如上述图解,排序前 25 在 25* 之前,而在排序后 25 在 25* 后。
四、堆排序
升序建大堆,降序建小堆
以升序为例,先将整个序列的元素建造成一个大堆,接着把堆顶元素和当前堆的最后一个元素进行交换,然后堆元素个数减1,接着从根节点通过向下调整使得当前堆恢复到大堆,重复上述过程,直到当前堆的元素个数为1时完成排序。
void AdjustDown(int *a, int size, int root)
{
int parent = root;
int child = parent * 2 + 1;
while (child < size)
{
int flag = 0;
if (child + 1 < size)
{
if (a[child + 1] > a[child])
{
child++;
}
}
if (a[child]>a[parent])
{
flag = 1;
Swap(&a[child], &a[parent]);
}
if (flag == 0)
{
break;
}
parent = child;
child = parent * 2 + 1;
}
}
void HeapSort(int *a, int size)
{
assert(a);
int i = (size - 2) / 2;
for (; i >= 0; i--)
{
AdjustDown(a, size, i);
}
for (i = size - 1; i > 0; i--)
{
Swap(&a[0], &a[i]);
AdjustDown(a, i, 0);
}
}
时间复杂度:O(N*lgN)
空间复杂度:O(1)
稳定性:不稳定
五、冒泡排序
从元素序列第一个位置开始,进行两两比较,根据大小交换位置,直到最后将最大(最小)的数据元素交换到了当前序列的最后一个位置,成为有序序列的一部分,然后元素个数减1,重复上述过程,直到所有数据元素都排好序。
void BubbleSort(int *a, int size)
{
assert(a);
for (int i = 0; i < size - 1; i++)
{
int flag = 0;
for (int j = 0; j < size - i - 1; j++)
{
if (a[j]>a[j + 1])
{
flag = 1;
Swap(&a[j], &a[j + 1]);
}
}
if (flag == 0)
{
break;
}
}
}
时间复杂度:
- 最好情况:O(N)
- 最坏情况:O(N^2)
- 平均情况:O(N^2)
空间复杂度:O(1)
稳定性:稳定
六、快速排序
任取待排列元素序列中的一个元素作为基准值,通过一趟排序将要排序的序列分割成独立的两子序列,其中左子序列的所有元素都比基准值小,右子序列的所有元素都比基准值大,然后左右子序列重复此过程,直到所有元素都排列在相应的位置上为止。
(1)左右指针法:
定义两个指针begin和end,将基准值放在最右边,begin从头开始找比基准值大的值(begin++),end从尾开始找比基准值小的值(end–),若都找到且begin小于end,则两者值交换,重复上述过程,直到begin>=end时,将begin所对应的值和最右边的基准值交换,此时整个序列被基准值划分成左右两个子序列。
int PartSort1(int *a, int left, int right)
{
int index = GetMid(a, left, right);
Swap(&a[index], &a[right]);
int key = a[right];
int begin = left;
int end = right;
while (begin < end)
{
while (begin < end && a[begin] <= key)
{
begin++;
}
while (begin < end && a[end] >= key)
{
end--;
}
if (begin < end)
{
Swap(&a[begin], &a[end]);
}
}
Swap(&a[begin], &a[right]);
return begin;
}
(2)挖坑法:
定义两个指针begin和end,将基准值放在最右边并保存该值,此时该位置可视为一个坑。begin从头开始找比基准值大的值,找到后将begin所对应的值填入到刚才的坑中,此时begin这个位置成为新的坑;begin不动,接着end从尾开始找比基准值小的值,找到后将end所对应的值填入到刚才的新坑中,此时end这个位置成为新的坑;end不动,begin从上次的位置接着往后找,重复上述过程,直到begin>=end时,将保存的基准值填入到begin所对应的坑中,此时整个序列被基准值划分成左右两个子序列。
int PartSort2(int *a, int left, int right)
{
int index = GetMid(a, left, right);
Swap(&a[index], &a[right]);
int key = a[right];
int begin = left;
int end = right;
while (begin < end)
{
while (begin < end && a[begin] <= key)
{
begin++;
}
a[end] = a[begin];
while (begin < end && a[end] >= key)
{
end--;
}
a[begin] = a[end];
}
a[begin] = key;
return begin;
}
(3)前后指针法:
定义两个指针prev和cur,将基准值放在最右边,prev初始位置在left-1处,cur初始位置在left处。cur从头开始找比基准值小的值,找到后若此时++prev的位置和cur的位置不在同一处(说明++prev对应的值一定比基准值大),则交换这两处的值,cur接着刚才的位置往后找,重复上述过程,直到cur>=right时,将最右边的基准值和++prev所对应的值进行交换,此时整个序列被基准值划分成左右两个子序列。
int PartSort3(int *a, int left, int right)
{
int index = GetMid(a, left, right);
Swap(&a[index], &a[right]);
int prev = left - 1;
int cur = left;
while (cur < right)
{
if (a[cur] < a[right] && ++prev != cur)
{
Swap(&a[cur], &a[prev]);
}
cur++;
}
Swap(&a[++prev], &a[right]);
return prev;
}
在选择基准值时,为了提高排序效率,我们常常利用三数取中法来选择基准值,所谓的三数指的是:序列最左端的值、中间位置的值和最右端的值,计算它们的中位数来作为基准值。
int GetMid(int *a, int left, int right)
{
int mid = (left + right) >> 1;
if (a[left] < a[right])
{
if (a[mid] < a[left])
{
return left;
}
else if (a[mid] > a[right])
{
return right;
}
else
{
return mid;
}
}
else
{
if (a[mid] > a[left])
{
return left;
}
else if (a[mid] < a[right])
{
return right;
}
else
{
return mid;
}
}
}
(1)递归法:
void QuickSortR(int *a, int left, int right)
{
assert(a);
if (left >= right)
{
return;
}
if (right - left < 10)
{
InsertSort(a, right - left + 1);
}
else
{
int div = PartSort1(a, left, right);
QuickSortR(a, left, div - 1);
QuickSortR(a, div + 1, right);
}
}
(2)非递归法:
借用栈的结构来模仿递归(相关栈的函数定义请查看顺序栈)
void QuickSort(int *a, int left, int right)
{
assert(a);
Stack s;
StackInit(&s);
StackPush(&s, left);
StackPush(&s, right);
while (!StackEmpty(&s))
{
int end = StackTop(&s);
StackPop(&s);
int begin = StackTop(&s);
StackPop(&s);
int div = PartSort1(a, begin, end);
if (begin < div - 1)
{
StackPush(&s, begin);
StackPush(&s, div - 1);
}
if (div + 1 < end)
{
StackPush(&s, div + 1);
StackPush(&s, end);
}
}
}
时间复杂度:
- 最好情况:O(N*lgN)
- 最坏情况:O(N^2)
- 平均情况:O(N*lgN)
空间复杂度:O(lgN)——递归深度
稳定性:不稳定
七、归并排序
将待排序的元素序列分成两个长度相等的子序列,对每一个子序列排序,然后再将它们合并成一个有序序列。
void _MergeSort(int *a, int left, int right, int *tmp)
{
if (left >= right)
{
return;
}
if (right - left < 10)
{
InsertSort(a, right - left + 1);
return;
}
int mid = left + ((right - left) >> 1);
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid + 1, right, tmp);
int p = left;
int q = mid + 1;
int index = 0;
while (p <= mid&&q <= right)
{
if (a[p] <= a[q])
{
tmp[index++] = a[p++];
}
else
{
tmp[index++] = a[q++];
}
}
while (p <= mid)
{
tmp[index++] = a[p++];
}
while (q <= right)
{
tmp[index++] = a[q++];
}
int j = 0;
for (int i = left; i <= right; i++)
{
a[i] = tmp[j++];
}
}
void MergeSort(int *a, int left, int right)
{
assert(a);
int *tmp = (int*)malloc((right - left + 1)*sizeof(int));
memset(tmp, 0, (right - left + 1)*sizeof(int));
_MergeSort(a, left, right, tmp);
free(tmp);
}
时间复杂度:O(N*lgN)
空间复杂度:O(N)——临时数组
稳定性:稳定
八、计数排序
统计待排序序列中每个元素出现的次数,再根据统计的结果重新对元素进行回收。
void CountSort(int *a, int size)
{
assert(a);
int max = a[0];
int min = a[0];
int index = 0;
for (index = 1; index < size; index++)
{
if (a[index]>max)
{
max = a[index];
}
if (a[index] < min)
{
min = a[index];
}
}
int range= max - min + 1;
int *tmp = (int*)calloc(range, sizeof(int));
for (index = 0; index < size; index++)
{
tmp[a[index] - min]++;
}
int i = 0;
for (index = 0; index < range; index++)
{
while (tmp[index])
{
a[i++] = index + min;
tmp[index]--;
}
}
free(tmp);
tmp = NULL;
}
时间复杂度:O(N+range)
空间复杂度:O(range)
稳定性:稳定
九、总结: