排序小结

打算对排序做个总结

一.冒泡,选择 ,插入排序

这三个排序应该是最基本的排序了吧。时间复杂度 都是 O(n^2) 空间复杂度 都是 O(1)冒泡和插入是稳定排序。

而选择排序是不稳定的。

注意 插入排序 有一个特殊的形式 希尔排序(虽然书上都是这么说 ,可是给我的感觉 希尔排序就是希尔排序。它的思想可以用冒牌,选择,插入来实现。不知道为什么 要说 希尔排序是插入排序的一种)。这个的复杂度是 O(n*lgn^2)。 一般情况 对于希尔排序的间隔选择 各类书籍都是建议 3H+1 来计算。总的来说 这3个排序 都是比较常见 也没什么难度。就不细看了。代码如下

1.希尔排序(用插入实现。 插入就是 h取1)

	public void shellSort(double[] data,int h){
		for(int i=0;i<data.length;i+=h){
			double shorData=data[i];
			int index=i-h;
			while(index>=0&&shorData<data[index]){ //从要插入的位置开始 后移一位 为要插入的数组腾出地方直到找到合适的位置 插入
				data[index+h]=data[index];
				index-=h;
			}
			data[index+h]=shorData;
		}
	}

2.冒泡排序

	public void bubbleSort(double[] data){
		for(int i=0;i<data.length-1;i++){
			for(int j=0;j<data.length-i-1;j++){
				double x;
				if(data[j]>data[j+1]){	
					exchange(data, j, j+1);
				}
			}
		}
	}

选择就不写了 一个德行。


下面主要 需要总结一下 稍微复杂一点的排序

二。快速排序

在稍微复杂一点的排序中 快速排序 应该是最常见的了。 快速排序的平均 时间复杂度为 O(n*lgn) 但是如果运气太差 也会变成O(n2) 但是在日常生活中 一般情况下 快速排序是最先考虑的排序。思路就是选择一个值 比这个值小的放在左边,比这个值大的放右边。这下变成2个部分 继续用这个思路递归下去。

随机化快排:
平衡快排
外部快排
三路基数快排

三 。归并排序

归并排序的速度 毋庸置疑。很快。 思想也简单 实现也简单。唯一的缺陷就是 需要 O(N)的辅助空间。
归并排序的思路 通俗点理解 就是 有2个排好序的 队列(递增)。 如何合并成一个有序的队列?  就是看这2个队列的第一个。 哪个小取哪个。 一直到结束。
ok 这样就了解了。 其实就是把一组数据 分成2部分 来排序。 递归调用。(其实这里如果 考虑的数据量超级巨大。递归影响性能的话 可以正序处理。 不过 如果真的那么大的话在乎递归的那点空间的话。 归并排序的空间要求太大了。反而不值了。)
public  double[] doMergeSort(double[] data1,double[] data2)
	{
		if(data1.length>1){
			data1=doMergeSort(Arrays.copyOfRange(data1, 0, data1.length/2), Arrays.copyOfRange(data1, data1.length/2,data1.length));
		}
		if(data2.length>1){
			data2=doMergeSort(Arrays.copyOfRange(data2, 0, data2.length/2), Arrays.copyOfRange(data2, data2.length/2,data2.length));
		}
		double[] temp=new double[data1.length+data2.length];
		int i=0,j=0,iter=0;
		for(;i<data1.length&&j<data2.length;)
		{
			if(data1[i]<=data2[j])
			{
				temp[iter]=data1[i];
				iter++;
				i++;
			}
			else
			{
				temp[iter]=data2[j];
				iter++;
				j++;
			}
		}
		for(;i<data1.length;i++,iter++)
		{
			temp[iter]=data1[i];
		}
		for(;j<data2.length;j++,iter++)
		{
			temp[iter]=data2[j];
		}
		return temp;
	}


四 。堆排序

      堆排序 其实是比较快的排序了。 思路主要是来自堆。 堆有两种 大头堆和小头堆。 就是根节点是不是都比叶节点大。
其实 要想了解堆排序。需要了解一些树的特性。
                  1                                1
       /      \                            
            2         3                            2
          /   \      /     \
       4     5   6      7                        3
     /   \    /\ 
  8     9 10                                     4
1  这是一个 深度为三的 树 
2 在第N层 之前的 总数 最大 不超多  2^N-1 个             s(n)  =  1*2^0+1*2^1+......+1*2^(n)= 1-2^(n)/(1-2)=2^n-1
3 这个是最重要的  一根位置为N的根的 左节点为 2N-1  右节点为 2N   
   证明一下:   假设 这个位置 N 是 第m成 第 a个   可以得出 他的位置 是 a(root)= 2^(m-1)-1+a  而 他的右节点  就是a(r)= 2^m-1+a*2= 2*2^(m-1)-2+1+a*2=2(2^(m-1)-1+a)+1=2a(root)+1
 这样 就可以 在一个数组里面表示 一个堆了 理解了这些 方便 理解堆排序

堆排序的步骤  (假设是一个 为N的数组)
1 。 首先 建立一个大头堆 这个操作 是何为大头堆  就是根节点 比叶节点大。
 假设这个树  除了 1,2,3 这三个节点 都已经满足大头堆 的性质。 那么 比较 1,2,3 将大的值 替换到1 上面去。 如果 是 1,2 交换的。 那么 因为 交换了2 那么2 ,4,5 就不满足 就要继续比较2,4,5 来建立大头堆。 递归调用 直到结尾。
所以对于数组为n的 数组。 从n /2 到1 遍历 调用建立大头堆过程。(从n/2+1 到 n 的节点 是没有叶子的 肯定满足大头堆)
       2.  此时已经是大头堆了。 。那么 第一个数 肯定是 整个数组最大的值。 把他和最后一个数 调换。此时 1,2,3 将不满足大头堆的性质。 调用 建立大头堆的过程(注意 此时 将堆的长度 变成 n-1  来处理。 不需要处理最后一个)。 这个 做N次 交换 并且创建大头堆 就可以了。
public static List<Integer> sort(List<Integer> data){

		data =createBigHeap(data,data.size());
		System.out.println(data);
		for(int i=data.size()-1;i>1;i--){
			gerMaxHeap(data, 1, i+1);
			int da=data.get(0);
			data.set(0, data.get(i));
			data.set(i, da);
			System.out.println(data);
		}
	
		return data;
	}
	
	private static List<Integer> createBigHeap(List<Integer> data,int length){
		for(int i=length>>>1;i>0;i--){
			gerMaxHeap(data, i,length);			
		}
		
		return data;
	}
	
	private static void gerMaxHeap(List<Integer> data, int i,int length){
		
		int nowData=data.get(i-1);
		int lagerIndex=i;
		int l=2*i;
		int r=2*i+1;
		int ldata=0;
		if(l<=length){
			ldata=data.get(l-1);
			if(data.get(lagerIndex-1)<ldata){
				lagerIndex=l;
			}
		}
		int rdata=0;
		if(r<=length){
			rdata=data.get(r-1);
			if(data.get(lagerIndex-1)<rdata){
				lagerIndex=r;
			}
		}
		
		if(lagerIndex!=i){
			data.set(i-1, data.get(lagerIndex-1));
			data.set(lagerIndex-1, nowData);
			gerMaxHeap(data, lagerIndex,length);
		}
		//return data;
	}
堆排序 其实用的不是很多 可能因为比较复杂吧。 他有个很经典的地方 就是 用于进程调度。 相一下。 一堆进程。 需要执行 优先级最高的。 大头堆 很好的描述了这个进程。 第一个就是。 
 对于一个进程管理 主要需要的方法有
   1。加入一个进程。
   2 。提高一个进程的优先级。
   3 。取出优先级最高的进程。
