排序算法合集

算法中很重要的一个问题就是排序,以下总结几种常用的排序算法:(以下都按升序来算)
一、插入排序
1.思路:数组从前往后遍历,将一个小于前面有序队列的数插入前面的数中,插入之前这些数字后移。
这里写图片描述
时间复杂度:O(N^2),
稳定性:稳定
最差情况:序列较长,逆序最差
最好情况:序列较短、接近有序
代码:

void InsertSort(int *a, size_t n)
{
    for (int i = 0; i<n-1; i++)
    {
        int end = i;
        int temp = a[end + 1];//保存这个数字
        while (end >= 0)//直到到数组中第一个数 停止移动
        {
            if (a[end] > temp)//判断是否小于前面的数
            {
                a[end + 1] = a[end];//向后移动
                end--;
            }
            else//不小于就跳出循环
                break;
            a[end+1] = temp;
        }
    }
}

测试:

void TestInsertSort()
{
    int a[] = { 4, 3, 7, 6, 5, 8, 9, 0, 2, 1, 12, 18, 15, 11 };
    PrintArray(a, sizeof(a) / sizeof(a[1]));
    InsertSort(a, sizeof(a) / sizeof(a[1]));
    PrintArray(a, sizeof(a) / sizeof(a[1]));
}

二、希尔排序
希尔排序是插入排序的优化,对于较大的数组,把较小的数更快的跳到更前面的位置。
这里写图片描述
时间复杂度: O(n^1.25) ~ 1.6*O(n^1.25)
使用场景:数据较大
稳定性:不稳定
代码:(gap=3时)

void ShellSort(int *a, size_t n)
{
    int gap = n;
    while (gap>1)
    {
        gap = gap / 3 + 1;每次跳三步
        for (int i = 0; i < n - gap; ++i)
        {
            int end = i;
            int temp = a[end + gap];
            while (end >= 0)
            {
                if (a[end]>temp)
                {
                    a[end + gap] = a[end];
                    end = end - gap;
                }
                else
                    break;
                a[end+gap]=temp;
            }
        }
    }
}

三、选择排序
原理:从第一个元素 下标为0 开始 向后遍历,找到最小的数 下标保存为min,和下标为0 处交换,完成一趟后,下次继续找,从1 开始往后找最小数和 1处的数交换,直到有序。
优化:从两边向中间找,道理一样,左边找最小的数,右边找最大的数,直到 left==right时 数组有序。
时间复杂度:O(N^2)
稳定性:不稳定
代码(优化版):

void SelectSort(int *a, size_t n)
{
    assert(a);
    int lhs = 0,rhs=n-1;
    while (lhs < rhs)
    {
        int max=lhs;//每次都从lhr开始
        int min = lhs;
        for (int i = lhs; i <= rhs; ++i)
        {
            if (a[i]>=a[max])
                max = i;//保存该区间最大数下标
            if (a[i] <= a[min])
                min = i;//保存该区间最小数
        }
        swap(a[min], a[lhs]);
        if (max == lhs)
            max = min;//判断max是否在最左边,防止交换两次 
         swap(a[max], a[rhs]);
        ++lhs;
        --rhs;
    }
}

四、冒泡排序
原理:冒泡排序的原理我们就不多讲了 直接上代码
时间复杂度:O(N^2)
空间复杂度:O(1)
最坏情况:逆序
最好情况:接近有序
优化:设置flag 判断每次冒泡的过程,如果这一躺未发生交换,则说明有序直接跳出

void BubbleSort(int *a, size_t n)
{
    assert(a);
    for (int i = 0; i < n - 1; i++)//冒泡次数为n-1次
    {
        bool flag = false;//设置flag
        for (int j = 0; j < n - 1 - i; j++)
        {   
            if (a[j]>a[j + 1])//判断是否大于后面的一个数
            {
                swap(a[j], a[j + 1]);
                flag = true;//
            }   
        }
        if (flag == false)发生交换 表示已经有序直接跳出
                break;
    }
}

五、堆排序
原理:用数组建大堆,然后 交换堆顶和堆尾,用end 来维护已经还未处理好的堆 。
这里写图片描述
时间复杂度:O(nlgn)
空间复杂度:O(1)
代码:

void _AdjustDown(int *a, int n, int root)//向下调整算法
{
    int parent = root;
    int child = parent * 2 + 1;
    while (child < n)
    {
        if (child + 1 < n&&a[child + 1] > a[child])//选出左右子树中比较小的,父节点数大于最小的就是大于子节点。
            child++;
        if (a[child] > a[parent])//父节点大于子节点就交换
        {
            swap(a[child], a[parent]);
            parent = child;
            child = parent * 2 + 1;//父子节点更新
        }
        else
            break;
    }
}
void HeapSort(int *a, size_t n)
{
    assert(a);
    for (int i = (n - 2) / 2; i >= 0; --i)
    {
        _AdjustDown(a, n, i);//从最后一个父节点开始向前进行向下调整算法
    }
    int end = n;
    while (end--)
    {
        swap(a[0], a[end]);//每次与最后一个交换进行 向下调整算法重置堆
        _AdjustDown(a, end, 0);
    }
}

