数据结构:史上最全排序算法合集

6 篇文章 0 订阅
6 篇文章 0 订阅

计数排序

计数排序是利用元素在序列中的名次,将元素移动到与其名次对应的位置。
我们先来看看代码:

template <class T>
void rank(T a[],int n,int r[])
{//给数组a排出名次,写到数组r中
    for(int i=0;i<n;i++)
        r[i]=0;
    //比较所有元素对
    for(int i=0;i<n-1;i++)
        for(int j=i+1;j<n;j++)
            if(a[j]>=a[i]) r[j]++;
            else r[i]++;
}
template <class T>
void rearrange(T a[],int n,int r[])
{//使用一个附加数组使元素排序
    T *u[] = new T[n];//创建附加数组

    for(int i=0;i<n;i++)//将a中的元素按顺序排入数组u中
        u[i] = a[r[i]];
    for(int i=0;i<n;i++)//将u中的元素复制进数组a中
        a[i] = u[i];
    delete []u;
}

假设调用new操作符分配空间的操作是成功的,那么执行函数rearrange,完成排序需要(n-1)n/2次比较与2n次移动,复杂度是Q(n2)。
如果我们不想使用附加数组,那我们来试试原地重排,代码如下:

template <class T>
void rearrange2(T a[],int n,int r[])
{//原地重排
    for(int i=0;i<n;i++)
        while (r[i]!=i)
        {
            swap(a[i],a[r[i]]);
            swap(r[i],r[r[i]]);
        }
}

在优化后的算法中,我们易知,交换次数最少为0,最多为2(n-1)(应为每次交换会使得至少一个元素在正确的位置上,所以会交换(n-1)次),由此可见,优化后的算法复杂度为Q(n-1),而且程序所需内存减少了。

选择排序

选择排序是指首先找到最大的元素,把它移到最高位,然后在找到其余元素中最大的元素,把它移到次高位。如此进行下去,直到剩下一个元素。
看看代码吧:

template <class T>
int indexOfMax(T a[],int n)
{//查找数组的最大元素
    int indexOfMax = 0;
    for(int i = 1;i<n;i++)
        if(a[indexOfMax]<a[i])
            indexOfMax = i;
    return indexOfMax;
}
template <class T>
void selectionSort(T a[],int n)
{//选择排序
    for(int i=n;i>1;i--) {
        int j = indexOfMax(a, i);
        swap(a[j], a[i - 1]);
    }
}

在这个算法中,我们需要比较(n-1)n/2次,元素的移动次数3(n-1)次,这样我们的比较次数与计数排序相同,但是移动次数更多,那么我们为了降低时间复杂度,我们看下一种选择排序算法:

template <class T>
void selectionSort2(T a[],int n)
{//及时终止的选择排序
    bool sort = false;//判断数列是否有序
    for(int i=n;!sort&&(i>1);i--){
        int indexOfMax = 0;
        sort = true;
        //查找最大的元素
        for(int j=1;j<i;j++) {
            if (a[indexOfMax] <= a[j]) indexOfMax = j;
            else sort = false;
        }
        swap(a[indexOfMax],a[i-1]);
    }
}

对于及时终止的选择排序,最好的情况是一开始数组元素有序,这是外部的for循环只运行一次,数组元素的比较次数为(n-1)次,最坏的情况是外部的for循环直到i=1使才停止,比较次数为(n-1)n/2。

冒泡排序

冒泡排序是一种简单的排序算法,它使用一种“冒泡策略”,把最大的元素一道序列的最右端。
在一次冒泡中,,相邻的元素比较,如果左边的元素大于右边的元素,则交换,反之,无需交换。
一次冒泡完成后,我们得到一个新的序列,在这个序列中,最大的元素已经到了序列最右端,我们只需要将除去最大数的序列再进行冒泡过程,反复至最后一个元素,则排序完成。
我们来看看代码:

template <class T>
void bubbleSort(T a[],int n)
{//对数组元素使用冒泡排序
    for(int i=n;i>1;i--){
        for(int j=0;j<i-1;j++)
            if(a[j]>a[j+1])
                swap(a[j],a[j+1]);
    }
}

这段代码结构十分的简单,嵌套的循环为一个序列的一次冒泡过程。在冒泡排序中,长度为n的序列,我们共需要比较n(n-1)/2次,所需交换次数最多为n(n-1)/2次,最少为0次。时间复杂度为Q(n2)。

插入排序

插入排序是采用一中插入策略,通过例子我们应该会比较好理解:
我们将3这个数插入有序数列:2,4,6,8,9,12,15;我们会如何做呢?我们通过数组基本的操作遍历,从数组最右端开始连续把一些元素向右移动一些位置,知道为新元素找到位置,将元素放入就可以了。在我们这个数组中我们要将2以后的数字向后移动一位,然后将3插入第二个位置。
当我们明白了一次插入的方法后,就要考虑插入排序了。我们先来看看代码:

