排序算法:
十种比较经典的排序算法:冒泡,选择,插入,希尔,堆,快速,归并,计数,基数,桶。每种算法在一定环境下各有各的优越性,需要根据数据的分布状况进行合理选择。但在笔面试需要掌握快排(上限)和冒泡(下限)两种,如果你情况不错,考你快排不过分吧;如果你状态不太行,考你冒泡总要会吧?
排序算法的稳定性:
当待排序的数据中有相同的数据,排序算法是否会更改它们的前后关系,不会更改的叫稳定排序,可能会更改的叫不稳定排序。
冒泡:
特点:对数据的有序性敏感
是否稳定:稳定
平均时间复杂度: O(n^2)
空间复杂度:O(1)
void bubble_sort(int* arr,size_t len)
{
bool flag = true;
for(int i=len-1; i>0 && flag; i--)
{
flag = false;
for(int j=0; j<i; j++)
{
if(arr[j] > arr[j+1])
{
swap(arr[j],arr[j+1]);
flag = true;
}
}
}
}
选择:
特点:数据的交换次数少,如果待排序的数据字节较多,选择排序是一个不错的方案。
是否稳定:不稳定
平均时间复杂度:O(n^2)
空间复杂度:O(1)
void select_sort(int* arr,size_t len)
{
for(int i=0; i<len-1; i++)
{
int min = i;
for(int j=i+1; j<len; j++)
{
if(arr[j] < arr[min])
min = j;
}
if(min != i)
swap(arr[min],arr[i]);
}
show_sort_result(__func__,arr,len);
}
插入:
特点:数据的交换次数少,适合对有序的数据添加新的数据。
是否稳定:稳定
平均时间复杂度:O(n^2)
空间复杂度:O(1)
void insert_sort(int* arr,size_t len)
{
for(int i=1,j; i<len; i++)
{
int tmp = arr[i];
for(j=i-1; j>=0 && tmp<arr[j]; j--)
{
arr[j+1] = arr[j];
}
arr[j+1] = tmp;
}
}
希尔:
特点:在插入排序的基础添加增量概念,提高了数据到达合适位置的速度。
是否稳定:不稳定
平均时间复杂度:O(nlogn)
空间复杂度:O(1)
void shell_sort(int* arr,size_t len)
{
for(int k=len/2; k>0; k/=2)
{
for(int i=k,j; i<len; i++)
{
int tmp = arr[i];
for(j=i-k; j>=0 && tmp<arr[j]; j-=k)
{
arr[j+k] = arr[j];
}
arr[j+k] = tmp;
}
}
}
堆:
特点:把数据看作完全二叉树,然后把这个二叉树调整为大根堆,然后把堆顶的数据交换到末尾,再把二叉树的数量-1,再调整为大根堆,直到二叉树的数量为1,排序完成。
是否稳定:不稳定
平均时间复杂度:O(nlogn)
空间复杂度:O(1)
void create_heap(int* arr,size_t len,int root)
{
while(root*2 <= len)
{
int max = root*2;
if(max+1<len && arr[max-1] < arr[max])
max++;
if(arr[root-1] > arr[max-1])
return;
swap(arr[root-1],arr[max-1]);
root = max;
}
}
void heap_sort(int* arr,size_t len)
{
for(int i=len/2; i>0; i--)
create_heap(arr,len,i);
for(int i=len-1; i>0; i--)
{
swap(arr[0],arr[i]);
create_heap(arr,i,1);
}
}
快速排序:
特点:
1.先从数据中取出一个数作为基准数。
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
是否稳定:不稳定
平均时间复杂度:O(nlogn)
空间复杂度:O(1)
挖坑法是相当出色的快排分区方法,以下非注释部分为挖坑法,注释部分时间复杂度相当,空间复杂度为O(n)
void _quick_sort(int* arr,int left,int right)
{
/*
if(left >= right)
return;
int len = right-left+1;
int pv = arr[left] , tmp[len] , l=0 , r=len-1;
for(int i=left+1; i<=right; i++)
{
if(arr[i] < pv)
tmp[l++] = arr[i];
else
tmp[r--] = arr[i];
}
tmp[l] = pv;
memcpy(arr+left,tmp,len*sizeof(arr[0]));
_quick_sort(arr,left,left+l-1);
_quick_sort(arr,left+l+1,right);
*/
if(left >= right)
return;
int pi = left , pv = arr[left];
for(int l=left,r=right; l<r;)
{
while(l<r && pv<=arr[r]) r--;
if(l<r)
{
arr[pi]=arr[r];
pi = r;
}
while(l<r && arr[l] < pv) l++;
if(l<r)
{
arr[pi] = arr[l];
pi = l;
}
}
arr[pi] = pv;
_quick_sort(arr,left,pi-1);
_quick_sort(arr,pi+1,right);
}
void quick_sort(int* arr,size_t len)
{
_quick_sort(arr,0,len-1);
}
归并排序:
特点:归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。先将序列拆成两个子序列,直到不能再分,对子序列排序后,两个子序列合并成上一次子序列,直到合并成序列。
是否稳定:不稳定
平均时间复杂度:O(nlogn)
空间复杂度:O(n)
void merge_sort(int* arr,size_t len)
{
int *dest = malloc(sizeof(arr[0])*len), *src = arr;
for(int k=1; k<len; k*=2)
{
for(int i=0,j; i<len; i+=k*2)
{
int s1 = j= i , e1 = min(i+k,len);
int s2 = e1 , e2 = min(i+k*2,len);
while(s1 < e1 && s2 < e2)
{
dest[j++] = src[s1]<src[s2] ? src[s1++] : src[s2++];
}
while(s1 < e1)
dest[j++] = src[s1++];
while(s2 < e2)
dest[j++] = src[s2++];
}
swap(dest,src);
}
if(src != arr)
{
memcpy(arr,src,sizeof(arr[0])*len);
dest = src;
}
free(dest);
}
计数排序:
特点:找出待排序的数组中最大和最小的元素,差值作为统计数组的长度;统计数组中每个值为i的元素出现的次数,存入数组的第i项;对序列中的数进行计数;最后按依次输出就是顺序输出。
是否稳定:稳定
平均时间复杂度:O(n+k)
空间复杂度:O(k)
void count_sort(int* arr,int len)
{
int max = arr[0] , min = arr[0];
for(int i=1; i<len; i++)
{
if(max < arr[i])
max = arr[i];
if(min > arr[i])
min = arr[i];
}
int* count = calloc(sizeof(count[0]),max-min+1);
for(int i=0; i<len; i++)
{
count[arr[i]-min]++;
}
for(int i=0,k=0; i<=max-min; i++)
{
for(int j=0; j<count[i]; j++)
{
arr[k++] = i+min;
}
}
free(count);
}
基数排序:
特点:只能排序数字。按照低位先排序,收集;再按照高位排序,再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。
是否稳定:稳定
平均时间复杂度:O(n+k)
空间复杂度:O(k)
void radix_sort(int* arr,size_t len)
{
QueueList* queue[10];
for(int i=0; i<10; i++)
queue[i] = create_queue_list();
int base = 1;
bool flag = true;
while(flag)
{
flag = false;
for(int i=0; i<len; i++)
{
push_queue_list(queue[arr[i]/base%10],arr[i]);
if(arr[i] >= base)
flag = true;
}
base *= 10;
for(int i=0,j=0; i<10; i++)
{
while(!empty_queue_list(queue[i]))
{
arr[j++] = front_queue_list(queue[i]);
pop_queue_list(queue[i]);
}
}
}
int i = 0;
while(!empty_queue_list(queue[0]))
{
arr[i++] = front_queue_list(queue[0]);
pop_queue_list(queue[0]);
}
}
桶排序:
特点:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。
是否稳定:稳定
平均时间复杂度:O(n+k)
空间复杂度:O(k)
桶排序和计数排序大差不差,主要是学习桶排序分块的思想,对具有相同或相似的数据进行细分。10000 ->10*1000