排序算法
排序算法分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序由于数据量大,排序过程中需要访问外存。
内部算法:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。
1.冒泡排序[bubble sort]
算法思想:
-
比较相邻的元素。如果a[m]>a[m+1],交换这两个元素
-
对所有的元素重复以上步骤,从a[0]到a[n-2]。直到没有任何一个数字需要比较。
时间复杂度:O(n2)
核心代码:
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]){
swap(arr[j],arr[j+1]);
}
}
}
}
2.选择排序[selection sort]
算法思想:
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
- 依次找到最小(大)元素,排列。
时间复杂度:O(n2):
核心代码:
void SelectionSort(int arr[],int len){
for(int i=0;i<len-1;i++){
int min=i;
for(int j=i+1;j<len;j++){//遍历未排序元素
if(arr[i]<arr[min]){
min=j;//记录最小值
}
}
swap(arr[min],arr[i]);
}
}
3. 插入排序[insertion sort]
算法思想:
- 以第一个元素为基准,取下后续元素,和基准对比,找到相应位置插入,形成新的基准。
- 重复以上步骤。
时间复杂度:O(n2)
核心代码:
void InsertionSort(int arr[],int length){
int key;
for(int i=1;i<length;i++){
key=arr[i];
j=i-1;
while((j>=0)&&(arr[j]>key)){
arr[j+1]=arr[j];
j--;
}
arr[j+1]=key;
}
}
4.希尔排序[shell’s sort]
又称增量递减算法,是冲破O(n2)的第一批算法之一。
算法思想:
- 对插入算法的改进,属于非稳定排序算法
- 采取跳跃式分组的策略,通过某个增量将数组元素划分为若干组,在组内进行插入排序。然后再逐步缩小增量。
- 在初始阶段从宏观上看基本有序。到增量为1时,多数情况下只需微调。
时间复杂度:O(nlogn)
核心代码:
void shell_sort(int arr[],int len){
int increasement=len;
do{
increasment=increasement/3+1; //确定增量大小
for(int i=0;i<creasement;i++){
for(int j=i+creasement;j<len;j+=increasement){//对每组进行直接插入排序
if(arr[j]<arr[j-increasement]){//找到正确位置
int tmp=arr[j];
for(int k=j-increasement;k>=0&&tmp<arr[k];k-=increasement){
arr[k+increasement]=arr[k];
}
arr[k+increasement]=tmp;
}
}
}
}
while(increasement>1);//直到增量为1
}
5.归并排序[merge sort]
是建立在归并操作上的一种有效的排序算法。采用分治法的思想,先使每个子序列有序,再使子序列段间有序。代价是要额外的内存空间。若将两个有序表合并成一个有序表,成为2-路归并。
算法思想:
- 将序列分为子序列
- 对子序列分别采用归并排序
- 合并
时间复杂度:O(nlogn)
核心代码:
void MergeSort(int arr[],int start,int end,int *tmp){
if(start>=end) return;
int mid=(start+end)/2;
MergeSort(arr,start,mid,tmp);
MergeSort(arr,mid+1,end,tmp);
//合并,利用辅助空间
int i_start=start,i_end=mid;
int j_start=mid+1,j_end=end;
while(i_start<=i_end&&j_start<=j_end){
if(arr[i_start]<arr[j_start]){//由于子序列有序,仅需要比较首个数字
tmp[length]=arr[i_start];
length++;
i_start++;
}else{
tmp[length]=arr[j_start];
length++;
j_start++;
}
}
//合并序列的剩余部分
while(i_start<=i_end){
tmp[length]=arr[i_start];
length++;
i_start++;
}
while(j_start<=j_end){
tmp[length]=arr[j_start];
length++;
j_start++;
}
//释放辅助空间,数据写回初始地
for(int i=0;i<length;i++) arr[start+i]=tmp[i];
}
6.快速排序
算法思想:
- 从子序列中挑出一个元素,成为基准
- 重新排序数列,所有比该基准小的放在前面,比基准大的放在后面。【分区操作】
- 递归的把小于基准值元素的子序列和大于基准值的子序列排序
时间复杂度:O(nlogn)
核心代码:
void QuickSort(int arr[],int start,int end){
if(start>=end) return;
int pivot=arr[start];//基准数
int i=start,j=end;
while(i<j){
//从右向左找比基准数小的数
while(i<j&&arr[j]>=pivot) j--;
if(i<j){
arr[i]=arr[j];
i++;
}
//从左向右找比基准数大的数
while(i<j&&arr[i]<pivot) i++;
if(i<j){
arr[j]=arr[i];
j++;
}
}
arr[i]=pivot;//把基准数放到i的位置上
QuickSort(arr,start,i-1);
QuickSort(arr,i+1,end);
}
7.堆排序[heap sort]
大顶堆:每个节点的值都大于等于其左右孩子节点的值
小顶堆:每个节点的值都小于等于其左右孩子节点的值
算法思想:
- 将初始待排序关键字序列构建成大顶堆,此堆为初始的无序区
- 堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区Rn。且满足前者全都小于Rn;
- 递归将前者无序区变成有序区,直至排序结束。
时间复杂度:O(nlogn)
核心代码:
void max_heapify(int arr[],int start,int end){
int father=start;
int son=father*2+1;
while(son<=end){
if(son+1<=end&&arr[son]<arr[son+1]) son++;
if(arr[father]>arr[son]) return;
else{
swap(&arr[father],&arr[son]);
father=son;
son=father*2+1;
}
}
}
void HeapSort(int arr[],int len){
//初始化,从最后一个父节点开始调整
for(int i=len/2-1;i>=0;i--){
max_heapify(arr,i,len-1);
}
//将第一个元素和有序区前一位交换,再调整
for(int i=len-1;i>=0;i--){
swap(&arr[0],&arr[i]);
max_heapify(arr,0,i-1);
}
}
8.计数排序
核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。该算法不是基于比较的排序算法,是一种线性空间复杂度的排序。当输入的元素是n个0-k之间的整数时,它的运行时间时O(n+k)
算法原理:
- 找出待排序的数组中的Max和Min
- 统计数组中每个值为i的元素出现的次数,存入c[i]
时间复杂度:O(n+k)
核心代码:
void counting_sort(int arr[],int *res,int n){
int *c=(int *)malloc(sizeof(int)*100);
for(int i=0;i<100;i++) c[i]=0;
for(int i=0;i<n;i++){
c[arr[i]]++;
}
for(int i=1;i<100;i++){
c[i]+=c[i-1];
}
for(int i=n;j>0;j--){
res[--c[arr[i-1]]]=arr[i-1];
}
free(c);
}
9.桶排序[bucket sort]
是计数排序的升级版。利用了函数的映射关系。工作原理是:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序。为了使桶排序更加高效,我们要在额外空间充足的情况下,尽量增大桶的数量。
算法原理:
- 设置一个定量的数组当作空桶
- 将数据放入对应的桶中,放完对不是空的桶进行排序
- 拼接桶
算法复杂度O(n+k)
10.基数排序
将整数按位数切割成不同的数字,然后按每个位数分别比较。可以用于浮点数等的比较。
算法思想:
- 取数组中的最大数,并取得位数i
- 对所有数进行i轮排序,从最低位开始取每个位组成radix数组,并对该数组进行计数排序
时间复杂度O(n+k)