目录
一. 直接插入排序
二:选择排序
三:冒泡排序
四.堆排序
五:希尔排序
六:快速排序(递归与非递归)
七.归并排序(递归与非递归)
一. 直接插入排序
🌟排序思路
直接插入排序的基本原理是将一条记录插入到已排好的有序表中,从而得到一个新的、记录数量增1的有序表,其思路就和我们摸扑克牌一样,每摸到一张牌按照大小把他插入到对应位置,这样等摸完全部的牌时,我们手里的牌就是有序的
⛲动态图解:
💬特点
🚩时间复杂度:
O(N^2)(若待排序表为有序的则时间复杂度为O(N))
🚩空间复杂度:
空间复杂度为O(1)
🚩稳定性:
稳定
⚡代码演示:
void InsertSort(int* a, int n)
{for (int i = 0; i < n - 1; i++){int end = i;//首先保存一下待插入的元素int tmp = a[end + 1];//比较的最坏情况时end=0while (end >= 0){//若待排序元素比a[end]小,则让a[end]向后移动腾位置if (tmp < a[end]){a[end + 1] = a[end];end--;}else{//由于本来数组就为有序数组,当不满足上述条件时,说明找到了待插入的位置break;}}//插入数据a[end + 1] = tmp;}
}
二:选择排序
🌟排序思路
每次选出数组的最小值和最大值,分别置于数组头尾处,缩小排序范围重复操作
⛲动态图解:
💬特点
🚩时间复杂度:
时间复杂度为:O(N^2)
🚩空间复杂度:
空间复杂度为O(1)
🚩稳定性:
不稳定
⚡代码演示:
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[mini]){mini = i;}if (a[i] > a[maxi]){maxi = i;}}Swap(&a[begin], &a[mini]);if (maxi == begin){maxi = mini;}Swap(&a[end], &a[maxi]);++begin;--end;}
}
三:冒泡排序
🌟排序思路
比较相邻元素的大小,将大的元素往后交换,每一轮比较能确定一个最大值的位置
⛲动态图解:
💬特点
🚩时间复杂度:
时间复杂度为:O(N^2)
🚩空间复杂度:
空间复杂度为O(1)
🚩稳定性:
稳定
⚡代码演示:
/* 冒泡排序 */
void BubbleSort(int arr[], int length)
{for (int i = 0; i < length; i++){for (int j = 0; j < length - i - 1; j++){if (arr[j] > arr[j + 1]){int temp;temp = arr[j + 1];arr[j + 1] = arr[j];arr[j] = temp;}}}
}
四.堆排序
🌟排序思路
1.首先将待排序数组建为大堆,此时堆顶元素就为数组最大值了
2.交换堆顶和堆尾元素,此时最大元素就到了堆尾,目前数组最大元素就排好了,现在就假设堆里没有当前这个最大元素了,堆头下面的左右子树仍然是大堆,只需要再将堆顶元素向下调整到合适位置,剩下的n-1个元素还是大堆
3.堆头堆尾交换,向下调整,如此反复就可排序
ps.排序以升序为例,升序建大堆,降序建小堆
💬特点
🚩时间复杂度:
O(N*lgN)
🚩空间复杂度:
O(1)
🚩稳定性:
不稳定
⚡代码演示:
#include<stdio.h>
typedef int HeapDataType;void swap(HeapDataType* a, HeapDataType* b)
{int tmp = *a;*a = *b;*b = tmp;
}
//大堆
void AdjustUp(HeapDataType* a, int child)
{int parent = (child - 1) / 2;while (child>0){if (a[parent] < a[child]){swap(&a[parent], &a[child]);child = parent;parent = (parent - 1) / 2;}else{break;}}
}
//大堆
void AdjustDown(HeapDataType* a, int size, int parent)
{int child = parent * 2 + 1;while (child<size){//找较大的孩子if (a[child + 1] > a[child] && child+1<size){child = child + 1;}if (a[parent] < a[child]){swap(&a[parent], &a[child]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapSort(HeapDataType* a, int size)
{//建堆for (int i = 1; i < size; i++){AdjustUp(a, i);}//排序:升序int end = size - 1;while (end>0){swap(&a[0], &a[end]);AdjustDown(a, end, 0); //end指最后一个元素,同时end的值为前面元素的个数end--;}
}
五:希尔排序
🌟排序思路
- 预排序:先将数组分组,将每个组排序,目的是让整个数组相对有序
- 插入排序:待数组相对有序后,进行直接插入排序,这样效率比较高
⛲图解:
假设待排序数组为[9 8 7 6 5 4 3 2 1]
1.我们将他们每隔三个坐标分为一组,假设gap=3
2.对三个数组分别进行直接插入排序,使数组整体相对有序
3.对数组整体进行插入排序
💬特点
🚩时间复杂度:
O(N^1.3)
🚩空间复杂度:
O(1)
🚩稳定性:
不稳定
⚡代码演示:
void ShellSort(int* a, int n)
{//首先将gap定为nint gap = n;/*while (gap > 1){gap = gap / 3 + 1;for (int j = 0; j < gap; j++){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;}}}*/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 (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}
}
六:快速排序(递归与非递归)
1.⚡hoare
🌟排序思路
将数组第一个元素定位关键值,定义begin和end指针,先让end从后往前找到比关键值小的数,begin从前往后找比关键值大的数,然后交换两数,直到 begin==end,再让关键值和begin所指的元素交换,最后返回关键值所在位置,便于后续进行递归或非递归操作
⛲动态图解:
⚡代码演示:
void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}//hoare
int PartSort1(int* a, int begin, int end)
{int left = begin, right = end;int keyi = begin;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[left], &a[keyi]);return left;
}
2.⚡挖坑法
🌟排序思路
首先将关键值定为数组第一个元素,并将坑位定为begin,先让end从后往前找到比关键值小的数,将这个数放到坑位,并更新坑位,再让begin从前往后找比关键值大的数,将这个数放到坑位,并更新坑位,直到 begin==end,再让关键值和坑位的元素交换,最后返回关键值所在位置
⛲动态图解:
⚡代码演示:
//挖坑法
int PartSort2(int* a, int begin, int end)
{int mid = GetMid(a, begin, end);swap(&a[begin], &a[mid]);int key = a[begin];int hole = begin;while (begin < end){//右边找小,填入坑内,更新坑位while (begin<end && a[end]>=key){--end;}a[hole] = a[end];hole = end;//左边找大,填入坑内,更新坑位while (begin<end && a[begin]<=key){++begin;}a[hole] = a[begin];hole = begin;}a[hole] = key;return hole;
}
3.⚡双指针法
🌟排序思路
将数组第一个元素定为关键值,定义两个指针prev和cur,先让prev指向数组的第一个元素,cur指向prev的下一个元素,cur的作用是找比关键值小的元素,若cur所指元素不小于关键值则cur++,直到cur所值元素小于关键值,此时,prev和cur之间的元素都是大于关键值的元素,若prev+1不是cur的话就可以让prev++所指元素与cur所指元素交换了,直到cur指向数组的最后一个元素
⛲动态图解:
⚡代码演示:
//双指针法
int PartSort3(int* a, int begin, int end)
{int mid = GetMid(a, begin, end);swap(&a[begin], &a[mid]);int key = begin;int prev = begin;int cur = prev + 1;while (cur <= end){if (a[cur] < a[key] && ++prev != cur){swap(&a[prev], &a[cur]);}cur++;}swap(&a[prev], &a[key]);return prev;
}
💬特点
🚩时间复杂度:
最好情况:O(n*lgn)
最坏情况:O(n^2)
🚩空间复杂度
O(lgn)
🚩稳定性
不稳定
🚩快速排序递归实现
⛲小优化:
上述三个方法都是快速排序的单趟排序,但是上述排序还有一个小缺陷,因为三个方法都是固定第一个元素为关键值的,如果数组为有序的,那么从后往前找小就要遍历整个数组,效率会很小,所以通常会再写一个找中间值的函数:在数组开头结尾和中间三个数中找出一个大小在中间的数,并让这个数和数组第一个数交换,这样就会减少上述情况的发生
int GetMid(int* a, int begin, int end)
{int mid = (begin + end) / 2;if (a[begin] > a[mid]){if (a[mid] > a[end])return mid;else if (a[end] > a[begin])return end;elsereturn begin;}else{if (a[begin] > a[end])return begin;else if (a[end] > a[mid])return mid;elsereturn end;}
}void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}//hoare
int PartSort1(int* a, int begin, int end)
{int mid = GetMid(a, begin, end);swap(&a[begin], &a[mid]);int left = begin, right = end;int keyi = begin;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[left], &a[keyi]);return left;
}//挖坑法
int PartSort2(int* a, int begin, int end)
{int mid = GetMid(a, begin, end);swap(&a[begin], &a[mid]);int key = a[begin];int hole = begin;while (begin < end){//右边找小,填入坑内,更新坑位while (begin<end && a[end]>=key){--end;}a[hole] = a[end];hole = end;//左边找大,填入坑内,更新坑位while (begin<end && a[begin]<=key){++begin;}a[hole] = a[begin];hole = begin;}a[hole] = key;return hole;
}
//双指针法
int PartSort3(int* a, int begin, int end)
{int mid = GetMid(a, begin, end);swap(&a[begin], &a[mid]);int key = begin;int prev = begin;int cur = prev + 1;while (cur <= end){if (a[cur] < a[key] && ++prev != cur){swap(&a[prev], &a[cur]);}cur++;}swap(&a[prev], &a[key]);return prev;
}
void QuickSort(int* a, int begin,int end)
{if (begin >= end)return;//三种方法任选其一即可int keyi = PartSort3(a, begin, end);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}
🚩快速排序非递归实现
🌟实现思路:
1.创建一个栈,将数组的右边界下标和左边界下标依次入栈
2.循环弹出数组的左右边界下标,并对该区间进行单趟排序,确定关键值的下标,分为左右两个区间
3.若左区间元素个数大于一个,将左区间右边界下标和左边界下标依次入栈,右区间同理
4.重复操作步骤2 3直到栈为空
⚡代码演示:
void QuickSortNonR(int* a, int begin, int end)
{ST s;STInit(&s);STPush(&s,end);STPush(&s,begin);while (!STEmpty(&s)){int left = STTop(&s);STPop(&s);int right = STTop(&s);STPop(&s);int key = PartSort1(a, left, right);if (left < key - 1){STPush(&s, key - 1);STPush(&s, left);}if (right > key + 1){STPush(&s, right);STPush(&s, key+1);}}STDestroy(&s);
}
七.归并排序(递归与非递归)
一:⛲递归实现
🌟算法思路
归并排序是用分治思想,分治模式在每一层递归上有三个步骤:
分解(Divide): 将n个元素分成个含n/2个元素的子序列。
解决(Conquer):用合并排序法对两个子序列递归的排序。
合并(Combine):合并两个已排序的子序列已得到排序结果。
该算法需要先将数组分解,直到每个子序列为一个元素,再将子序列两两合并排序,思路可以参考二叉树的后序递归
💬特点
平均时间复杂度:O(nlogn)
最佳时间复杂度:O(n)
最差时间复杂度:O(nlogn)
空间复杂度:O(n)
⚡动图展示:
⚡代码演示:
void _MergeSort(int* a, int begin, int end, int* tmp)
{if (begin >= end)return;//分解int mid = (begin + end) / 2;_MergeSort(a, begin, mid, tmp);_MergeSort(a, mid+1, end, tmp);// 归并int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;int i = begin;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++];}memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}_MergeSort(a, 0, n - 1, tmp);free(tmp);
}
二:⛲非递归实现
🌟算法思路:
归并排序的递归实现是先将数组分解,直到每个子序列只有一个元素,在将子序列两两归并,非递归的实现思路则没有了分解的过程,直接将数组元素一个与一个归并,在两个与两个归并,在四个与四个归并.......,直到最后两组归并,数组变为有序数组
⚡代码演示:
void MergeSortNonR(int* a, int begin, int end)
{int n = end - begin + 1;int* tmp = (int*)malloc(sizeof(int) * (n));if (tmp == NULL){perror("malloc");return;}int gap = 1;//当gap小于n时两个组才都有元素,才需要归并while (gap < n){int j = 0;for (size_t i = 0; i < n; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;//边界管理if (end1 >= n||begin2>=n){break;}if (end2 >= n){end2 = n - 1;}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 + i, tmp + i, sizeof(int) * (end2 - i + 1));}gap *= 2;}free(tmp);
}