《算法》系列——排序算法之初级排序算法(C++描述)

《算法》系列 知识整理(C++描述)

算法学习历程

  • 排序算法
  • 查找算法
  • 字符串问题
  • 智能算法

学习目录

  • 排序算法
    • 初级排序算法
    • 归并排序
    • 快速排序
    • 优先队列
    • 排序算法的应用

本文主要内容

本片文章主要讲述初级排序算法。
包括:选择排序、插入排序、希尔排序、选择排序与插入排序的比较 。

初级排序算法

选择排序

算法宏观描述:首先,在数组中选择其中最小(或最大)的一个元素,将其与第一个元素进行交换,重复选择操作,在剩余的数组中选择最小的元素,将其与第二个元素交换,依次推类。

在宏观描述中可以看到,有两个核心的步骤:选择交换 ,而在“选择”这个步骤内,核心的是进行比较
因此要衡量和实现选择排序算法,就要分析比较交换的次数。

首先来看结论,然后我们来进行分析和证明。

结论:对于长度为N的数组,先择排序需要大约 N²/2次比较和N次交换。

推导:
先分析0~N-1中每排好一个元素需要的交换和比较次数,再累加便得到总的次数。
在确定0~N-1中的第i个位置元素时,需要在这个元素及其后共N-i个元素中选择出最小(或最大)的元素,即需要比较N-i-1次,找到最小元素后进行交换,即交换1次。固:
总的交换次数为N
总的比较次数为:(N-1)+(N-2)+……+2+1= N ( N − 1 ) 2 \frac{N(N-1)}{2} 2N(N1)~ N ² 2 \frac{N²}{2} 2N²

总结出选择排序的特点如下:

  • 运行时间与输入的待排序数组内容无关。
    当选择排序扫描完一次数组后,会完成一个位置的排序,但本次扫描并不能为下一次扫描提供任何信息。因此当我们分别用“完全无序”以及“基本有序”的两种序列测试选择排序算法时,会发现所花费的时间几乎完全一样。
  • 数据的移动次数和其他算法相比,几乎是最少的。
    每交换一次,即代表完成了一个位置的排序,对于长度为N的序列,排好序仅需N次交换。

最后附上C++描述的选择排序代码。

void exch(int *a,int i,int j)
{
	int t=a[i];
    	a[i]=a[j];
    	a[j]=t;
}
void Selection_sort(int *a,int N)
{
	//将a按照升序排列,n代表数组长度.
        for(int i=0;i<N;i++)
        {
            int minn=i;
            for(int j=i+1;j<N;j++)
            {
                if(a[j]<a[minn])
                    minn=j;
            }
            exch(a,i,minn);
        }
}

插入排序

算法宏观描述: 同选择排序,当前索引的左侧元素都为有序的(初始时单个元素即为有序),对于当前状态,将索引的右侧的元素依次插入到索引左侧,并保证每次插入过后索引左侧仍为有序。当索引到达数组右侧时,排序完成。

在算法的描述中,我们可以发现,插入排序对于接近有序的序列排序是非常快的。

关于插入排序,我们也来分析一下它的比较和交换次数。
对于当前的索引元素,其左侧是已经有序的序列,索引元素及右侧元素为待排序元素。
现考虑平均状况,对于每个索引元素(记为第i个元素,从0开始标号),都有可能插入至其左侧序列的中间位置,即需要交换 i 2 \frac{i}{2} 2i次,比较 i 2 \frac{i}{2} 2i+1次。其中除法均为整除。
为方便下一步计算,举例:第4个元素可能经过会经过2次交换和3次比较,第5个元素同样可能会经过2次交换和3次比较;同样地,第6、7个元素会经过3次交换和4次比较……第n-1和第n个元素经过 N 2 \frac{N}{2} 2N次交换和 N 2 \frac{N}{2} 2N+1次比较。

所以我们得到,平均状态下:
总的交换次数为1+1+2+2+……+ N 2 \frac{N}{2} 2N+ N 2 \frac{N}{2} 2N= N ² 4 \frac{N²}{4} 4N²+ N 2 \frac{N}{2} 2N~ N ² 4 \frac{N²}{4} 4N²
总的比较次数为交换的次数加上一个常数项,在上面的例子中我们看到,比较次数会比交换次数大1,原因是当索引元素到达目的地后,通过比较前面的一个元素,发现不需要再往前移动了,即该元素排序完成,这最后一次比较并没有发生交换,因此会多1。所有的元素会多出N-1(第一项不需要进行比较)。
所以总的比较次数在平均状况下同交换次数相同,大致满足 N ² 4 \frac{N²}{4} 4N²

