一、冒泡排序
基本原理:两两比较相邻记录的关键字,如果反序则交换。
时间复杂度:最好的情况O(n),最坏的情况O(n²)。
代码:
void bubbleSort(int arr[],size_t len){
for(int i=0;i<len-1;i++){
bool isSwap = false; //记录有没有交换 每次循环开始前置false
for(int j=1;j<len-i;j++){//从第一个到除去每次大循环减掉的一个数(最后的最大值)
if(arr[j]<arr[j-1]){ //如果当前这个值比它后一个值大
swap(&arr[j],&arr[j-1]);//将这两个值交换
isSwap = true;
}
}
if(!isSwap){ //如果没有再交换说明已经是有序的,直接退出循环,可以减少循环的次数
break;
}
}
}
二、直接插入排序
基本原理:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
时间复杂度:时间复杂度也为O(n²), 比冒泡法和选择排序的性能要更好一些。
代码:
void insertSort(int arr[],size_t len){
for(int i=1;i<len;i++){//把arr[i]插入到前面,使用arr[0..i]区间都有序
int j=i-1;
int data = arr[i];
for(;j>=0&&data < arr[j];--j){
arr[j+1] = arr[j];
}
arr[j+1] = data;
}
}
三、选择排序
基本原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
时间复杂度:与冒泡排序同为O(n^2),但简单选择排序的性能要略优于冒泡排序
代码:
void selectSort(int arr[],size_t len){
for(int i=0;i<len-1;i++){
int maxInd = 0; //记录最大值
for(int j=1;j<len-i;j++){
if(arr[maxInd] < arr[j]){
maxInd = j;
}
}
if(maxInd != len-1-i)
swap(&arr[maxInd],&arr[len-1-i]);
}
}
四、鸡尾酒排序
基本原理:定义最大值和最小值的下标,找出最大值放在最右边,最小值放在最左边,直到排成有序数列。
时间复杂度:其时间复杂度最好情况为O(n)、最差与平均情况为O(n²),
空间复杂度:O(1)。
代码:
void cookSort(int arr[],size_t len){
for(int i=0;i<len/2;i++){
int minInd = i;
int maxInd = i;
for(int j=i+1;j<len-i;j++){
if(arr[j]<arr[minInd]){
minInd = j;
}
if(arr[maxInd]<arr[j]){
maxInd = j;
}
}
if(minInd != i){
swap(&arr[minInd],&arr[i]);
}
if(maxInd == i){
maxInd = minInd;
}
if(maxInd != len-1-i){
swap(&arr[maxInd],&arr[len-1-i]);
}
}
}
五、二分插入排序
基本原理:采用二分查找,来找到待排序元素的插入位置,然后移动元素,将待排序的元素插入序列中。
时间复杂度:时间复杂度为O(n2)。
空间复杂度:O(1)。
代码:
void binSort(int arr[],size_t len){
for(int i=1;i<len;i++){
int left = 0;
int right = i-1;
int data = arr[i];
while(left<=right){
int mid = (left+right)/2;
if(data<arr[mid]){
right = mid-1;
}else{
left = mid+1;
}
}
int j = i-1;
for(;j>right;--j){
arr[j+1] = arr[j];
}
arr[j+1] = data;
}
}
六、希尔排序
基本原理:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序。
时间复杂度:其时间复杂度为O(n^3/2),要好于直接插入排序的O(n^2)
代码:
void shellSort(int arr[],size_t len){
for(int step = len/2;step>0;step = step/2){//分组
for(int j = step;j<len;j++){
int data = arr[j];
int k = j-step;
for(;k>=0&&data<arr[k];k-=step){
arr[k+step] = arr[k];
}
arr[k+step] = data;
}
}
}
七、快速排序
基本原理:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
时间复杂度:时间复杂度为O(nlogn)。
代码:
void quick(int arr[],int left,int right){
int data = arr[left];
int i = left;
int j = right;
while(i<j){
while(i<j&&data<=arr[j]){--j;}
if(i<j)
arr[i] = arr[j];
while(i<j&&arr[i]<=data){++i;}
if(i<j)
arr[j] = arr[i];
}
arr[i] = data;
if(i-left>1){
quick(arr,left,i-1);
}
if(right-i>1){
quick(arr,i+1,right);
}
}
void quickSort(int arr[],size_t len){
quick(arr,0,len-1);
}
八、归并排序
基本原理:假设初始序列含有n个记录,则可以看成n个有序的子序列,每个子序列的长度为1,然后两两归并,得到(不小于n/2的最小整数)个长度为2
或1的有序子序列,再两两归并,...如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。
时间复杂度:O(nlogn)。
空间复杂度:O(n+logn),如果非递归实现归并,则避免了递归时深度为logn的栈空间 空间复杂度为O(n)。
代码:
//合并数组的两部分,使数组的这两部分合并成一个有序的部分
void mergerArr(int arr[],size_t left,size_t right){
int mid = (left+right)/2; //[left,mid] [mid+1,right]分别是有序的
//合并 [left,right]区间有序
int lds = mid+1-left;
int *pd = malloc(lds*sizeof(int));
for(int i=0;i<lds;i++){
pd[i] = arr[left+i];
}
int i = 0,j = mid+1;//i记录pa的下标 j记录arr[mid+1,right]下标
int k = left;
while(i<lds && j<=right){
if(pd[i] < arr[j]){
arr[k++] = pd[i++];
}else{
arr[k++] = arr[j++];
}
}
while(i<lds){
arr[k++] = pd[i++];
}
free(pd);
/*不需要
while(j<=right){
arr[k++] = arr[j++];
}
*/
}
//递归调用
void merger(int arr[],size_t left,size_t right){
if(left>=right){
return;
}
int mid = (left+right)/2;
if(mid-left>=1)
merger(arr,left,mid);//对[left,mid]区间进行排序
if(right-mid-1>=1)
merger(arr,mid+1,right);//对[mid+1,right]区间进行排序
mergerArr(arr,left,right);//把[left,mid],[mid+1,right]两部分有序的合并成一个整体有序的
}
void mergerSort(int arr[],size_t len){
merger(arr,0,len-1);
}
九、堆排序
基本原理:将待排序的序列构造成一个大顶堆.此时,整个序列的最大值就是堆顶 的根结点.将它移走(其实就是将其与堆数组的末尾元素交换, 此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素的次大值.如此反复执行,便能得到一个有序序列了。
时间复杂度: 时间复杂度为 O(nlogn),好于冒泡,简单选择,直接插入的O(n^2)。
代码:
//排成大栈堆的过程
void reheap(int arr[],size_t index,size_t len){ //从index的根节点开始
int data = arr[index]; //记录根节点
int child = 0; //记录较大的子节点
for(;index<len;index=child){ //根节点存在 每循环一次把较大子节点作为根节点
child = 2*index+1;
if(child>=len){ //左子节点不存在直接跳出循环
break;
}
if(child+1<len && arr[child]<arr[child+1]){ //右子节点存在 右子节点的值大于左子节点时
++child; //将右子节点作为较大子节点
}
if(data < arr[child]){ //较大子节点的值 大于 data
arr[index] = arr[child]; //将较大子节点的值放到根节点的位置
}else{
break; //如果data比 子节点都要大 直接跳出循环 说明下面都已经时大栈堆了
}
}
arr[index] = data; //把data放到最后的根节点处
}
//从最后一个有子节点的根节点开始一直往前排成大栈堆
void heap(int arr[],size_t len){
for(int i = len/2-1;i>=0;--i){ //len/2-1 是最后一个有子节点的根节点
reheap(arr,i,len); //从最后一个有子节点的根节点开始,到最后,排成大栈堆
}
}
//将排成大栈堆的二叉树的最大值和最后一个值交换,然后将除去最后一个值的剩余部分再进行大栈堆排序,重复步骤直到排成有序。
void heapSort(int arr[],size_t len){
heap(arr,len);//排成大栈堆
for(int i=len-1;i>0;--i){ //len-1是最后一个
swap(&arr[0],&arr[i]); //最后一个和第一个交换
reheap(arr,0,i); //剩余的部分排成大栈堆
}
}
十、计数排序
基本原理:计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。
算法步骤:
- 找出待排序的数组中最大和最小的元素
- 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
- 对所有的计数累加(从C中的位置为1的元素开始,每一项和前一项相加)
- 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
时间复杂度:O(n+k)。
代码:
void countSort(int arr[],size_t len){
int max = arr[0];
int min = arr[0];
for(int i=0;i<len;i++){
if(max < arr[i]){
max = arr[i];
}
if(min > arr[i]){
min = arr[i];
}
}
int cnt = max+1-min;//最大值和最小值之间相差多少个数
int *pCnt = calloc(cnt, sizeof(int));//pCnt[0] pCnt[cnt-1]
//pCnt[0] == 0 代表 min这个数个数为0
//pCnt[0] == 1 代表 min这个数有一个
//pCnt[i] == n 代表 min+i 这个数有 n 个
for(int i=0;i<len;i++){//arr[i]这个值 pCnt[arr[i]-min]
pCnt[arr[i]-min]++; //arr[i]这个值的个数加1
}
int j = 0;
for(int i=0;i<cnt;i++){
while(pCnt[i] != 0){
arr[j++] = i+min;
--pCnt[i];
}
}
free(pCnt);
}
十一、基数排序
基本原理:将整数按位数切割成不同的数字,然后按每个位数分别比较。
时间复杂度:O(n*k)。
算法复杂度总结:
