1.冒泡排序
顾名思义,冒泡排序就是每次遍历一次数列,将较大的数下s沉,较小的数上浮,然后再重复排序后面剩下的数列,直至排完最后一个数。
时间复杂度:O(n*2)
稳定性:稳定.
代码实现:
void BubbleSort(int* a, size_t n)
{
for (size_t end = n; end > 0; --end)
{
bool exchange = false;//
for (size_t i = 1; i < end; ++i)
{
if (a[i-1] > a[i])
{
swap(a[i-1], a[i]);
exchange = true;
}
}
if (exchange == false)
{
break;
}
}
}
注:上面加上bool判断,是因为有可能在循环条件结束前就已经排好序了,此时如果排好序了,将不会再发生交换,exchange==false,排序过程结束。
2.选择排序
顾名思义,就是每次在数组中剩余的数中找出最小的数,依次完成排序。
时间复杂度:O(n*2)
稳定性:不稳定
void SelectSort(int* a, size_t n)
{
assert(a);
int begin = 0;
int end = n-1;
while(begin < end)
{
int min=begin, max=begin;
for (size_t i = begin; i <= end; ++i)
{
if (a[i] < a[min])
min = i;
if (a[i] > a[max])
max = i;
}
swap(a[max], a[end]);
// 修正
if (min == end)
min = max;
swap(a[min], a[begin]);
++begin;
--end;
}
}
3.插入排序
插入排序就类似于插入扑克牌一样,将数字插入到合适的位置。
最优复杂度:当数组有序时,时间复杂度为O(n)
最差复杂度:当数组无序时,时间复杂度为O(n*2)
稳定性:稳定
代码如下:
// 时间复杂度O(N^2) 逆序最坏 顺序最好 最好情况O(N)
void InsertSort(int* a, size_t n)
{
//[0, end] end+1插入
for (int i = 0; i < n-1; ++i)
{
int end = i;
int tmp = a[end+1];
while (end >= 0 && a[end] > tmp)
{
a[end+1] = a[end];
--end;
}
a[end+1] = tmp;
}
}
4.希尔排序
先将序列分成较多个子序列分别进行排序,再分成较少个子序列分别进行排序,直到最后为一个序列排序,相比较简单交换排序算法,先比较相邻的数,而希尔排序可以快速减少大量无序的情况。
时间复杂度:O(nlogn)
稳定性:不稳定
代码如下:
// 相比直接插入排序。顺序情况下不如直接插入。逆序效果最好。
void ShellSort(int* a, size_t n)
{
int gap = n;
while (gap > 1) // 3^x = N
{
gap = gap/3 + 1;
for (size_t i = 0; i < n-gap; ++i)
{
int end = i;
int tmp = a[end+gap];
while (end >= 0 && a[end] > tmp)
{
a[end+gap] = a[end];
end -= gap;
}
a[end+gap] = tmp;
}
}
}
5.归并排序
归并排序采用“分而治之”的思想,先将整个数组分为两份,两份分为四份,直到每组只有一个元素,然后可以将每组组内元素排序,然后再依次归并回原来的组里,这样就会依次完成排序,归并排序需要开辟一个新的数组。
时间复杂度:O(nlogn)
稳定性:稳定
代码如下:
void _MergeSort(int* a, int left, int right, int* tmp)
{
if (left >= right)
{
return;
}
int mid = left + ((right-left)>>1);
// [left,mid], [mid+1, right] 有序
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid+1, right, tmp);
int begin1 = left, end1 = mid;
int begin2 = mid+1, end2 = right;
size_t index = left;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
tmp[index++] = a[begin1++];
else
tmp[index++] = a[begin2++];
}
while (begin1 <= end1)
tmp[index++] = a[begin1++];
while (begin2 <= end2)
tmp[index++] = a[begin2++];
memcpy(a+left, tmp+left, (right-left+1)*sizeof(int));
}
void MergeSort(int* a, size_t n)
{
assert(a);
int* tmp = new int[n];
_MergeSort(a, 0, n-1, tmp);
delete[] tmp;
}
6.快速排序
找一个基点,一般都会选择数组第一个数字,第一遍将比该数小的放到左边,比该数大的放在右边,然后递归分别对两边的数进行排序。
时间复杂度:O(nlogn)
稳定性:不稳定
代码如下:
int PartSort2(int* a, int begin, int end)
{
int key = a[end];
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;
}
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int div = PartSort3(a, left, right);
// [left, div-1]
// [div+1, right]
QuickSort(a, left, div-1);
QuickSort(a, div+1, right);
}
7.堆排序
堆排序会将所有的数据建立成一个堆,最大的数据在堆顶,将其与堆底最后一个数字交换,接下来重建堆,交换数据,依次下去。
时间复杂度:O(nlogn)
稳定性:不稳定
代码如下:
void AdjustDown(int* a, size_t size, int root)
{
int parent = root;
int child = parent*2+1;
while (child < size)
{
if (child+1 < size && a[child+1] > a[child])
{
++child;
}
if (a[child] > a[parent])
{
swap(a[child], a[parent]);
parent = child;
child = parent*2+1;
}
else
{
break;
}
}
}
// O(N*log(N))
void HeapSort(int* a, size_t n)
{
for(int i = (n-2)/2; i >= 0; --i)
{
AdjustDown(a, n, i); // log(N)
}
size_t end = n-1;
while (end > 0)
{
swap(a[0], a[end]);
AdjustDown(a, end, 0);
--end;
}
}