六、快速排序
原理:快速排序,采用递归的思想 单步思想是:每次从数组中选一个数当做key值,让比key大的数字排在key左边,让比key大的数字排在key后面,这样key的位置就是它应该在的位置
然后把key左边和右边分别作为两个数组,进行快排,就是一个递归的过程。
方法一:左右指针法
左右设置两个指针,begin,end 。begin遇到比key大的数就停止,end遇到比key小的数字就停止,这时begin和end指向的数交换,直到begin遇到end结束本次交换。
这里写图片描述
代码:

int Partition1(int *a, int left, int right)
{
    int start = left;
    int end = right;
    int key = a[right];//定义key的值
    while (start < end)
    {
        while (start < end&&a[start] <= key)
            start++;//start指向的数比key大
        while (start < end&&a[end] >= key)
            end--;//end指向的数比keyif (start < end)
            swap(a[start], a[end]);//交换
    }
    if (start==end)//最后把a[right]和a[start]交换,注意交换a[right]而不是临时变量key
        swap(a[start], a[right]);
    return start;   
}

方法二:填坑法
一个很形象的名字,填坑法。
原理如下:把key拿出来 此时key的位置就空出来了(实际上没有,只不过保存了key的值,可以覆盖)
设置一个指针从头到位变量
图示:
这里写图片描述
这里写图片描述
这里写图片描述
代码:

int Partition2(int *a, int left, int right)
{
    assert(a&&left < right);
    int key = a[right];
    while (left < right)
    {
        while (left < right&&a[left] <= key)
            left++;
        a[right]=a[left];
        while (left < right&&a[right] >= key)
            right--;
        a[left] = a[right];
    }
    if (left == right)
        a[left]=key;
    return left;
}

方法三:前后指针法
原理:设置两个指针 一前一后,两个指针先后走,prev指向比key大的就停止,cur继续向前走找到比key小的就停止,交换两个数,直到cur走到底之后,在交换 ++prev的值和cur指向的值。

int  Partition3(int *a, int left, int right)
{
    int cur = left;
    int prev = left - 1;
    int key = a[right];
    while (cur < right)
    {
        if (a[cur] < key)
        {
            if (++prev != cur)
                swap(a[cur], a[prev]);
        }
            cur++;//cur 一直走
    }
    if (a[cur] == key)
        swap(a[cur], a[++prev]);
    return prev;
}

快速排序代码:

void QuickSort(int *a ,int left , int right)
{
    if (a == NULL || right <= left)
        return;
        int div = Partition3(a, left, right);//随意一种即可
        if (div>left)
        QuickSort(a, left, div-1);
        if (div<right)
        QuickSort(a, div+1, right);
}

快速排序的时间复杂度:O(nlgn)
最坏情况:每次拿到的都是最小值,有序情况最坏。O(N^2)
最好情况:每次拿到中间值
优化:三数取中法(数组开始、结束、中间、的三个数去一个中间值的数作为key)可以避免最坏情况产生
和小区间优化(当区间很小时,可以选择插入排序进行排序,避免递归层数很深)。
非递归版本:
非递归版本单趟排序还是和上面一致,不同的是不采用递归操作,而是拿栈存储要操作的组的左右位置,每次都把左右push进栈,再拿出来排序,直到栈为空结束。

void QuickSortNonR(int *a, int left, int right)
{
    assert(a);
    stack<int > s;
    s.push(right);
    s.push(left);
    while (!s.empty())
    {
        int begin = s.top();
        s.pop();
        int end = s.top();
        s.pop();
        int div = Partition1(a, begin, end);
        if (begin < div)
        {
            s.push(div - 1);
            s.push(begin);
        }
        if (end>div)
        {
            s.push(end);
            s.push(div + 1);
        }
    }
}

七、归并排序
归并排序原理:归并排序将数组分为两个部分,分别将两个子数组排成有序,再用递归的方法,最后有序
如何让两个有序数组,合并为一个有序数组:
创建一个和该数组一样大的temp空间 ,就像合并两个链表一样 从头到尾变量然后排列在新的数组上。
这里写图片描述
代码:

void merge(int *a, int *temp, int left, int mid, int right)
{
    int  begin1 = left;
    int end1 = mid;
    int begin2 = mid + 1;
    int end2 = right;
    int index = begin1;
    while (begin1 <= end1&&begin2 <= end2)
    {
        if (a[begin1] <= a[begin2])
            temp[index++] = a[begin1++];
        else
            temp[index++] = a[begin2++];
    }
    if (begin1 > end1)//begin1走完
    {
        while (begin2 <= end2)
            temp[index++] = a[begin2++];
    }
    else if (begin2 > end2)
    {
        while (begin1 <= end1)
            temp[index++] = a[begin1++];
    }
}

void _mergeSort(int *a ,int * temp, int left, int right)
{
    if (left >= right)
        return;
    int mid = left - (left - right) / 2;
    _mergeSort(a, temp, left, mid);
    _mergeSort(a, temp, mid + 1, right);
    merge(a, temp, left, mid, right);
    memmove(a + left, temp + left, (right - left + 1)*sizeof(int));
}
void MergeSort(int * a, size_t n)
{
    if (a==NULL||n <= 0)
        return;
    int left = 0;
    int right = n - 1;
    int *temp = new int[n];
    _mergeSort(a,temp, left, right);
    delete[] temp;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值