经典排序算法

来源:互联网 发布:Tensorflow 商用 编辑:程序博客网 时间:2024/06/05 06:03

前言

       数据结构和算法是写代码的基础。基本功很重要,所谓根基深度决定成长高度。以前没吃好的饭,总有一天要回来吃的。这段时间项目不忙,回来吃饭,决定花一段时间捋一捋数据结构和算法的基础知识。

       当然,能捋多少捋多少吧,这些知识的深度,也不是朝夕之间就能有所成的,本身就是一件需要潜心花费大量时间来学习巩固的东西。夯实基础,越坚实越好。


正文

    这篇博客简要总结了七个算法:冒泡排序,选择排序,插入排序,希尔排序,快速排序,归并排序和堆排序。本文所有的描述都是根据自己的理解手打的,为的是方便读懂,示例代码可以实现算法,但是不敢保证就是最优的。如描述内容有误,请指正。

      好了开始吧..

     1.冒泡排序

        从数组的一端开始两两比较,依次将当前最值移动到数组另一端端的排序方法。经实测,在这几个算法中速度是最慢的。

代码:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.      * 冒泡排序(由小到大) 
  3.      *  
  4.      * @param array 
  5.      * @return 
  6.      */  
  7.     private static void bubbleSequence(int[] array) {  
  8.         if (null != array) {  
  9.             long ts = System.currentTimeMillis();  
  10.             int length = array.length;  
  11.             for (int i = 0; i < length; i++) {  
  12.                 for (int j = 0; j < length - 1 - i; j++) {  
  13.                     if (array[j] > array[j + 1])  
  14.                         exchange(array, j, j + 1);  
  15.                 }  
  16.             }  
  17.             long te = System.currentTimeMillis();  
  18.             System.out.println("冒泡排序  time cost(ms):" + (te - ts));  
  19.         }  
  20.     }  
2.选择排序
从数组的一端开始选取元素依次和其他所有元素对比,将最值依次交换到目标位置的排序方法

代码:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.      * 选择排序(由小到大) 
  3.      *  
  4.      * @param array 
  5.      * @return 
  6.      */  
  7.     private static void selectSequence(int[] array) {  
  8.         if (null != array) {  
  9.             long ts = System.currentTimeMillis();  
  10.             int length = array.length;  
  11.             for (int i = 0; i < length; i++) {  
  12.                 for (int j = i + 1; j < length; j++) {  
  13.                     if (array[j] < array[i])  
  14.                         exchange(array, j, i);  
  15.                 }  
  16.             }  
  17.             long te = System.currentTimeMillis();  
  18.             System.out.println("选择排序  time cost(ms):" + (te - ts));  
  19.         }  
  20.     }  

3.插入排序

把数组中的无序序列元素依次插入到有序序列中的排序方法,对于部分有序序列效率很高。如果是完全无序序列,则初始有序序列长度为1.

代码:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.      * 插入排序 由小到大 
  3.      *  
  4.      * @param array 
  5.      * @return 
  6.      */  
  7.     private static void insertSequence(int[] array) {  
  8.         long ts = System.currentTimeMillis();  
  9.         if (null != array) {  
  10.             int length = array.length;  
  11.             int temp = 0;  
  12.   
  13.             // 开始插入排序,第i位依次在前i-1位中找位置  
  14.             for (int i = 1; i < length; i++) {  
  15.                 if (array[i] > array[i - 1]) {// 被比较值比前面都大  
  16.                     continue;  
  17.                 }  
  18.   
  19.                 for (int j = 0; j < i - 1; j++) {// 前i位依次对比  
  20.   
  21.                     if (array[i] < array[j]) {  
  22.                         temp = array[i];  
  23.                         for (int k = i; k > j; k--) {  
  24.                             array[k] = array[k - 1];// 被插入位置之后的元素依次后挪  
  25.                         }  
  26.                         array[j] = temp;// 插入值  
  27.                         break;  
  28.                     }  
  29.                 }  
  30.             }  
  31.   
  32.             long te = System.currentTimeMillis();  
  33.             System.out.println("插入排序  time cost(ms):" + (te - ts));  
  34.         }  
  35.     }  