template <class T>
void insertSort(T a[],int n)
{
    for(int i=1;i<n+1;i++){
        T t = a[i];
        int j;
        for (j = i-1; j >= 0 && t < a[j]; j--)
            a[j+1] = a[j];
        a[j+1] = t;
    }
}

在这段代码中,我们明显的可以看到,我们将一个数字取出,虽然开始时为乱序,但我们的插入策略是插入到比自己打的数前面,具体实现为:

for (j = i-1; j >= 0 && t < a[j]; j--)

但我们将所有的数字遍历完成后,就会变为有序的数列。
现在我们来进行程序性能分析,在这种排序方法中,最好的比较次数是n-1,最坏的比较次数是n(n-1)/2,数组的移动最坏为n(n-1)/2,效率相对其他集中来讲比较低,但是逻辑简单,特别是一次有序插入,用处比较多。

基数排序

基数排序实际上是对数据结构中箱子排序的扩展,时间复杂度仅为Q(n),就可以对0~nc之间的n个整数进行排序,它不直接对数进行排序,而是把数按照某个基数分解为数,例如我们说1024=1103+0102+2*10+4;就是把1024按照10为基数进行排序。为了更好地进行理解,我们来看一道例题:
无需数列: 99,1,28,64,1024,2346,57,3;
1.我们要利用基数排序将其变为由小到大的有序数列,那我们先从低位由基数取数字,分别为:9,1,8,4,4,6,7,3,将其对应的数排序后得:1,3,64,1024,2346,57,28,99;
2.之后,我们在取十位的数字:0,0,6,2,4,5,2,9;将其对应得数进行排序可得:1,3,1024,28,2346,57,64,99
3.然后,我们再取百位的数字:0,0,0,0,3,0,0,0,将其对用的数进行排序可得:
1,3,1024,28,57,64,99,2346
4,最后,大家想必也猜到了,取千位的数字:0,0,1,0,0,0,0,2;将其对应的数进行排序:1,3,28,57,64,99,1024,2346
最终,我们得到了有序的数列。
在基数排序中,我们的基本思路就是将0~9作为十个桶,将位上的数字分别放入这十个桶中再取出,通过每一位的排序,最终可以得到有序的序列。
接下来,我们来看看代码:

int bitMaxNum(int a[],int n)
{
    int maxNum = 0;
    for(int i=0;i<n;i++){
        int temp = a[i];
        int max = 0;
        while (temp!=0){
            temp=temp/10;
            max++;
        }
        if(max>maxNum)
            maxNum = max;
    }
    return maxNum;
}

这个函数是用来确定数列中数的最大的位数,那么我们再来看看基数排序的函数部分:

void bucketSort(int a[],int bitMaxNum,int n)
{
    int bit = 1;//确定计算的数位
    for(int i=0;i<bitMaxNum;i++){
        int temp = 0;
        int bucket[10][n];//定义十个桶
        int count[10];//每个桶中元素个数
        for(int j=0;j<10;j++)//初始化
            count[j] = 0;
        for(int j=0;j<10;j++)//初始化
            for(int k=0;k<n;k++)
                bucket[j][k] = 0;
        for(int j=0;j<n;j++){
            int num = a[j]/bit;//确定数位
            int bucketNum = num%10;//取该位上的数字
            bucket[bucketNum][count[bucketNum]] = a[j];//放入桶
            count[bucketNum]++;
        }
        for(int j=0;j<10;j++){//从桶中取出数
            if(count[j]>0)
                for(int k=0;k<count[j];k++){
                    a[temp] = bucket[j][k];
                    temp++;
                }
        }
        bit = bit*10;//取更高位
    }
}

由此,我们可以完成了基数排序。

题外话:稳定排序

先来讲讲稳定排序的概念:当我们说一个排序算法能保证同值元素的相对位置不变,我们就说这个排序算法是稳定的。通俗的说,例如a1=a2 ,排序完成后,a1,a2的相对顺序没有变,那么这个排序算法是稳定的。
那我们来看看我们刚说的几种排序算法:
基数排序,冒泡排序无疑是稳定的,而选择排序,计数排序,插入排序是不稳定的。
当然了,事无绝对,我们对算法进行改进,不稳定的算法也可以变为稳定的,这里就不多赘述了。

(由于学艺不精,可能不全的以后会补上…)

  • 127
    点赞
  • 903
    收藏
    觉得还不错? 一键收藏
  • 18
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值