经典排序算法分析和代码-上篇

前言:这一篇文章中我们将讨论数组排序的问题,对于数据量比较大的,不能在内存中完成排序的,

必须在磁盘上完成排序类型叫作外部排序,本篇将不讨论。

对于内部排序的一些相关知识:

存在几种容易的算法以排序,如插入排序。

有一种算法叫做谢尔排序(ShellSort),它编程非常简单,以运行,并在实践中很有效。

还有一些稍微复杂的的排序算法。

任何通用的排序算法均需要次比较。


1.插入排序

最简单的排序算法之一是插入排序(insertion sort)。这是一个对少量元素进行排序的有效算法,

插入排序基于这样一种假设:位置0到位置p-1上的元素已经是经过排序的,下图显示了一个简单的数组在每一趟排序后的情况。

  

或者可以这样理解,插入排序的机理与打扑克牌时,整理手中牌时的做法一样,在开始摸牌时手中牌是空的,

牌面朝下放在桌子上。接着依次从桌上取一张牌插入到左手牌中正确的位置上,当牌摸完时,左手中的牌就成了有序的状态。


下面给出算法具体代码:

//插入排序  
void InsertSort(int Nums[], int Length)  
{  
    for (int i = 0; i < Length; i++)  
    {  
        int j, temp = Nums[i];//将第i个数保存起来  
        for (j = i - 1; Nums[j] > temp && j >= 0; j--)//依次将第i个数与前面的数比较  
        {  
            Nums[j + 1] = Nums[j];//如果前面的书比第i个数大的,则向后移动  
        }  
        Nums[j + 1] = temp;  //最后将空出的位置装入上面保存的第i个数  
    }  
}  

2.谢尔排序

谢尔排序(Shellsort)的名称源于它的发明者Donald Shell,他通过比较相距一定间隔的元素来工作,

各趟比较所用的距离随着算法的进行二减小,知道只比较相邻元素的最后一趟排序位置。由于这个原因,

谢尔排序有时也叫作缩减增量排序。


上图可以看到增量分别为5,3,1的排列状态,5排序之后,从第一个元素开始相隔为5的元素变为有序,

同理3排序和1排序之后元素全部为有序了。

下面给出算法的具体代码

//谢尔排序  
void ShellSort(int Nums[], int Length)
{
	for (int gap = Length / 2; gap > 0; gap /= 2)
	{
		//根据增量gap进行插入排序
		for (int i = gap; i < Length; i += gap)
		{
			int j, temp = Nums[i];
			for (j = i; j - gap >= 0; j -= gap)
			{
				if (temp < Nums[j - gap])
					Nums[j] = Nums[j - gap];
				else
					break;
			}
			Nums[j] = temp;
		}
	}
}

上面代码中的gap增量叫做谢尔增量,增量的使用对于算法性能有影响,使用谢尔增量时,
谢尔排序的最坏情形运行时间为 Hibbard 提出一个稍微不同的增量序列1,3,7....,
使用Hibbard增量的谢尔排序的最坏情形运行时间为


3.堆排序
对排序的运行时间为,也是一种原地排序算法(任何时候数组中只有常数个元素存储在输入数组以外),
堆的数据结构可以构成一个有效的优先队列,
它的应用可以参考我以前的文章http://blog.csdn.net/itcastcpp/article/details/12999595 。
堆是一棵被填满的二叉树,但底部可以例外,底部的节点从左到右的填充,这样的树被称为完全二叉树(complete binary tree)。
如下图:


完全二叉树有一个很有规律,可以用一个数组表示,而不需要链。对于数组任一位置i上的元素,其左儿子节点在2i上,
右儿子节点在2i+1上,其父节点在2/i上。因此这里不仅不需要链,遍历该树所需要的操作也及简单,
在大部分计算机上运行得非常快,这种实现的唯一问题在于,最大堆的大小需要事先估计,
但一般情况下这不成问题(而且如果需要我们可以重新调整)。
那么对于堆排序我们可以结合上图的树来理解就非常容易了,最后一个元素位置为p,
那么我们将p/2节点和之前分别进行调整,使得1 到 p/2的节点都比其儿子节点大,从p/2开始调整,
递减到第一个根节点,即可以得到根节点最大,再将根节点和最后节点p交换,然后将节点p排除在树之外,
从(p-1)/2的节点开始调整,把最大的根节点跟第p-1个节点交换,那么重复上面过程,直到只剩下根节点,
即完成了一次堆排序,排序结果位从小到大排列。
我们现在对下面实例进行分析:

可以看到上面有10个元素,那么先对第5个元素,发现节点5大于10,不用交换,
进入下一步,第4个节点和他的第8、9两个儿子节点比较:

发现第四个节点比第八个节点小,此时交换节点,然后处理继续处理第3个节点,依次类推,直到第一个节点(根节点)如下图:


最后得到图上图F,可以看到根节点16是最大的。

这时我们将根节点和最后一个节点互换,并且从数种去掉,那么只剩下9个节点了。

我们接着调整根节点在整棵数种的位置,发现根节点比左右子节点都大,不用调整,

这时我们继续将根节点14和最后一个节点1互换,那么1就变成了根节点,再调整1在根节点中的位置:


调整后如上图,根节点为10了,再与最后一个节点互换,然后调整根节点位置:


依此类推,知道所有节点从树中分离:


全部分离后,排列的内容为,可以看到内容变为有序了。


下面给出算法代码:

#include <stdio.h>   
  
//这个函数调整对数组中第n个元素的位置  
void HeapAdjust(int array[], int n, int length)  
{  
    int Child;  
    for (int i = n; i * 2 <= length; i = Child)  
    {  
        Child = i * 2;  
        if (Child + 1 <= length && array[Child] < array[Child + 1])  
            Child++;  
        //如果较大的子节点大于父节点则交换位置  
        if (array[i] < array[Child])  
        {  
            int Temp = array[i];  
            array[i] = array[Child];  
            array[Child] = Temp;  
        }  
        else  
        {  
            break;  
        }  
    }  
}  
  
void HeapSort(int array[], int length)  
{  
    //调整前半部分,保证了最大的值都在前半部分  
    for (int i = length / 2; i > 0; i--)  
    {  
        HeapAdjust(array, i, length);  
    }  
    for (int i = length-1; i > 0; i--)  
    {  
        //将最大的数移动到尾部  
        int Temp = array[1];  
        array[1] = array[i+1];  
        array[i+1] = Temp;  
        //除去尾部后,调整第一个元素位置  
        HeapAdjust(array, 1, i);  
    }  
}  
  
void HeapAdjustLittle(int array[], int num, int length)  
{  
    //如果输入的数小于这些数,直接返回  
    if (num < array[1])  
    {  
        return;  
    }  
  
    //如果输入的数大于数组中最小的数,则赋值,然后调整堆数组  
    array[1] = num;  
    int Child;  
    for (int i = 1; i * 2 <= length; i = Child)  
    {  
        Child = i * 2;  
        if (Child + 1 <= length && array[Child] > array[Child + 1])  
            Child++;  
        //如果较小的子节点大于父节点则交换位置  
        if (array[i] > array[Child])  
        {  
            int Temp = array[i];  
            array[i] = array[Child];  
            array[Child] = Temp;  
        }  
        else  
        {  
            break;  
        }  
    }  
}  
  
//打印出数组内容  
void PrintArray(int array[], int size)  
{  
    printf("最大的前%d个数:\n", size);  
    for (int i = 0; i < size; i++)  
    {  
        printf("%3d", array[i]);  
    }  
    printf("\n");  
}  
  
int myarray[] = { 0, 1, 9, 2, 8, 3, 7, 4, 6, 5 , 10};  
  
int main()  
{  
    //将前十个数进行一次堆排序,并输出结果  
    HeapSort(myarray, sizeof(myarray) / 4 - 1);  
    PrintArray(myarray + 1, sizeof(myarray) / 4 - 1);  
  
    //输入数字,打印出前十个最大的数  
    while (1)  
    {  
        int num = 0;  
        scanf("%d", &num);  
        HeapAdjustLittle(myarray, num, sizeof(myarray) / 4 - 1);  
        PrintArray(myarray + 1, sizeof(myarray) / 4 - 1);  
    }  
  
    return 0;  
}  

其他几种排序算法我们在下篇将会讲解,敬请期待.....



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尹成

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值