排序算法总结--Java

文章参考:

开发者头条@zhutoulwz

http://student.zjzk.cn/course_ware/data_structure/web/paixu/paixu8.1.1.1.htm

不同的排序算法在不同的场景或应用中会有不同的表现,我们需要对各种排序算法熟练才能将它们应用到实际当中,才能更好地发挥它们的优势,这也是面试经常问到的,以下是常见的几种算法的复杂度和稳定性以及代码示例:

复杂度和稳定性


常见排序代码示例

冒泡排序

是基于比较的排序算法,时间复杂度为O(n^2),其优点是实现简单,n较小时性能较好。
  • 算法原理
相邻的数据进行两两比较,小数放在前面,大数放在后面,这样一趟下来,最小的数就被排在了第一位,第二趟也是如此,如此类推,直到所有的数据排序完成
  • 代码实现
private int[] resultArr;
	
	/**
	 * 从小到大
	 * 冒泡排序--它是基于比较的排序算法,时间复杂度为O(n^2),其优点是实现简单,n较小时性能较好。
	 * <h3>算法原理</h3>
	         相邻的数据进行两两比较,小数放在前面,大数放在后面,这样一趟下来,最小的数就被排在了第一位,第二趟也是如此,如此类推,直到所有的数据排序完成
	 * @param int[] arr
	 * @return int[] resultArr
	 * */
	public int[] bubbleSort(int[] arr) {
		int len = arr.length;
		int MAX = 0;
		resultArr = arr;
		for (int i = 0; i < len-1; i++) {
			for (int j = len-1; j > 0; j--) {
				if (resultArr[j] < resultArr[j-1]) {
					MAX = resultArr[j-1];
					resultArr[j-1] = resultArr[j];
					resultArr[j] = MAX;
				}
				
			}
		}
		return resultArr;
	}

选择排序

是一种简单直观的排序算法, 选择排序是不稳定的排序方法(比如序列[5, 5, 3]第一次就将第一个[5]与[3]交换,导致第一个5挪动到第二个5后面),选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。
  • 算法原理
 先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
  • 代码实现
private int[] resultArr;
	/**
	 * 从小到大
	 * 选择排序--是一种简单直观的排序算法, 选择排序是不稳定的排序方法(比如序列[5, 5, 3]第一次就将第一个[5]与[3]交换,导致第一个5挪动到第二个5后面)。
	 * <h3>算法原理</h3>
	         先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
	 * @param int[] arr
	 * @return int[] resultArr
	 * */
	public int[] selectSort(int[] arr) {
		int len = arr.length;
		resultArr = arr;
		for (int i = 0; i < len; i++) {
			int index = i;
			for (int j = i+1; j < len; j++) {
				if (resultArr[j] < resultArr[index]) {
					index = j;
				}
				
				if (index != i) {
					int temp = resultArr[i];
					resultArr[i] = resultArr[index];
					resultArr[index] = temp;
				}
			}
		}
		return resultArr;
	}

插入排序

直接插入排序

每一步都将一个待排数据按其大小插入到已经排序的数据中的适当位置,直到全部插入完毕。 时间复杂度为O(n^2),是一种稳定的排序算法。
  • 算法原理
将数据分为两部分,有序部分与无序部分,一开始有序部分包含第1个元素,依次将无序的元素插入到有序部分,直到所有元素有序.
  • 代码实现
private int[] resultArr;
	/**
	 * 从小到大
	 * 插入排序(直接插入排序)--每一步都将一个待排数据按其大小插入到已经排序的数据中的适当位置,直到全部插入完毕。 时间复杂度为O(n^2),是一种稳定的排序算法。
	 * <h3>算法原理</h3>
	         将数据分为两部分,有序部分与无序部分,一开始有序部分包含第1个元素,依次将无序的元素插入到有序部分,直到所有元素有序.
	 * @param int[] arr
	 * @return int[] resultArr
	 * */
	public int[] insertSort(int[] arr) {
		int len = arr.length;
		resultArr = arr;
		for (int i = 1; i < len; i++) { 
			int j = i-1; 
			int k = arr[i]; 
			while (j > -1 && k < arr[j]) {
				arr[j+1] = arr[j];
				j--;
	        }
			arr[j+1] = k;
	      }
		return resultArr;
	}

二分插入排序

二分插入排序是稳定的与二分查找的复杂度相同,也属于稳定的算法;最好的情况是当插入的位置刚好是二分位置,所用时间为O(n);最坏的情况是当插入的位置不在二分位置 所需比较次数为 (n^2)/2;平均时间复杂度为:O(n^2)
  • 算法原理
