排序算法之希尔、归并、堆和基数排序

//希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本.但希尔排序是非稳定排序算法.
    希尔排序是基于插入排序的以下两点性质而提出改进方法的  :
        1. 插入排序在对几乎已经排好序的数据操作时,效率高,既可以达到线性排序的效率
        2. 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位

    希尔排序的基本思想是 : 先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,
    再堆全体记录进行依次直接插入排序.

    算法步骤 :
        1: 选择一个增量序列t1,t2,,,tk,其中ti > tj,tk = 1;
        2: 按增量序列个数k,对序列进行k趟排序.
        3: 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m的子序列,分别对各个表进行直接插入排序.仅增量因为为1时,
        整个序列作为一个表来处理,表长度度即为整个序列的长度.

    package com.baidurunnable;

    import java.util.Arrays;

    public class Demo5 {
        //希尔排序
        public static void main(String[] args) {
            int[] a = {1,4,3,5,2,7,6,8,9,0};
            quickSork(a);
            System.err.println(Arrays.toString(a));
        }

        private static void quickSork(int[] a) {
            int core = a.length/2;
            while(true) {
                for(int i = 0; i < core ;i++) {
                    for(int j = i;j+core < a.length  ; j+= core) {
                        int temp; //j = 0,core = 5  第二次 : j=5
                        if(a[j] > a[j+core]) {
                            temp = a[j];
                            a[j] = a[j+core];
                            a[j+core] = temp;
                        }

                    }
                }
                if(core == 1) {
                    break;
                }
                core--;
            }
        }

    }

归并排序(Mergo sort) : 是建立在归并操作上的一种有效的排序算法. 该算法是采用分治法(Divide and Conquer)的一个非常典型的应用.
    算法步骤 :
        1: 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列.
        2: 设定两个指针,最初位置分别为两个已经排序序列的起始位置.
        3: 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一个位置.
        4: 重复步骤3直到某一指针达到序列尾.
        5: 将另一序列剩下的元素直接复制到合并序列尾.

    package cn.baidu.client;

import java.util.Arrays;

public class DemoTest {

    public static void main(String[] args) {
        int[] arr = {9,2,4,5,3,1,6,7,8};
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }

    private static void sort(int[] arr) {
        //在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间.
        int[] temp = new int[arr.length];
        sort(arr,0,arr.length-1,temp);
    }

    private static void sort(int[] arr, int left, int right, int[] temp) {
        if(left < right) {
            int mid = (left + right)/2;
            //左边归并排序,使得左子序有序
            sort(arr,left,mid,temp);
            //右边归并排序,使得右子序列有序
            sort(arr,mid+1,right,temp);
            //将两个有序子数组合并操作
            merge(arr,left,mid,right,temp);

        }
    }

    private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
        //左序列指针
        int i = left;
        //右序列指针
        int j = mid + 1;
        //临时数组指针
        int t = 0;
        while(i <= mid && j <= right) {
            if(arr[i] <= arr[j]) {
                temp[t++] = arr[i++];
            }else {
                temp[t++] = arr[j++];
            }
        }

        //将左边剩余元素填充进temp中
        while(i <= mid) {
            temp[t++] = arr[i++];
        }
        //将右序列剩余元素填充进temp中
        while(j <= right) {
            temp[t++] = arr[j++];
        }

