排序

选择排序

思想

    每次都找到数组中最小的那个元素,并依次与第一个,第二个直到最后一个元素交换位置;
    不断地选择剩余元素中的最小者。

codes

public static void selectionSort(int[]a)
	{
		for(int i = 0; i < a.length;i++)
		{
			int min = i;
			for(int j = i+1;j < a.length - 1;j++)
			{
				//找出最小值
				if(a[j] < a[min])
					min = j;
				
				//交换
				int temp = a[min];
				a[min] = a[i];
				a[i] = temp;
					
			}
			
		}
	}

特点

  • 运行时间和输入无关。
  • 数据移动最少。
    N次。

插入排序

思想

    对于循环中的每一个元素a[i],将其与a[0]到a[i-1]中比它小的所有元素依次有序交换。
    索引左侧的元素总是有序的。

codes

public static void InsertionSort(int[] a)
	{
		for(int i = 0; i < a.length;i++)
		{
			for(int j = i; j > 0;j--)
			{
				//判断
				if(a[j] < a[j-1])
				{
					//交换
					int temp = a[j];
					a[j] = a[j-1];
					a[j-1] = temp;
				}
			}
		}
	}

特点

  • 所需时间取决于输入中元素的初始顺序。
  • 对于部分有序的数组很有效,也适合小规模数组。

练习与补充

public static void InsertSort0(int[] a) {
		//2.1.24 插入排序的哨兵 先找出最小元素放在最左边作为哨兵,这样可以去掉内循环的判断条件j > 0.

		int N = a.length;

		boolean isExchanged = false;
		// 找出最小元素
		for (int i = N - 1; i > 0; i--) {

			if (a[i] < a[i-1]) {

				int temp = a[i];
				a[i] = a[i-1];
				a[i-1] = temp;

				isExchanged = true;

			}

		}
		//若没变,则原本就是升序
		if (!isExchanged) {

			return;

		}

		for (int i = 2; i < N; i++) {

			for (int j = i; ; j--) {

				if(a[j] < a[j-1])
				{
					//交换
					int temp = a[j];
					a[j] = a[j-1];
					a[j-1] = temp;
				}

			}

		}

	}
	public static void InsertionSort1(int[] a)
	//2.1.25 不需要交换的插入排序,使当前下标之前的较大元素右移
	//思想:若当前下标元素比它前面的元素大,符合升序规则, 则不需要插入;
	//若比它前面的元素小,则找出该插入的位置,因为有一个位置的空闲,所以较大元素可以右移一位。
	{
		for(int i = 0; i < a.length;i++)
		{
			int temp = a[i];
			int j = i;
			for(; j > 0;j--)
			{
				//判断
				if( temp < a[j-1])
				{
					a[j] = a[j-1];
				}
				a[j] = temp;
			}
		}
	}

希尔排序

介绍

    基于插入排序;
    对于大规模乱序数组,为了加快速度,交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。

思想

    使数组中任意间隔为h的元素都是有序的。
    即h个互相独立的有序数组编织在一起组成最终的有序数组。
    
    使用增量序列1/2(3^k - 1),从N/3开始递减至1

方法

    在对希尔排序理解时,我们倾向于对于每一个分组,逐组进行处理。
    更简单的实现是在h子数组中将每个元素交换到比它大的元素之前去(将比它大的元素向右移动一格)。

codes

public static void shellSort(int[] a)
	{
		int N = a.length;
		int h = 1;
		while(h < N/3 )	h = 3*h + 1;//1 4 13 40 121 364……
		while(h >= 1)
		{
			//将数组变为h有序
			for(int i = h;i < N;i++)
			{
				for(int j = i; j>=h;j-=h)
				{
					if(a[j] < a[j-h])
					{
						//交换
						int temp = a[j];
						a[j] = a[j-1];
						a[j-h] = temp;
					}
				}
				h = h/3;
			}
		}
		
	}


//简洁的希尔排序:在插入排序中加入一个外循环将h按照递增序列递减

特点

  • 希尔排序可以用于大型数组
  • 运行时间达不到平方级别

归并排序

思想

    递归;
        若要将一个数组排序,可以先递归的将它分成两半分别排序,然后将结果归并起来。
    原地归并:
        将两个子数组归并成一个有序的数组并将结果存放在原数组。
    自顶向下的归并排序:
        分治;
        递归归并
    自底向上的归并排序:
        先归并微型数组,再成对归并得到的子数组,直到将整个数组归并到一起。
        即两两归并、四四归并、八八归并……

codes

//自顶向下
public class mergeSort {
	private static int[] aux;//归并所需的辅助数组
	
	public static void sort(int[] a)
	{
		aux = new int[a.length];
		sort(a,0,a.length - 1);
	}
	
	private static void sort(int[] a,int lo, int hi)
	{
		//将数组排序
		if(hi <= lo)	return;
		int mid = lo + (hi - lo)/2;
		sort(a,lo,mid);
		sort(a,mid+1,hi);
		merge(a,lo,mid,hi);
	}
	
	public static void merge(int[] a,int lo, int mid, int hi)
	{
		int i = lo, j = mid + 1;
		for(int k = lo; k <= hi;k++)
			aux[k]  = a[k];
		for(int k = lo;k <= hi;k++)
		{
			if (i > mid)	a[k] = aux[j++];
			else if (j > hi)	a[k] = aux[i++];
			else if (aux[j] < aux[i])	a[k] = aux[j++];
			else	a[k] = aux[i++];
		}
	}
}

调用轨迹

//自底向上
public class mergeSortBU {
	private static int[] aux;
	
	public static void mergeSortBU(int[] a)
	{
		aux = new int[a.length];
		for(int size = 1;size < a.length;size = size+size)
			for(int lo = 0;lo < a.length - size; lo += size+size)
				merge(a,lo,lo+size-1,Math.min(lo+size+size-1, a.length-1));
	}
	
	public static void merge(int[] a,int lo, int mid, int hi)
	{
		int i = lo, j = mid + 1;
		for(int k = lo; k <= hi;k++)
			aux[k]  = a[k];
		for(int k = lo;k <= hi;k++)
		{
			if (i > mid)	a[k] = aux[j++];
			else if (j > hi)	a[k] = aux[i++];
			else if (aux[j] < aux[i])	a[k] = aux[j++];
			else	a[k] = aux[i++];
		}
	}
}

练习与补充

//2.2.10 去掉内循环中检测某半边是否用尽的代码(结果不稳定)
private static void merge(Comparable[] a, int lo, int mid, int hi) { 
   for (int i = lo; i <= mid; i++)
      aux[i] = a[i]; 
   
   for (int j = mid+1; j <= hi; j++)
      aux[j] = a[hi-j+mid+1];
  
   int i = lo, j = hi; 
   for (int k = lo; k <= hi; k++) 
      if (less(aux[j], aux[i])) a[k] = aux[j--];
      else                      a[k] = aux[i++];
} 

快速排序

思想

    分治:将一个数组分为两个子数组,将两部分独立地排序。
    当两个子数组有序时,整个数组也就有序了。
    
    快速排序递归的将子数组排序,先切分,排定一个元素,然后再递归调用将其他位置的元素排序。        

    切分:排定一个元素,使得在该数组中,这个元素之前的元素都不大于它,之后的元素都不小于它。
    切分方法的实现;

codes

public class quickSort {
	public static void sort(int[] a)
	{
		sort(a,0,a.length-1);
	}
	private static void sort(int[] a,int lo, int hi)
	{
		if(hi <= lo) return;
		int j = partition(a,lo,hi);
		sort(a,lo,j-1);
		sort(a,j+1,hi);
	}
	public static int partition(int[] a,int lo,int hi)
	{
		//将数字切分为a[lo……i-1],a[i],a[i+1..hi]
		int i = lo, j = hi+1;
		int v = a[lo];//切分元素
		while(i < j)
		{
			while(a[++i] < v)	if(i == hi) break;
			while(v > a[--j])	if(j == lo) break;
			int temp = a[i];
			a[i] = a[j];
			a[j] = temp;
			
		}
		int temp = a[lo];	//v=a[j]放入正确位置
		a[lo] = a[j];
		a[j] = temp;
		
		return j;
	}
}

改进

  • 前提:多次执行快排或用于大型数组

1.排序小数组时,切换到插入排序

if (hi <= lo) return;

替换为

if (hi <= lo + M)    {Insertion.sort(a,lo,hi);    return;}

2.使用数组的一小部分元素的中位数来切分数组,取样大小为3效果最好。

优先队列

思想

    删除最大元素和插入元素
    使用(二叉)堆实现
        当一棵二叉树的每个结点都大于等于它的两个子结点时,被称为堆有序
        二叉堆是一组能够用堆有序的完全二叉树排序的元素。
        位置k的结点的父结点的位置为k/2下取整,子结点为2k,2k+1
    
  
//插入新元素时加到数组末尾,增加堆的大小并上浮;
//删除最大元素时从数组顶端删除最大的元素,并把最后一个元素放到顶端,减小堆的大小并下沉;

堆排序

思想

    分为两个阶段:
        1.在堆的构造阶段中,将原始数组安排进一个堆中
        2.在下沉排序阶段,从堆中按递减顺序取出所有元素并得到排序结果
    
    从右至左用sink函数构造子堆。

codes

public  static void sort(int[] a)
{
    int N = A.length;
    for(int k = N/2;k >=1;k--)    //构造堆
        sink(a,k,n);
    while(N > 1)    //将最大的元素a[1]和a[n]交换,并修复堆
    {
    exch(a,1,N--);
    sink(a,1,N);
    }
}
posted @ 2020-04-08 16:52  c1utchfan  阅读(156)  评论(0)    收藏  举报