您的位置:首页 > 理论基础

常见的排序方法

2016-07-08 22:41 363 查看
每天进步一点,珍惜生活,珍惜时间!

每天交工作日志时,都有一种时间真快的赶脚。年纪越大,越发觉得时间真是快啊,每天对着代码,然后不知不觉时间没了。想想都害怕。就像一梦千年一样。好了,不啰嗦了,今天主要学习一下八个排序方法。

排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。

在网上找了两张图片:





这两张图很清晰的表现了排序法的结构。作为程序员,基础的底层知识还是必须知道的。一起详细了解一下吧!

一-插入排序

1:直接插入排序

(1)基本思想:在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。

(2)实例



如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。



2:希尔排序

希尔排序(Shell Sort)是插入排序的一种,是针对直接插入排序算法的改进,是将整个无序列分割成若干小的子序列分别进行插入排序,希尔排序并不稳定。该方法又称缩小增量排序,因DL.Shell于1959年提出而得名。

算法先将要排序的一组数按某个增量d(n/2,n为要排序数的个数)分成若干组,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。

public class ShellSort {
public static <T extends Comparable<? super T>> void shellSort(T[] array, int len) {
int d = len; //选择一个小于n的数,作为分组的依据
while (d > 1) {
d = (d + 1) / 2; //d的增量规律
for (int i = 0; i < len - d; i++) {//for循环交换数据位置
if (array[i + d].compareTo(array[i]) < 0) {
T temp = array[i + d];
array[i + d] = array[i];
array[i] = temp;
}
}
}
}
public static void main(String[] args) {
Integer[] testArray = {23, 25, 12, 42, 35};
ShellSort.shellSort(testArray, 5);
System.out.println("The result is:");
for (Integer item : testArray) {
System.out.print(item);
System.out.print(' ');
}
}
}


二:交换排序

所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。交换排序分为冒泡排序和快速排序。

(3)冒泡基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
package com.study.sort;

public class MaoPao {

/**
* @冒泡排序:比较相邻的两元素,交换位置把小的放前面.这样一路下来,所有数组中最小的就跑前面去了.
*    接下来把剩下的元素再遍历两两对比并交换,又会得到第二小的一直这样重复
*/
public static void main(String[] args) {
int[] data = { 11, 2, 6, 12, 17, 8, 1 };
boolean isChange;
int temp=0;//较小值小标
for (int i = 0; i < data.length-1; i++) {// 外循环控制比较次数
isChange=false;//如果循环后发现标记没有改变,说明已经是有序数组
for (int j = i; j < data.length; j++) {// 内循环控制
if (data[i] > data[j]) {
temp =data[i];
data[i]=data[j];//交换数据,重元素往后排
data[j]=temp;
isChange=true;
}
}
if(!isChange)
break;
}
for(int a:data){
System.out.println(a);
}
}
}


2.快速排序
原理:不断寻找一个序列的中点,然后对中点左右的序列递归的进行排序,直至全部序列排序完成,使用了分治的思想。
要点:递归、分治
实现:
package com.study.sort;

public class QuackSoft {

static void QuickSort(int[] arr, int left, int right){ // 这里只需要数组第一和最后一个下标.
if (left < right){
int i = left;
int j = right;
int tmp = arr[left]; // 就取左边第一个数为基准值.
while (i < j) {// 当i == j时退出循环
while (i < j && arr[j] >= tmp)// 从后向前遍历,碰到小于tmp的值时停止,该值的索引肯定是j
j--;
if (i < j){
arr[i] = arr[j]; // 把小于tmp的值arr[j]放到位置i
i++;
}
while (i < j && arr[i] < tmp)// 从前向后遍历,碰到大于tmp的值停止,该值的索引肯定是i
i++;
if (i < j){
arr[j] = arr[i]; // 把大于tmp的值arr[i]放到位置j
j--;
}
}
arr[i] = tmp; // 当i = j时退出循环,基准传值被交换到位置i
QuickSort(arr, left, i - 1); // 以基准值tmp为界,用同样的方式递归调用tmp左边的部分
QuickSort(arr, i + 1, right); // 递归调用右边的部分
}
}
public static void main(String[] args) {
int[] data={11, 2, 6, 12, 17, 8, 1};
QuickSort(data,0,data.length-1);
for(int a:data){
System.out.println(a);
}
}

}
三:归并排序