4.希尔排序

相比而言,前三种都是比较基础的排序方法,容易理解。从这里开始,要费点脑子了。希尔排序用到了基础排序方法中的插入排序,它的基本思想是:将数组按照一定的步长分成若干个子数组,(通俗的举个例子,就像让一排人报数,循环报1、2、3,报完过后喊1的人为一组,喊2的人为一组,喊3的人为一组,这一队人就被分成了三个小组。)然后对子数组进行插入排序,使之有序。然后缩小步长,继续分组,排序。等到步长为1时,排序完成。经实测希尔排序的效率是很高的,比前面三种算法速度快十几到几十倍不等。

代码:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.      * 希尔排序 
  3.      *  
  4.      * @param array 
  5.      * @return 
  6.      */  
  7.     private static void shellSequence(int[] array) {  
  8.         if (null != array) {  
  9.             long ts = System.currentTimeMillis();  
  10.             int length = array.length;  
  11.             int stepLength = 1;// 步长  
  12.             // 计算初始步长  
  13.             while (stepLength < length / 3) {  
  14.                 stepLength = stepLength * 3 + 1;  
  15.             }  
  16.   
  17.             while (stepLength >= 1) {  
  18.                 // 使间隔为h的数组变为有序  
  19.                 for (int i = 0; i < stepLength; i++) // 直接插入排序  
  20.                 {  
  21.                     for (int j = i + stepLength; j < length; j += stepLength)  
  22.                         if (array[j] < array[j - stepLength]) {  
  23.                             int temp = array[j];  
  24.                             int k = j - stepLength;  
  25.                             while (k >= 0 && array[k] > temp) {  
  26.                                 array[k + stepLength] = array[k];  
  27.                                 k -= stepLength;  
  28.                             }  
  29.                             array[k + stepLength] = temp;  
  30.                         }  
  31.                 }  
  32.   
  33.                 stepLength /= 3;// 步长缩短  
  34.             }  
  35.   
  36.             long te = System.currentTimeMillis();  
  37.             System.out.println("希尔排序  time cost(ms):" + (te - ts));  
  38.         }  
  39.     }  

5.归并排序

归并排序依赖归并操作,即将两个已经排序的序列合并成一个序列的操作,并排序的过程是:将一个数组拆分成两个子数组,再对子数组进行拆分......直到最后子数组的长度为1,然后子数组按顺序合并,两两合并,两两合并,直到恢复到原数组长度,排序完成。有一个生动的动图:


代码递归实现:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.      * 归并排序 
  3.      *  
  4.      * 先将数组拆分为子序列,递归进行,直至分为所有子序列长度都为1,然后将两两子序列合并排序,合并完成后,排序完成. 
  5.      *  
  6.      * @param array 
  7.      *            待排序数组 
  8.      * @param result 
  9.      *            作为挪动空间的辅助数组 
  10.      * @return 
  11.      */  
  12.     private static void mergeSequence(int[] array, int result[], int start, int end) {  
  13.         // 参考维基百科的动图,先拆再合,递归执行  
  14.         if (start >= end)  
  15.             return;  
  16.   
  17.         if (null != array) {  
  18.   
  19.             int length = end - start;  
  20.             int middle = (length >> 1) + start;  
  21.             int start1 = start, end1 = middle;  
  22.             int start2 = middle + 1, end2 = end;  
  23.             mergeSequence(array, result, start1, end1);  
  24.             mergeSequence(array, result, start2, end2);  
  25.   
  26.             int k = start;  
  27.             while (start1 <= end1 && start2 <= end2) // 挨个比较值,合并排序  
  28.                 result[k++] = array[start1] < array[start2] ? array[start1++] : array[start2++];  
  29.             while (start1 <= end1) // 合并1的尾巴  
  30.                 result[k++] = array[start1++];  
  31.             while (start2 <= end2) // 合并2的尾巴  
  32.                 result[k++] = array[start2++];  
  33.             for (k = start; k <= end; k++)// 合并后的值赋回原数组  
  34.                 array[k] = result[k];  
  35.   
  36.         }  
  37.     }  