在最坏情况下,每个元素都可能会插入到已排序序列的左端,即需要经历整个有序序列。此时:
总的交换次数为:1+2+……+N= N ² 2 \frac{N²}{2} 2N²+ N 2 \frac{N}{2} 2N~ N ² 2 \frac{N²}{2} 2N²
总的比较次数为:总的交换次数+(N-1)~ N ² 2 \frac{N²}{2} 2N²

在最好的情况下,序列已经有序,因此每个元素只需要比较1次,不需要经过交换。即:
总的交换次数为0
总的比较次数为N-1

最后,附上插入排序的C++代码:

void exch(int *a,int i,int j)
{
	int t=a[i];
        a[i]=a[j];
        a[j]=t;
}
void Insertion_sort(int *a,int N)
{
    for(int i=1;i<N;i++)
    { 
	for(int j=i;j>0;j--)
        {
            if(a[j]<a[j-1])
                exch(a,j,j-1);
        }
    }
}

对于插入排序,我们考虑一种情况——部分有序。在这种情况下,选择排序的效率几乎可以说比其他任何排序算法都要高。
介绍部分有序的概念前,先介绍倒置

倒置:制两个顺序颠倒的元素。
部分有序:当数组中倒置的数量较少时(官方定义为小于数组大小的某个倍数,没有太精确的范围),称这个数组为部分有序的。

几个典型的部分有序数组:

  1. 数组中每个元素举例它的最终位置都不远
  2. 一个有序的大数组接一个不一定有序的小数组
  3. 数组中只有几个元素的位置不正确

在这种情况下,插入排序非常有效,且此时:
交换次数=数组中倒置的数量
倒置的数量≤比较次数≤倒置的数量+数组的大小-1

证明这个并不难:
每次交换会改变一对元素的位置,因此每次交换相当于减少一对倒置,即交换次数与倒置数量相同。
每次交换会伴随一次比较,因此最少的比较次数与交换的数量相同,一般的情况下,1到N-1之间的每个i都可能需要一次比较。

选择排序与插入排序的比较

  • 对于随机排序的无重复数组,插入排序与选择排序的运行时间是平方级别,二者之比为一个较小的常数。

希尔排序

希尔排序算法是基于插入排序算法的改进。
当数组混乱无序且规模较大时,插入排序的很慢,因为插入排序交换的只是相邻的元素,当数组很长时,很容易出现移动举例较远的情况,而此时元素只能一步一步地通过交换移动到目的地。
这让我们不禁猜想,是否有可能让元素“大步走“,通过交换不相邻的元素,使数组快速地进行排序。

算法宏观描述:
将数组中任意间隔为h的元素看作一个子数组,通过插入排序,将h个子数组独立地排序,再逐步缩小h的值,使数组逐渐有序。

举个栗子:
排序:S H E L L S O R T E X A M P L E
取h=13,h/=3
输入:S H E L L S O R T E X A M P L E
取13:P H E L L S O R T E X A M S L E
取4 :L E E A M H L E P S O L T S X R
取1 :A E E E H L L L M O P R S S T X

从其中不难发现一个问题:h的值如何确定?

很遗憾,这个问题并没有确定的回答。,常使用的递增序列为:1,3,13,40,121,……,3*(n-1)+1,……
当使用这个序列时,比较的次数不超过N的若干倍乘递增序列长度。

目前关于希尔排序效率的最重要结论为:运行时间达不到平方级别。

最后,附上希尔排序的C++代码:

void exch(int *a,int i,int j)
{
	int t=a[i];
        a[i]=a[j];
        a[j]=t;
}
void shell_sort(int *a,int N)
{
        int h=1;
        while(h<N/3)
            h=3*h+1;  //h=1,4,13,40,121...
        while(h>=1)
        {
            for(int i=h;i<N;i++)
            {
                for(int j=i;j>=h;j-=h)
                {
                    if(a[j]<a[j-h])
                        exch(a,j,j-h);
                }
            }
            h=h/3;
        }
}

关于初级排序算法,暂且先整理这些内容,后续若有新的想法,或应评论区执政,会再进行补充、修改。

欢迎提出宝贵意见。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值