插入排序
有序或者接近于有序的序列,插入排序的效率很高
//最坏时间复杂度O(N^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)
{
a[end + 1] = a[end];
--end;
}
else
break;
}
a[end + 1] = tmp;
}
}
时间复杂度——O(N^2)
希尔排序
希尔排序是优化后的插入排序,要先进行预排序,然后再进行插入排序
预排序是将间隔为gap的数据分为一组,进行插入排序
gap越大,就会更快的隔着gap进行跳跃;gap越小,越有序
插入排序是gap为1的希尔排序
gap的确定:一种是gap/2,一种是gap/3+1,两种都能保证gap一定最后到1
逆序的序列希尔排序效率很高
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; ++i)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
break;
}
a[end + gap] = tmp;
}
}
}
时间复杂度——O(N^1.3)
直接选择排序
每一轮选择最小的和最大的,遍历整个数组
无论数据是否有序,时间复杂度相同
void SelectSort(int* a, int n)
{
int begin = 0;
int end = n - 1;
while (begin < end)
{
//选最小的放在begin位置,最大的放在end位置
int mini = begin;
int maxi = end;
for (int i = begin; 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;
}
}
时间复杂度——O(N^2)
冒泡排序
有序或者接近于有序的序列,插入排序的效率很高
最坏时间复杂度——O(N^2) 最好时间复杂度——O(N)
void BubbleSort(int* a, int n)
{
for (int j = 0; j < n; ++j)
{
int exchange = 0;
for (int i = 0; i < n - j - 1; i++)
{
if (a[i] > a[i + 1])
{
Swap(&a[i], &a[i + 1]);
exchange = 1;
}
}
if (!exchange)
break;
}
}
Hoare快速排序
选择一个key(一般是第一个或者最后一个)
单趟排序,要求小于key的在key左边,大于key的在key的右边
两个指针头尾相向移动,头指针找一个比key大的,尾指针找一个比key小的,互相交换,直到相遇,与key互换
头部为key,保证相遇位置值比key小——尾指针先移动(尾部做key的时候,头指针先动)
一趟排序确定key的最终位置,同时两个子区间分别递归使其有序
int PartSort(int* a, int left, int right)
{
int keyi = left;
while (left < right)
{
//R找小的
while (a[right] >= a[keyi] && right > left)
{
--right;
}
//L找大的
while (a[left] <= a[keyi] && left < right)
{
++left;
}
if(left < right)
Swap(&a[left], &a[right]);
}
int meeti = left;
Swap(&a[keyi], &a[meeti]);
return meeti;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
return;
int keyi = PartSort(a, begin, end);
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
时间复杂度——O(N*logN)
最好情况——每一轮keyi正好都移动到中位数左右
最坏情况——整体大致有序 O(N^2)——可能会发生栈溢出
优化选择keyi
随机选择keyi
针对有序的情况,选择正中间的位置做keyi
三数选中——拿到头、尾和中间的中位数——不可能取到最值
int GetMidIndex(int* a, int left, int right)
{
int mid = left + (right - left) / 2;
if (a[left] < a[mid])
{
if (a[mid] < a[right])
return mid;
else if (a[left] > a[right])
return left;
else
return right;
}
else
{
if (a[mid] > a[right])
return mid;
else if (a[left] < a[right])
return left;
else
return right;
}
}
int PartSort(int* a, int left, int right)
{
int mid = GetMidIndex(a, left, right);
Swap(&a[left], &a[mid]);
int keyi = left;
while (left < right)
{
//R找小的
while (a[right] >= a[keyi] && right > left)
{
--right;
}
//L找大的
while (a[left] <= a[keyi] && left < right)
{
++left;
}
if(left < right)
Swap(&a[left], &a[right]);
}
int meeti = left;
Swap(&a[keyi], &a[meeti]);
return meeti;
}
小区间优化——最后几层改用插入排序
挖坑法快速排序
int PartSort2(int* a, int left, int right)
{
int mid = GetMidIndex(a, left, right);
Swap(&a[0], &a[mid]);
int key = a[left];
int hole = left;
while (left < right)
{
while (right > left && a[right] >= key)
{
--right;
}
a[hole] = a[right];
hole = right;
while (left < right && a[left] <= key)
{
++left;
}
a[hole] = a[left];
hole = left;
}
a[hole] = key;
return hole;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
return;
if (end - begin <= 7)
{
InsertSort(a + begin, end - begin + 1);
}
else
{
//int keyi = PartSort(a, begin, end);
int keyi = PartSort2(a, begin, end);
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
}
前后指针法快速排序
cur指向的数据比key小,cur走,prev紧跟着走,同时交换
cur指向的数据比key大,cur自己走
key与prev交换
int PartSort3(int* a, int left, int right)
{
int mid = GetMidIndex(a, left, right);
Swap(&a[left], &a[mid]);
int keyi = left;
int prev = left;
int cur = left + 1;
while (cur <= right)
{
if (a[cur] < a[keyi] && ++prev != cur)
Swap(&a[cur], &a[prev]);
cur++;
}
Swap(&a[prev], a[keyi]);
return prev;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
return;
if (end - begin <= 7)
{
InsertSort(a + begin, end - begin + 1);
}
else
{
//int keyi = PartSort(a, begin, end);
//int keyi = PartSort2(a, begin, end);
int keyi = PartSort3(a, begin, end);
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
}
非递归快速排序(栈实现)
void QuickSortNonR(int* a, int begin, int end)
{
ST st;
StackInit(&st);
StackPush(&st, begin);
StackPush(&st, end);
while(!StackEmpty(&st))
{
int right = StackTop(&st);
StackPop(&st);
int left = StackTop(&st);
StackPop(&st);
int keyi = PartSort3(a, left, right);
if (keyi + 1 < right)
{
StackPush(&st, keyi + 1);
StackPush(&st, right);
}
if (left < keyi - 1)
{
StackPush(&st, left);
StackPush(&st, keyi - 1);
}
}
}
归并排序
已经有序的两个区间,借助第三个数组,把小的值进行尾插
先分解成有序的小区间进行合并——类似二叉树的后序遍历
时间复杂度O(N*logN)
void _MergeSort(int* a, int begin, int end, int* tmp)
{
if (begin == end)
return;
int mid = (begin + end) / 2;
_MergeSort(a, begin, mid, tmp);
_MergeSort(a, mid + 1, end, tmp);
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;
int i = begin;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else
tmp[i++] = a[begin2++];
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
tmp = NULL;
}
归并排序非递归实现
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
int gap = 1;
while (gap < n)
{
for (int j = 0; j < n; j += 2 * gap)
{
int begin1 = j, end1 = j + gap - 1;
int begin2 = j + gap, end2 = j + 2 * gap - 1;
int i = j;
if (end1 >= n)
{
break;
}
if (begin2 >= n)
{
break;
}
if (end2 >= n)
{
end2 = n - 1;
}
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else
tmp[i++] = a[begin2++];
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
memcpy(a + j, tmp + j, (end2 - j + 1) * sizeof(int));
}
gap *= 2;
}
free(tmp);
tmp = NULL;
}
计数排序
非比较排序,不比较数字的大小
基数排序和桶排序运用实际运用较少
计数排序本质是通过统计次数进行排序
数组中每个位置的值是该下表对应的数字出现的次数——映射
根据出现的次数进行覆盖
为了节省空间,不使用直接映射而是间接映射
适合对范围相对集中的数据进行排序处理
void CountSort(int* a, int n)
{
int max = a[0];
int min = a[0];
for (int i = 0; i < n; i++)
{
if (a[i] > max)
max = a[i];
if (a[i] < min)
min = a[i];
}
int range = max - min + 1;
int* countA = (int*)malloc(sizeof(int) * range);
if (countA == NULL)
{
perror("malloc fail");
return;
}
memset(countA, 0, sizeof(int) * range);
for (int i = 0; i < n; i++)
{
countA[a[i] - min]++;
}
int j = 0;
for (int i = 0; i < range; i++)
{
while (countA[i]--)
{
a[j++] = i + min;
}
}
free(countA);
countA = NULL;
}
时间复杂度 O(N + range)
空间复杂度O(N)
缺点——只能排序整数,不适合比较字符串或者浮点数
总结
排序注重思想和实现
复杂度及其稳定性