排序算法和分析史上最全

  一直都想总结下排序算法,趁今天有空,自己总结一番,本文参考了

http://www.2cto.com/kf/201109/104886.html

http://blog.csdn.net/column/details/aray.html

http://blog.csdn.net/morewindows/article/details/6678165

1:冒泡排序(交换型排序)
     (1)最常见的排序,处理小数据量很不错,大数据量耗费资源过多,时间过长,为啥这么说,请看下面
     (2)思想,对数据进行两次循环,第一层循环,每次找出一个最大的数(或最小的数),第二次循环,相邻的元素交换
    
package com.sort;

import java.util.Random;

public class MaoPaoSort {
	public static void main(String[] arg) {

		int size = 1000;
		int[] a = new int[size];
		Random random = new Random();

		for (int i = 0; i < size; i++) {
			a[i] = random.nextInt(size);
		}

		MaoPaoSort sort = new MaoPaoSort();

		Long start = System.currentTimeMillis();
		sort.sort(a);
		Long end = System.currentTimeMillis();

		for (int j = 0; j < size; j++) {
			System.out.println(a[j]);
		}

		System.out.println("Sorted " + size + " Data , Cost " + (end - start)
				+ " ms");
	}

	public void sort(int[] a) {
		int length = a.length;
		int tmp;
		for (int i = 1; i < length; i++) {
			for (int j = 0; j < length - i; j++) {
				if (a[j] > a[j + 1]) {
					tmp = a[j + 1];
					a[j + 1] = a[j];
					a[j] = tmp;
				}
			}
		}
	}
}
分析: 代码就不过多分析了,值得注意的是,
          (1)sort方法里面, 第一层循环从i=1开始,因为第二次j <  length - i,第二层不需要每次都遍历一遍数组了,因为执行完之后,最后一个数就是最大(或者最小),这都想不通就不适合搞编程了大笑
          (2)main 方法里面,size = 1000,本机执行时间10ms,size = 10000,执行时间179ms,  size = 100000,执行时间17293ms,size= 1000000,执行时间(还未执行完尴尬),其实是1854501 ms,30多分钟,擦
          (3)可以看出,在数据量增加的时候,时间会以几何级数增长,无法应对大数据
          (4)冒泡的时间复杂度度为o(n*n/2) = o(n^2)

2:快速排序(交换型排序)
     目前最流行的排序算法,巧妙的思想,稳定的开销

首先上图:    

 

从图中我们可以看到:

left指针,right指针,base参照数。

其实思想是蛮简单的,就是通过第一遍的遍历(让left和right指针重合)来找到数组的切割点。

第一步:首先我们从数组的left位置取出该数(20)作为基准(base)参照物。

第二步:从数组的right位置向前找,一直找到比(base)小的数,

            如果找到,将此数赋给left位置(也就是将10赋给20),

            此时数组为:10,40,50,10,60,

            left和right指针分别为前后的10。

第三步:从数组的left位置向后找,一直找到比(base)大的数,

             如果找到,将此数赋给right的位置(也就是40赋给10),

             此时数组为:10,40,50,40,60,

             left和right指针分别为前后的40。

第四步:重复“第二,第三“步骤,直到left和right指针重合,

             最后将(base)插入到40的位置,

             此时数组值为: 10,20,50,40,60,至此完成一次排序。

第五步:此时20已经潜入到数组的内部,20的左侧一组数都比20小,20的右侧作为一组数都比20大,

            以20为切入点对左右两边数按照"第一,第二,第三,第四"步骤进行,最终快排大功告成。

public void tim(int[] a, int left, int right) {
		if (left < right) {
			int index = division(a, left, right);
			tim(a, left, index - 1);
			tim(a, index + 1, right);
		}
	}
	
	public int division(int[] a, int left, int right){
		int base = left;
		
		while (left < right) {
			while (left < right && a[right] >= a[base]) {
				right--;
			}
			
			a[left] = a[right];
			
			while (left < right && a[left] <= a[base]) {
				left++;
			}
			
			a[right] = a[left];
		}
		
		a[left] = a[base];
		
		return left;
	}

10000条无序数据的运行时间比冒泡排序减少了一个数量级。


3:直接选择排序(选择排序)
     (1)常见的排序,处理小数据量很不错,同样大数据量耗费资源过多
     (2)思想,对数据进行两次循环,第一层循环,把a[i]当做最小的数,第二层循环,从i往后,如果找到比a[i]还小的,交换数值,并且min_index = j,继续执行第二层循环执行完,那么第一次循环执行一遍即取出了最大值(最小值),跟冒泡排序有些许类似
package com.sort;

import java.util.Random;

