排序算法
排序概论
假设含有n个记录的序列为{R1,R2,...Rn},其相应的关键字序列为{K1,K2,...Kn}。将这样的记录重新排序为{Ri1,Ri2,...Rin},使得相应的关键字值满足条件Ki1<=Ki2<=...<=Kin,这样一种操作称为排序。
通常来说,排序的目的是快速查找
对于一个排序算法来说,一般从一下3方面衡量算法的优势。
时间复杂度:主要分析关键字的比较次数和记录的移动次数
空间复杂度:分析排序算法中需要多少辅助内存
稳定性:如果两个记录A和B关键字值相等,但排序后A、B的先后次数保持不变,则称这种算法是稳定的。
外部排序:
如果参与排序的数据元素非常多,数据量非常大,计算无法把整个排序过程放在内存中完成,必须借助外部存储器(如磁盘),这种排序就被称为外部排序。
外部排序最常用的是多路归并算法,将源文件分解分别排序,然后对有序的多个子文件进行归并并排序。
实际上也可以认为外部排序是由多次内部排序组成的。
内部排序:
分类:
选择排序(直接选择排序、堆排序)
交换排序(冒泡排序、快速排序)
插入排序(直接插入排序、折半插入排序、Shell排序)
归并排序
桶式排序
基数排序
-----------------------------------------------------------------------------
直接选择排序:
思路:
第1次,用第1个数据依次和后面的每个数据进行比较,如果第一个数据大于后面的某个数据,交换它们...依次类推(就是交换后的元素替代第一个数据和后面的进行比较,交换。。),第一轮得到最小的首位。
第2次比较,从第2个数据开始得到第二小的数据放在第二位。
...
n01次比较,选出数据第n-1小(第二大)的数据,排在n-1位(倒数第一位),剩下的就是最大的数据,它排在最后。
优点:
算法简单,容易实现
缺点:
一次只能确定一个元素,n个元素需要n-1次比较
代码:
class DataWrap implements Comparable<DataWrap>{ int data; String flag; public DataWrap(int data , String flag){ this.data = data; this.flag = flag; } public String toString(){ return data + flag; } public int compareTo(DataWrap dw) { return this.data >dw.data ? 1 : (this.data ==dw.data ? 0 : -1); } } public class SelectSort { public static void selectSort(DataWrap[] data){ System.out.println("开始排序"); int arrayLength = data.length; for(int i = 0; i <arrayLength -1; i++){ for(int j = i + 1 ;j < arrayLength; j++){ if(data[i].compareTo(data[j]) > 0){ DataWrap temp = data[i]; data[i] = data[j]; data[j] = temp; } } System.out.println(java.util.Arrays.toString(data)); } } public static void selectSort1(DataWrap[] data){ //优化后排序代码,减少交换次数 System.out.println("开始排序"); int arrayLength = data.length; for(int i = 0; i <arrayLength -1; i++){ int minIndex = i; for(int j = i + 1 ;j < arrayLength; j++){ if(data[i].compareTo(data[j]) > 0){ minIndex = j; } } if(minIndex != i){ DataWrap temp = data[i]; data[i] = data[minIndex]; data[minIndex] = temp; } System.out.println(java.util.Arrays.toString(data)); } } public static void main(String[] args){ DataWrap[] data = { new DataWrap(21 , ""), new DataWrap(30 , ""), new DataWrap(49 , ""), new DataWrap(30 , "*"), new DataWrap(16 , ""), new DataWrap(9 , ""), }; System.out.println("排序前:\n" + java.util.Arrays.toString(data)); selectSort(data); System.out.println("排序后:\n" + java.util.Arrays.toString(data)); } }
通过结果可以知道,两个data为30的位置改变了,该排序算法是不稳定的。
直接选择排序如果n个元素,交换次数n-1次,比较次数多,总体来说,时间效率为O(2^n)
直接选择排序的空间效率很高,只需要一个附加程序单元用于交换,其空间效率为O(1)
------------------------------------------------------------------------------------
堆排序
堆有关的概念:
假设有n个数据元素的序列k0,k1,...,k(n-1),当且仅当满足如下关系时,可以将这组数据成为小堆顶:
ki <= k(2i+1)且ki<=k(2i+2)
或者,满足如下关系时,可以将这组数据称为大顶堆:
k1>=k(2i+1)且ki>=k(2i+2)
对于满足小顶堆的数据列顺序排成一棵完全二叉树,则此树的特点是:树中所有节点的值都小于其左右节点的值,此树的根节点的值必然最小。反之,满足大顶堆的数据排成的完全二叉树根节点的值必然最大,小顶堆的任何子树也是小顶堆,大顶堆的任何子树也是大顶堆
堆排序的步骤就是重复以下2步:
1、建堆
2、拿堆的根节点和最后一个节点交换
package com.test_two; import java.util.Arrays; /** * Created by Administrator on 2017/3/13. */ public class HeapSort { public static void heapSort(DataWrap[] data){ System.out.println("开始排序"); int arrayLength = data.length; for(int i = 0; i < arrayLength - 1; i++){ buildMaxHeap(data , arrayLength - 1 - i); swap(data , 0 , arrayLength - 1 - i); System.out.println(Arrays.toString(data)); } } /** * 建大顶堆 */ private static void buildMaxHeap(DataWrap[] data , int lastIndex){ for(int i = (lastIndex - 1) / 2; i >= 0; i-- ){ /** * 从最后一个父节点开始一直到最后 */ int k = i; while(k*2 + 1 <= lastIndex){ int biggerIndex = 2 * k + 1; if(biggerIndex < lastIndex){ /** * 左子节点和右子节点比较,找出最大的子节点 */ if(data[biggerIndex].compareTo(data[biggerIndex + 1]) < 0){ biggerIndex++; } } if(data[k].compareTo(data[biggerIndex]) < 0){ /** * 最大子节点和父节点比较,如果父节点小于的话交换位置 */ swap(data , k , biggerIndex); /** * 将父节点赋值为最大的节点,这个不知道什么意思,为了跳出循环吗? */ k = biggerIndex; }else{ break; } } } } private static void swap(DataWrap[] data , int i , int j){ DataWrap tmp = data[i]; data[i] = data[j]; data[j] = tmp; }
public static void main(String[] args){
DataWrap[] data = new DataWrap[]{
new DataWrap(21 , ""),
new DataWrap(30 , ""),
new DataWrap(49 , ""),
new DataWrap(30 , "*"),
new DataWrap(21 , "*"),
new DataWrap(16 , ""),
new DataWrap(9 , ""),
};
System.out.println("排序之前:\n" + Arrays.toString(data));
heapSort(data);
System.out.println("排序之后:\n" + Arrays.toString(data));
}
}
得到结果:

堆排序算法而言,假设有n项数据,需要进行n-1次建堆,每次建堆耗时为log2^n,则其时间效率为O(nlog2^n)
堆排序算法空间效率很高,它只需要一个附加程序单元用于交换,其空间效率为O(1)
--------------------------------------------------------
交换排序
冒泡排序:
思路:水泡,越来越大,第一轮过后最大值到末尾。先第一个和第二个比较,大的放后位,不满足交换,之后第二个和第三个比较...
最坏的情况n-1轮过去就ok了,如果有一轮没有交换,则不需要比较了。
package com.test_two; import java.util.Arrays; /** * Created by Administrator on 2017/3/14. */ public class BubbleSort { public static void bubbleSort(DataWrap[] data){ System.out.println("开始排序"); int arrLength = data.length; for(int i = 0; i < arrLength - 1; i++){ boolean flag = false; for(int j = 0; j <arrLength - 1 - i ; j++){ if(data[j].compareTo(data[j + 1]) > 0){ DataWrap tmp = data[j + 1]; data[j + 1] = data[j]; data[j] = tmp; flag = true; } } System.out.println(Arrays.toString(data)); //如果某趟没有发生交换,则表示已经处于有序状态 if(!flag){ break; } } } public static void main(String[] args){ DataWrap[] data = new DataWrap[]{ new DataWrap(9 , ""), new DataWrap(16 , ""), new DataWrap(21 , "*"), new DataWrap(23 , ""), new DataWrap(30 , ""), new DataWrap(49 , ""), new DataWrap(21 , ""), new DataWrap(30 , "*") }; System.out.println("排序前:\n" + Arrays.toString(data)); bubbleSort(data); System.out.println("排序后:\n" +Arrays.toString(data)); } }

冒泡排序而言时间效率是不确定的:最好的情况下,执行一趟冒泡即可,无需做任何交换。最坏的情况下,需要执行n-1趟。
冒泡排序的空间效率很高,只需要一个附加程序单元用于交换,其空间效率为O(1).
冒泡排序是稳定的。
------------------------------------------------------------------------------------------------------------------------------
快速排序:
快速排序是一个速度非常块的交换排序方法,它的基本思路很简单:从待排的数据序列中任取一个数据(如第一个数据)作为分界线,所有比他小的数据一律放在左边,所有比他大的数据一律放在右边。经过这样一趟下来,该序列形成左右两个子序列,左序列中元素的值都比分界值小,右序列中元素值都比分界值大。
接下来对左右两个子序列进行递归,重新选择中心排序并依次规律调整,直到每一个子表元素只剩一个,排序完成。
快速排序的关键在于:
1、找出指定的分界值
2、将所有比分界值小的数据元素放在左边(不要被影响,左边代表的不是开始的分界值的位置左边,而是最后才会知道分界点的文职)
3、将所有比分解值大的数据元素放在右边
两种方式现在:
1、方式如所http://baidu.ku6.com/watch/8495694393207415318.html展示的
2、如图:

代码:
package com.test_two; import java.util.Arrays; /** * Created by Administrator on 2017/3/14. */ public class QuickSort { private static void swap(DataWrap[] data , int i , int j){ DataWrap tmp; tmp = data[i]; data[i] = data[j]; data[j] = tmp; }
//方式2 private static void subSort(DataWrap[] data , int start , int end){ if(start < end){ DataWrap base = data[start]; int i = start; int j = end + 1; while(true){ while(i < end && data[++i].compareTo(base) <= 0); while(j > start && data[--j].compareTo(base) >= 0); if(i < j){ swap(data , i , j); }else{ break; } } swap(data , start , j); subSort(data , start , j - 1); subSort(data , j + 1 , end); } } public static void quickSort(DataWrap[] data){ subSort(data , 0 , data.length - 1); }
//方式1 public static void testQuickSort(DataWrap[] datas , int start , int end){ int base = start , i = start; int j = end; while( true){ if(i >= j){ break; } if(base <= i){ if(datas[base].compareTo(datas[j]) > 0){ swap(datas , base , j); i++; base = j; }else{ j--; } }else{ if(datas[i].compareTo(datas[base]) > 0){ swap(datas , i , base); j--; base = i; }else{ i++; } } } System.out.println(Arrays.toString(datas) + " " + datas[base]); testQuickSort(datas , start , base - 1); testQuickSort(datas , base + 1 , end); } public static void main(String[] args){ DataWrap[] data = new DataWrap[]{ new DataWrap(9 , ""), new DataWrap(-16 , ""), new DataWrap(21 , "*"), new DataWrap(23 , ""), new DataWrap(-30 , ""), new DataWrap(-49 , ""), new DataWrap(21 , ""), new DataWrap(30 , "*"), new DataWrap(13 , "*"), }; // System.out.println("排序之前:\n" + Arrays.toString(data)); subSort(data , 0 , data.length - 1); // System.out.println("排序之后:\n" + Arrays.toString(data)); } }
----------------------------------------------------------------------------------------------------
插入排序
插入排序也是一种常见的排序方法,它主要包括直接插入、Shell排序和折半插入等几种常见的排序方法。
直接插入排序:
思路:一次将待排序的数据元素按其关键字值的大小插入前面的有序序列。
1、从第2个元素开始拿起,一个元素一定有序,和第一个元素比较,然后插入
2、第3个和前面的有序进行比较插入到合适的位置
3、依次执行到最后一个元素。
我脑子不好使,妈蛋。
package com.test_two; import java.util.Arrays; /** * Created by Administrator on 2017/3/14. */ public class InsertSort {
//这是标准做法 public static void insertSort(DataWrap[] data){ System.out.println("开始排序:\n"); int arrLength = data.length; for(int i = 1; i < arrLength - 1; i++){ DataWrap tmp = data[i]; //如果当前元素<前一格元素,说明在前面的有序序列中有一个位置应该属于data[i],且序列要在该位置之后全部后移一段距离 if(data[i].compareTo(data[i - 1]) < 0){ //倒序,这样的话方便推移 int j = i - 1; //如果当前位置比指定元素大,则向后走一位 for( ; j >= 0 && data[j].compareTo(tmp) > 0 ;j--){ data[j + 1] = data[j]; } //这个时候j已经不再是i - 1 了,而是比data[i]小的那个数的下表,所以后一位是i对应元素的存放地址 data[j + 1] = tmp; } } } /** * 我写的插入用了三个循环,应该很容易看出思路 * @param data */ public static void myInsert1(DataWrap[] data){ for(int j = 0; j < data.length; j++){ for(int i = 0; i < j; i++){ if(data[j].compareTo(data[i]) < 0){ DataWrap tmp = data[j]; int z = j -i; while(z > 0){ data[i+z] = data[i+z-1]; z--; } data[i] = tmp; } } } } public static void main(String[] args){ DataWrap[] data = new DataWrap[]{ new DataWrap(21 , ""), new DataWrap(30 , ""), new DataWrap(49 , ""), new DataWrap(30 , "*"), new DataWrap(21 , "*"), new DataWrap(16 , ""), new DataWrap(9 , ""), }; System.out.println("排序之前:\n" + Arrays.toString(data)); myInsert1(data); System.out.println("排序之后:\n" + Arrays.toString(data)); } }
-----------------------------------------------------------------------------------------------------------------------
折半插入排序:
折半插入排序是对直接插入排序的改进。对于简单插入排序而言,当第i-1趟需要将第i个元素插入到前面的0~i-1个元素序列时,它总是从i-1个元素开始,逐步比较每个元素,直到找到它的位置。这个没有利用前面0~i-1元素已经有序的特点,折半插入排序则改进了这一点。
做法:
1、计算0~i-1索引的中间点,也就是用i索引处的元素和(0+i-1)/2索引处的元素进行比较,如果i索引处的元素大,就直接在(0+i-1)/2半个范围内搜索,繁殖,就在0~(0+i-1)/2半个范围内搜索,这就是所谓的折半。
2、在半个范围内搜索时,再按(1)的方法进行折半搜索。总是不断地折半,这样就可以将范围缩小到1/2,1/4,1/8,从而快速确定第i个元素的插入位置
3、确定了第i个元素的插入位置,剩下的事情就简单了。程序将该位置以后的元素整体后移一位,然后将第i个元素放入该位置。
代码:
package com.test_two; import java.util.Arrays; /** * Created by Administrator on 2017/3/14. */ public class BinaryInsertSort { public static void binaryInsertSort(DataWrap[] data){ System.out.println("开始排序:\n"); int arrLength = data.length; for(int i = 1; i < arrLength; i++){ DataWrap tmp = data[i]; int low = 0; int high = i - 1;
//不停折半,找到i应该插入的位置,这里的前提是前面是一个有序序列。所以i从1开始 while(low <= high){ int mid = (low + high)/2; if(tmp.compareTo(data[mid]) > 0){ low = mid + 1; }else{ high = mid - 1; } } for(int j = i; j > low; j--){ data[j] = data[j - 1]; } data[low] = tmp; } } public static void main(String[] args){ DataWrap[] data = new DataWrap[]{ new DataWrap(21 , ""), new DataWrap(30 , ""), new DataWrap(49 , ""), new DataWrap(30 , "*"), new DataWrap(21 , "*"), new DataWrap(16 , ""), new DataWrap(9 , ""), }; System.out.println("排序之前:\n" + Arrays.toString(data)); binaryInsertSort(data); System.out.println("排序之后:\n" + Arrays.toString(data)); } }
------------------------------------------------------------
Shell排序
于直接插入排序而言,当插入排序执行到一半时,待插入左边的所有数据都是已经处于有序状态,直接插入排序将待插值存储在一个临时变量里。然后,从待插值左边第一个数据单元开始,只要该数据单元的值大于待插值,该单元格就右移一格,知道找到第一个小于待插值的数据单元。接下来,将临时变量里的值放入小于待查值的数据单元之后。
上面算法可以发现一个问题:如果一个很小的数据单元唯一很靠右的位置上,为了把这个数据单元移动到左边正确的位置上,中间所有的数据单元都需要向右移动一格。这个步骤对每个数据项打偶执行了近N次复制。插入排序的执行效率是O(N^2)。
Shell排序对直接插入排序进行了简单改进:它通过加大插入排序中元素之间的间隔,并且在这些有间隔的元素中进行插入排序,从而使数据项大跨度地移动。当这些数据项一次排序后,Shell算法减小数据项的间隔再排序,依次进行下去。

一句话描述:
希尔排序是直接插入排序的变体,由多个直接插入排序组成。增量变化。
可以说直接插入排序就是增量为1的希尔排序。
希尔排序比直接插入排序快得多,h比较大的时候,移动得少,移动距离长,效率高,h减小,每一次移动的项增多,但是此时数据项已经接近于他们排序后的最终位置,这对于插入排序更有效率。两种情况的结合使得Shell排序效率这么高
常用的h序列由kunth提出,该序列从1开始,通过以下公式产生:
h = 3 * h+1;
有些地方h取值数列的半,然后一直取半,直到1

代码:
package com.test_two; import java.util.Arrays; /** * Created by Administrator on 2017/3/15. */ public class ShellSort { public static void shellSort(DataWrap[] data){ System.out.println("开始排序"); int arrayLength = data.length; int h = 1; //这里h的值是最后为1值为h * 3 + 1. while(h <= arrayLength/3){ h = h * 3 + 1; } while(h > 0){ System.out.println("====h的值:" + h + "===="); for(int i = h; i < arrayLength; i++){ DataWrap tmp = data[i]; if(data[i].compareTo(data[i - h]) < 0){ int j = i - h; for(; j >= 0 && data[j].compareTo(tmp) > 0; j-=h ){ data[j + h] = data[j]; } data[j + h] = tmp; } } h = (h - 1) / 3; } } public static void main(String[] args){ DataWrap[] data = new DataWrap[]{ new DataWrap(9 , ""), new DataWrap(-16 , ""), new DataWrap(21 , "*"), new DataWrap(23 , ""), new DataWrap(-30 , ""), new DataWrap(-49 , ""), new DataWrap(21 , ""), new DataWrap(30 , "*"), new DataWrap(30 , "") }; System.out.println("排序之前:\n" + Arrays.toString(data)); shellSort(data); System.out.println("排序之后:\n" + Arrays.toString(data)); } }
----------------------------------------------
归并排序
归并排序的基本思想是将两个(或以上)的有序序列合并为一个新的有序序列。
细化来说:归并排序先将长度为n的无序序列看成是n个长度为1的有序子序列,首先做两两合并,得到n/2个长度为2的有序子序列,再做两两合并。。。,不断重复这个过程,最终可以得到一个长度为n的有序序列。
难点在于合并:
1、定义变量i,i从0开始,一次等于A序列中每个元素的索引
2、定义变量j,j从0开始,一次等于B序列中每个元素的索引
3、拿A序列中i处元素和b序列中j索引处的元素进行比较,将比较小的复制到另一个临时数组中
4、如果i索引处的元素小,i++;如果j索引处的元素小j++

我想说一句:其实我并不觉得合并难,思路我也想到了,就是看插入的跳舞视频的那种比较,但是尼玛,书上的代码忒专业了点,都优化过,以至于我看不出写的是什么,擦擦。
代码:
package com.test_two; import java.util.Arrays; /** * Created by Administrator on 2017/3/15. */ public class MergeSort { public static void mergeSort(DataWrap[] data){ sort(data , 0 , data.length - 1); } private static void sort(DataWrap[] data , int left , int right){ if(left < right){ int center = (left + right)/2; sort(data , center + 1 , right); sort(data , left , center); merge(data , left , center , right); } } private static void merge(DataWrap[] data , int left , int center , int right){ DataWrap[] tmpArr = new DataWrap[data.length]; int mid = center + 1; int third = left; int tmp = left; //将两个数组中ij重合处的代码从小到大放入临时数组中,留下来的数据都比临时数据中大,可能留下的是i的数据也可能是j的数据,因为不一定有顺序地一一排序过去 while(left <= center && mid <= right){ if(data[left].compareTo(data[mid]) < 0){ tmpArr[third++] = data[left++]; }else{ tmpArr[third++] = data[mid++]; } } //如果是右边的数据有剩余的话,将剩下的右边数据放入临时数组的对应位置 while(mid <= right){ tmpArr[third++] = data[mid++]; } //反之,如果左边的数据有剩余的话,将左边的数据放入临时数组的对应位置 while(left <= center){ tmpArr[third++] = data[left++]; } //将临时数组的left~right的内容复制到初始数组 while(tmp <= right){ data[tmp] = tmpArr[tmp++]; } tmpArr = null; } public static void main(String[] args){ DataWrap[] data = new DataWrap[]{ new DataWrap(9 , ""), new DataWrap(-16 , ""), new DataWrap(21 , "*"), new DataWrap(23 , ""), new DataWrap(-30 , ""), new DataWrap(-49 , ""), new DataWrap(21 , ""), new DataWrap(30 , "*"), new DataWrap(13 , "*"), }; System.out.println("排序之前:\n" + Arrays.toString(data)); mergeSort(data); System.out.println("排序之后:\n" + Arrays.toString(data)); } }
--------------
桶式排序和基数排序有点懵,不想看了。

浙公网安备 33010602011771号