6.快速排序

快速排序的过程是,1.选一个元素作为基准值,将大于基准值的元素放到一边,小于基准值的元素放到另一边,形成左右两个子序列。然后对子序列继续进行这样的操作,直到子序列长度为1,排序完成。

代码:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.      * 快速排序 
  3.      * 按照某个基准值,将数组分为大于该值和小于该值的两个子序列,然后递归的对子序列继续按此方法排序,直到最终子序列长度为1时,排序完成. 
  4.      * 小规模实验测得,排序效率并不稳定,时快时慢,与具体数组有关. 
  5.      * @param array 待排序数组 
  6.      * @param start 
  7.      * @param end 
  8.      */  
  9.     private static void quickSequence(int[] array,int start, int end) {  
  10.         if (start >= end)  
  11.             return;  
  12.         int mid = array[end];//将最后一位元素作为基准值  
  13.         int left = start, right = end - 1;  
  14.         //设定:左边序列小于基准值,右边序列大于基准值  
  15.         //从左右两边向中间分别搜索小于和大于基准值的元素,将两边不符合条件的元素相互交换  
  16.         while (left < right) {  
  17.             while (array[left] < mid && left < right)  
  18.                 left++;  
  19.             while (array[right] >= mid && left < right)  
  20.                 right--;  
  21.             exchange(array,left, right);  
  22.         }  
  23.           
  24.         //搜索完毕后,如果左边的元素还大于基准值,则将其与基准值交换,这种情况一股出现在基准值恰好选成了序列中的最小值  
  25.         if (array[left] >= array[end])  
  26.             exchange(array,left, end);  
  27.         else  
  28.             left++;  
  29.           
  30.         //递归操作左边序列  
  31.         quickSequence(array,start, left - 1);  
  32.         //递归操作右边序列  
  33.         quickSequence(array,left + 1, end);  
  34.     }  

7.堆排序

堆排序是一种利用堆这种数据结构的排序算法。堆是一个完全二叉树,满足条件:所有父节点的值都不大于或不小于子节点。如果,父节点值大于子节点,叫大顶堆,反之,叫小顶堆。
堆排序排序过程是:
1.首先,将数组构造成堆结构,见下图:

根结点存在序号0处, i结点的父结点下标就为(i-1)/2。i结点的左右子结点下标分别为2*i+12*i+2
2.构造好基本结构后,开始调整,使数据符合堆的特点,调整时,应该从最后一个子树结构开始,依次往上调整。
3.调出堆后,开始排序。
排序的过程是,将堆顶的数据(最大或最小的数据)依次交换到最后,交换到最后的数据固定不动(因为已经调好顺序,视为有序序列),然后再调整出堆,继续交换最值到无序序列的最后。即调整的过程,是不断将当前阶段最大值往后挪的过程。当挪到后面的有序序列占满整个数组,排序完成。所以,堆排序的本质其实就是选择排序,但是它比直接选择排序减少了许多重复的比较过程,效率更高。
所以,堆排序的最重要一个操作,就是调整堆结构。

下面演示一个示例
{3,4,6,7,2,1,8,5}
1.构造堆结构

2.调整后,构造出大顶堆

3.构造出对数据结构后开始排序
按照上面第三步,此时,数组中最大的数据已经在堆顶了,要排出由小到大的数据,要依次将最大的数挪到最后。
红色框为待交换项,绿色框为已固定的有序部分。
每次调整,将无序部分构建为大顶堆结构。每次交换,将最值交换到无序序列的最后。