        t=0;
        //将temp中的元素全部拷贝到原数组中
        while(left <= right) {
            arr[left++] = temp[t++];
        }
    }

}

    基本思想
    堆排序是一种树形选择排序,是对直接选择排序的改进。

    首先,我们来看看什么是堆(heap):
    (1)堆中某个节点的值总是不大于或不小于其父节点的值;
    (2)堆总是一棵完全二叉树(Complete Binary Tree)。
     完全二叉树是由满二叉树(Full Binary Tree)而引出来的。除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树称为满二叉树。
    如果除最后一层外,每一层上的节点数均达到最大值;在最后一层上只缺少右边的若干结点,这样的二叉树被称为完全二叉树。

    一棵完全二叉树,如果某个节点的值总是不小于其父节点的值,则根节点的关键字是所有节点关键字中最小的,称为小根堆(小顶堆);如果某个节点的值总是不大于其父节点的值,则根节点的关键字是所有节点关键字中最大的,称为大根堆(大顶堆)。
    从根节点开始,按照每层从左到右的顺序对堆的节点进行编号:

    可以发现,如果某个节点的编号为i,则它的子节点的编号分别为:2i、2i+1。据此,推出堆的数学定义:
    具有n个元素的序列(k1,k2,...,kn),当且仅当满足

    时称之为堆。
    需要注意的是,堆只对父子节点做了约束,并没有对兄弟节点做任何约束,左子节点与右子节点没有必然的大小关系。

    如果用数组存储堆中的数据,逻辑结构与存储结构如下:

    初始时把要排序的n个数看作是一棵顺序存储的完全二叉树,调整它们的存储顺序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依次类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。这个过程就称为堆排序。

    写代码之前,我们要解决一个问题:如何将一个不是堆的完全二叉树调整为堆。
    例如我们要将这样一个无序序列:
    49,38,65,97,76,13,27,49
    建成堆,将它直接映射成二叉树,结果如下图的(a):

    (a)是一个完全二叉树,但不是堆。我们将它调整为小顶堆。
    堆有一个性质是:堆的每个子树也是堆。
    调整的核心思想就是让树的每棵子树都成为堆,以某节点与它的左子节点、右子节点为操作单位,将三者中最小的元素置于子树的根上。
    (a)中最后一个元素是49,在树中的序号为8,对应的数组下标则为7,它的父节点对应的数组下标为3(如果一个元素对应的存储数组的下标为i,则它的父节点对应的存储数组的下标为(i-1)/2),49小于97,所以两者交换位置。
    此时,以第三层元素为根节点的所有子树都已是堆了,下一步继续调整以第二层元素为根节点的子树。
    先调整以65为根的子树,再调整以38为根的子树(满足堆的要求,实际上不用调整)。
    然后调整以第一层元素为根的子树,即以49为根,以38为左子节点,以13为右子节点的子树,交换13与49的位置。
    一旦交换位置,就有可能影响本来已经是堆的子树。13与49交换位置之后,破坏了右子树,将焦点转移到49上面来,继续调整以它为根节点的子树。如果此次调整又影响了下一层的子树,继续调整,直至叶子节点。
    以上就是由数组建堆的过程。

    堆建好之后开始排序,堆顶就是最小值,取出放入数组中的最后一个位置,将堆底(数组中的最后一个元素)放入堆顶。这一操作会破坏堆,需要将前n-1个元素调整成堆。
    然后再取出堆顶,放入数组的倒数第二个位置,堆底(数组中的倒数第二个元素)放入堆顶,再将前n-2个元素调整成堆。
    按照上面的思路循环操作,最终就会将数组中的元素按降序的顺序排列完毕。

    如果想要升序排列,利用大顶堆进行类似的操作即可。下面的java实现就是使用大顶堆完成的。

    堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
    堆排序的平均时间复杂度为Ο(nlogn) 。

    算法步骤:
    1)创建一个堆H[0..n-1]
    2)把堆首(最大值)和堆尾互换
    3)把堆的尺寸缩小1,并调用shift_down(0),目的是把新的数组顶端数据调整到相应位置
    4) 重复步骤2,直到堆的尺寸为1

    java实现
    [java] view plain copy print?
    //堆排序
          public void heapSort(){  

                 buildHeap();
                 System.out.println("建堆:");
                 printTree(array.length);  

                 int lastIndex = array.length-1;
                 while(lastIndex>0){
                        swap(0,lastIndex);  //取出堆顶元素,将堆底放入堆顶。其实就是交换下标为0与lastIndex的数据
                        if(--lastIndex == 0) break;  //只有一个元素时就不用调整堆了,排序结束
                        adjustHeap(0,lastIndex);  //调整堆  

                        System.out.println("调整堆:");
                        printTree(lastIndex+1);
                 }  

          }  

          /**
           * 用数组中的元素建堆
           */
          private void buildHeap(){
                 int lastIndex = array.length-1;
                 for(inti= (lastIndex-1)/2;i>=0;i--){ //(lastIndex-1)/2就是最后一个元素的根节点的下标,依次调整每棵子树
                        adjustHeap(i,lastIndex);  //调整以下标i的元素为根的子树
                 }
          }  

          /**
           * 调整以下标是rootIndex的元素为根的子树
           *@param rootIndex 根的下标
           *@param lastIndex 堆中最后一个元素的下标
           */
          private void adjustHeap(int rootIndex,intlastIndex){  

                 int biggerIndex = rootIndex;
                 int leftChildIndex = 2*rootIndex+1;
                 int rightChildIndex = 2*rootIndex+2;  

                 if(rightChildIndex<=lastIndex){  //存在右子节点,则必存在左子节点  

                        if(array[rootIndex]<array[leftChildIndex] || array[rootIndex]<array[rightChildIndex]){ //子节点中存在比根更大的元素
                         biggerIndex = array[leftChildIndex]<array[rightChildIndex] ? rightChildIndex :leftChildIndex;
                        }  

                 }else if(leftChildIndex<=lastIndex){  //只存在左子节点  

                        if(array[leftChildIndex]>array[rootIndex]){  //左子节点更大
                               biggerIndex = leftChildIndex;
                        }
                 }  

                 if(biggerIndex != rootIndex){  //找到了比根更大的子节点  

                        swap(rootIndex,biggerIndex);  

                        //交换位置后可能会破坏子树,将焦点转向交换了位置的子节点,调整以它为根的子树
                        adjustHeap(biggerIndex,lastIndex);
                 }
          }  

          /**
           * 将数组按照完全二叉树的形式打印出来
           */
          private void printTree(int len){  

                 int layers = (int)Math.floor(Math.log((double)len)/Math.log((double)2))+1;  //树的层数
                 int maxWidth = (int)Math.pow(2,layers)-1;  //树的最大宽度
                 int endSpacing = maxWidth;
                 int spacing;
                 int numberOfThisLayer;
                 for(int i=1;i<=layers;i++){  //从第一层开始,逐层打印
                        endSpacing = endSpacing/2;  //每层打印之前需要打印的空格数
                        spacing = 2*endSpacing+1;  //元素之间应该打印的空格数
                        numberOfThisLayer = (int)Math.pow(2, i-1);  //该层要打印的元素总数  

                        int j;
                        for(j=0;j<endSpacing;j++){
                               System.out.print("  ");
                        }  

                        int beginIndex = (int)Math.pow(2,i-1)-1;  //该层第一个元素对应的数组下标
                        for(j=1;j<=numberOfThisLayer;j++){
                               System.out.print(array[beginIndex++]+"");
                               for(intk=0;k<spacing;k++){  //打印元素之间的空格
                                      System.out.print("  ");
                               }
                               if(beginIndex == len){  //已打印到最后一个元素
                                      break;
                               }
                        }  

                        System.out.println();
                 }
                 System.out.println();
          }      

    第二种实现方式 :
            package com.dn.sort;

        public class HeapSort {
        //堆排序
            public static void main(String[] args){
                int[] array = {39,44,1,0,8,66,23,67,9,15,100,70,22,3,6,54};
                HeapSort heapSort = new HeapSort();
                heapSort.heapSort(array);
                for(int i = 0;i<array.length;i++){
                    System.out.println(" "+array[i]);
                }
            }

            public void heapSort(int [] a){
                if(a == null||a.length<=1){
                    return;
                }
                //创建大堆
                buildMaxHeap(a);
                for(int i = a.length-1;i>=1;i--){
                    //最大元素已经排在了下标为0的地方
                    exchangeElements(a, 0, i);//每交换换一次,就沉淀一个大元素
                    maxHeap(a, i, 0);
                }
            }

            private void buildMaxHeap(int[] a) {
                int half = (a.length -1)/2;//假设长度为9
                for(int i = half;i>=0;i--){
                    //只需遍历43210
                    maxHeap(a,a.length,i);
                }
            }

            //length表示用于构造大堆的数组长度元素数量
            private void maxHeap(int[] a, int length, int i) {
                int left = i*2+1;
                int right = i*2+2;
                int largest = i;
                if(left<length&&a[left]>a[i]){
                    largest = left;
                }
                if(right<length&&a[right]>a[largest]){
                    largest = right;
                }
                if(i!=largest){
                    //进行数据交换
                    exchangeElements(a,i,largest);
                    maxHeap(a, length, largest);
                }
            }

            //在数组a里进行两个下标元素交换
            private void exchangeElements(int[] a, int i, int largest) {
                int temp = a[i];
                a[i] = a[largest];
                a[largest] = temp;
            }
        }

    基数排序
    基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
    说基数排序之前,我们简单介绍桶排序:

    算法思想:是将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。

    简单来说,就是把数据分组,放在一个个的桶中,然后对每个桶里面的在进行排序。

    例如要对大小为[1..1000]范围内的n个整数A[1..n]排序
    首先,可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储 (10..20]的整数,……集合B存储( (i-1)*10, i*10]的整数,i = 1,2,..100。总共有 100个桶。

    然后,对A[1..n]从头到尾扫描一遍,把每个A放入对应的桶B[j]中。 再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任 何排序法都可以。

    最后,依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这 样就得到所有数字排好序的一个序列了。

    假设有n个数字,有m个桶,如果数字是平均分布的,则每个桶里面平均有n/m个数字。如果对每个桶中的数字采用快速排序,那么整个算法的复杂度是
    O(n + m * n/m*log(n/m)) = O(n + nlogn – nlogm)

    从上式看出,当m接近n的时候,桶排序复杂度接近O(n)。当然,以上复杂度的计算是基于输入的n个数字是平均分布这个假设的。这个假设是很强的 ,实际应用中效果并没有这么好。如果所有的数字都落在同一个桶中,那就退化成一般的排序了。

    前面说的几大排序算法 ,大部分时间复杂度都是O(n2),也有部分排序算法时间复杂度是O(nlogn)。而桶式排序却能实现O(n)的时间复杂度。但桶排序的缺点是:
    1)首先是空间复杂度比较高,需要的额外开销大。排序有两个数组的空间开销,一个存放待排序数组,一个就是所谓的桶,比如待排序值是从0到m-1,那就需要m个桶,这个桶数组就要至少m个空间。
    2)其次待排序的元素都要在一定的范围内等等。

