当 gap > 1 时都是预排序,目的是让数组更接近于有序。当 gap == 1 时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。
希尔排序的时间复杂度不好计算,因为 gap 的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间复杂度都不固定:
《数据结构(C语言版)》--- 严蔚敏:
《数据结构-用面相对象方法与C++描述》--- 殷人昆
因为下面的 gap 是按照Knuth提出的方式取值的,而且Knuth进行了大量的试验统计,大致估计为 O(N^1.3)
稳定性: 不稳定
三)代码实现
这里的 gap 取值才用 gap/3+1 的办法,让排序更快调用起来。
c复制代码
void ShellSort(int* a, int n)
{
int gap = n;
// gap > 1时是预排序,目的让他接近有序
// gap == 1是直接插入排序,目的是让他有序
while (gap > 1)
{
//gap = gap / 2 + 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 (a[end] > tmp)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
在上面的基础上,多增加一次交换,变成一次交换最大值和最小值分别到尾和头,同时,如果最大值就是在开头,会出现问题,因此加入判定 maxi == begin 要重新对 maxi 赋值。
c复制代码
void SelectSort(int* a, int n)
{
int begin = 0, end = n - 1;
while (begin < end)
{
int mini = begin, 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;
}
}
int GetMidi(int* a, int begin, int end)
{
int midi = (begin + end) / 2;
int max =a[begin] > a[end] ? begin : end;
max = a[max] > a[midi] ? max : midi;
int min = a[begin] > a[end] ? end : begin;
min = a[min] > a[midi] ? midi : min;
return begin + end + midi - max - min;
}
这个版本实现快速排序有很多坑!!
首先是 left 和 right 的初始值,其次是左右循环条件要设为 <= 或者 >= ,同时要加入判定条件,不让数组越界,并且相遇就停止。条件错一个,那么结果都是错的。
c复制代码
// end为数组最大下标
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
return;
int left = begin, right = end;
int midi = GetMidi(a, begin, end);
Swap(&a[midi], &a[begin]);
int keyi = begin;
while (left < right)
{
// 右边找小
while (left < right && a[right] >= a[keyi])
{
--right;
}
// 左边找小
while (left < right && a[left] <= a[keyi])
{
++left;
}
Swap(&a[left], &a[right]);
}
Swap(&a[keyi], &a[left]);
QuickSort(a, begin, left - 1);
QuickSort(a, left + 1, end);
}
int PartSort2(int* a, int begin, int end)
{
int left = begin, right = end;
int midi = GetMidi(a, begin, end);
Swap(&a[midi], &a[begin]);
int key = a[begin];
int hole = begin;
while (left < right)
{
// 右边找小,填到左边的坑
while (left < right && 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;
int keyi = PartSort2(a, begin, end);
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
前后指针版本
因为 hoare 太多坑了,不方便使用,因此有人发明出前后指针的办法:
1)当 cur>key 时, ++cur
2)当 cur<key 时,++prev 然后交换 prev 和 cur 的值,再 ++cur
3)当 cur>end 时,循环结束,交换 prev 和 keyi 的值,即最开始的值。
c复制代码
int PartSort3(int* a, int begin, int end)
{
int midi = GetMidi(a, begin, end);
Swap(&a[midi], &a[begin]);
int keyi = begin;
int prev = begin;
int cur = begin + 1;
while (cur <= end)
{
if (a[cur] < a[keyi] && ++prev != cur)
Swap(&a[prev], &a[cur]);
++cur;
}
Swap(&a[prev], &a[keyi]);
keyi = prev;
return keyi;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
return;
int keyi = PartSort3(a, begin, end);
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
非递归实现快排(利用栈)
这里要利用栈的特性,先把头位置放进栈,出栈就代表走一遍排序,然后可以拿到 keyi 的值,再将两边分别入栈,当 left < key-1 (表示不能等于自身或者大于自身)时,或者 right > keyi+1 (表示不能等于自身或者小于自身)时,重复此动作,直到结束。
这里不明确头尾哪个先入栈,但出栈顺序必须要匹配出栈的值。
c复制代码
void QuickSortNonR(int* a, int begin, int end)
{
ST s;
STInit(&s);
STPush(&s, end);
STPush(&s, begin);
while (!STEmpty(&s))
{
int left = STTop(&s);
STPop(&s);
int right = STTop(&s);
STPop(&s);
int keyi = PartSort3(a, left, right);
if (left < keyi - 1)
{
STPush(&s, keyi - 1);
STPush(&s, left);
}
if (right > keyi + 1)
{
STPush(&s, right);
STPush(&s, keyi + 1);
}
}
STDestroy(&s);
}
这里需要注意的是,要防止 end1/begin2/end2 越界,因此对其进行判定。其次每排完一次都要进行 memcpy 操作,不然的话没有更新到原数组,后面的( gap 增加后)排序就可能会出错。
c复制代码
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail\n");
exit(-1);
}
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n - gap; i += gap * 2)
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + gap * 2 - 1;
// 防止越界
if (end1 >= n || begin2 >= n)
return;
if (end2 >= n)
end2 = n - 1;
int k = i;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
tmp[k++] = a[begin1++];
else
tmp[k++] = a[begin2++];
}
while (begin1 <= end1)
{
tmp[k++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[k++] = a[begin2++];
}
memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
}
gap *= 2;
}
free(tmp);
}
void CountSort(int* a, int n)
{
int min = a[0], max = a[0];
for (int i = 1; i < n; ++i)
{
if (min > a[i])
min = a[i];
if (max < a[i])
max = a[i];
}
int range = max - min + 1;
int* count = (int*)calloc(range, sizeof(int));
if (count == NULL)
{
perror("ralloc fail\n");
exit(-1);
}
for (int i = 0; i < n; ++i)
{
// 相对映射统计a数组中每个数据出现的次数
count[a[i] - min]++;
}
int j = 0;
for (int i = 0; i < range; ++i)
{
while (count[i]--)
{
a[j++] = i + min;
}
}
free(count);
}