排序算法稳定性
如果在元素序列中有两个元素R[i]和R[j],它们的排序码K[i] == k[j],且 在排序之前,元素R[i]在R[j]的前面。如果在排序之后,元素R[i]仍在R[j] 之前,则称这个排序算法是稳定的,否则称这个排序算法是不稳定的。
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能 在内外存之间移动数据的排序
直接插入排序:
适用场景:数据元素接近有序,数据量少,时间复杂度O(n^2) 空间复杂度O(1) 稳定
思想:默认第一个元素前的元素都已经有序,将之后的元素全部插入到前面
插入方法:标记待插数,与前面的元素进行比较,找到插入的位置,将要插入的位置的元素全向后搬移,腾出位置插入待插数,一次循环直到有序
void InsretSort(int *arr,int size)
{
int i=1;
int key=0;
int end=0;
for(i=1;i=0 && key
优化:因为上面的方法中,主要就是找位置和搬移元素,但是每次找位置都是在有序数据中找,所以这里可以将找位置与搬移元素分开,用二分查找找位置,代码如下:
void InsretSort(int *arr,int size)
{
int i=1;
int end=0;
int key=0;
int left=0;
int right=0;
for(i=1;i
希尔排序:
基本思想:又称缩小增量排序,是对直接插入排序的优化,当数据量非常大时,因为插入排序的特性,处理起来会非常麻烦,这里我们可以将数据按照一定的间隔进行分组,然后分别对每组用插入排序的思想进行排序,然后减小间间隔,再继续,直到间隔为1
void ShellSort(int *arr,int size)
{
int i=0;
int gap=size;
while(gap>1)
{
gap=gap/3+1;
for(i=gap;i=0 && arr[end]>key)
{
arr[end+gap]=arr[end];
end-=gap;
}
arr[end+gap]=key;
}
}
}
时间复杂度:O(n^1.25 ~ 1.6n^1.25)
适用场景:应用于数据量大
稳定性:不稳定,区间插入
直接选择排序
基本思想:每一趟(第i趟,i=0,1,...,n-2)在后面(前面)n-i个待排序的数据元素集合中选出关键码最小(最大的元素),待到第n-2趟做完,待排序远视眼集合中只剩下一个元素,排序结束
void Swap(int *left,int *right)
{
int tmp=*left;
*left=*right;
*right=tmp;
}
void SelectSort(int *arr,int size)
{
int i=0;
for(i=0;i
时间复杂度:O(n^2)
稳定性:不稳定
堆排序
基本思想:因为大堆或者小堆,堆顶元素一定是最大值或最小值,所以可以将堆顶元素与最后一个元素交换,然后删除最后一个元素,在调整使之为堆。升序--大堆 降序--小堆
void _Adjust_down(int *arr,int parent,int size)
{
int child=parent*2+1;//默认左孩子为最大值
while(child>1;
int end=size-1;
for(int i=root;i>=0;i--)
_Adjust_down(arr,i,size);
while(end)
{
Swap(arr,arr+end);
_Adjust_down(arr,0,end);
end--;
}
}
时间复杂度:O(nlog2^n)
稳定性;不稳定
冒泡排序
void BubbleSort(int *arr,int size)
{
int i=0;
for(i=0;iarr[j+1])
{
flag=1;
Swap(arr+j,arr+j+1);
}
}
if(flag==0)
break ;
}
}
时间复杂度:O(n^2)
稳定性:稳定
快速排序
基本思想:任取待排序元素序列中的某元素作为基准值,按照该排序码将排序集合分割成两子序列,左子序列中的所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止
交换法:两个索引,一个找比基准值大的,一个从后找比基准值小的,最后交换,停下来的位置,就是基准值应该放的位置
int GetMiddleData(int *arr, int left, int right)
{
int mid = left + (right - left) / 2;
if (arr[left] < arr[right])
{
if (arr[mid] < arr[left])
return left;
else if (arr[mid] > arr[right])
return right;
else
return mid;
}
else
{
if (arr[mid] < arr[right])
return right;
else if (arr[mid] > arr[left])
return left;
else
return mid;
}
}
int Partion1(int *arr, int left, int right)
{
int begin = left;
int end = right - 1;
int key = 0;
int midIdx = GetMiddleData(arr, left, end);
if (midIdx != end)
Swap(arr + midIdx, arr + end);
//这里给出的基准值每次都取区间的最后一个元素
key = arr[end];
while (begin < end)
{
//从前往后找比基准值大的元素
while (begin < end && arr[begin] <= key)
++begin;
//从后往前找比基准值小的元素
while (begin < end && arr[end] >= key)
--end;
//比基准值小的放到前面,比基准值大的放到后面(升序)
if (begin < end)
Swap(arr + begin, arr + end);
}
//出了循环,begin这个位置如果不是在最后,就将最后一个元素(基准值)和begin上的
//元素交换,这样,begin之前的元素都小于基准值,begin之后的元素都大于基准值
if (begin != right -1)
Swap(arr + begin, arr + right -1);
return begin;
}
void _QuickSort(int *arr, int left, int right)
{
//只有一个数据一定有序
if (right - left > 1)
{
int div = Partion1(arr, left, right);
_QuickSort(arr, left, div);
_QuickSort(arr, div + 1, right);
}
}
void QuickSort(int *arr, int size)
{
//区间为左闭右开
_QuickSort(arr, 0, size);
}
时间复杂度:O(n^2)
适用场景:数据比较随机
稳定性:不稳定
基本思想:将待排序的元素序列分成两个长度相等的子序列,对每个子序列排序然后将他们合并,合并两个子序列的过程称为二路合并
void MergeData(int *arr, int left, int mid, int right, int* tmp)
{
//区间为左闭右开
int beginL = left, endL = mid;
int beginR = mid, endR = right;
int index = left;//用于索引辅助空间
while (beginL < endL && beginR < endR)
{
if (arr[beginL] <= arr[beginR])
tmp[index++] = arr[beginL++];
else
tmp[index++] = arr[beginR++];
}
//左区间没有搬移完
while (beginL < endL)
tmp[index++] = arr[beginL++];
//右区间没有搬移完
while (beginR < endR)
tmp[index++] = arr[beginR++];
}
void _MergeSort(int *arr, int left, int right, int *tmp)
{
if (left+1 < right)
{
//每次进来都对数据进行平均切分区间,因为归并必须是两组有序的
//数据,所以,切分到只剩一个数据时才是有序,才能进行归并.
int mid = left + (right - left) / 2;
//数据均分为两个区间(左闭右开),左区间和右区间,先排左区间
_MergeSort(arr, left, mid, tmp);
//排右区间
_MergeSort(arr, mid, right, tmp);
//走到这里说明左右两个区间都已经有序,在进行归并
MergeData(arr, left, mid, right, tmp);
//将辅助空间已经排好序的数据拷贝到arr
memcpy(arr + left, tmp + left, (right - left)*sizeof(arr[0]));
}
}
void MergeSort(int *arr, int size)
{
int *tmp = (int*)malloc(sizeof(int)*size);
if (NULL == tmp)
return;
//区间为左闭右开
_MergeSort(arr, 0, size, tmp);
free(tmp);
}
时间复杂度:O(nlog2^n) 空间复杂度:O(n)
适用场景:数据量大的情况
稳定性:稳定
基本思想:计数排序又称鸽巢原理,是对哈希直接定制法的变形应用,先统计相同元素出现次数,再根据统计的结果将序列回收到原来的序列中
void CountSort(int *arr, int size)
{
int range = 0;//用来标识这组数据的范围
int index = 0;
int MaxValue = arr[0];//用来标识数据的最大值
int MinValue = arr[0];
int i = 0;
int *count = NULL;//用来计数的空间
for (i = 1; i < size; i++)
{
if (arr[i] > MaxValue)
MaxValue = arr[i];
if (arr[i] < MinValue)
MinValue = arr[i];
}
//计算范围,这里一定要+1
range = MaxValue - MinValue + 1;
//这里用calloc是为了将空间元素初始化为0
count = (int*)calloc(range, sizeof(arr[0]));
if (NULL == count)
return;
//统计每个元素出现次数
for (i = 0; i < size; i++)
{
//arr[i]-MinValue为count数组的下标
count[arr[i] - MinValue]++;
}
//回收元素
for (i = 0; i < range; i++)
{
//只要count[i]不为0,说明里面还有i+MinValue这个元素
while (count[i]--)
arr[index++] = i + MinValue;
}
free(count);
}
时间复杂度:O(n)
适用场景:适用于数据比较密集集中于某个范围
稳定性:稳定