一、排序算法的分类
排序首先分为四种:
- 插入排序
- 选择排序
- 交换排序
- 归并排序
具体分类如下图所示:
二、直接插入排序
思路:通过构建有序序列,当要插入第i个元素是,前面的array[0]、array[1]、array[2]….,array[i-1]已经有序,然后用array[i]和之前的有序序列中的数依次进行比较,选择合适的位置插入,然后将插入位置的数顺序往后移。
实现代码:
void InsertSort(int array[], int size)
{
int i, j;
int key;
for (i = 0; i < size; i++)
{
key = array[i];
for (j = i-1; j >= 0; j--)
{
if (array[j] <= key)
{
break;
}
array[j + 1] = array[j];
}
array[j+1] = key;
}
}
原理图演示:
算法分析:
最优情况下(序列本来就有序):时间复杂度为Q(n)
最差情况下(逆序):时间复杂度为O(n^2)
空间复杂度为:O(1)
稳定性:稳定
三、希尔排序
算法思路:
希尔排序是希尔(Donald Shell)于1959年提出来的一种排序算法,希尔排序其实也是插入排序,希尔在原有的简单插入排序上进行了优化,优化后的快速排序相比之前更为高效。
希尔排序的思路:将要排序的序列按照步长gap进行分组,先在这几组内进行插入排序,之后再进行整体的插入排序。这样可以让一个元素
一次性地朝最终位置走一大步。然后再取越来越小的步长进行排序,算法的最后一步就变成了简单的插入排序,但是当到达这一步时整个序列几乎是已经排好的。
gap步长的选择是希尔排序最重要的部分,要保证最后一次排序的步长为1,这样就会保证整个数组将会被排序,并且步长必须小于数组长度。
代码实现:
void ShellSort1(int array[], int size)
{
int i, j;
int key;
int gap = size;
while (gap > 1)
{
gap = gap / 3 + 1;
for (i = gap; i < size; i++)
{
key = array[i];
for (j = i - gap; j >= 0; j -= gap)
{
if (array[j] <= key)
{
break;
}
else
{
array[j + gap] = array[j];
}
array[j + gap] = key;
}
}
}
}
原理图:
算法分析:
最好情况:时间复杂度为O(n)
最坏情况下:时间复杂度为O(n^2)
空间复杂度为:O(1)
稳定性:不稳定
四、直接选择排序
算法思路:
- 在数组中选择一个最大(最小)的数
- 若它不是数组元素的最后一个元素(第一个元素)就将它与数组元素的最后一个元素(第一个元素)进行交换
- 重复以上步骤,直到数组中只剩下一个元素为止
代码实现:
void Swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void SelectSort1(int array[], int size)
{
int i,j;
int minpos = 0;
for (i = 0; i < size; i++)//存放最小值的位置
{
minpos = i;
for (j = i + 1; j < size; j++)//寻找最小值
{
if (array[minpos] >array[j])
{
minpos = j;
}
if (array[minpos]<array[i])
{
Swap(array + minpos, array + i);
}
}
}
}
原理图:
算法分析:
最好情况时间复杂度为:O(n^2)
最坏情况下时间复杂度为:O(n^2)
空间复杂度为O(1)
稳定性:不稳定
五、堆排序
堆是一个按照完全二叉树存储的数组,它是一个近似的完全二叉树但是同时它又满足堆的性质。
堆排序思路:
1. .从小到大排序建大堆,从大到小排序建小堆
2. 把堆顶的元素和当前堆的最后一个元素交换
3. 堆的元素个数减一
4. 从根节点向下调整
代码实现:
void Swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void AdjustDown(int array[], int size, int parent)//向下调整
{
int left, right;
while (1)
{
left = parent * 2 + 1;
right = parent * 2 + 2;
int Maxchild = left;
if (left >= size)
{
break;
}
if (right<size&&array[right]>array[left])
{
Maxchild = right;
}
if (array[parent] <array[Maxchild])
{
Swap(array + parent, array + Maxchild);
}
if (array[parent] <= array[Maxchild])
{
break;
}
parent = Maxchild;
}
}
void CreatHeap(int array[], int size)//建堆
{
int i;
for (i = (size - 2) / 2; i >= 0; i--)
{
int left = i * 2 + 1;
int right = i * 2 + 2;
int Maxchild = left;
if (right <size&&array[right] > array[left])
{
Maxchild = right;
}
if (array[i] < array[Maxchild])
{
Swap(array + i, array + Maxchild);
}
AdjustDown(array, size, Maxchild);
}
}
void HeapSort(int array[], int size)
{
int i = 0;
for (i = 0; i < size; i++)
{
Swap(array, array + size - i - 1);
AdjustDown(array, size - i - 1, 0);
}
}
思路图:
算法分析:
最好情况下时间复杂度为:O(n*lgn)
最坏情况下时间复杂度为:O(n*lgn)
空间复杂度为:O(1)
稳定性:不稳定
六、冒泡排序
算法思路:
冒泡排序是一种简单的排序算法,它不断地重复遍历数组,每次与其相邻的数进行比较,如果他们的顺序错误就交换,知道数组只剩下一个元素的候,说明该数组已经排好序,之所以成为冒泡排序,是因为越小的元素会经由交换慢慢“浮”到数列的前面。
代码实现:
void Swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void Bubble_Sort(int array[], int size)
{
int i, j;
for (i = 0; i < size-1; i++)//趟数
{
for (j = 0; j < size-i-1; j++)//比较次数
{
if (array[j]>array[j + 1])
{
Swap(array + j, array + j + 1);
}
}
}
}
思路图:
算法分析:
最好情况下(数组已经有序)时间复杂度为:O(n)
最坏情况下(数组逆序)时间复杂度为:O(n^2)
空间复杂度为:O(1)
稳定性:稳定
七、快速排序
算法思路:
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两个子序列,左序列中的所有元素均小于基准值,右子序列中的所有元素均大于基准值,然后做右子序列重复该过程,知道所有元素在相应的位置上为止。
由于划分左右子区间的方式有多种,按照这种方式快速排序又分为以下几种。
- 左右指针法
算法思路:
每次选择数组的最后一个 元素为基准值,去划分数组。
代码实现:
void Swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//左右指针法
int PartSort(int array[], int left, int right)
{
int key = right;
int begin = left;
int end = right;//right-1 返例1 2 3 4 5
while (begin < end)
{
while (begin < end&&array[begin] <= key)
{
begin++;
}
while (begin < end&&array[end] >= key)
{
end--;
}
if (begin < end)
{
Swap(array + begin, array + end);
}
}
Swap(array + begin, array + right);
return begin;//返回哨兵数所在的下标
}
void QuickSortRecursion1(int array[], int left, int right)
{
if (left >= right)//[left,right]数量只剩1个 或者为0的时候说明排完序了
{
return;
}
//[left , div)这里的数都比array[div]小 (div , right]这里的是都比array[div]大
int div = PartSort(array, left, right);//每一次分割的哨兵数的下标
QuickSortRecursion1(array, left, div - 1);
QuickSortRecursion1(array, div + 1, right);
}
思路图:
挖坑法
思路:
设置基准值,也就是数组的最后一个元素
- 先将key所在的位置设置为坑,然后从begin开始找比key大的数,找到后填上刚才key处所设的坑,并且将找到的最大数的位置设为坑。
- end开始找比key小的数放到刚才最大数位置所设的坑处
- 这样就将数组分为两个子区间,如此循环
代码实现:
//挖坑法
int DigHolePart(int array[], int left, int right)
{
int key = array[right];
int begin = 0;
int end = right;
while (begin < end)
{
while (begin<end&&array[begin]<=key)
{
begin++;
}
array[end] = array[begin];//在begin的位置上挖坑
while (begin < end&&array[end] >= key)
{
end--;
}
array[begin] = array[end];//用end的值去填刚才的坑,然后end出留坑
}
array[begin] = key;//用key去填end处留的坑
return begin;
}
void QuickSortRecursion1(int array[], int left, int right)
{
if (left >= right)//[left,right]数量只剩1个 或者为0的时候说明排完序了
{
return;
}
//[left , div)这里的数都比array[div]小 (div , right]这里的是都比array[div]大
int div = DigHolePart(array, left, right);//每一次分割的哨兵数的下标
QuickSortRecursion1(array, left, div - 1);
QuickSortRecursion1(array, div + 1, right);
}
- 前后指针法
- 定义两个指针数组中第一个最大的数firstBigger和pCur
- 基准数等于数组的最后一个元素,用pCur遍历数组,若遇到比key小的数就将数组中第一个最大的数和pCur下标所对应的数交换
- 然后更新第一个最大的数
- 直到pCur遍历完整个数组,然后将fistBigger所对应的数和key交换
int FrontLastIndex(int array[], int left, int right)
{
int key = array[right];
int cur = left;
int firstBigger=left;
while (cur < right)
{
if (array[cur] < key)
{
Swap(array + firstBigger, array + cur);
firstBigger++;
}
cur++;
}
Swap(array + firstBigger, array + right);
return firstBigger;
}
void QuickSortRecursion(int array[], int left, int right)
{
if (left >= right)
{
return;
}
int div = FrontLastIndex(array, left, right);
QuickSortRecursion(array, left, div - 1);
QuickSortRecursion(array, div + 1, right);
}
快速排序算法分析:
最好情况的时间复杂度:O(n*lgn)
最坏情况的时间复杂度:O(n^2)
空间复杂度:O(lgn)
稳定性:不稳定
八、归并排序
算法思想:
将待排序的数组分成两个长度相等的子序列,对每一个子序列排序,然后将它们合成一个序列。
归并排序的核心步骤:
- 分组
- 归并
代码实现:
void Merge(int array[], int left, int right, int mid)
{
int *newarray = (int*)malloc(sizeof(int)*(right+1));
memset(newarray, 0x00, sizeof(int)*(right+1));
int curleft = left;
int curright = mid;
int i = left;
while (curleft <mid&&curright<=right)
{
if (array[curleft] <= array[curright])
{
newarray[i++] = array[curleft++];
}
else
{
newarray[i++] = array[curright++];
}
}
while (curright <= right)
{
newarray[i++] = array[curright++];
}
while (curleft < mid)
{
newarray[i++] = array[curleft++];
}
for (i = left; i <= right; i++)
{
array[i] = newarray[i];
}
free(newarray);
}
void MergeSortRecursion(int array[], int left, int right)
{
//递归结束条件:当左边大于等于右边的时候说明合并完成
if (left >= right)
{
return;
}
int mid = left + (right - left)/2;
//[left,mid]
MergeSortRecursion(array, left, mid);
//[mid+1,right]
MergeSortRecursion(array, mid+1,right);
Merge(array, left, right , mid+1);
}
思路图:
算法分析:
最好情况下的时间复杂度:O(n*lgn)
最坏情况下的时间复杂度:O(n*lgn)
空间复杂度:O(n)
稳定性:稳定
###九、各个排序算法的比较###
更为详细的排序算法解析请见:https://blog.csdn.net/hellozhxy/article/details/79911867