public class XuanZeSort {
	public static void main(String[] arg) {

		int size = 1000;
		int[] a = new int[size];
		Random random = new Random();

		for (int i = 0; i < size; i++) {
			a[i] = random.nextInt(size);
		}

		MaoPaoSort sort = new MaoPaoSort();

		Long start = System.currentTimeMillis();
		sort.sort(a);
		Long end = System.currentTimeMillis();

		for (int j = 0; j < size; j++) {
			System.out.println(a[j]);
		}

		System.out.println("Sorted " + size + " Data , Cost " + (end - start)
				+ " ms");
	}

	public void sort(int[] a) {
		int length = a.length;
		int tmp;
		int min_index = 0;
		for (int i = 1; i < length; i++) {
			min_index = i - 1;
			
			for (int j = i; j < length; j++) {
				if(a[j] < a[min_index]){
					tmp = a[min_index];
					a[min_index] = a[j];
					a[j] = tmp;
					min_index = j;
				}
			}
			
		}
	}
}
分析:
          (1)main 方法里面,size = 1000,本机执行时间11ms,size = 10000,执行时间181ms,  size = 100000,执行时间17316ms,size= 1000000,执行时间很长
          (3)可以看出,在数据量增加的时候,时间会以几何级数增长,无法应对大数据,适用于小数据
    (4)选择排序的时间复杂度度为o(n*n/2) = o(n^2)



4:堆排序(选择排序)

要知道堆排序,首先要了解一下二叉树的模型。

下图就是一颗二叉树,具体的情况我后续会分享的。

那么堆排序中有两种情况(看上图理解):

    大根堆:  就是说父节点要比左右孩子都要大。

    小根堆:  就是说父节点要比左右孩子都要小。

 

那么要实现堆排序,必须要做两件事情:

   第一:构建大根堆。

           首先上图:

           

首先这是一个无序的堆,那么我们怎样才能构建大根堆呢?

     第一步: 首先我们发现,这个堆中有2个父节点(2,,3);

     第二步: 比较2这个父节点的两个孩子(4,5),发现5大。

     第三步: 然后将较大的右孩子(5)跟父节点(2)进行交换,至此3的左孩子堆构建完毕,

                 如图:

                         

     第四步: 比较第二个父节点(3)下面的左右孩子(5,1),发现左孩子5大。

     第五步: 然后父节点(3)与左孩子(5)进行交换,注意,交换后,堆可能会遭到破坏,

                 必须按照以上的步骤一,步骤二,步骤三进行重新构造堆。

           

最后构造的堆如下:

                 

 

   第二:输出大根堆。

             至此,我们把大根堆构造出来了,那怎么输出呢?我们做大根堆的目的就是要找出最大值,

         那么我们将堆顶(5)与堆尾(2)进行交换,然后将(5)剔除根堆,由于堆顶现在是(2),

         所以破坏了根堆,必须重新构造,构造完之后又会出现最大值,再次交换和剔除,最后也就是俺们

         要的效果了,

public void heapSort(int[] a){
		if (a == null || a.length == 0) {
			System.out.println("error");
		}
		
		int arrayLength = a.length;
		for (int i = arrayLength / 2 - 1; i >= 0; i--) {
			makeMaxHeap(a, i, arrayLength - 1);
		}
		
		//output
		
	}
	
	public void makeMaxHeap(int[] a, int parent, int length){
		
		int leftChild = 2 * parent + 1;
		int rightChild = leftChild + 1;
		
		if (rightChild <= length) {
			if (a[leftChild] < a[rightChild]) {
				swap(a, leftChild, rightChild);
			}
		}
		
		if (a[leftChild] > a[parent]) {
			swap(a, leftChild, parent);
		}
		
		if (rightChild <= length) {
			if (a[leftChild] < a[rightChild]) {
				swap(a, leftChild, rightChild);
			}
		}
	}
	
	public void swap(int[] a, int left, int right){
		int tmp = a[left];
		a[left] = a[right];
		a[right] = tmp;
	}
堆排序的时间复杂度:O(NlogN)


5:直接插入排序

直接插入排序:

       这种排序其实蛮好理解的,很现实的例子就是俺们斗地主,当我们抓到一手乱牌时,我们就要按照大小梳理扑克,30秒后,

   扑克梳理完毕,4条3,5条s,哇塞......  回忆一下,俺们当时是怎么梳理的。

       最左一张牌是3,第二张牌是5,第三张牌又是3,赶紧插到第一张牌后面去,第四张牌又是3,大喜,赶紧插到第二张后面去,

   第五张牌又是3,狂喜,哈哈,一门炮就这样产生了。

 

     怎么样,生活中处处都是算法,早已经融入我们的生活和血液。

     

     下面就上图说明:

             

      看这张图不知道大家可否理解了,在插入排序中,数组会被划分为两种,“有序数组块”和“无序数组块”,

     

      对的,第一遍的时候从”无序数组块“中提取一个数20作为有序数组块。

              第二遍的时候从”无序数组块“中提取一个数60有序的放到”有序数组块中“,也就是20,60。

              第三遍的时候同理,不同的是发现10比有序数组的值都小,因此20,60位置后移,腾出一个位置让10插入。

                      然后按照这种规律就可以全部插入完毕。