原文地址:https://www.cnblogs.com/haizai/p/11742846.html

时间: 2024-10-06 18:12:03

排序算法之希尔、归并、堆和基数排序的相关文章

排序算法(四)——归并排序、基数排序

前面三篇文章分别介绍了插入排序.选择排序和交换排序,今天将最后两个排序讲完,分别是归并排序和基数排序. ****************************************************************************************************** 1.归并排序: 定义:所谓归并就是将两个或两个以上的有序文件合并成为一个新的有序文件.归并排序就是有n个记录的无序文件看成是由n个长度为1的有序子文件组成的文件,然后两两归并,得到n/2个长

排序算法之希尔排序

文章转载自http://www.cnblogs.com/chengxiao/ 希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法.希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一.本文会以图解的方式详细介绍希尔排序的基本思想及其代码实现. 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序:随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组

经典排序算法之希尔排序

? 希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法.希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一.希尔排序适合数据量中等情况,几十个到几万个. ? 网上看了好久才彻底明白希尔排序是什么,简单的说将就是按照步进对数据进行分组,对每组分别进行插入排序,直到步进是1的时候则全部完成.在此感谢 dreamcatcher-cx的博客 的讲解. function sortShell(arr)

各种排序算法总结篇(高速/堆/希尔/归并)

1.高速排序 交换排序有:冒泡(选择)排序和高速排序,冒泡和选择排序的时间复杂度太高,思想非常easy临时不讨论.高速排序基于一种分治的思想,逐步地使得序列有序. #include <iostream> #include <conio.h> using namespace std; int arrs[] = { 23, 65, 12, 3, 8, 76, 345, 90, 21, 75, 34, 61 }; int arrLen = sizeof(arrs) / sizeof(ar

