zoukankan      html  css  js  c++  java
  • 改进排序:希尔排序、堆排序

    1,希尔排序。

    希尔排序是对插入排序的改进。它的原理是将元素分为设定步长的多个序列,对每个序列进行插入排序;然后改变步长,直到元素序列不可分,整个序列排序完毕。

    比如:data[10]={0,5,7,1,6,3,9,2,4,8};那么如何来排序呢?

    首先设定步长为3,那么第一次步长为3,此时数组被分为:

    {0,5,7}

    {1,6,3}

    {9,2,4}

    {8}

    接着对列进行排序,得到以下序列:

    {0,2,3}

    {1,5,4}

    {9,6,7}

    {8}

    即:{0,2,3,1,5,4,9,6,7,8}

    然后改变步长,分开的数组长度为3*3,原来数组的长度为10<3*3,故此时对整个数组进行插入排序。

    仔细观察,此时数组几乎已经快有序了,只需要简单的进行比较即可,并不需要大量的移动元素。

    得到:{0,1,2,3,4,5,6,7,8,9}

    相比快速排序而要提高了效率,其时间复杂度为O(n log2 n),但是是不稳定的,需要选择好步长。

    下面给出代码

    void shellsort(int *data, int length)
    {
    	for (int gap = length / 3; gap > 0; gap /= 3)//gap是指步长。第一次每个数组元素是3,步长为3;第二次每个数组元素为3*3,步长为1
    	{
    		for (int i = gap; i < length; ++i)//插入排序,由于是对数组的列进行操作,所以把数组的第一行当成是初始有序列表。
    		{
    			int temp = data[i];
    			int j = 0;
    			for( j = i -gap; j >= 0 && data[j] > temp; j -= gap)//对列的元素进行排序
    			{
    				data[j+gap] = data[j];//移动数据
    			}  
    			data[j+gap] = temp;//插入最小数据
    		}
    	}
    }
    

    可以看出,实际上希尔排序是把数组当成二维数组再进行排序,然后对列进行排序。直到二维数组变换成一维数组,此时数组基本有序,再进行简单的插入排序,提高了效率。

    2,堆排序

    这里我们研究的是二叉堆。二叉堆是一种数据结构,是完全二叉树。

    这里我们设定堆的遍历顺序为层级遍历。

    堆分为大顶堆和小顶堆,顾名思义,指父母节点比子节点的值大称为大顶堆,否则是小顶堆。

    那么堆排序,实际上就简化为如何建立大顶堆或者小顶堆的过程。这里我们按递增排列,故建立大顶堆。

    如何建立大顶堆呢?由完全二叉树的定义可知,对于父母节点i,那么左孩子为2i,右孩子为2i+1,其关系是data[i]>=data[2i]>=data[2i+1].

    那么看一下推导过程:data[10]={0,5,7,1,6,3,9,2,4,8};

    1,初始化大顶堆。

    如果根节点位置为0,那么左子树,右子树均为0,故从尾部开始找到根节点位置。

    由2n<=length-1 得到根节点位置是4,左节点位置为8,右节点为9,而且由于其子节点为叶子节点,并不需要调整。

    即根节点为6,其子节点为4,8,右节点比根节点大,所以需要交换,得到{0,5,7,1,8,3,9,2,4,6}

    继续建立大顶堆,根节点位置减1,为3,此时经过交换得{0,5,7,9,8,3,1,2,4,6}

    继续得到{0,5,8,9,7,3,1,2,4,6}...最终得到{9,8,7,5,6,3,1,2,4,0}

    2,调整大顶堆,按照大顶堆的建立方法得到递减数组。

    即让所有根节点都放在数组最前面或者最后面。如果把根节点放在数组最后面则成为递增数组。

    {9,8,7,5,6,3,1,2,4,0}-->{8,7,6,5,0,3,1,2,4,9}-->{7,6,4,5,0,3,1,2,8,9}...{0,1,2,3,4,5,6,7,8,9}

    下面来看代码:

    //建立大顶堆
    void CreateHeap(int *data,int root,int size)
    {
        int max=root;//假定当前根节点为最大值
        int lChild=2*root;//左节点
        int rChild=lChild+1; //右节点
    
        if(root<=(length-1)/2)//由完全二叉树的性质可以得知,根节点不能大于长度的一半
        {
            if(lChild<=size&& data[lChild]>data[max])//左节点大于根节点
                max=lChild;
            if(rChild<=size&& data[rChild]>data[max])//右节点大于根节点
                max=rChild;
            if(root!=max)//最大节点发生变化,把子节点交换到根节点
            {
                int temp=data[root];
                data[root]=data[max];
                data[max]=temp;
    
                CreateHeap(data,max,size);//避免子树不是大顶堆,故需要进行重新调整
            }
        }
    }
    void heapSort(int *data,int length)
    {
        //首先初始化堆
        for(int i = (length-1) / 2; i >= 0; i--)//因为数组下标是从0起,所以length-1
            CreateHeap(data,i,length-1);
    
        //然后对堆的子树进行调节,使其成为递增数组
        for(int i=length-1;i>=0;i--)
        {
            int root=data[0];
            data[0]=data[i];
            data[i]=root;
            CreateHeap(data,0,i-1);//此时长度变为i-1.
        }
    
    }
    

      

    上面是用递归的方式,完成了堆的排序,有时候在栈空间极为宝贵的情况下,我们不需要递归的形式。

    相关算法见:http://zh.wikipedia.org/wiki/%E5%A0%86%E6%8E%92%E5%BA%8F

    由上看出,实际上堆排序,是对选择排序的改进,用了二叉树的形式使得效率大大提升。

     

  • 相关阅读:
    Redis缓存穿透
    如何应对缓存穿透和缓存雪崩问题
    Redis缓存雪崩
    redis缓存机制
    C# LINQ学习笔记三:LINQ to OBJECT之操作字符串
    C# LINQ学习笔记二:LINQ标准查询操作概述
    C# LINQ学习笔记一:走进LINQ的世界
    C# Lambda表达式学习笔记
    C#委托与事件学习笔记
    C#泛型学习笔记
  • 原文地址:https://www.cnblogs.com/273809717/p/2835213.html
Copyright © 2011-2022 走看看