【数据结构】排序

声明:本篇博客的所有排序默认升序

【插入排序】
插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。如下图:
这里写图片描述
插入排序代码实现:

void InsertSort(int* a, size_t n)           
{
    for (size_t i = 0; i < n-1; ++i)
    {
        int end = i;
        int tmp = a[end + 1];
        while (end >= 0)
        {
            if (a[end]>tmp)
            {
                a[end + 1] = a[end];
                --end;
            }
            else
            {
                break;
            }
        }
        a[end + 1] = tmp;
    }
}

希尔排序
希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

假定给定数组arr={9,8,7,6,5,4,3,2,1,0};
分组gap=3;

如图:
这里写图片描述
这里写图片描述

参考代码如下:

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

}

【选择排序】
这里写图片描述

void Select_Sort(int* a, size_t n)
{
    int begin = 0;
    int end = n - 1;
    while (begin <= end)
    {
        int max = begin, min = begin;
        for (int i = begin; i <= end; ++i)
        {
            if (a[i]>a[max])
            {
                max = i;
            }
            if (a[i] < a[min])
            {
                min = i;
            }
        }
        swap(a[begin], a[min]);
        if (begin == max)
        {
            max = min;
        }
        swap(a[end], a[max]);
        ++begin;
        --end;
    }
}

堆排序
将指定大小为n的数组建立成一个大堆,找到最大的数a[0],将其与数组a[n-1]的数进行交换;每次将n-1,重复上述操作,直到n=0。

这里写图片描述
代码如下:

void _AdjustDown(int* a, size_t parent, size_t count)
{
    size_t child = parent * 2 + 1;
    while (child<count)
    {
        if ((child + 1) < count&&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)
{
    for (int i = (n-2)/2; i >=0; --i)
    {
        _AdjustDown(a, i, n);
    }
    size_t end = n - 1;
    while (end)
    {
        swap(a[0], a[end]);
        _AdjustDown(a, 0, end);
        --end;
    }
}

快速排序
寻找pos位,然后将其分为两段数组,然后对这两段数组排序;

//递归
void QuicklySort(int* a, int  left,int right)
{
    if (left >= right)
    {
        return;
    }

    int pos = PartSort(a, left, right);
    /*if (right - left < 5) //可以利用插入排序进行优化
    {
        Insert_Sort(a + left, right - left + 1);//插入排序
    }*/
    //else
    {
        QuicklySort(a, left, div - 1);
        QuicklySort(a, div + 1, right);
    }
}

//非递归
#include<stack>

void QuicklySortNonR(int* a, int left, int right)
{
    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 pos = PartSort(a, begin, end);

        if (begin < div - 1)
        {
            s.push(div - 1);
            s.push(begin);
        }
        if (div + 1 < end)
        {
            s.push(end);
            s.push(div + 1);
        }
    }
}

1.左右指针法
(1) 有一个数组a[size],左指针begin指向下标为0的位置,右指针end指向下标为size-1的位置,同时标志key=a[end](这里key的标记也可以是a[begin]);
(2)begin找a[begin]>key的元素的下标,end找a[end] < key的元素的下标。begin先开始走,当遇到比key大的数时,停下来,再由end开始走,当遇到比key小的数时,停下来。
(3)如果此时begin小于end, 将begin和end分别指的数交换,如果begin>=end就说明一次快排结束。
(4)如果此时a[begin]>key,再将begin指向的值与key值交换即可。此时的数组呈现出左边的值都比key值小,右边的值都比key值大。一次快排将数组分为两个区间,我们再对每个区间进行上述的排序方式。直到每个小区间已不能再划分
这里写图片描述
这里写图片描述
代码如下:

int GetMidIndex(int* a, int begin, int end)
//找到中间的一个数值作为key,三数取中法的优化
{
    int mid = begin +(end - begin) >> 1;
    if (a[begin] < a[mid])
    {
        if (a[mid] < a[end])
        {
            return mid;
        }
        else if (a[begin] > a[end])
        {
            return begin;
        }
        else
        {
            return end;
        }
    }
    else   //a[mid] <= a[begin]
    {
        if (a[end] > a[begin])
        {
            return begin;
        }
        else if (a[mid] > a[end])
        {
            return mid;
        }
        else
        {
            return end;
        }
    }


}

int PartSort(int* a, int begin, int end)
{
    int& key = a[end];
    --end;

    int mid = GetMidIndex(a, begin, end);  //三数取中法优化
    swap(a[mid], a[end]);

    while (begin < end)
    {
        while (begin < end && a[begin] < key)
        {
            ++begin;
        }
        while (begin < end && a[end] >= key)
        {
            --end;
        }
        if (begin < end)
        {
            swap(a[begin], a[end]);
        }
    }
    if (a[begin]>key)
    {
        swap(a[begin], key);
    }

        return begin;
}

2.挖坑法
(1) 有一个数组a[size],左指针begin指向下标为0的位置,右指针end指向下标为size-1的位置,同时标志key=a[end](这里key的标记也可以是a[begin]);
(2)begin先开始走,当遇到比key大的数时,停下来,将begin所指向的数据赋给a[end],这时begin就成了第一个坑
(3)end开始走,当遇到比key小的数时,停下来,将end所指向的数据赋给a[begin],这时end就成了一个新坑
(4)重复(2)(3),直到begin=end ,这时将key的值赋给a[begin],此时的数组呈现出左边的值都比key值小,右边的值都比key值大。一次快排将数组分为两个区间,我们再对每个区间进行上述的排序方式。直到每个小区间已不能再划分
这里写图片描述
这里写图片描述
这里写图片描述
代码如下:

int  PartSort(int* a, int begin, int end)
{
    int key = a[end];
    while (begin < end)
    {
        while (begin < end && a[begin] < key)
        {
            ++begin;
        }
        a[end] = a[begin];
        while (begin < end && a[end] >= key)
        {
            --end;
        }
        a[begin] = a[end];

    }
    a[end] = key;
    return begin;

}

3.前后指针法
定义两个指针,一前一后,前面指针找比基数key小的数,后面指针找比基数key大的数,前面的指针找到后,如果后面的指针加1不等于前面的指针,将前后指针所指向的数据交换,当前面的指针遍历完整个数组时,将基数值与后指针的下一个位置的数据进行交换,然后以后指针的下一个位置作为分界,然后将数组分开,进行排序。
这里写图片描述

int PartSort(int* a, int left, int right)
{
    int cur = left;
    int prev = cur - 1;
    int key = a[right];

    while (cur < right)
    {

        while (a[cur] < key && ++prev != cur)
        {
            swap(a[cur], a[prev]);
        }
        ++cur;
    }

    swap(a[right], a[++prev]);

    return prev;
}

归并排序
归并排序采用了分治策略(divide-and-conquer),就是将原问题分解为一些规模较小的相似子问题,然后递归解决这些子问题,最后合并其结果作为原问题的解。
这里写图片描述

void _MergeSort(int* a, int left, int right, int* tmp)
{

    if (left >= right)
    {
        return;

    }

    int mid = left + ((right - left) >> 1);

    _MergeSort(a, left, mid, tmp);
    _MergeSort(a, mid + 1, right, tmp);

    size_t index = left;
    int begin1 = left, end1 = mid;
    int begin2 = mid + 1, end2 = right;
    while (begin1 <= end1 && begin2 <= end2)
    {
        if (a[begin1] < a[begin2])
        {
            tmp[index++] = a[begin1++];
        }
        else
        {
            tmp[index++] = a[begin2++];
        }
    }

    while (begin1 <= end1)
    {
        tmp[index++] = a[begin1++];
    }

    while (begin2 <= end2)
    {
        tmp[index++] = a[begin2++];
    }

    memcpy(a + left, tmp + left, sizeof(int)*(right - left + 1));
}

void MergeSort(int* a, size_t n)
{
    int* tmp = new int[n+1];
    _MergeSort(a, 0, n, tmp);
    delete []tmp;
}

计数排序
1、首先找到原数组最大值和最小值,新建一个辅助数组,其大小=最大值-最小值+1,并将其初始化为0;
2、统计原数组数据减去其最小值的个数,并对相应辅助数组的位置进行加加操作;
3、对于重复的数,每排好一个数则对其位置数进行减减操作,以此对完成其余相同的数字进行排序。

这里写图片描述

void CountSort(int* a, size_t n)
{
    int max = a[0], min = a[0];

    for (size_t i = 0; i <= n; i++)   //找到该数组的最大和最小的数
    {
        if (a[i]>max)
            max = a[i];
        if (a[i] < min)
            min = a[i];
    }

    int range = max - min + 1;  //最大与最小的数之间共有多少个数,例如[1,5],range=5;
    int* counts = new int(range);  //创建一个range大小的数组

    memset(counts, 0, range*sizeof(int));

    for (size_t i = 0; i <= n; i++)
    {
        counts[a[i] - min]++;
    }

    size_t index = 0;
    for (int i = 0; i < range; i++)
    {
        while (counts[i]--)
        {
            a[index++] = i + min;
        }
    }
}

基数排序
首先根据个位数的数值,在遍历数据时将它们各自分配到编号0至9的桶(个位数值与桶号一一对应)中。
这里写图片描述

接着,再进行一次分配,这次根据十位数值来分配:
这里写图片描述


int GetDigits(int* a, size_t n)
{
    int base = 10;
    int digits = 1;
    for (size_t i = 0; i < n; ++i)
    {
        while (a[i] >= base)
        {
            {
                ++digits;
                base *= 10;
            }
        }
    }
    return digits;
}
void LSDSort(int* a, size_t n)
{
    int digits = GetDigits(a, n);

    int base = 1;
    int* buckets = new int[n];
    for (size_t i = 0; i < digits; ++i)
    {
        int counts[10] = { 0 };
        for (size_t i = 0; i < n; i++)  //统计0—9号桶中数据的个数
        {
            int num = (a[i] / base) % 10;
            counts[num]++;
        }

        //计算定位数组
        int starts[10] = { 0 };
        starts[0] = 0;
        for (size_t i = 1; i < 10; ++i)
        {
            starts[i] = starts[i - 1] + counts[i - 1];
        }

        for (size_t i = 0; i < n; i++)
        {
            int num = (a[i] / base) % 10;
            int& pos = starts[num];
            buckets[pos] = a[i];
            ++pos;
        }

        memcpy(a, buckets, sizeof(int)*n);
        base *= 10;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值