把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列
当插入第i个元素时,前面的i个元素已经排好序,此时array[i]与array[i-1],array[i-2],…进行比较,找到插入位置将array[i]插入,原来位置上的元素顺序后移
具体代码实现:
typedef int ISType;
void InsertSort1(ISType* nums, int size)
{
int end2 = 0;
int end1 = end2 + 1;
while (end1 < size)
{
if (nums[end1] < nums[end2])
{
ISType temp = nums[end1];
while (end2 >= 0 && temp < nums[end2])
{
nums[end2 + 1] = nums[end2];
end2--;
}
nums[end2 + 1] = temp;
}
end2++;
end1 = end2+1;
}
}
调用直接插入排序:
int main()
{
int nums[] = { 3,5,15,36,38,44,47,26,27,2,46,4,19,50,48 };
int size = sizeof(nums) / sizeof(nums[0]);
InsertSort(nums, size);
for (int i = 0; i < size; i++)
{
printf("%d ", nums[i]);
}
return 0;
}
- 元素集合越接近有序,直接插入排序算法的时间效率越高
- 时间复杂度:O(N^2)
- 空间复杂度:O(1),它是一种稳定的排序算法
希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序
具体代码实现:
void ShellSort(int* nums, int size)
{
int gap = size;
while (gap > 1)
{
gap = (gap / 3) + 1;
int end2 = 0;
int end1 = end2 + gap;
while (end1 < size)
{
if (nums[end1] < nums[end2])
{
ISType temp = nums[end1];
while (end2 >= 0 && temp < nums[end2])
{
nums[end2 + gap] = nums[end2];
end2 -= gap;
}
nums[end2 + gap] = temp;
}
end2++;
end1 = end2 + gap;
}
}
}
- 希尔排序是对直接插入排序的优化
- 当gap>1时都是预排序,目的时让数组更接近有序。当gap==1时,数组已经接近有序了,这样就会很快,对整体而言,可以达到优化的效果
- 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算。
*时间复杂度平均为O(N^1.3), 最坏O(log3(N)N)( 以3为底N的对数)- 稳定性:不稳定
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
- 在元素集合array[i]—array[n-1]中选择值最大(小)的数据元素
- 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个元素)交换
- 在剩余的array[i]—array[n-2](array[i+1]—array[n-1])集合中,重复上述步骤,直到集合剩余1个元素
举个栗子(第一趟的排序过程)
原始序列:49、38、65、97、76、13、27、49
1)在进行选择排序过程中分成有序和无序两个部分,开始都是无序序列
结果:49、38、65、97、76、13、27、49
2)从无序序列中取出最小的元素13,将13同无序序列第一个元素交换,此时产生仅含一个元素的有序序列,无序序列减一
结果:{13、} {38、65、97、76、49、27、49}
3)从无序序列中取出最小的元素27,将27同无序序列第一个元素交换,此时产生仅两个元素的有序序列,无序序列减一
结果:{13、27、} {65、97、76、49、38、49}
。。。。。
具体代码实现:
void SelectSort(int* nums, int size)
{
for (int i = 0; i < size - 1; i++)
{
int min = nums[i];
int j = 0,tip = 0;
for (j = i+1; j < size; j++)
{
if (min > nums[j])
{
tip = j;
min = nums[j];
}
}
if (min < nums[i])
{
int temp = nums[i];
nums[i] = nums[tip];
nums[tip] = temp ;
}
}
}
- 直接选择排序思考好理解,但是效率不高,实际很少用
- 时间复杂度O(n^2)
- 空间复杂度O(1)
- 稳定性:不稳定
堆排序是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆
基本思想:
构造大堆在之前关于《C语言实现堆》介绍过,这里就不多介绍了,数交换后,左右子树还是大堆,所以可以使用向下调整算法再次建成大堆
具体代码实现:
void swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
AdjustDown(int* nums, int size, int parent)
{
int child = parent * 2 + 1;
while (child < size)
{
if (child+1 < size && nums[child] < nums[child + 1])
child = child + 1;
if (nums[child] > nums[parent])
{
swap(&nums[child], &nums[parent]);
}
parent = child;
child = parent * 2 + 1;
}
}
void HeapSort(int* nums, int size)
{
//建大堆
for (int i = (size - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(nums, size, i);
}
for (int n = size-1; n >= 1; n--)
{
swap(&nums[0], &nums[n]);
AdjustDown(nums, n - 1, 0);
}
}
- 堆排序使用堆来选数,效率高了很多
- 时间复杂度O(N*logN)
- 空间复杂度O(1)
- 稳定性:不稳定
基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
可以点开冒泡排序的动图,查看冒泡排序的轨迹:
- 从下标为0开始,依次与后一位相比较,谁大谁在后
- 第一轮完成交换,最后一个元素必定是集合最大的元素
- 再依次进行n-1、n-2…个元素的比较,最后得到有序数组
具体代码实现:
void swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void BubbleSort(int* nums, int size)
{
for (int i = 0; i < size - 1; i++)
{
for (int j = 0; j < size - i -1 ; j++)
{
if (nums[j] > nums[j + 1])
{
swap(&nums[j] ,&nums[j + 1]);
}
}
}
}
基本思想: 任取待排序元素序列中的某元素作为基准值,按照该排序码将排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止
0
处++
,R从下标为size-1
处--
具体代码实现:
void swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void QuickSort(int* nums, int begin, int end)
{
if (end <= begin )
{
return ;
}
int left = begin;
int right = end;
int key = left;
while (left < right)
{
while (left < right && nums[right] >= nums[key])
{
--right;
}
while (left < right && nums[left] <= nums[key])
{
++left;
}
swap(&nums[left], &nums[right]);
}
int meet = left;
swap(&nums[key], &nums[left]);
//递归
QuickSort(nums,begin, meet);
QuickSort(nums,meet+1, end);
}
0
处++
,R从下标为size-1
处--
具体代码实现:
void QuickSort2(int* nums, int begin, int end)
{
if (end <= begin)
{
return;
}
int left = begin;
int right = end;
int key = begin;
while (left < right)
{
while (left < right && nums[right] >= nums[key])
{
--right;
}
nums[left] = nums[right];
while (left < right && nums[left] <= nums[key])
{
++left;
}
nums[right] = nums[left];
}
nums[left] = nums[key];
int meet = left;
QuickSort2(nums, begin, meet);
QuickSort2(nums, meet + 1, end);
}
prev++
,交换prev和cur所指向的值具体代码实现:
void QuickSort3(int* nums, int begin, int end)
{
if (end <= begin)
{
return;
}
int prev = begin;
int cur = prev+1;
int keyi = begin;
while (cur <= end)
{
if (nums[cur] < nums[keyi] && ++prev != cur)
{
swap(&nums[cur], &nums[prev]);
}
++cur;
}
swap(&nums[keyi], &nums[prev]);
int meet = prev;
QuickSort3(nums, begin, meet);
QuickSort3(nums, meet + 1, end);
}
- 三数取中法选key
- 递归到小的子区间时,可以考虑使用插入排序
所以key的指向的值越接近中位数越接近二分,key最好是位于数组元素的中间位置,这样可以避免最坏的情况发生
解决方案:加入一个三数取中函数,然后交换首元素和所取中位数的位置,key依然指向第一个元素
具体代码实现: (挖坑法为例)
//交换
void swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
//三数取中
int GetMidIndex(int* nums, int left, int right)
{
int mid = (left + right) >> 1;
if (nums[left] < nums[mid])
{
if (nums[mid] < nums[right])
{
return mid;
}
else if (nums[left] > nums[right])
{
return left;
}
else
{
return right;
}
}
else
{
if (nums[mid] > nums[right])
{
return mid;
}
else if (nums[left] < nums[right])
{
return left;
}
else
{
return right;
}
}
}
//挖坑法快排
void QuickSort2(int* nums, int begin, int end)
{
int mid = GetMidIndex(nums, begin, end);
swap(&nums[begin], &nums[mid]);
if (end <= begin)
{
return;
}
int left = begin;
int right = end;
int key = begin;
while (left < right)
{
while (left < right && nums[right] >= nums[key])
{
--right;
}
nums[left] = nums[right];
while (left < right && nums[left] <= nums[key])
{
++left;
}
nums[right] = nums[left];
}
nums[left] = nums[key];
int meet = left;
QuickSort2(nums, begin, meet);
QuickSort2(nums, meet + 1, end);
}
2.小的子区间,使用插入排序
- 如果这个子区间数据较多,继续选择key单躺,分割子区间分治递归
- 如果这个子区间数据较少,去分治递归很浪费
解决方案: 设定一个子区间的范围,到达这个范围时,不再递归而是使用插入排序
具体代码实现: (挖坑法为例)
void QuickSort2(int* nums, int begin, int end)
{
int mid = GetMidIndex(nums, begin, end);
swap(&nums[begin], &nums[mid]);
if (end <= begin)
{
return;
}
//子区间范围大于10时,继续递归分治
if (end - begin > 10)
{
int left = begin;
int right = end;
int key = begin;
while (left < right)
{
while (left < right && nums[right] >= nums[key])
{
--right;
}
nums[left] = nums[right];
while (left < right && nums[left] <= nums[key])
{
++left;
}
nums[right] = nums[left];
}
nums[left] = nums[key];
int meet = left;
QuickSort2(nums, begin, meet);
QuickSort2(nums, meet + 1, end);
}
//子区间小于10,使用插入排序
else
{
InsertSort(nums + begin, end - begin + 1);
}
}
基本思想: 用Stack存储数据模拟递归过程
具体代码实现:(这里的栈沿用之前文章中C语言实现的栈)
void swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
//三数取中
int GetMidIndex(int* nums, int left, int right)
{
int mid = (left + right) >> 1;
if (nums[left] < nums[mid])
{
if (nums[mid] < nums[right])
{
return mid;
}
else if (nums[left] > nums[right])
{
return left;
}
else
{
return right;
}
}
else
{
if (nums[mid] > nums[right])
{
return mid;
}
else if (nums[left] < nums[right])
{
return left;
}
else
{
return right;
}
}
}
void QuickSortNonR(int* nums, int left, int right)
{
Stack st;
StackInit(&st);
StackPush(&st, left); //先入栈
StackPush(&st, right);
//栈为空时结束
while (!StackEmpty(&st))
{
int left, right;
right = StackTop(&st);
StackPop(&st);
left = StackTop(&st);
StackPop(&st);
int mid = GetMidIndex(nums, left, right);
swap(&nums[left], &nums[mid]);
int key = left;
while (left < right)
{
while (left < right && nums[right] >= nums[key])
{
--right;
}
nums[left] = nums[right];
while (left < right && nums[left] <= nums[key])
{
++left;
}
nums[right] = nums[left];
}
nums[left] = nums[key];
int meet = left;
if (left < meet - 1)
{
StackPush(&st, left);
StackPush(&st, meet-1);
}
if (meet + 1 < right)
{
StackPush(&st, meet+1);
StackPush(&st, right);
}
}
StackDestrory(&st);
}
- 快速排序整体的综合性能和使用场景都是比较好的
- 时间复杂度:O(N*logN)
- 空间复杂度:O(logN)
- 稳定性:不稳定
基本思想: 是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并
具体代码实现:
void _MergeSort(int* nums, int left, int right, int* temp)
{
if (left >= right)
return;
int mid = (left + right) >> 1;
//分解
_MergeSort(nums, left, mid, temp);
_MergeSort(nums, mid+1, right, temp);
//归并
int begin1 = left, end1 = mid;
int begin2 = mid+1, end2 = right;
int i = left;
while (begin1 <= end1 && begin2 <= end2)
{
if (nums[begin1] < nums[begin2])
{
temp[i++] = nums[begin1++];
}
else
{
temp[i++] = nums[begin2++];
}
}
while (begin1 <= end1)
{
temp[i++] = nums[begin1++];
}
while (begin2 <= end2)
{
temp[i++] = nums[begin2++];
}
//拷贝
for (int j = left; j <= right; j++)
{
nums[j] = temp[j];
}
}
void MergeSort(int* nums, int size)
{
int* temp = (int*)malloc(sizeof(int) * size);
if (temp == NULL)
{
perror("malloc fail\n");
exit(-1);
}
_MergeSort(nums, 0, size - 1, temp);
free(temp);
temp = NULL;
}
具体代码实现:
void _MergeSort2(int* nums, int* temp, int begin1, int end1, int begin2, int end2)
{
int j = begin1;
int i = begin1;
while (begin1 <= end1 && begin2 <= end2)
{
if (nums[begin1] < nums[begin2])
{
temp[i++] = nums[begin1++];
}
else
{
temp[i++] = nums[begin2++];
}
}
while (begin1 <= end1)
{
temp[i++] = nums[begin1++];
}
while (begin2 <= end2)
{
temp[i++] = nums[begin2++];
}
//拷贝
for (; j <= end2; j++)
{
nums[j] = temp[j];
}
}
void MergeSort2(int* nums, int size)
{
int* temp = (int*)malloc(sizeof(int) * size);
if (temp == NULL)
{
perror("malloc fail\n");
exit(-1);
}
int gap = 1;
while (gap < size)
{
for (int i = 0; i < size; i += 2 * gap)
{
//如果第二个小区间不存在就不需要归并了,结束本次循环
int begin1 = i, end1 = i + gap - 1, begin2 = i + gap, end2 = i +\
2 * gap - 1;
if (begin2 >= size)
break;
//如果第二个小区间存在,但是第二个小区间不够gap个,结束位置越界了,需要修正
if (end2 >= size)
{
end2 = size - 1;
}
_MergeSort2(nums, temp, begin1, end1, begin2, end2);
}
gap *= 2;
}
}
基本思想: 是对哈希直接定址法的变形应用
具体代码实现:
void CountSort(int* nums, int size)
{
int min = nums[0], max = nums[0];
for (int i = 0; i < size; i++)
{
if (nums[i] > max)
max = nums[i];
if (nums[i] < min)
min = nums[i];
}
int range = max - min + 1;
int* countnums = (int*)calloc(range,sizeof(int));
for (int i = 0; i < size; i++)
{
countnums[nums[i] - min]++;
}
int i = 0;
for (int j = 0; j < range; ++j)
{
while (countnums[j]--)
{
nums[i++] = j + min;
}
}
}
稳定性: 是指数组中相同的值,排完序以后,相对顺序不变,就是稳定的,否则就是不稳定的