其实也属于插入法类型,分已排序和未排序部分.然后将未排序部分元素逐个排序插入, 但是插入的过程不同,需要每次求一个中间位置,和中间位置元素比较大小,然后根据大小情况,
将高位左移或者将低位右移,再求中间元素比较,直到找到合适位置后,将其后已排序元素全部后移一位,再插入该匀速即可。
  • 代码实现
private int[] resultArr;
	
	/**
	 * 从小到大
	 * 插入排序(二分插入排序)--二分插入排序是稳定的与二分查找的复杂度相同;
	 * 最好的情况是当插入的位置刚好是二分位置 所用时间为O(n);
	 * 最坏的情况是当插入的位置不在二分位置 所需比较次数为 n^2/2;时间复杂度为:O(n^2)
	 * <h3>算法原理</h3>
	         其实也属于插入法类型,分已排序和未排序部分.然后将未排序部分元素逐个排序插入,
	         但是插入的过程不同,需要每次求一个中间位置,和中间位置元素比较大小,然后根据大小情况,
	         将高位左移或者将低位右移,再求中间元素比较,直到找到合适位置后,将其后已排序元素全部后移一位,再插入该匀速即可。
	 * @param int[] arr
	 * @return int[] resultArr
	 * */
	public int[] binarySort(int[] arr) {
		int len = arr.length;
		resultArr = arr;
		for (int i = 0; i < len; i++) {
			int temp = resultArr[i];
			int left = 0;
			int right = i - 1;
			int mid = 0;
			while (left <= right) {
				mid = (left + right) >> 1;
				if (temp < resultArr[mid]) {
					right = mid - 1;
				} else {
					left = mid + 1;
				}
			}
			for (int j = i - 1; j >= left; j--) {
				resultArr[j + 1] = resultArr[j];
			}
			if (left != i) {
				resultArr[left] = temp;
			}
		}
		return resultArr;
	}

希尔插入排序

实质是分组插入排序,也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。Shell排序的执行时间依赖于增量序列。
好的增量序列的共同特征:
  ① 最后一个增量必须为1;
  ② 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。
希尔排序的时间性能优于直接插入排序的原因:
  ①当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。
  ②当n值较小时,n和n2的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(n2)差别不大。
  ③在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。
 因此,希尔排序在效率上较直接插人排序有较大的改进。
  • 算法原理
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中。先在各组内进行直接插人排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
  • 代码实现
private int[] resultArr;
/**
	 * 从小到大
	 * 插入排序(希尔排序)--是一种是插入排序的一种类型,也可以用一个形象的叫法缩小增量法。降低了直接插入排序的移动次数,是一种不稳定的排序,时间复杂度O(nlogn).
	 * 基本思想就是把一个数组分为好几个数组,有点像分治法,不过这里的划分是用一个常量increment(增量)来控制。
	 * <h3>算法原理</h3>
	         算法先将要排序的一组数按某个增量increment(n/2,n为要排序数的个数)分成若干组,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(increment/2)对它进行分组,
	         在每组中再进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。
	 * @param int[] arr
	 * @return int[] resultArr
	 * */
	public int[] shellSort(int[] arr) {
		int len = arr.length;
		resultArr = arr;
		int j = 0;  
        int temp = 0;  
        for (int increment = len / 2; increment > 0; increment /= 2) {  
            for (int i = increment; i < len; i++) {  
                temp = resultArr[i];  
                for (j = i; j >= increment; j -= increment) {  
                    if(temp > resultArr[j - increment]){  
                    	resultArr[j] = resultArr[j - increment];  
                    }else{  
                        break;  
                    }  
                }   
                resultArr[j] = temp;  
            }  
        }  
		return resultArr;
	}
	
	/**
	 * 希尔排序方法2
	 * */
	public int[] shellSort2(int[] arr){
		int len = arr.length;
		resultArr = arr;
		int j, gap;
		for (gap = len / 2; gap > 0; gap /= 2)
			for (j = gap; j < len; j++)//从数组第gap个元素开始
				if (resultArr[j] < resultArr[j - gap]){//每个元素与自己组内的数据进行直接插入排序
					int temp = resultArr[j];
					int k = j - gap;
					while (k >= 0 && resultArr[k] > temp){
						resultArr[k+gap] = resultArr[k];
						k -= gap;
					}
					resultArr[k + gap] = temp;
				}
		return resultArr;
	}
	
	/**
	 * 希尔排序方法3
	 * */
	public int[] shellSort3(int[] arr){
		int len = arr.length;
		resultArr = arr;
		int i, j, gap;
		for (gap = len/2; gap>0; gap/=2)
			for (i = gap; i < len; i++)
				for (j = i-gap; j>=0 && resultArr[j] > resultArr[j+gap]; j -= gap){
					int temp = resultArr[j];
					resultArr[j] = resultArr[j+gap];
					resultArr[j+gap] = temp;
				}
		return resultArr;
	}

