排序:就是将一组杂乱无章的数据按照一定的规律(升序或降序)组织起来。
我们通常所说的排序算法往往指的是内部排序算法,及数据记录在内存中进行排序。
而外部排序指的是数据元素太多不能同时放在内存中,根据排序过程的要求不能在内存之间移动数据的排序。
排序算法大致可分为两种:
一种是比较排序,时间复杂度在 O(nlogn)~O(n^2)之间,主要由:冒泡排序、选择排序、插入排序、堆排序、希尔排序、归并排序、快速排序;
另一种是非比较排序,时间复杂度可以达到O(n),主要有:基数排序、计数排序、桶排序。
我们这里只探讨比较排序,下表是各排序算法的比较:
我们很容易忽略一点,就是算法的稳定性。
什么是稳定性呢?比如,在排序之前,元素 A 在元素 B 前面,那么在排序后,元素A 仍在元素 B 之前,这种算法是稳定的,否则,不稳定。简单来说,就是要保证排序前后两元素的相对位置不变。
对于一个不稳定的算法,只需列举一个实例,即可说明它的不稳定性,而稳定的排序算法,要通过对算法进行分析。需要注意的是,排序算法是否稳定是由具体算法决定的,不稳定的算法在某种条件下可变为稳定的排序算法。
下面的排序我们都采用升序的方式。
冒泡排序
思想:1.遍历数组的每一个元素,.比较两个相邻元素的大小,如果前一个元素比后一个元素大,就交换他们两个的位置。一次遍历完成,最后的元素即为数组中最大的元素。
2.重复上述步骤,除过上次已经有序的元素。
我们以往的思路就是上面这种,今天用另一种方法来实现。
1.定义一个有序区间 [0,bound) 和待排序区间 [bound,size)
2.每次从后往前的比较待排序区间的元素大小然后进行交换,直到所有元素都是有序的。
代码如下:
//////////////////////////////////
// 冒泡排序
// 时间复杂度:O(N * N)
// 空间复杂度:O(1)
// 稳定性:稳定
///////////////////////////////////
void Swap(int* a,int* b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
return;
}
void BubbleSort(int arr[],int size)
{
if(arr == NULL)
{
return;//非法输入
}
if(size <= 1)
{
return;//只有一个元素或数组为空
}
//[0,bound) 有序区间
//[bound,size) 待排序区间
int bound = 0;
for(;bound < size;bound++)
{
int cur = size - 1;
for( ;cur > bound;cur--)
{
if(arr[cur] < arr[cur - 1])
{
Swap(&arr[cur],&arr[cur-1]);
}
}
}
return;
}
选择排序
思想:类似于打擂台的方式,先定义一个擂主bound,后续的每个元素都和这个擂主进行比较,如果比擂主更符合条件即比它小,就交换两个元素的位置。
重复以上步骤,直到所有元素都有序。
代码如下:
//////////////////////////////////////
// 选择排序
// 时间复杂度:O(N * N)
// 空间复杂度:O(1)
// 稳定性:不稳定
/////////////////////////////////////
void SelectSort(int arr[],int size)
{
if(arr == NULL)
{
return;
}
if(size <= 1)
{
return;
}
//[0,bound) 有序区间
//[bound,size) 待排序区间
int bound = 0;
for(;bound < size;bound++)
{
//打擂台,bound为擂主
//bound后面每个元素和擂主进行比较,
//如果小于擂主,就进行交换(升序)
int cur = bound + 1;
for( ;cur < size;cur++)
{
if(arr[cur] < arr[bound])
{
Swap(&arr[cur],&arr[bound]);
}
}
}
return;
}
插入排序
思想:每一步将一个待排序元素,根据其关键码的大小,插入到前面已经有序的区间的合适的位置上,直到所有元素全部插入完毕。
具体描述如下:
1.先定义一个边界[0,bound)为有序区间,[bound,size)为待排序区间;
2.保存bound指向的元素,该元素为要插入的元素;
3.在有序区间从后往前找一个合适的位置将刚才保存的的元素插入进去,一边找一边进行搬运;
4.重复上述步骤,直到所有元素全部插入完毕。
代码如下·:
//////////////////////////////////
// 插入排序
// 时间复杂度:O(N * N)
// 空间复杂度:O(1)
// 稳定性:稳定
/////////////////////////////////
void InsertSort(int arr[],int size)
{
if(arr == NULL)
{
return;
}
if(size <= 1)
{
return;
}
// [0,bound) 有序区间
// [bound,size) 待排序区间
//把有序区间看做一个线性表,
//把当前bound指向的元素插入到这个线性表中
int bound = 1;
for( ;bound < size;bound++)
{
int cur = bound;
int bound_key = arr[bound];
//保存bound指向的元素
for( ;cur > 0;cur--)
{
//从后往前的去找一个合适的位置放置bound_key,一边找,一边搬运
if(arr[cur - 1] > bound_key)
{
arr[cur] = arr[cur - 1];
}
else
{
break;//找到一个合适的位置
}
}
arr[cur] = bound_key;
}
return;
}
插入排序的特点:
1.数组元素个数比较少的时候,执行效率比较快;
2.如果数组基本有序,执行效率也很快。
堆排序
思想:分两步,第一步,首先要建一个堆,升序建小堆,降序建大堆,这里我们建一个大堆(建堆有两种方式,上浮式调整和下沉式调整,我们这里两种方法都实现了,看代码)
第二步,从堆顶开始循环删除堆中的每一个元素,删除完毕,排序完成(交换要删除元素和最后一个元素的位置)。
代码如下:
///////////////////////////////////
// 堆排序
// 时间复杂度:
// 空间复杂度:
// 稳定性:
////////////////////////////////////
void AdjustUp(int arr[],int size,int index)
{
int child = index;
int parent = (child - 1)/2;
while(child > 0)
{
if(arr[parent] < arr[child])
{
Swap(&arr[parent],&arr[child]);
}
else
{
break;
}
child = parent;
parent = (child - 1)/2;
}
return;
}
void AdjustDown(int arr[],int size,int index)
{
if(size <= 1)
{
return;
}
int parent = index;
int child = parent * 2 + 1;
while(child < size)
{
if(child + 1 < size && arr[child] < arr[child + 1])
{
child = child + 1;
}
if(arr[parent] < arr[child])
{
Swap(&arr[parent],&arr[child]);
}
parent = child;
child = parent * 2 + 1;
}
return;
}
void HeapPop(int arr[],int heap_index )
{
if(heap_index <= 1)
{
return;
}
Swap(&arr[0],&arr[heap_index - 1]);
AdjustDown(arr,heap_index - 1,0);
return;
}
void HeapCreate(int arr[],int size)
{
if(size <= 1)
{
return;
}
#if 0
int i = (size - 1 - 1)/2;//先找到最后一个叶子结点的父节点
for( ; i > 0;i--)
{
//下沉式调整
AdjustDown(arr,size,i);
}
AdjustDown(arr,size,0);
#else
int bound = 0;
for( ;bound < size;bound++)
{
AdjustUp(arr,size,bound);
}
#endif
}
void HeapSort(int arr[],int size)
{
if(arr == NULL)
{
return;
}
if(size <= 1)
{
return;
}
//1.基于数组建一个堆(升序建大堆)
HeapCreate(arr,size);
//2.循环的删除堆顶元素,将所有元素都删除完,排序完成
int i = 0;
for( ;i < size - 1;i++)
{
//第二个参数表示哪部分符合堆的规则
HeapPop(arr,size - i);
}
return;
}