需要注意的是,我们必须从后面开始移位

//O(log n^2)
	public void insertSort(int[] a){
		int j = 0;
		int tmp = 0;
		for (int i = 1; i < a.length; i++) {
			j = i;
			tmp = a[j];
			while (j > 0 && a[j] < tmp) {
				a[j] = a[j - 1];
				j--;
			}
			a[j] = tmp;
		}
	}

分析:
          (1)main 方法里面,如果数据本来有序,那么时间很短,适用于有序数据
          (4)选择排序的时间复杂度度为o(n*n/2) = o(n^2)

希尔排序

        观察一下”插入排序“:其实不难发现她有个缺点:

              如果当数据是”5, 4, 3, 2, 1“的时候,此时我们将“无序块”中的记录插入到“有序块”时,估计俺们要崩盘,

       每次插入都要移动位置,此时插入排序的效率可想而知。

   

      shell根据这个弱点进行了算法改进,融入了一种叫做“缩小增量排序法”的思想,其实也蛮简单的,不过有点注意的就是:

  增量不是乱取,而是有规律可循的。

首先要明确一下增量的取法:

      第一次增量的取法为: d=count/2;

      第二次增量的取法为:  d=(count/2)/2;

      最后一直到: d=1;

看上图观测的现象为:

        d=3时:将40跟50比,因50大,不交换。

                   将20跟30比,因30大,不交换。

                   将80跟60比,因60小,交换。

        d=2时:将40跟60比,不交换,拿60跟30比交换,此时交换后的30又比前面的40小,又要将40和30交换,如上图。

                   将20跟50比,不交换,继续将50跟80比,不交换。

        d=1时:这时就是前面讲的插入排序了,不过此时的序列已经差不多有序了,所以给插入排序带来了很大的性能提高。



//O(log n^1.5)
	public void shellSort(int[] a){
		int range = a.length / 2;
		for (; range > 0; range = range / 2) {
			int j = 0;
			int tmp = 0;
			for (int i = 0; i < a.length / range; i += range) {
				j = i;
				tmp = a[j];
				while (j > 0 && a[j - range] > tmp) {
					a[j] = a[j - range];
					j -= range;
				}
				a[j] = tmp;
			}
		}
	}


归并排序:

       个人感觉,我们能容易看的懂的排序基本上都是O (n^2),比较难看懂的基本上都是N(LogN),所以归并排序也是比较难理解的,尤其是在代码

 编写上,本人就是搞了一下午才搞出来,嘻嘻。

 

首先看图:

归并排序中中两件事情要做:

            第一: “分”,  就是将数组尽可能的分,一直分到原子级别。

            第二: “并”,将原子级别的数两两合并排序,最后产生结果。

代码:


/**
 *  Copyright 2014 By Eclipse
 * 
 *  All right reserved
 *
 *  Created on 2014-2-8 下午1:08:42
 *
 *  By Administrator
 */
package com;

/**
 *  Copyright 2014 By Eclipse
 * 
 *  All right reserved
 *
 *  Created on 2014-2-8 下午1:08:42
 *
 *  By Administrator
 */
public class MergeSort {
         public static void main(String[] args){
        	 int[] a = {4545,5656,9, 8, 7, 6, 5, 4, 3, 2, 1};
        	 int[] tmp = null;
        	 mergeSort(a, tmp, 0, a.length-1);
        	 
        	 for(int i = 0; i< a.length; i++){
        		 System.out.println(a[i]);
        	 }
         }
         
         public static void mergeSort(int[] a, int[] tmpArray, int left, int right){
        	 if(left < right){
        		 int middle = (left + right) / 2;
        		 mergeSort(a, tmpArray, left, middle);
        		 mergeSort(a, tmpArray, middle + 1, right);
        		 merge(a, tmpArray, left, middle + 1, right);
        	 }
         }

		/**
		 * @param a
		 * @param left
		 * @param right
		 */
		private static void merge(int[] a, int[] tmpArray, int left, int middle, int right) {
			tmpArray = new int[right - left + 1];
			
			int leftEnd = middle - 1;
			int rightStart = middle;
			int index = 0;
			
			while(left <= leftEnd && rightStart <= right){
				if(a[left] > a[rightStart]){
					tmpArray[index++] = a[rightStart++];
				}else{
					tmpArray[index++] = a[left++];
				}
			}
			
			while(left <= leftEnd){
				tmpArray[index++] = a[left++];
			}
			
			while(rightStart <= right){
				tmpArray[index++] = a[rightStart++];
			}
			
			for(int i = tmpArray.length - 1; i >= 0 ; i--){
				a[right] = tmpArray[i];
				right--;
			}
		}
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值