目录
插入排序
直接插入排序
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 (a[end] > tmp) {
a[end + 1] = a[end];
end--;
}
else {
break;
}
}
a[end + 1] = tmp;
}
}
希尔排序
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) {
if (a[end] > tmp) {
a[end + gap] = a[end];
end -= gap;
}
else {
break;
}
}
a[end + gap] = tmp;
}
}
}
选择排序
直接选择排序
void SelectSort(int* a, int n) {
int begin = 0;
int end = n - 1;
while (begin < end) {
int mini = begin;
int maxi = begin;
for (int i = begin + 1; i <= end; i++) {
if (a[i] < a[mini]){
mini = i;
}
if (a[i] > a[maxi]) {
maxi = i;
}
}
Swap(&a[begin], & a[mini]);
if (begin==maxi) {
maxi = mini;
}//假如初始数组的最大值在最开始,最小值交换与最开始的值交换位置后,最大的值在原先最小值的位置
Swap(&a[end], &a[maxi]);
begin++;
end--;
}
}
堆排序
// 堆排序(升序建大堆)
void AdjustDown(int* a, int n, int root) {
int child = root * 2 + 1;
while (child < n) {
if (child + 1 < n && a[child + 1] > a[child]) {
child++;
}
if (a[child] > a[root]) {
Swap(&a[child], &a[root]);
root = child;
child = root * 2 + 1;
}
else {
break;
}
}
}
void Swap(int* num1, int* num2) {
int tmp = *num1;
*num1 = *num2;
*num2 = tmp;
}
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--;
}
}
交换排序
冒泡排序
void BubbleSort(int* a, int n) {
for (int i = 0; i < n - 1; i++) {
int exchange = 0;
for (int j = 0; j < n - i - 1; j++) {
if (a[j + 1] < a[j]) {
exchange = 1;
int tmp = a[j + 1];
a[j + 1] = a[j];
a[j] = tmp;
}
}
if (exchange == 0) {
break;//如果这一轮没有数字交换,说明本次开始数组是有序的,没有交换
}
}
}
快速排序(递归)
三数取中
假如每次的key都是在数组的最左边,就会出现每次递归为单链的情况,时间复杂度为O(N^2),数据量较大的时候,可能出现栈溢出。所以尽量把key设在偏中间,可以减少地递归深度。
三数取中后,递归深度明显减少。
//为了避免栈溢出,要让a[keyi]的数尽量在中间,选出不是最大,也不是最小的数
//选用三数取中
int GetMidIndex(int* a,int left, int right) {
int mid = (left + right) / 2;
if (a[left] < a[mid]) {
if (a[right] > a[mid]) {
return mid;
}
//走到这一步,a[mid]>a[right],a[mid]最大,中间数在a[left]和a[right]中的一个
else if (a[left] > a[right]) {
return left;
}
else {
return right;
}
}
//走到这一步,a[left]>a[mid]
else {
if (a[mid] > a[right]) {
return mid;
}
//走到这一步,a[mid]<a[right], a[mid]最小,中间数在a[left]和a[right]中的一个
else if (a[left] > a[right]) {
return right;
}
else {
return left;
}
}
}
快速排序(hoera版)
int PartSort1(int* a, int left, int right) {
int mid=GetMidIndex(a, left, right);
Swap(&a[mid], &a[left]);
int keyi = left;
while (left < right) {
//当左边的数做key时,要让右边的数先走
while (left<right && a[right] >= a[keyi]) {//右边找小,注意当数组所有值相等时,right可能会越界,所以在这层循环要设置left<right
right--;
}
while (left<right && a[left] <= a[keyi]) {//左边找大
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
return keyi;
}
快速排序(挖坑版)
int PartSort2(int* a, int left, int right) {
if (left >= right) {
return -1;
}
int mid = GetMidIndex(a, left, right);
Swap(&a[mid], &a[left]);
int key = a[left];
int piti = left;
while (left < right) {
while (left < right && a[right] >= key) {
right--;
}
//找到小的数
a[piti] = a[right];
piti = right;
while (left < right && a[left] <= key) {
left++;
}
//找到大的数
a[piti] = a[left];
piti = left;
}
a[left] = key;
piti = left;
return piti;
}
快速排序(前后指针版)
int PartSort3(int* a, int left, int right) {
int prev = left;
int cur = left + 1;
int keyi = left;
/*int mid = GetMidIndex(a, left, right);
Swap(&a[mid], &a[keyi]);*/
while (cur <= right) {
//找小,判断a[cur]是否小于key,若小于,prev后移一位,a[prev]与a[cur]交换位置,cur++
if (a[cur] < a[keyi] && ++prev != cur) {
Swap(&a[prev], &a[cur]);
}
cur++;
}
Swap(&a[keyi], &a[prev]);
keyi = prev;
return keyi;
}
快速排序适用于处理大量数据,但是越往深处递归,可能会出现栈溢出,后面的区间越分越小,递归次数占比总次数较多,为减少递归次数,可以在数据量较小时使用插入排序。
void QuickSort(int* a, int left, int right) {
if (left >= right) {
return;
}
if (right - left+1 > 20) {
hoare版本
//int keyi = PartSort1(a, left, right);
//QuickSort(a, left, keyi - 1);
//QuickSort(a, keyi+1, right);
挖坑法
//int piti = PartSort2(a, left, right);
//QuickSort(a, left, piti - 1);
//QuickSort(a, piti+1, right);
//前后指针法
int keyi = PartSort3(a, left, right);
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;
InitStack(&st);
StackPush(&st, right);
StackPush(&st, left);
while (!StackEmpty(&st)){
int begin = StackTop(&st);
StackPop(&st);
int end = StackTop(&st);
StackPop(&st);
int keyi=PartSort3(a, begin, end);
if (keyi + 1 < end) {
StackPush(&st, end);
StackPush(&st, keyi + 1);
}
if (keyi - 1 >begin) {
StackPush(&st, keyi-1);
StackPush(&st, begin);
}
}
StackDestroy(&st);
return;
}
归并排序
归并(递归)
void _MergeSort(int* a, int left, int right,int* tmp) {
if (left >= right) {
return;
}
int mid = (left + right)/2;
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid+1, right, tmp);
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int i = begin1;
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++];
}
int m = right - left + 1;
memcpy(a + left, tmp + left, m*sizeof(int));
}
// 归并排序递归实现
void MergeSort(int* a, int n) {
int* tmp = (int*)malloc(sizeof(int) * n);
assert(tmp);
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
tmp = NULL;
}
归并(非递归)
// 归并排序非递归实现
void MergeSortNonR(int* a, int n) {
int* tmp = (int*)malloc(sizeof(int) * n);
assert(tmp);
int gap =1;
//分组
while (gap < n) {
for (int i = 0; i < n; i += 2 * gap){
int begin1 = i, end1 = i + gap - 1;//[i,i+gap-1] [i+gap,i+2*gap-1]
int begin2 = i + gap, end2 = i + 2 * gap - 1;
//如果n不是2次方的数,需要修正
//如果end1越界,第二区间修正为不存在的范围,就不用归并
if (end1 >= n) {
end1 = n - 1;
begin2 = n;
end2 = n - 1;
}
//begin2开始越界
else if (begin2 >= n) {
begin2 = n;
end2 = n - 1;
}
//end2开始越界
else if (end2 >= n) {
end2 = n - 1;
}
int j = begin1;
while (begin1 <= end1 && begin2 <= end2) {
if (a[begin1] < a[begin2]) {
tmp[j++] = a[begin1++];
}
else {
tmp[j++] = a[begin2++];
}
}
while (begin1 <= end1) {
tmp[j++] = a[begin1++];
}
while (begin2 <= end2) {
tmp[j++] = a[begin2++];
}
}
memcpy(a, tmp, sizeof(int) * n);
gap *= 2;
}
free(tmp);
tmp = NULL;
}
计数排序
先遍历一边数组,找出最大值和最小值,数组a对应的数据采用相对映射到tmp中,例如
tmp数组对应下标的值,就是待排序数组每个数据出现的次数。
void CountSort(int* a, int n) {
int min = a[0], max =a[0];
int i = 0;
for (int i = 0; i < n; i++) {
if (a[i] < min)
min = a[i];
if (a[i] > max)
max = a[i];
}
int m = max - min + 1;
int* tmp = (int*)calloc(m,sizeof(int));
assert(tmp);
for (i = 0; i < n; i++) {
tmp[a[i] - min]++;
}
int j = 0;
for (i = 0; i < m; i++) {
while (tmp[i]--) {
a[j++] = min+i;
}
}
}
这种排序方法局限性较大,不能堆浮点数和字符排序,且数据要相对集中,如果数据范围较大,空间开销很大。
总结
排序算法 | 最好时间复杂度 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
直接插入排序 | O(N)(有序) | O(N^2) | O(N^2) | O(1) | 稳定 |
希尔排序 | O(NlogN)~O(N^2) | O(N^1.3) | O(N^2) | O(1) | 不稳定 |
直接选择排序 | O(N^2) | O(N^2) | O(N^2) | O(1) | 不稳定 |
堆排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(1) | 不稳定 |
冒泡排序 | O(N) | O(N^2) | O(N^2) | O(1) | 稳定 |
快速排序 | O(NlogN) | O(NlogN) | O(N^2)(有序) | O(logN)~O(N) | 不稳定 |
归并排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(N+logN) ->O(N) | 稳定 |