快速排序

是目前在实践中非常高效的一种排序算法,它不是稳定的排序算法,平均时间复杂度为O(nlogn),最差情况下复杂度为O(n^2)。
  • 算法原理
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序, 整个排序过程可以递归进行,以此达到整个数据变成有序序列。
  • 代码实现
private int[] resultArr;
	/**
	 * 从小到大
	 * 快速排序 --是目前在实践中非常高效的一种排序算法,它不是稳定的排序算法,平均时间复杂度为O(nlogn),最差情况下复杂度为O(n^2)。
	 * <h3>算法原理</h3>
	         通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,
	         整个排序过程可以递归进行,以此达到整个数据变成有序序列。
	 * @param int[] arr
	 * @return int[] resultArr
	 * */
        public int[] quickSort(int[] arr) {
		_quickSort(arr, 0 , arr.length - 1);
		return resultArr;
	}
	public void _quickSort(int[] arr, int left, int right) {
		resultArr = arr;
		if (left < right) {
			int i = left, j = right, target = resultArr[left];
			while (i < j) {
				while (i < j && resultArr[j] > target)
					j--;
				if (i < j)
					resultArr[i++] = arr[j];
				while (i < j && resultArr[i] < target)
					i++;
				if (i < j)
					resultArr[j] = resultArr[i];
			}
			resultArr[i] = target;
			_quickSort(resultArr, left, i - 1);
			_quickSort(resultArr, i + 1, right);
		}
	}

归并排序

归并排序是稳定的排序算法,其时间复杂度为O(nlogn),如果是使用链表的实现的话,空间复杂度可以达到O(1),但如果是使用数组来存储数据的话,在归并的过程中,需要临时空间来存储归并好的数据,所以空间复杂度为O(n)。
  • 算法原理
归并排序具体工作原理如下(假设序列共有n个元素):
1.将序列每相邻两个数字进行归并操作(merge),形成floor(n/2)个序列,排序后每个序列包含两个元素。
2.将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素。
3.重复步骤2,直到所有元素排序完毕。
  • 代码实现
private int[] resultArr;
	/**
	 * 从小到大
	 * 归并排序 --是稳定的排序算法,其时间复杂度为O(nlogn),如果是使用链表的实现的话,空间复杂度可以达到O(1),
	 * 但如果是使用数组来存储数据的话,在归并的过程中,需要临时空间来存储归并好的数据,所以空间复杂度为O(n)。
	 * <h3>算法原理</h3>
	         归并排序具体工作原理如下(假设序列共有n个元素):
		1.将序列每相邻两个数字进行归并操作(merge),形成floor(n/2)个序列,排序后每个序列包含两个元素
		2.将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素
		3.重复步骤2,直到所有元素排序完毕
	 * @param int[] arr
	 * @return int[] resultArr
	 * */
	public int[] mergeSort(int[] arr) {
		_mergeSort(arr, 0, arr.length - 1);
		return resultArr;
	}

	private void _mergeSort(int[] arr, int left, int right) {
		if (left >= right)
			return;
		// 找出中间索引
		int center = (left + right) / 2;// 对左边数组进行递归
		_mergeSort(arr, left, center);// 对右边数组进行递归
		_mergeSort(arr, center + 1, right);
		// 合并
		merge(arr, left, center, right);
		resultArr = arr;
	}

	/**
	 * 将两个数组进行归并,归并前面2个数组已有序,归并后依然有序
	 * 
	 * @param data
	 *            数组对象
	 * @param left
	 *            左数组的第一个元素的索引
	 * @param center
	 *            左数组的最后一个元素的索引,center+1是右数组第一个元素的索引
	 * @param right
	 *            右数组最后一个元素的索引
	 */
	private void merge(int[] arr, int left, int center, int right) {
		int[] tmpArr = new int[arr.length];// 临时数组
		int mid = center + 1;// 右数组第一个元素索引
		int third = left;// third 记录临时数组的索引
		int tmp = left;// 缓存左数组第一个元素的索引
		while (left <= center && mid <= right) {
			// 从两个数组中取出最小的放入临时数组
			if (arr[left] <= arr[mid]) {
				tmpArr[third++] = arr[left++];
			} else {
				tmpArr[third++] = arr[mid++];
			}
		}
		// 剩余部分依次放入临时数组(实际上两个while只会执行其中一个)
		while (mid <= right) {
			tmpArr[third++] = arr[mid++];
		}
		while (left <= center) {
			tmpArr[third++] = arr[left++];
		}
		// 将临时数组中的内容拷贝回原数组中
		// (原left-right范围的内容被复制回原数组)
		while (tmp <= right) {
			arr[tmp] = tmpArr[tmp++];
		}
	}