提高一个个线程的优先级   反向调用 建堆 就可以了  时间复杂度 为 O(lgn
        加入线程 就是在堆 的末尾加入一个负无穷大的线程。然后 调用提高一个线程的优先级( 这里注意 书上强调 一定要这么处理,不能直接将值放到末尾。 但是为什么 我不知道!!!!!
       取出最高进程 也简单。拿出第一个就可以了。 将最后一个放到第一个来 然后调用建堆。
         

五 。计数排序

         计数排序 是一个限制很大的排序。 也需要一定的空间辅助。
计数排序的思想 : 对一个  1到 k  之间的数字 进行排序    用一个 数组。a【k】  记录着每个数字 前面的数字个数(包括自己)。 比如 a[5]=3 记录的就是 给定的数组中 比5小的数字的个数为3个(包括自己)。那么 排序之后 的第三位 一定就是5. 
    那么 理解了思想 需要知道 计数排序  对 N个 1到 k之间的数字a[n]排序  需要 一个 输出数组b[n] 一个辅助数组c[k]。

处理步骤:1 初始化 c[k] 设为0
                            2  遍历a[n]  如果值为5  就将 c[5] 的值 加一;
                            3  遍历 c[k]  是 c[i]+=c[i-1];  这样c[i] 记录的就是给定数组 比i小的个数(包含自己)
                            4  继续遍历 a[n] 如果值是 5  那么取出 c[5]的 值  如果是 3  那么 5排序后 就在3的位置  所以 b[3]=5.因为已经找到一个 所以c[5]-- 遍历结束 排序 结束。

如此一来排序完成。 这个排序 最大的障碍就是c[k]  如果k的值太大 需要的辅助数组 太大。
 其实计数排序 本身不常用。 但是 它的思想 却是解决很多问题的方法。
 
	public static List<Integer> countSort(List<Integer> data,int k){
		//data=Utils.getRandomList(10, 10);
		System.out.println(data);
		List<Integer> returnData=new ArrayList<Integer>(data.size());
		List<Integer> auxiliaryData=new ArrayList<Integer>(k+1);
		/
		for(int i=0;i<data.size();i++){
			returnData.add(0);
		}
		for(int i=0;i<=k;i++){
			auxiliaryData.add(0);
		}
		for(int i=0;i<data.size();i++){
			int c=data.get(i);
			auxiliaryData.set(c, auxiliaryData.get(c)+1);
		}
		for(int i=1;i<k;i++){
			auxiliaryData.set(i, auxiliaryData.get(i)+auxiliaryData.get(i-1));
		}
		
		for(int i=0;i<data.size();i++){ //这里 应该从 size 到 1的循环 这样就可以保证 排序是稳定的
			int c=data.get(i);
			int count=auxiliaryData.get(c);
			returnData.set(count-1, c);
			auxiliaryData.set(c,auxiliaryData.get(c)-1);
		}
		System.out.println(returnData);
		return returnData;
	}

六。基数排序

基数排序其实通常 要和计数排序联合使用
       基数排序的原理是  一组数据  从 先排序个位 在排序 十位 一直到第一位。需要注意的是 排序必须是稳定的。这样 用计数排序最好了。举例说明
一组数据        
329 720 720 329
457    355 329 355
657 436 436 436
839 437 839 457
436 657 355 657
720 329 457 720
355 839 657 839
 

七。桶排序      

桶排序就是将将给定数组 划分成了 N份(桶)。然后对每个桶 再进行排序。关键 就是桶的划分。 划分就是通过一种方法将给定的数组映射到对应的桶里面去。 这个过程其实 就为排序做了很多工作。 例子都是 举得很简单的。
比如 10个 0到1的数a[10]。我们定制10个桶 b[10]  遍历a[10] 用 10*a[i]的到值就是这个值将要放到哪个桶里面去  也就是 b[  10*a[i] ] 注意取整。
      当然 这个例子的划分 映射方法很简单 实际过程 可以用跟复杂的方法来处理。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值