七大排序算法

一、排序算法的分类

排序首先分为四种:

  • 插入排序
  • 选择排序
  • 交换排序
  • 归并排序
    具体分类如下图所示:
    这里写图片描述

二、直接插入排序

思路:通过构建有序序列,当要插入第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)
稳定性:不稳定

四、直接选择排序

算法思路:

  1. 在数组中选择一个最大(最小)的数
  2. 若它不是数组元素的最后一个元素(第一个元素)就将它与数组元素的最后一个元素(第一个元素)进行交换
  3. 重复以上步骤,直到数组中只剩下一个元素为止
    代码实现:
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

  • 5
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值