堆排序

堆排序的时间复杂度为O(nlogn)。是一树形选择排序。
  堆排序的特点是:在排序过程中,将R[l..n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系【参见二叉树的顺序存储结构】,在当前无序区中选择关键字最大(或最小)的记录。
    和直接排序的区别:直接选择排序中,为了从R[1..n]中选出关键字最小的记录,必须进行n-1次比较,然后在R[2..n]中选出关键字最小的记录,又需要做n-2次比较。事实上,后面的n-2次比较中,有许多比较可能在前面的n-1次比较中已经做过,但由于前一趟排序时未保留这些比较结果,所以后一趟排序时又重复执行了这些比较操作。堆排序可通过树形结构保存部分比较结果,可减少比较次数。
    堆的存储
二叉堆是完全二叉树或者近似完全二叉树,满足两个特性
  • 父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值
  • 每个结点的左子树和右子树都是一个二叉堆
当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆。一般二叉树简称为堆。一般都是数组来存储堆,i结点的父结点下标就为(i – 1) / 2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如第0个结点左右子结点下标分别为1和2。
  • 算法原理(以最大堆为例)
  • 先将初始数据R[1..n]建成一个最大堆,此堆为初始的无序区。
  • 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key。
  • 由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。
  • 重复2、3步骤,直到无序区只有一个元素为止。
  • 代码实现
private int[] resultArr;
	
	/**
	 * 从小到大
	 * 堆排序 --堆排序的时间复杂度为O(nlogn)。是一树形选择排序。
	 * <h3>算法原理</h3>
		1.先将初始数据R[1..n]建成一个最大堆,此堆为初始的无序区。
		2.再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key。
		3.由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。
		4.重复2、3步骤,直到无序区只有一个元素为止。
	 * @param int[] arr
	 * @return int[] resultArr
	 * */
	public int[] heapSort(int[] arr) {
		for (int i = 0; i < arr.length; i++) {
			createMaxdHeap(arr, arr.length - 1 - i);
			swap(arr, 0, arr.length - 1 - i);
		}
		resultArr = arr;
		return resultArr;
	}
	private void swap(int[] data, int i, int j) {
		if (i == j) {
			return;
		}
		data[i] = data[i] + data[j];
		data[j] = data[i] - data[j];
		data[i] = data[i] - data[j];
	}

	private void createMaxdHeap(int[] data, int lastIndex) {
		for (int i = (lastIndex - 1) / 2; i >= 0; i--) {
			// 保存当前正在判断的节点
			int k = i;
			// 若当前节点的子节点存在
			while (2 * k + 1 <= lastIndex) {
				// biggerIndex总是记录较大节点的值,先赋值为当前判断节点的左子节点
				int biggerIndex = 2 * k + 1;
				if (biggerIndex < lastIndex) {
					// 若右子节点存在,否则此时biggerIndex应该等于 lastIndex
					if (data[biggerIndex] < data[biggerIndex + 1]) {
						// 若右子节点值比左子节点值大,则biggerIndex记录的是右子节点的值
						biggerIndex++;
					}
				}
				if (data[k] < data[biggerIndex]) {
					// 若当前节点值比子节点最大值小,则交换2者得值,交换后将biggerIndex值赋值给k
					swap(data, k, biggerIndex);
					k = biggerIndex;
				} else {
					break;
				}
			}
		}
	}

基数排序

基数排序又被称为桶排序,基数排序首先将待排序数据元素依次“分配”到不同的桶里,然后再把各桶中的数据元素“收集”到一起。 通过使用对多关键字进行排序的这种“分配”和“收集”的方法, 基数排序实现了对多关键字进行排序。

  • 算法原理
设待排序的数据元素关键字是m位d进制整数(不足m位的关键字在高位补0),设置d个桶,令其编号分别为0,1,2.... d-1
    首先,按关键字最低位的数值一次把各数据元素放到相应的桶中
    然后,按照桶号从小到大和进入桶中数据元素的先后次序收集分配在各桶中的数据元素,
这样就形成了数据元素集合的一个新的排列,此为一次基数排序。重复m次,就得到了排好序的数据元素序列。

例:
每张扑克牌有两个“关键字”:花色和面值。其大小顺序为:
花色:§<¨<©<ª
面值:2<3<……<K<A
扑克牌的大小先根据花色比较,花色大的牌比花色小的牌大;花色一样的牌再根据面值比较大小。所以,将扑克牌按从小到大的次序排列,可得到以下序列:
§2,…,§A,¨2,…,¨A,©2,…,©A,ª2,…,ªA
这种排序相当于有两个关键字的排序,一般有两种方法实现。
其一:可以先按花色分成四堆(每一堆牌具有相同的花色),然后在每一堆牌里再按面值从小到大的次序排序,最后把已排好序的四堆牌按花色从小到大次序叠放在一起就得到排序的结果。
其二:可以先按面值排序分成十三堆(每一堆牌具有相同的面值),然后将这十三堆牌按面值从小到大的顺序叠放在一起,再把整副牌按顺序根据花色再分成四堆(每一堆牌已按面值从小到大的顺序有序),最后将这四堆牌按花色从小到大合在一起就得到排序的结果。
  • 代码实现

private int[] resultArr;
	/**
	 * 从小到大
	 * 基数排序 --基数排序又被称为桶排序,基数排序首先将待排序数据元素依次“分配”到不同的桶里,
	 * 然后再把各桶中的数据元素“收集”到一起。 通过使用对多关键字进行排序的这种“分配”和“收集”的方法,
	 * 基数排序实现了对多关键字进行排序。
	 * <h3>算法原理</h3>
	 * 设待排序的数据元素关键字是m位d进制整数(不足m位的关键字在高位补0)
	 * 设置d个桶,令其编号分别为0,1,2.... d-1
	 *    首先,按关键字最低位的数值一次把各数据元素放到相应的桶中
	 *    然后,按照桶号从小到大和进入桶中数据元素的先后次序收集分配在各桶中的数据元素,
	 * 这样就形成了数据元素集合的一个新的排列,此为一次基数排序。重复m次,就得到了排好序
	 * 的数据元素序列
	 * 
		最高位优先(Most Significant Digit first)法,简称MSD法:
			先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,
			之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。 
  		最低位优先(Least Significant Digit first)法,简称LSD法:
			先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列
	 * @param int[] arr
	 * @return int[] resultArr
	 * */
	public int[] radixSort(int[] arr) {
		int d = getDistance(arr);
		_radixSort(arr, 10, d);
		return resultArr;
	}
	
	private int getDistance(int[] array) {
		int distance = 0;
		int num = getMax(array);
		if (num == 0) {
			return 0;
		}
		while (num/10 != 0) {
			++distance;
			num /= 10;
		}
		return distance + 1;
	}
	
	private int getMax(int[] array) {
		int max = array[0];
		for (int i = 1; i < array.length; i++) {
			if (max < array[i]) {
				max = array[i];
			}
		}
		return max;
	}

	private void _radixSort(int[] array,int radix, int distance) {
		//array 为待排序数组
		//radix 代表基数
		//distance 代表排序元素的位数
		
		int length = array.length;
		int[] temp = new int[length];//用于暂存元素 
		int[] count = new int[radix];//用于计数排序,存储0~9
		int divide = 1;
		
		for (int i = 0; i < distance; i++) {

			System.arraycopy(array, 0, temp, 0, length);
			Arrays.fill(count, 0);
			for (int j = 0; j < length; j++) {
				int tempKey = (temp[j] / divide) % radix;
				count[tempKey]++;
			}

			for (int j = 1; j < radix; j++) {
				count[j] = count[j] + count[j - 1];
			}

			for (int j = length - 1; j >= 0; j--) {
				int tempKey = (temp[j] / divide) % radix;
				count[tempKey]--;
				array[count[tempKey]] = temp[j];
			}
			divide *= radix;
		}
		resultArr = array;
	}

计数排序

计数排序就是计算相同key的元素各有多少个,然后根据出现的次数累加而获得最终的位置信息。 但是计数排序有两个限制条件,那就是存在一个正整数K,使得数组里面的所有元素的key值都不大于N,且key值都是非负整数。

  • 算法原理
建一个长度为K+1的的数组C,里面的每一个元素初始都置为0(Java里面默认就是0)。
遍历待排序的数组,计算其中的每一个元素出现的次数,比如一个key为i的元素出现了3次,那么C[i]=3。
累加C数组,获得元素的排位,从0开始遍历C, C[i+1]=C[i]+C[i-1]
建一个临时数组T,长度与待排序数组一样。从数组末尾遍历待排序数组,把元素都安排到T里面,直接从C里面就可以得到元素的具体位置, 不过记得每处理过一个元素之后都要把C里面对应位置的计数减1。
  • 代码实现
private int[] resultArr;
	
	/**
	 * 从小到大
	 * 计数排序 --计数排序就是计算相同key的元素各有多少个,然后根据出现的次数累加而获得最终的位置信息。
	 * 但是计数排序有两个限制条件,那就是存在一个正整数K,使得数组里面的所有元素的key值都不大于N,且key值都是非负整数。
	 * <h3>算法原理</h3>
	 * 计数排序算法步骤:
		建一个长度为K+1的的数组C,里面的每一个元素初始都置为0(Java里面默认就是0)。
		遍历待排序的数组,计算其中的每一个元素出现的次数,比如一个key为i的元素出现了3次,那么C[i]=3。
		累加C数组,获得元素的排位,从0开始遍历C, C[i+1]=C[i]+C[i-1]
		建一个临时数组T,长度与待排序数组一样。从数组末尾遍历待排序数组,把元素都安排到T里面,直接从C里面就可以得到元素的具体位置, 不过记得每处理过一个元素之后都要把C里面对应位置的计数减1。
	 * @param int[] arr
	 * @return int[] resultArr
	 * */
	public int[] countSort(int[] arr) {
		_countSort(arr, getMax(arr));
		return resultArr;
	}
	
	private int getMax(int[] array) {
		int max = array[0];
		for (int i = 1; i < array.length; i++) {
			if (max < array[i]) {
				max = array[i];
			}
		}
		return max;
	}
	
	private void _countSort(int[] array, int range) {  //range 最大元素
        if (array.length <= 1) {  
            return;  
        }  

        int[] countArray = new int[range + 1];  
        for (int i = 0; i < array.length; i++) {  
            int value = array[i];  
            if (value < 0 || value > range) {  
                return;
            }  
            countArray[value] += 1;  
        }  

        for (int i = 1; i < countArray.length; i++) {  
            countArray[i] += countArray[i - 1];  
        }  

        int[] temp = new int[array.length];  
        for (int i = array.length - 1; i >= 0; i--) {  
            int value = array[i];  
            int position = countArray[value] - 1;  

            temp[position] = value;  
            countArray[value] -= 1;  
        }  

        for (int i = 0; i < array.length; i++) {  
            array[i] = temp[i];  
        }  
        
        resultArr = array;
    }

总结:

一、稳定性:
  稳定:冒泡排序、插入排序、归并排序和基数排序
  不稳定:选择排序、快速排序、希尔排序、堆排序
二、平均时间复杂度
  O(n^2):直接插入排序,简单选择排序,冒泡排序。
  在数据规模较小时(9W内),直接插入排序,简单选择排序差不多。当数据较大时,冒泡排序算法的时间代价最高。性能为O(n^2)的算法基本上是相邻元素进行比较,基本上都是稳定的。
  O(nlogn):快速排序,归并排序,希尔排序,堆排序。
  其中,快排是最好的, 其次是归并和希尔,堆排序在数据量很大时效果明显。
三、排序算法的选择
  1.数据规模较小
  (1)待排序列基本序的情况下,可以选择直接插入排序;
  (2)对稳定性不作要求宜用简单选择排序,对稳定性有要求宜用插入或冒泡
  2.数据规模不是很大
  (1)完全可以用内存空间,序列杂乱无序,对稳定性没有要求,快速排序,此时要付出log(N)的额外空间。
  (2)序列本身可能有序,对稳定性有要求,空间允许下,宜用归并排序
  3.数据规模很大
  (1)对稳定性有求,则可考虑归并排序。
  (2)对稳定性没要求,宜用堆排序
  4.序列初始基本有序(正序),宜用直接插入,冒泡
——lovey Hy.


你可能感兴趣的:(排序算法总结--Java)