如果按照固定代码逻辑,到上面一步还会继续调整两轮,然后排序完成。
代码:
[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. package com.test;  
  2.   
  3. /** 
  4.  * 堆排序 
  5.  *  
  6.  * @author Administrator 
  7.  * 
  8.  */  
  9. public class HeapSequence {  
  10.   
  11.     /** 
  12.      * 整理堆 (整理为大顶堆) 
  13.      *  
  14.      * @param array 
  15.      */  
  16.     public void createHeap(int[] array) {  
  17.         if (null != array) {  
  18.               
  19.             int startPst = getFatherPosition(array.length - 1);  
  20.             for (int i = startPst; i >= 0; i--) {  
  21.                 sortNode(array, i, array.length);  
  22.             }  
  23.         }  
  24.     }  
  25.   
  26.     /** 
  27.      * 排序 
  28.      *  
  29.      * @param array 
  30.      */  
  31.     public void sortHeap(int[] array) {  
  32.         if (null != array) {  
  33.             for (int i = array.length - 1; i > 0; i--) {  
  34.                 exchange(array, 0, i);  
  35.                 sortNode(array, 0, i);  
  36.             }  
  37.         }  
  38.     }  
  39.   
  40.     public void sort(int[] array) {  
  41.         createHeap(array);  
  42.         sortHeap(array);  
  43.     }  
  44.   
  45.     /** 
  46.      * 整理单个节点的堆结构 
  47.      *  
  48.      * @param array 
  49.      *            目标数组 
  50.      * @param nodePosition 
  51.      *            节点位置 
  52.      */  
  53.     private void sortNode(int[] array, int nodePosition, int heapSize) {  
  54.         int leftChildPst = getLeftChildPosition(nodePosition);  
  55.         int rightChildPst = getRightChildPosition(nodePosition);  
  56.   
  57.         int largestPosition = nodePosition;  
  58.         if (leftChildPst < heapSize && array[largestPosition] < array[leftChildPst]) {  
  59.             largestPosition = leftChildPst;  
  60.         }  
  61.   
  62.         if (rightChildPst < heapSize && array[largestPosition] < array[rightChildPst]) {  
  63.             largestPosition = rightChildPst;  
  64.         }  
  65.   
  66.         // 子节点值有变化,继续整理子节点  
  67.         if (largestPosition != nodePosition) {  
  68.             exchange(array, largestPosition, nodePosition);  
  69.             sortNode(array, largestPosition, heapSize);  
  70.         }  
  71.   
  72.     }  
  73.   
  74.     /** 
  75.      * 父节点位置 
  76.      *  
  77.      * @param currPst 
  78.      * @return 
  79.      */  
  80.     public int getFatherPosition(int currPst) {  
  81.         return (currPst - 1) / 2;  
  82.     }  
  83.   
  84.     /** 
  85.      * 左边子节点位置 
  86.      *  
  87.      * @param currPst 
  88.      * @return 
  89.      */  
  90.     public int getLeftChildPosition(int currPst) {  
  91.         return currPst * 2 + 1;  
  92.     }  
  93.   
  94.     /** 
  95.      * 右边子节点位置 
  96.      *  
  97.      * @param currPst 
  98.      * @return 
  99.      */  
  100.     public int getRightChildPosition(int currPst) {  
  101.         return currPst * 2 + 2;  
  102.     }  
  103.   
  104.     /** 
  105.      * 交换数组中两个位置的值 
  106.      *  
  107.      * @param a 
  108.      *            数组 
  109.      * @param x 
  110.      *            位置1 
  111.      * @param y位置2 
  112.      */  
  113.     private static void exchange(int[] a, int x, int y) {  
  114.         int temp = 0;  
  115.         temp = a[x];  
  116.         a[x] = a[y];  
  117.         a[y] = temp;  
  118.     }  
  119. }  
最后,随机对长度为十万的数组测试一次排序,示例代码在本电脑上的运行速度:
0 0
原创粉丝点击