文章目录
前言
本文会介绍一些常见的排序算法。
提示:以下是本篇文章正文内容,下面案例可供参考
一、排序的概念
排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性:在排序过程中,碰到一样的元素,在排序完成后,相同元素的前后位置没有改变就是稳定的,反之不稳定。
内部排序:数据元素全部放到内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
二、插入排序
1.直接插入排序
思想:把前面的数当成排好的序列,然后后面的数字跟前面的序列比较,小于它往前面插,大于就指向下一个数字。
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)
{
//找到比tmp小的值,跳出循环
if (tmp < a[end])
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
//然后再tmp插入到,end的下一个位置
a[end + 1] = tmp;
}
}
时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:稳定
2.希尔排序
对数据先进行预排序,让它接近有序,然后再直接插入排序
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)
{
//找到比tmp小的值,跳出循环
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
//然后再tmp插入到,end的下一个位置
a[end + gap] = tmp;
}
}
}
时间复杂度:O(N^1.3)
空间复杂度:O(1)
稳定性:不稳定
三、选择排序
1.直接选择排序
依次选出最大的和最小的,放到对应的位置上
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void SelectSort(int* a, int n)
{
int left = 0;
int right = n - 1;
while (left < right)
{
int min = left, max = right;
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]);
//如果最大的元素,在left位置上,要换一下
if (left == max)
{
max = min;
}
Swap(&a[max], &a[right]);
right--;
left++;
}
}
时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:不稳定
2.堆排序
void AdjustDown(int* a, int parent, int n)
{
int child = 2 * parent + 1;
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child])
child++;
if (a[parent] < a[child])
Swap(&a[parent], &a[child]);
parent = child;
child = parent * 2 + 1;
}
}
void HeapSort(int* a, int n)
{
//从最小的孩子开始建堆,建大堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, i, n);
}
int end = n-1;
while (end >= 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, 0, end);
end--;
}
}
时间复杂度:O(N*logN)
空间复杂度:O(1)
稳定性:不稳定
四、交换排序
1.冒泡排序
void BubbleSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int flag = 0;
for (int j = 0; j < n - 1 - i; j++)
{
if (a[j + 1] > a[j])
{
Swap(&a[j], &a[j + 1]);
flag = 1;
}
}
if (flag == 0)
break;
}
}
时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:稳定
2.快速排序
基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止
//左右指针法
int PartSort1(int* a, int left, int right)
{
int keyi = left;
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]);
return left;
}
//挖坑法
int PartSort2(int* a, int left, int right)
{
int keyi = a[left];
while (left < right)
{
while (left < right && a[right] >= a[keyi])
right--;
a[left] = a[right];
while (left < right && a[left] <= a[keyi])
left++;
a[right] = a[left];
}
a[left] = keyi;
return left;
}
//前后指针法
int PartSort3(int* a, int left, int right)
{
int prev = left, cur = left + 1;
int keyi = left;
while (cur <= right)
{
if (a[cur] < a[keyi] && ++prev != cur)
Swap(&a[cur], &a[prev]);
cur++;
}
Swap(&a[keyi], &a[prev]);
return prev;
}
void QuickSort(int* a, int left,int right)
{
if (left >= right)
return;
int keyi = PartSort3(a, left, right);
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
优化
优化:如果keyi取不好,时间复杂度可能会达到O(N^2)
1.三数取中法:找到一个中间值作为keyi
2.递归到小的区间时,可以用插入排序
int GetMidIndex(int* a, int left, int right)
{
int keyi = (left + right) >> 1;
if (a[left] > a[keyi])
{
if (a[keyi] > a[right])
return keyi;
else if (a[right] > a[left])
return left;
else
return right;
} //a[keyi]>a[left]
else
{
if (a[left] > a[right])
return left;
else if (a[right] > a[keyi])
return keyi;
else
return right;
}
}
void QuickSort(int* a, int left,int right)
{
if (left >= right)
return;
int keyi = PartSort1(a, left, right);
if (right - left > 20)
{
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
else
InsertSort(a + left, right - left + 1);
}
快排非递归
用栈来实现,依次把头尾传入栈中,然后对这段区间进行一次快排,返回一个中间值的序号,以这个序号把区间分成两段,再分别进行上述操作,直到最后一个元素,就是排好了
void QuickSortNonR(int* a, int left, int right)
{
Stack st;
StackInit(&st);
StackPush(&st, left);
StackPush(&st, right);
while (!StackEmpty(&st))
{
int right1 = StackTop(&st);
StackPop(&st);
int left1 = StackTop(&st);
StackPop(&st);
int keyi = PartSort1(a, left1, right1);
if (left1 < keyi - 1)
{
StackPush(&st, left1);
StackPush(&st, keyi - 1);
}
if (keyi + 1 < right1)
{
StackPush(&st, keyi + 1);
StackPush(&st, right1);
}
}
StackDestroy(&st);
}
时间复杂度:O(N*log(N))
空间复杂度:O(logN)
稳定性:不稳定
五、归并排序
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
也就是说要把序列划分成好多个小区间,再合并
void _MergeSort(int* a, int left, int right, int* tmp)
{
if (left >= right)
return;
int mid = (left + right) >> 1;
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid + 1, right, tmp);
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int i = left;
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++];
for (int i = left; i <= right; i++)
{
a[i] = tmp[i];
}
}
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);
}
归并非递归
设置一个gap,刚开始gap=1,就是每个小区间为1个元素,两个区间去比,小的排到前面,然后gap*=2,按照上面的步骤接着比
有两点要注意:1.比到最后的时候,有可能最后一个区间不够gap个
2.有可能不存在最后一个区间
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 i = 0; i < n; i += 2 * gap)
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
//第二个小区间不存在
if (begin2 >= n)
break;
//第二个小区间存在,但是不够gap个,结束位置越界了
if (end2 >= n)
end2 = n - 1;
_Merge(a, begin1, end1, begin2, end2, tmp);
}
gap *= 2;
}
}
时间复杂度:O(N*log(N))
空间复杂度:O(N)
稳定性:稳定
六、非比较排序 - 计数排序
思路:统计相同元素出现的次数
根据统计的结果再放回原来的序列中
void CountSort(int* a, int n)
{
//找到最大的和最小的,确定区间
int max = a[0], 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* tmp = (int*)malloc(sizeof(int) * range);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
memset(tmp, 0, sizeof(int) * range);
for (int i = 0; i < n; i++)
{
tmp[a[i] - min]++; //对应位上的个数有几个
}
int j = 0;
for (int i = 0; i < range; i++)
{
while (tmp[i]--)
{
a[j++] = i + min;
}
}
free(tmp);
}
只适合整数,数据范围要集中
时间复杂度:O(N+range)
空间复杂度:O(range)
稳定性:稳定
总结
以上就是今天要讲的内容,如果读者对于代码没读懂,建议多去画图