(5)归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并
归并过程为:比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素a[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。

归并排序具体工作原理如下(假设序列共有n个元素):
将序列每相邻两个数字进行归并操作(merge),形成floor(n/2)个序列,排序后每个序列包含两个元素
将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素
重复步骤2,直到所有元素排序完毕

package algorithm;
public class MergeSort {
// private static long sum = 0;
/**
 * <pre>
 * 二路归并
 * 原理:将两个有序表合并和一个有序表
 * </pre>
 * 
 * @param a
 * @param s
 * 第一个有序表的起始下标
 * @param m
 * 第二个有序表的起始下标
 * @param t
 * 第二个有序表的结束小标
 * 
 */
private static void merge(int[] a, int s, int m, int t) {
int[] tmp = new int[t - s + 1];
int i = s, j = m, k = 0;
while (i < m && j <= t) {
if (a[i] <= a[j]) {
tmp[k] = a[i];
k++;
i++;
} else {
tmp[k] = a[j];
j++;
k++;
}
}
while (i < m) {
tmp[k] = a[i];
i++;
k++;
}
while (j <= t) {
tmp[k] = a[j];
j++;
k++;
}
System.arraycopy(tmp, 0, a, s, tmp.length);
}
/**
 * 
 * @param a
 * @param s
 * @param len
 * 每次归并的有序集合的长度
 */
public static void mergeSort(int[] a, int s, int len) {
int size = a.length;
int mid = size / (len << 1);
int c = size & ((len << 1) - 1);
// -------归并到只剩一个有序集合的时候结束算法-------//
if (mid == 0)
return;
// ------进行一趟归并排序-------//
for (int i = 0; i < mid; ++i) {
s = i * 2 * len;
merge(a, s, s + len, (len << 1) + s - 1);
}
// -------将剩下的数和倒数一个有序集合归并-------//
if (c != 0)
merge(a, size - c - 2 * len, size - c, size - 1);
// -------递归执行下一趟归并排序------//
mergeSort(a, 0, 2 * len);
}
public static void main(String[] args) {
int[] a = new int[] { 4, 3, 6, 1, 2, 5 };
mergeSort(a, 0, 1);
for (int i = 0; i < a.length; ++i) {
System.out.print(a[i] + " ");
}
}
}


四:基数排序

(6)基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O
(nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。



五:选择排序法

(7)直接选择排序(Straight Select Sorting) 也是一种简单的排序方法,它的基本思想是:第一次从R[0]~R[n-1]中选取最小值,与R[0]交换,第二次从R[1]~R[n-1]中选取最小值,与R[1]交换,....,第i次从R[i-1]~R[n-1]中选取最小值,与R[i-1]交换,.....,第n-1次从R[n-2]~R[n-1]中选取最小值,与R[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列·

实例如下

package com.study.sort;

public class Heapsort {

private static int[] sort = new int[] { 1, 0, 10, 20, 3, 5, 6, 4, 9, 8, 12,
17, 34, 11 };
public static void main(String[] args) {
buildMaxHeapify(sort);
heapSort(sort);
print(sort);
}

private static void buildMaxHeapify(int[] data) {
// 没有子节点的才需要创建最大堆,从最后一个的父节点开始
int startIndex = getParentIndex(data.length - 1);
// 从尾端开始创建最大堆,每次都是正确的堆
for (int i = startIndex; i >= 0; i--) {
maxHeapify(data, data.length, i);
}
}

/**
* 创建最大堆
* @paramheapSize需要创建最大堆的大小,一般在sort的时候用到,因为最多值放在末尾,末尾就不再归入最大堆了
* @paramindex当前需要创建最大堆的位置
*/
private static void maxHeapify(int[] data, int heapSize, int index) {
// 当前点与左右子节点比较
int left = getChildLeftIndex(index);
int right = getChildRightIndex(index);

int largest = index;
if (left < heapSize && data[index] < data[left]) {
largest = left;
}
if (right < heapSize && data[largest] < data[right]) {
largest = right;
}
// 得到最大值后可能需要交换,如果交换了,其子节点可能就不是最大堆了,需要重新调整
if (largest != index) {
int temp = data[index];
data[index] = data[largest];
data[largest] = temp;
maxHeapify(data, heapSize, largest);
}
}

/**
* 排序,最大值放在末尾,data虽然是最大堆,在排序后就成了递增的
*/
private static void heapSort(int[] data) {
// 末尾与头交换,交换后调整最大堆
for (int i = data.length - 1; i > 0; i--) {
int temp = data[0];
data[0] = data[i];
data[i] = temp;
maxHeapify(data, i, 0);
}
}
private static int getParentIndex(int current) {
return (current - 1) >> 1;
}

/**
* 左子节点position注意括号,加法优先级更高
*/
private static int getChildLeftIndex(int current) {
return (current << 1) + 1;
}

/**
* 右子节点position
*/
private static int getChildRightIndex(int current) {
return (current << 1) + 2;
}

private static void print(int[] data) {
int pre = -2;
for (int i = 0; i < data.length; i++) {
if (pre < (int) getLog(i + 1)) {
pre = (int) getLog(i + 1);
System.out.println();
}
System.out.print(data[i] + "|");
}
}

/**
* 以2为底的对数
*/
private static double getLog(double param) {
return Math.log(param) / Math.log(2);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息