基本排序算法
排序
- 插入排序
- 希尔排序
- 选择排序
- 堆排序
- 冒泡排序
- 计数排序
- 快排
- 实现快排的几种方式:
- 1.hoare版本
- 2.前后指针法
- 3.挖坑法
- 4.快排非递归
- 归并排序
插入排序
基本思想:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。
插入排序代码:
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 (tmp < a[end]){a[end+1] = a[end];end--;}else{break;}}a[end + 1] = tmp;}}
插入排序的时间复杂度:O(N^2);
希尔排序
希尔排序也是插入排序,它是插入排序的优化,插入排序有一个非常明显的缺点,如果插入排序数据是逆序的话,它的效率就下来了。
希尔排序大致分为两个步骤:
1.预排序(让数组接近有序)。
2.插入排序。
1.预排序。
设置一个gap(gap为间隔),间隔gap为一组,进行插入排序。
例如:假设gap = 3
先尝试一下对每个gap组进行排序:
void ShellSort(int* a, int n)
{int gap = 3;for(int j = 0;j<gap;j++)//gap组依次排序{for (int i = j; i < n-gap; i + gap)//每组排序{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;}}
}
优化(效率没有变):
void ShellSort(int* a, int n)
{int gap = 3;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;}
}
关于gap:
gap越大,大的可以越快跳到后面,小的数可以越快跳到前面,越不接近有序
gap越小,跳得越慢,但是越接近有序。当gap==1相当于插入排序就有序了
所以gap到底取多少呢?
有人算出:gap = gap/3+1
比较合理。
希尔排序代码:
void ShellSort(int* a, int n)
{int gap = n;while (gap > 1){//gap>1就是预排序//gap==1就是插入排序gap = gap / 3 + 1;//+1保证最后一个gap一定等于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, end = n - 1;while (begin < end){int mini = begin, maxi = begin;for (int i = begin + 1; i <= end; ++i){if (a[i] > a[maxi]){maxi = i;}if (a[i] < a[mini]){mini = i;}}Swap(&a[begin], &a[mini]);if (a[begin] == a[maxi])Swap(&a[end], &a[mini]);elseSwap(&a[end], &a[maxi]);++begin;--end;}
}
时间复杂多:O(N^2);(不推荐)
堆排序
void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
void AdjustDown(int* a, int n, int parent)
{//要对比左,右孩子的大小//假设左孩子小int child = parent * 2 + 1;//找出小的那个孩子while (child < n)//chile>=n,越界了{if (child + 1 < n && a[child + 1]> a[child])//右孩子也要n{++child;}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}
void HeapSort(int *a,int n)
{for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}int end = n - 1;while (end > 0){Swap(&a[0],&a[end]);AdjustDown(a, end, 0);--end;}
}
具体见:堆的基本认识
时间复杂度:O(N*logN);
冒泡排序
void BubbleSort(int* a, int n)
{for (int j = 0; j < n ; j++){for (int i = 0; i < n -j; i++){if (a[i] > a[i + 1]){Swap(&a[i], &a[i + 1]);}}}}
计数排序
思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:
- 统计相同元素出现次数
- 根据统计的结果将序列回收到原来的序列中。
如:对数组a进行计数排序
相对映射:在以上a数组中,它的范围是0~10,那如果出现这样的一组数组呢?
a[]={100,105,107,104,101,109}
,count数组如果从头开到109就太浪费空间了,就想要进行相对映射。
什么是相对映射?简单来说,就是count数组按范围开空间,其下标就对应代表a数组其中的某一个值。
计数排序的代码:
void CountSort(int* a, int n)
{
//求范围int min = a[0];int max = a[0];for (int i = 0; i < n; i++){if (a[i] < min)min = a[i];if (a[i] > max)max = a[i];}int range = max - min + 1;//开辟count数组int* count = (int*)calloc(range, sizeof(int));//要判空if (count == NULL){perror("calloc fail");return;}//计数for (int i = 0; i < range; i++){count[a[i] - min]++;}//排序int j = 0;for (int i = 0; i < range; i++){while (count[i]--){a[j++] = min + i;}}free(count);
}
时间复杂度:O(MAX(N,范围))
空间复杂度:O(范围)
快排
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
实现快排的几种方式:
void QuickSort(int* a, int left, int right)
{if (left >= right)return;//结束条件int keyi = PartSort1(a, left, right);//hoare版本int keyi = PartSort2(a, left, right);//前后指针法// [left, keyi-1] keyi [keyi+1, right]QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}
关于优化:
1.三数取中
2.小区间优化
1.hoare版本
//小区间优化if ((right - left + 1) < 10){InsertSort(a, right - left + 1);}else{// 三数取中int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int keyi = left;int begin = left, end = right;while (begin < end){// 右边找小while (begin < end && a[end] >= a[keyi]){--end;}// 左边找大while (begin < end && a[begin] <= a[keyi]){++begin;}Swap(&a[begin], &a[end]);}Swap(&a[keyi], &a[begin]);return begin;}}
2.前后指针法
int PartSort2(int* a, int left, int right)
{// 三数取中int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int keyi = left;int prev = left;int cur = prev + 1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[prev], &a[cur]);cur++;}Swap(&a[prev], &a[keyi]);return prev;
}
3.挖坑法
4.快排非递归
为避免快排的深度太深,可以采用非递归。
#include"stack.h"
//非递归排序
void QuickSortNonR(int* a, int left, int right)
{ST st;STInit(&st);STPush(&st, right);STPush(&st, left);while (!STEmpty(&st)){int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);int keyi = PartSort2(a, begin, end);if (keyi + 1 < end){STPush(&st, end);STPush(&st, keyi+1);}if (begin < keyi - 1){STPush(&st, keyi-1);STPush(&st, begin);}}}
为什么左边做key,右边先走,可以保证相遇位置比key小?
相遇的场景分析:
L遇R:R先走,停下来,R停下条件是遇到比key小的值,R停的位置一定比key小L没有找大的,遇到R停下了
R遇L:
R先走,找小,没有找到比key小的,
直接跟L相遇了。L停留的位置是上一轮交换的位置,上一轮交换,把比key小的值,换到L的位置了
归并排序
没写,不想写了