java排序算法之希尔排序

希尔排序是冲破二次时间屏障的第一批算法之一. 它是通过比较相距一定间隔的元素来工作,各趟比较所用的距离随着算法的进行而减小,直到最后一趟(比较相邻元素)为止.因此希尔排序也叫缩减增量排序. 希尔排序使用一个序列h1,h2,h3...hk来排序. 具体的意思是 第一趟排序比较的是相隔为hk的元素,也就是比较a[i]与a[i+hk],保证a[i]<=a[i+hk]. 第二次比较的是相隔为hk-1的元素,也就是比较a[i]与a[i+hk-1],保证a[i]<=a[i+hk-1]. 直到最后比较的是相

我的Java开发学习之旅------&gt;Java经典排序算法之希尔排序

一.希尔排序(Shell Sort) 希尔排序(Shell Sort)是一种插入排序算法,因D.L.Shell于1959年提出而得名.Shell排序又称作缩小增量排序. 二.希尔排序的基本思想 希尔排序的中心思想就是:将数据进行分组,然后对每一组数据进行排序,在每一组数据都有序之后 ,就可以对所有的分组利用插入排序进行最后一次排序.这样可以显著减少交换的次数,以达到加快排序速度的目的.       希尔排序的中心思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组.所有距离

Java学习笔记——排序算法之希尔排序(Shell Sort)

