排序算法大体可分为两种:
一种是比较排序,时间复杂度O(nlogn) ~ O(n^2),主要有:冒泡排序,选择排序,插入排序,归并排序,堆排序,快速排序等。
另一种是非比较排序,时间复杂度可以达到O(n),主要有:计数排序,基数排序,桶排序等。
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
《一》交换排序:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置。
交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
void swap_sort(int *arr,int len) //时间复杂度: O(n^2)
{
for (int i=0;i<len-1;++i)
{
for (int j=i+1;j<len;++j)
{
if (arr[i] > arr[j])
{
swap(&arr[i],&arr[j]);
}
}
}
}
《二》冒泡排序(Bubble Sort)
基本思想:两两比较相邻记录的关键字,如果反序则进行交换,直到没有反序的记录为止。本质为一种交换排序。
数据结构 ---------- 数组
最差时间复杂度 ---- O(n^2)
最优时间复杂度 ---- 如果序列在一开始已经大部分排序过的话,会接近O(n)
平均时间复杂度 ---- O(n^2)
所需辅助空间 ------ O(1)
稳定性 ------------ 稳定
{ 6, 5, 3, 1, 8, 7, 2, 4 }进行冒泡排序的实现过程如下
void bubble_sort(int *arr,int len)//O(n^2)
{
for (int i=0;i<len-1;++i)//代表循环的趟数
{
for (int j=0;j<len-1-i;j++)//每一趟比较的次数
{
if (arr[j] > arr[j+1])
{
swap(&arr[j],&arr[j+1]);
}
}
}
}
冒泡排序优化版本
void bubble_sort_p(int *arr,int len)
{
for (int i=0;i<len-1;++i)//代表循环的趟数
{
bool flag = 1;
for (int j=0;j<len-1-i;j++)//每一趟比较的次数
{
if (arr[j] > arr[j+1])
{
flag = 0;
swap(&arr[j],&arr[j+1]);
}
}
if (flag == 1)
{
break;
}
}
}
《三》选择排序
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
通过n-i次比较,从n-i+1个数据中找到最小的数据,并和第i个数据交换。
数据结构 ---------- 数组
最差时间复杂度 ---- O(n^2)
最优时间复杂度 ---- O(n^2)
平均时间复杂度 ---- O(n^2)
所需辅助空间 ------ O(1)
稳定性 ------------ 不稳定
{ 8, 5, 2, 6, 9, 3, 1, 4, 0, 7 }进行选择排序的实现过程如图
void sort(int *arr,int len)
{
int min = 0;
for (int i=0; i<len-1; i++)
{
min = i; //定义当前下标为最小值
for (int j=i+1;j<len;j++)
{
if (arr[min] > arr[j]) //如果存在更小的值
{
min = j; //调整min下标
}
}
if (i != min) //若i不为最小值,交换
{
swap(&arr[min], &arr[i]);
}
}
}
《四》插入排序
数据结构 ---------- 数组
最差时间复杂度 ---- 最坏情况为输入序列是降序排列的,此时时间复杂度O(n^2)
最优时间复杂度 ---- 最好情况为输入序列是升序排列的,此时时间复杂度O(n)
平均时间复杂度 ---- O(n^2) 所需辅助空间 ------ O(1)
稳定性 ------------ 稳定
void insert_sort(int *arr,int len)
{
for(int k=1;k<len;++k)
{
int key = arr[k];
int i;
for(i=0;i<k;++i)//找位置
{
if(arr[i] > key)
{
break;
}
}//i值 就是要找的位置
for(int j=k-1;j>=i;--j) //挪位置
{
arr[j+1] = arr[j];
}
arr[i] = key; //插入元素
}
}
插入排序的优化
void insert_sort_p(int *arr,int len)
{
for (int k=1;k<len;++k)
{
int temp = arr[k];
int i;
for(i=k-1;i>=0;i--)
{
if(temp < arr[i])
{
arr[i+1] = arr[i];
}
else
{
break;
}
}
arr[i+1] = temp;
}
}
《五》希尔排序(shell sort)
将待排序列划分为若干组,在每一组内反复使用直接插入排序,以使整个序列基本有序然后再对整个序列进行插入排序。
数据结构 ---------- 数组
最差时间复杂度 ---- 根据步长序列的不同而不同。已知最好的为O(n(logn)^2)
最优时间复杂度 ---- O(n)
平均时间复杂度 ---- 根据步长序列的不同而不同。
所需辅助空间 ------ O(1)
稳定性 ------------ 不稳定
希尔排序是不稳定的排序算法,虽然一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱。
void shell(int *arr,int len,int gap)
{
int temp;
int j;
for (int k=0;k<gap;++k)
{
for (int i=k+gap;i<len;i+=gap)
{
temp = arr[i];
for (j=i-gap;j>=0;j-=gap)
{
if (arr[j] > temp)
{
arr[j+gap] = arr[j];
}
else
{
break;
}
}
arr[j+gap] = temp;
}
}
}
void shell_sort(int *arr,int len)
{
shell(arr,len,23);
shell(arr,len,17);
shell(arr,len,13);
shell(arr,len,7);
shell(arr,len,5);
shell(arr,len,3);
shell(arr,len,1);
}
《六》快速排序
数据结构 --------- 数组
最差时间复杂度 ---- 每次选取的基准都是最大(或最小)的元素,导致每次只划分出了一个分区,需要进行n-1次划分才能结束递归,时间复杂度为O(n^2)
最优时间复杂度 ---- 每次选取的基准都是中位数,这样每次都均匀的划分出两个分区,只需要logn次划分就能结束递归,时间复杂度为O(nlogn)
平均时间复杂度 ---- O(nlogn)
所需辅助空间 ------ 主要是递归造成的栈空间的使用(用来保存left和right等局部变量),取决于递归树的深度,一般为O(logn),最差为O(n)
稳定性 ---------- 不稳定
int partition_p(int *arr,int low,int high)
{
int temp = arr[low];
while (low < high)
{
while (arr[high] >= temp && low < high)
{
high--;
}
arr[low] = arr[high];
while (arr[low] <= temp && low < high)
{
low ++;
}
arr[high] = arr[low];
}
arr[low] = temp;
return low;
}
void quick_sort(int *arr,int len)
{
quick(arr,0,len-1);
}
void quick(int *arr,int low,int high)
{
if (low < high)
{
int pos = partition_p(arr,low,high);
quick(arr,low,pos-1);
quick(arr,pos+1,high);
}
}
《七》堆排序
堆排序利用二叉树(想象成为二叉树)的树形结构进行排序,堆中的每个结点都大于(或小于)孩子结点。
大根堆和小根堆:
根结点的数据是堆里所有结点数据中最小者的堆称为小根堆,又称最小堆(父小于子)。
根结点的数据是堆里所有结点数据中最大者的堆称为大根堆,又称最大堆(父大于子)。
PS:堆的根结点一定是所有结点元素值的最大值或最小值
数据结构 ---------- 数组
最差时间复杂度 ---- O(nlogn)
最优时间复杂度 ---- O(nlogn)
平均时间复杂度 ---- O(nlogn)
所需辅助空间 ------ O(1)
稳定性 ------------ 不稳定
堆排序是不稳定的排序算法,不稳定发生在堆顶元素与arr[i]交换的时刻。
void heap_adjust(int *arr,int start,int end)
{
int temp = arr[start];
for (int i=2*start+1;i<=end;i=2*i+1)
{
if (2*start+2 <= end && arr[2*start+1] < arr[2*start+2])
{
i++;
}
if (arr[i] > temp)
{
arr[start] = arr[i];
start = i;
}
else
{
break;
}
}
arr[start] = temp;
}
void heap_sort(int *arr,int len)//9
{
for (int i=len/2-1;i>=0;--i)
{
heap_adjust(arr,i,len-1);
}
int end = len-1;
while(end > 0)
{
swap(&arr[0],&arr[end]);
heap_adjust(arr,0,--end);
}
}
《八》归并排序
归并排序(merge sort)的算法思想是将两个或两个以上的有序序列合并为一个有序序列。
PS:待排序数组被划分为若干个子数组,每个子数组都是有序的,将子数组合并为整体有序的数组,就是归并排序。
二路归并排序:将每个元素作为一个有序的序列,然后将相邻的两个子序列两两归并,依次进行两两归并,四四归并,八八归并....直到全部序列归并有序。
数据结构 ---------- 数组
最差时间复杂度 ---- O(nlogn)
最优时间复杂度 ---- O(nlogn)
平均时间复杂度 ---- O(nlogn)
所需辅助空间 ------ O(n)
稳定性 ------------ 稳定
void merge(int *arr,int start,int mid,int end)
{
int *brr = (int *)malloc(sizeof(int) * (end-start+1));
int i = 0;
int L1 = start;
int R1 = mid;
int L2 = mid+1;
int R2 = end;
while (L1 <= R1 && L2 <= R2)
{
if (arr[L1] < arr[L2])
{
brr[i++] = arr[L1++];
}
else
{
brr[i++] = arr[L2++];
}
}
while (L1 <= R1)
{
brr[i++] = arr[L1++];
}
while (L2 <= R2)
{
brr[i++] = arr[L2++];
}
i = 0;
for (int j=start;j<=end;j++)
{
arr[j] = brr[i++];
}
free(brr);
}
void mer_sort(int *arr,int start,int end)
{
int mid;
if (start < end)
{
mid = (start+end)/2;
mer_sort(arr,start,mid);
mer_sort(arr,mid+1,end);
merge(arr,start,mid,end);
}
}
void merge_sort(int *arr,int len)
{
mer_sort(arr,0,len-1);
}
八大排序比较: