java实现八大排序算法详解

排序算法关系如下:

性能比较如下:

一、直接插入排序

插入排序是一种最简单直观的排序算法, 它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

算法步骤:

1)将第一待排序列的第一个元素看成一个有序序列,把第二个元素到最后一个元素看作是未排序序列。

2)从头到尾依次扫描未排序序列(通过外层循环),将扫描到的每个元素插入到有序序列的适当位置(通过内层循环)。(如果待插入元素和有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)

代码实现:

package web;

public class InsertSort {

	public static int[] sort(int[] ins) {
		for(int i = 1;i<ins.length;i++) { //从头到尾依次扫描待排序列(将第一个元素ins[0]看成有序的,所以待排序列从ins[1]开始扫描)
			for(int j = i;j>0 && ins[j]<ins[j-1];j--) {  //操作当前待排元素:将当前待排元素与前面已经排好的有序序列做比较(比较方法是依次与前一个元素比较,若比前一个元素小就交换位置),插入到正确位置
				//if(ins[j]<ins[j-1]) {
					int tmp = ins[j-1];
					ins[j-1] = ins[j];
					ins[j] = tmp;
				//}
			}
		}
		return ins;
	}
	
	//我们可以吧所有的大于需要插入的数先保存,然后进行比较,然后将最后的正确位置空出来。吧之前保存的需要插入的数放到正确位置上;
    public static int[] sort2(int[] ins){
		
		for(int i=1; i<ins.length; i++){
			int temp = ins[i];//保存每次需要插入的那个数
			int j;
			for(j=i; j>0&&ins[j-1]>temp; j--){//这个较上面有一定的优化
				ins[j] = ins[j-1];//吧大于需要插入的数往后移动。最后不大于temp的数就空出来j
			}
			ins[j] = temp;//将需要插入的数放入这个位置
		}
		return ins;
    }

	
	public static void main(String[] args) {
		int[] ins = {2,3,5,1,23,6,78,34};
		
		System.out.print("待排序列:");
		for(int i : ins) {
			System.out.print( i + " ");
		}
		
		System.out.println();
		int[] ins2 = sort(ins);
		System.out.print("有序序列(方法一):");
		for(int i : ins2) {
			System.out.print( i + " ");
		}
		
		System.out.println();
		int[] ins3 = sort(ins);
		System.out.print("有序序列(方法二):");
		for(int i : ins3) {
			System.out.print( i + " ");
		}
	}
}

 

二、希尔排序

希尔排序是插入排序的一种,又称“缩小增量排序”,是直接插入排序算法的一种更高效的改进版本,它的实质是一种分组插入方法。希尔排序是非稳定排序算法。

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减少至1时,整个文件恰好被分成一组,算法便终止。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

1、插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。

2、但是插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。

代码实现:

 

希尔排序不稳定,原因:

由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔排序是不稳定的。

 

代码实现:

package web;

public class ShellSort {

	public static void main(String[] args) {
		int[] ins = {12,34,1,0,56,34,2,45,78,10,89,2,0};
		
		for(int i : ins) {
			System.out.print(i + " ");
		}
		
		System.out.println();
		
		int[] ins2 = sort1(ins);
		
		for(int i : ins2) {
			System.out.print(i + " ");
		}
	}
	

	//先分组,内层用了直接选择排序(不是希尔的思想,但是可以实现)
	private static int[] sort(int[] ins) {

		int a = ins.length/2;
		while(a > 0) {//控制分组的步长
			for(int i = 0;i < a;i++) { //外层控制分组
				for(int j = i;j<ins.length;j=j+a) {//对每一小组中的每个待排序元素进行遍历,执行直接选择排序
					
					int min = ins[j];
					int minflag = j;
					for(int k=j+a;k<ins.length;k=k+a) {// 遍历出最小的来进行保存
						if(ins[k]<min) {
							min = ins[k];
							minflag = k;
						}
					}
					int tmp = ins[j];//将一趟遍历后的最小元素替换到当前待排序元素处
					ins[j]= min;
					ins[minflag] = tmp;
				}
			}
			a/=2;
		}
		return ins;
	}
	
	//希尔先分组,内层用了直接插入排序
	private static int[] sort1(int[] ins) {

		int a = ins.length/2;
		while(a > 0) {//控制分组的步长
			for(int i = 0;i < a;i++) { //外层控制分组
				for(int j = i+a;j<ins.length;j=j+a) {//对当前分组执行直接插入排序
					for(int k = j;k>i;k=k-a) {//前边是排好序的,将当前元素向前找位置插入
						if(ins[k]<ins[k-a]) {
							int tmp = ins[k-a];
							ins[k-a]= ins[k];
							ins[k] = tmp;
						}
					}
					
				}
			}
			a/=2;
		}
		return ins;
	}
}

三、直接选择排序

选择排序也是一种简单直观的排序算法。

算法步骤:

1)首先在未排序序列中找到最小元素,存到排序序列的起始位置。

2)再从剩余未排序序列中寻找最小元素,然后放到已排序序列的末尾。

3)重复第二步,直到所有元素均排序完毕。

算法实现:

package web;

public class SelectSort {

	public static int[] sort(int[] ins) {
		
		for(int i = 0;i < ins.length;i++) { //对数组中每一个未排序序列的初始元素执行选择排序操作
			
			//min 记录当前未排序序列的首元素的值,x记录其下标
			int min = ins[i];
			int x = i;
			
			for(int j = i + 1;j<ins.length;j++) { //对当前未排序序列进行比较遍历,找出最小的值和下标记录下来
				if(ins[j] < min) {
					min = ins[j];
					x = j;
				}
			}
			
	        //将上面遍历过程记录下来的值和下标进行替换,即完成当前这一趟的排序
			int tmp = ins[i];
			ins[i]= min;
			ins[x] = tmp;
			
			//System.out.print("第"+i+"趟  ");
			//for(int m : ins) {
			//	System.out.print( m + " ");
			//}
			//System.out.println();
		}
		return ins;
	}
	
	public static void main(String[] args) {
		int[] ins = {12,34,1,0,56,34,2,45,78,10,89,2,0};
		
		for(int i : ins) {
			System.out.print(i + " ");
		}
		
		System.out.println();
		
		int[] ins2 = sort(ins);
		
		for(int i : ins2) {
			System.out.print(i + " ");
		}
	}
}

直接选择排序不稳定,原因:

选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的。依此类推,直到第n-1个元素,第n个元素不用选择了,因为只剩下他一个最大的元素了。那么在一趟选择,如果一个元素比当前元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。举例如下:5 8 5 2 9,我们知道第一遍选择第一个元素5会和2交换,那么原序列中两个5的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法。

 

四、堆排序

堆排序是利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆的性质:即子节点的键值或索引总是小于(或大于)它的父节点。

堆排序分析:

毫无疑问,排序两个字没必要去死磕,这里的重点在于排序的方式,堆排序,就是以堆的形式去排序,所以了解堆很重要。

那么,什么是堆呢?

这里必须引入一个完全二叉树的概念,然后过渡到堆的概念。

上图就是一个完全二叉树,其特点在于:

1、从作为第一层的根开始,除了最后一层之外,第n层的元素个数都必须是2的n次方;第一层2个元素,第二层4个,第三层8个,以此类推。
2、最后一层的元素,都要紧贴在左边。换句话说,每一行的元素都从最左边开始安放,两个元素之间不能有空闲。

具备了上述两个特点的树,就是一颗完全二叉树。

那么,完全二叉树与堆有什么关系呢?

我们假设有一颗完全二叉树,在满足作为完全二叉树的基础上,对于任意一个拥有父节点的子节点,其数值均不小于父节点的值,这样层层递推,就是跟节点的值最小,这样的树,称为小根堆。

同理,又有一棵完全二叉树,对于任意一个子节点来说,均不大于其父节点的值,如此递推,就是根节点的值是最大的,这样的树称为大根堆。

如上图,左边是大根堆,右边是小根堆。这里需要注意一点,只要求子节点与父节点的关系,两个节点的大小关系与其左右位置没有任何关系。

下面说堆排序:

现在,对于堆排序来说,我们要先做的是,把待排序的一堆无序的数,整理成一个大根堆或者小根堆,下面讨论以大根堆为例子。

给定一个列表array=[16,7,3,20,17,8],对其进行堆排序(使用大根堆)。

步骤一 构造初始堆。将给定的无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。

a.假设给定无序序列结构如下:

b.此时我们从最后一个非叶子节点开始(叶子节点自然不用调整,第一个非叶子节点arr.length/2-1=5/2=1=1,也就是下面的6节点),从左至右,从下至上进行调整。

此处需要注意,我们把6和9交换之后,必须考量9这个节点对于其子节点会不会产生影响呢?因为其是叶子节点,所以不加考虑;但是,一定要熟练这种思维,写代码的时候就比较容易理解为什么会出现一次非常重要的交换了。

c.找到第二个非叶子节点4,由于[4,9,8]中9元素最大,4和9交换。

在真正的代码实现中,这是4和9交换之后,必须考虑9所在的这个节点位置,因为其上的值变了,必须判断其对于它的两个子节点是否造成了影响,实际上就是判断其作为根结点的那棵子树,是否还满足大根堆的原则,每一次交换都必须要循环把自树部分判别清楚。

这时,交换导致了子根[4,5,6]关系混乱,继续调整,[4,5,6]中6最大,交换4和6。

牢记上面说的规则,每次交换都要把改变了的那个节点所在的树重新判定一下,这里就用上了,4和9交换了,变动了的那棵子树就必须重新调整,一直调整到符合大根堆的规则为止。

此时,我们就把一个无序序列构造成了一个大顶堆。

步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

a.将堆顶元素9与末尾元素4进行交换。

这里需要说明一下,所谓的交换,实际上就是把最大值从树里面拿掉了,剩下参与到排序中的树,其实只有总节点的个数减去拿掉的节点的个数了。所以图中用的是虚线。

b.重新调整结构,使其继续满足堆定义

c.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8。

d.后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序。

代码实现:

package com;
import java.util.Arrays;
public class HeapSort {
    public static void main(String []args){
        int []arr = {3,1,4,2,8,5,9,7,6};
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }
    public static void sort(int []arr){
        //1.构建大顶堆
        for(int i=arr.length/2-1;i>=0;i--){
            //从第一个非叶子结点从下至上,从右至左调整结构
            adjustHeap(arr,i,arr.length);
        }
        //2.调整堆结构+交换堆顶元素与末尾元素
        for(int j=arr.length-1;j>0;j--){
            swap(arr,0,j);//将堆顶元素与末尾元素进行交换
            adjustHeap(arr,0,j);//重新对堆进行调整
        }
 
    }
 