落日楼头,断鸿声里,江南游子.把吴钩看了,栏杆拍遍,无人会,登临意. --水龙吟·登建康赏心亭 希尔算法是希尔(D.L.Shell)于1959年提出的一种排序算法.是第一个时间复杂度突破O(n2)的算法之一. 其基础是插入排序. 上代码: 1 public class ShellSort { 2 3 public static void shellSort(int[] arr){ 4 5 int increment = arr.length; 6 int temp;//牌 7 int i; 8

排序算法系列——希尔排序

希尔排序同之前介绍的直接插入排序一起属于插入排序的一种.希尔排序算法是按其设计者希尔(Donald Shell)的名字命名,该算法由1959年公布,是插入排序的一种更高效的改进版本.它的作法不是每次一个元素挨一个元素的比较.而是初期选用大跨步(增量较大)间隔比较,使记录跳跃式接近它的排序位置:然后增量缩小:最后增量为 1 ,这样记录移动次数大大减少,提高了排序效率.希尔排序对增量序列的选择没有严格规定. 希尔排序是基于插入排序的以下两点性质而提出改进方法的: 插入排序在对几乎已经排好序的数据操作

排序算法一希尔排序

希尔排序(Shell Sort) 插入排序的一种.也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本. 希尔排序是非稳定排序算法. 1 class ShallSort { 2 public void sort() { 3 int[] arr = {1,2,5,1,4,2,12}; 4 5 //增量 6 int flag = arr.length; 7 while (flag>1){ 8 //获取增量间隔,递减 9 flag=flag/3+1; 10 //交叉排序:所有组交叉一起排序 11