    /**
     * 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
     * @param arr
     * @param i
     * @param length
     */
    public static void adjustHeap(int []arr,int i,int length){
        int temp = arr[i];//先取出当前元素i
        for(int k=i*2+1;k<length;k=k*2+1){//从i结点的左子结点开始,也就是2i+1处开始
            if(k+1<length && arr[k]<arr[k+1]){//如果左子结点小于右子结点,k指向右子结点
                k++;
            }
            if(arr[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
                arr[i] = arr[k];
                i = k;
            }else{
                break;
            }
        }
        arr[i] = temp;//将temp值放到最终的位置
    }
 
    public static void swap(int []arr,int a ,int b){
        int temp=arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}

五、冒泡排序

它重复的走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序错误就把他们较缓过来。走访元素的工作是重复的进行直到没有相邻元素需要交换,也就是说该元素序列已经排序完成。

这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

基本思想:

1、比较相邻的元素。如果第一个比第二个大,就交换他们两个。

2、对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。

3、针对所有的元素重复以上的步骤,除了最后一个。

4、持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

package web;

public class maopaoSort {

	public static void main(String[] args) {
		int[] ins = {2,3,5,1,23,6,78,34};
		int[] ins2 = sort(ins);
		for(int in: ins2){
			System.out.print(in + " ");
		}
	}

	private static int[] sort(int[] ins) {

		for(int i = 0; i<ins.length-1;i++) {//ins.length-1是需要比较的回合数
			for(int j =0;j < ins.length-i-1;j++) {
				if(ins[j] > ins[j+1] ) {
					int tmp = ins[j+1];
					ins[j+1] = ins[j];
					ins[j] = tmp;
				}
			}
			
		}
		return ins;
	}
}

六、快速排序

基本思想:

1、先从数列中取出一个数作为基准数。

2、分区过程,将比这个数大的数全都放到它的右边,小于它的数全都放到它左边。

3、再对左右分区重复第二步,直到各区间只有一个数。

理解:挖坑填数+分治法

举例如下:

以一个数组作为示例,取区间第一个数为基准数。

初始时,i = 0;  j = 9;   X = a[i] = 72

由于已经将 a[0] 中的数保存到 X 中,可以理解成在数组 a[0] 上挖了个坑,可以将其它数据填充到这来。

从j开始向前找一个比X小或等于X的数。当j=8,符合条件,将a[8]挖出再填到上一个坑a[0]中。a[0]=a[8]; i++;  这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。这次从i开始向后找一个大于X的数,当i=3,符合条件,将a[3]挖出再填到上一个坑中a[8]=a[3]; j--;

数组变为:

i = 3;   j = 7;   X=72

再重复上面的步骤,先从后向前找,再从前向后找。

从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;

从i开始向后找,当i=5时,由于i==j退出。

此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将X填入a[5]。

数组变为:

可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。

对挖坑填数进行总结:

     1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。

      2.j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。

      3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。

      4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。

代码实现:

package sort;

public class QuickSort {

	public static int[] sort(int[] ins,int start,int end) {
		
		if(start>end) {
			return ins;
		}
		
		int mid = ins[start];
		int low = start;
		int high = end;
		
		while(low < high) {
			
			while(low < high && ins[high] >= mid) {
				high--;
			}
			ins[low] = ins[high];
			
			while(low < high && ins[low] < mid) {
				low ++;
			}
			ins[high] = ins[low];
		}
		
		ins[low] = mid;
		
		sort(ins,start,low-1);
		sort(ins, low+1, end);
		return ins;
	}
	
	public static void main(String[] args) {
		int[] ins = {2,3,5,1,34,88,56,23};
		int[] ins2 = sort(ins, 0, ins.length-1);
		for(int i : ins2) {
			System.out.print(i + " ");
		}
	}
}

七、归并排序

归并排序是建立在归并操作上的一种有效的排序算法,是分治法的一个非常典型的应用。将已有序的自序列合并,得到完全有序的序列;即先使每个自序列有序,再使自序列段间有序,若将两个有序表合并成一个有序表,称为二路归并。

算法描述:

1、申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列。

2、设定两个指针,最初位置分别为两个已经排序序列的起始位置。

3、比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置。

重复步骤3直到某一指针超出序列尾。

将另一序列剩下的所有元素直接复制到合并序列尾部。

稳定性:

归并排序为稳定排序算法(即相等元素的顺序不会改变),速度仅次于快速排序,归并排序的比较次数小于快速排序的比较次数,移动次数一般多于快速排序的移动次数。

代码实现:

package web;

public class MergeSort {

	public static void main(String[] args) {
		int[] ins = {12,34,1,0,56,34,2,45,78,10,89,2,0};
		
		for(int i : ins) {
			System.out.print(i + " ");
		}
		
		System.out.println();
		
		int[] ins2 = sort(ins,0,ins.length-1);
		
		for(int i : ins2) {
			System.out.print(i + " ");
		}
	}

	private static int[] sort(int[] ins,int l,int h) {
		if(l == h) {
			return new int[]{ins[l]};
		}
		
		int mid = l + (h - l) / 2;
		int[] leftArr = sort(ins, l, mid);//左有序数组
		int[] rightArr = sort(ins, mid+1, h);//右有序数组
		int[] newIns = new int[leftArr.length + rightArr.length];//新有序数组
		
		int m = 0, i = 0, j = 0;
		while(i < leftArr.length && j < rightArr.length) {
			newIns[m++] = leftArr[i] < rightArr[j] ? leftArr[i++] : rightArr[j++];
		}
		while(i < leftArr.length) {
			newIns[m++] = leftArr[i++];
		}
		while(j < rightArr.length) {
			newIns[m++] = rightArr[j++];
		}
		
		return newIns;
	}
}

八、基数排序 

基数排序是通过“分配”和“收集”两个过程来实现排序的。

举例如下:

(1)有以下待排序列

309 385 867 183 341 605 385

(2)第一次分配和收集:

首先按照各个数据的个位数字(即按照9,5,7,3,1,5,5)分配到0-9的10个区间内

结果如下:

分配结束后。接下来将所有空间中的数据按照序号由小到大依次重新收集起来,得到如下仍然无序的数据序列:

(3)第二次分配和收集:

 这次按照十位数字进行分配,步骤同上

结果如下:

分配结束后。接下来将所有空间中的数据按照序号由小到大依次再次收集起来,得到如下仍然无序的数据序列:

(4)第三次分配和收集:

 这次按照百位数字进行分配,步骤同上

结果如下:

分配结束后。接下来将所有空间中的数据按照序号由小到大依次再次收集起来;

数据排序完成。

代码实现:

package sort;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

//基数排序
public class RadixSort1 {
	
	public static void main(String[] args) {
		
		int[] a = {250,340,520,25,7,63,55,49,0};
		
		//获取最大的值
		int max = Arrays.stream(a).max().getAsInt();
		
		//获取最大的位数
		int i = (max + "").length();
		
		for(int n = 1; n <= i; n++) {
			List<Integer>[] list = new ArrayList[10];
			
			for(int m = 0; m < 10; m++) {
				list[m] = new ArrayList<>();
			}
			
			for(int num : a) {
				int bit = getBit(num,n);
				list[bit].add(num);
			}
			
			list2array(list, a);
		}
		
		for(int x : a) {
			System.out.print(x +  " ");
		}
	}
	
	//对每个数获取某一位的值
	private static int getBit(int num, int n) {
		return (int)(num/Math.pow(10, n-1)%10);
	}

	//每轮排完之后重新调整a数组
	public static void list2array(List<Integer>[] lists , int[] a) {
		int i = 0;
		for(List<Integer> integers : lists) {
			for(int m = 0;m<integers.size() && i < a.length;m++,i++) {
				a[i] = integers.get(m);
				//i++;
			}
		}
	}
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值