1 经典算法对比
1.1 算法分类
1.2 算法复杂度
1.3 相关概念
(1)稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
(2)不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
(3)时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
(4)空间复杂度:是指算法在计算机
1.4 参考链接
2 初级排序
2.1 选择排序(了解)
/**
* 选择排序
* 思想:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置;
* 然后,再从剩余未排序元素中继续寻找最小(大)元素;最后放到 前面已排序数组的 末尾。
*
* 时间复杂度:O(n^2),这里n是数组的长度;
* 空间复杂度:O(1),使用到常数个临时变量。
*
* @param array
* @return
*/
fun selectSort(array: IntArray) : IntArray {
if (array.size <= 1) return array
// i = 1 逐步递增,是指 前面已排序数组的 最后一个位置
for (i in array.indices) {
var minIndex = i
// 从下一个位置后,找到最小数的下标
for (j in i + 1 until array.size) {
// 将最小数的索引保存
if (array[j] < array[minIndex]) minIndex = j
}
// 最后交换元素
val temp = array[i]
array[i] = array[minIndex]
array[minIndex] = temp
}
return array
}
2.2 插入排序(了解)
/**
* 插入排序
*
* 思想:将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素, 就是数组的第一个元素。
* 核心思想是:从1到n,取未排序区间中的第一个元素,在已排序区间中找到合适的插入位置将其插入,
* 并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。
*
* 时间复杂度:O(n^2),这里n是数组的长度;
* 空间复杂度:O(1),使用到常数个临时变量。
*
* @param array
* @return
*/
fun insertSort(array: IntArray) : IntArray {
if (array.size <= 1) return array
// i 从 1 到 n,是指 未排序区间中的第一个元素
for (i in 1 until array.size) {
// 先临时暂存这个变量
val temp = array[i]
var pre = i - 1
// 然后前面比插入元素大的值逐个后移,空出一个位置
while(pre >= 0 && temp < array[pre]) {
array[pre + 1] = array[pre]
pre--
}
// 最后把「临时变量」插入到空位,并保证已排序区间数据一直有序
array[pre + 1] = temp
}
return array
}
2.3 冒泡排序(了解)
/**
* 冒泡排序 超时
* 思想:将数组中的数据分为两个区间,未排序区间和已排序区间。
*
* 操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看大小是否相等,如果不相等就让它俩互换;
* 一次冒泡,进行 n-1 趟比较并交换,会让至少一个元素移动到已排序区间的第一个位置;
* 重复 n 次,就完成了 n 个数据的排序工作。
*
* 时间复杂度:O(n^2),这里n是数组的长度;
* 空间复杂度:O(1),使用到常数个临时变量。
*
* @param array
* @return
*/
fun bubbleSort(array: IntArray): IntArray {
if (array.size <= 1) return array
// array.size - 1 逐步递减,是指 后面已排序数组 前一个位置
for (i in array.size - 1 downTo 0) {
var sorted = true
for (j in 0 until i) {
// 相邻元素两两对比
if (array[j] > array[j + 1]) {
// 元素交换
val temp = array[j]
array[j] = array[j + 1]
array[j + 1] = temp
sorted = false
}
}
if (sorted) break
}
return array
}
3 高级排序
3.1 快速排序(重点)
/**
* 快速排序 9, 8, 6, 3, 2, 4
*
* 基本思路:分治思想,通过一趟排序划分数组,将排序的数据分割成独立的两部分,分在新的基准位置左右;
* 然后递归地去排序它左边的部分(比它小的值)和右边的部分(比它大的值),依次进行下去,直到数组有序;
*
* 对比:归并排序的处理过程是由下到上的,先处理子问题,然后再合并。而快排正好相反,它的处理过程是由上到下的,先分区,然后再处理子问题。
*
* 时间复杂度:O(nlogn)。
* 空间复杂度:O(logn)。
*
* @param array
* @param begin
* @param end
* @return
*/
fun quickSort(array: IntArray, begin: Int, end: Int) {
if (end <= begin) return
// 获取分区点
val q = partition(array, begin, end)
// 对两个子序列左边进行快排,直到序列为空或者只有一个元素
quickSort(array, begin, q - 1)
// 对两个子序列右边进行快排
quickSort(array, q + 1, end)
}
/**
* 划分数组:
* 定义 counter 是从 begin 到 end 元素的位置,最初以 end 作为临时基准位置;
* 如果存在比 end 的值小的元素,都和 counter 交换,然后+1;遍历结束以后,将 counter 和 end 元素交换,成为新的基准位置;
* 这样,排序的数据分割成独立的两部分,分在新的基准位置左右。
*
* @param array
* @param begin
* @param end 最初的基准位置
* @return
*/
private fun partition(array: IntArray, begin: Int, end: Int): Int {
// 定义 counter 是从 begin 到 end 元素的位置,最初以 end 作为分区点
var counter = begin
// 如果存在比 end 的值小的元素,都和 counter 交换,然后+1
for (i in begin until end) {
if (array[i] < array[end]) {
val temp = array[counter]
array[counter] = array[i]
array[i] = temp
counter++
}
}
// 遍历结束以后,将 pivot 和 end 元素交换,成为新的分区点
val temp = array[end]
array[end] = array[counter]
array[counter] = temp
return counter
}
3.2 归并排序(重点)
/**
* 归并排序(Merge Sort) 采用分治法的一个非常典型的应用
* 思路:1.把长度为n的输入序列分成两个长度为n/2的子序列;
* 2.对这两个子序列分别采用归并排序;
* 3.将两个排序好的子序列合并成一个最终的排序序列。
*
* 时间复杂度:O(n * log n),这里n是数组的长度:
* 空间复杂度:O(n),辅助数组与输入数组规模相当。
*
* @param array
* @param left
* @param right
*/
fun mergeSort(array: IntArray, left: Int, right: Int) {
if (right <= left) return
val mid = (left + right) shr 1 // 右移 (left + right) / 2
mergeSort(array, left, mid)
mergeSort(array, mid + 1, right)
merge(array, left, mid, right)
}
fun merge(array: IntArray, left: Int, mid: Int, right: Int) {
// 临时数组
val temp = IntArray(right - left + 1)
var i = left
var j = mid + 1
var k = 0
// 比较两个小数组相应下标位置的数组大小,小的先放进缓存数组
while (i <= mid && j <= right) {
temp[k++] = if (array[i] <= array[j]) array[i++] else array[j++]
}
// 如果左边还有数据需要拷贝,把左边数组剩下的拷贝到缓存数组
while (i <= mid) temp[k++] = array[i++]
// 如果右边还有数据需要拷贝,把左边数组剩下的拷贝到缓存数组
while (j <= right) temp[k++] = array[j++]
// 将缓存数组中的内容复制回原数组
for (p in temp.indices) {
array[left + p] = temp[p]
}
}
3.3 堆排序(重点)算法刻意练习之堆、二叉堆
/**
* 堆排序(heap sort)
*
* 核心思想:先将待排序的序列建成大根堆,使得每个父节点的元素大于等于它的子节点。此时整个序列最大值即为堆顶元素,
* 我们将其与末尾元素交换,使末尾元素为最大值,然后再调整堆顶元素使得剩下的 n-1n−1 个元素仍为大根堆,再重复执行以上操作我们即能得到一个有序的序列
*
* 时间复杂度:O(nlogn)。初始化建堆的时间复杂度为O(n),建完堆以后需要进行n−1次调整,一次调整(即maxHeapify) 的时间复杂度为O(logn),
* 那么n−1次调整即需要O(nlogn)的时间复杂度。因此,总时间复杂度为O(n+nlogn)=O(nlogn);
* 空间复杂度:O(1)。只需要常数的空间存放若干变量。
*
* @param array
*/
fun heapSort(array: IntArray) : IntArray {
if (array.size <= 1) return array
// 建立最大堆:遍历父节点,索引为的父结点的索引是(i-1)/2,假设有5个
for (i in (array.size - 1) / 2 downTo 0) {
// 调整大堆,只需遍历 i = 1、0
maxHeap(array, array.size, i)
}
// 排序:每次忽略最后一个最大的值,假设有5个,i = 4、3、2、1
for (i in array.size - 1 downTo 1) {
// 最大的在0位置,那么开始沉降,这样每交换一次最大的值就丢到最后了
val temp = array[0]
array[0] = array[i]
array[i] = temp
// 调整大堆
maxHeap(array, i, 0)
}
return array
}
/**
* 调整大堆
*
* @param array 堆数组
* @param length 表示用于构造大堆的数组长度元素数量
* @param index 从哪位置开始
*/
private fun maxHeap(array: IntArray, length: Int, index: Int) {
// 左/右节点:索引为i的左孩子的索引是(2*i+1),索引为i的左孩子的索引是(2*i+2)
val left = index * 2 + 1
val right = index * 2 + 2
// 目标序号:父节点,largest == 变化的值
var largest = index
// left < length。左节点大于根节点,将左序号赋值为目标序号
if (left < length && array[left] > array[index]) largest = left
// 右节点大于根节点(新目标序号),将右序号赋值为目标序号
if (right < length && array[right] > array[largest]) largest = right
// 目标序号元素不是最大值
if (index != largest) {
// 数据交换
val temp = array[index]
array[index] = array[largest]
array[largest] = temp
// 继续调整大堆
maxHeap(array, length, largest)
}
}
4 特殊排序
4.1 基数排序
4.1.1 核心思想
基本思想是:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
3.9.2 演示图
(1)个位—把个位的数值排序好:
(2)十位—把十位的数值排序好:
(3)百位—把百位的数值排序好:
3.9.3 源码
package com.dn.sort;
public class BasicSort {
public void basicSort(int[] array) {
int max = 0;// 获取最大值
int digit = 10;//0-9
int times = 0;// 获取最大值位数
for (int num : array) {
if (max < num) {
max = num;
}
}
while (max > 0) {
max = max / 10;
times++;
}
//建立10个集合(0-9)
List<ArrayList> baseList = new ArrayList<ArrayList>();// 多维数组
for (int i = 0; i < digit; i++) {
ArrayList list1 = new ArrayList<>();
baseList.add(list1);
}
//进行times次分配和收集;
for (int i = 0; i < times; i++) {
//分配数组元素
for (int j = 0; j < array.length; j++) {
// 获取对应的位的值(pow是平方,i为0是个位,i为1是10位,i为2是百位)
int x = array[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);//取余、取整
ArrayList list2 = baseList.get(x);
list2.add(array[j]);// 把元素添加进对应下标数组
}
int count = 0;//元素计数器;
//收集队列元素;
for (int j = 0; j < digit; j++) {//0-9
while (baseList.get(j).size() > 0) {
ArrayList<Integer> list3 = baseList.get(j);// 拿到每一个集合
array[count] = list3.get(0);//获取集合第一个,每次都是新的数据
list3.remove(0);//删除分配给集合的数据
count++;
}
}
}
}
public static void main(String[] args) {
BasicSort basicSort = new BasicSort();
int[] a = { 136, 2, 6, 8, 9, 2, 8, 11, 23, 56, 34, 90, 89, 29, 145,209, 320, 78, 3 };
basicSort.basicSort(a);
for (int n : a) {
System.out.print(" " + n);
}
}
}
4.2 桶排序
3.10.1 背景
给阿里 2 万多名员工按年龄排序应该选择哪个算法?阿里员工特征,2万人,规模较小;处于18-99,尤其是23-40占了大部分,使用桶排序适合。
3.10.2 核心思想
基本思想是:桶排序(Bucket Sort)的原理很简单,它是将数组分到有限数量的桶子里。
过程:假设待排序的数组a中共有N个整数,并且已知数组a中数据的范围[0, MAX)。在桶排序时,创建容量为MAX的桶数组r,并将桶数组元素都初始化为0;将容量为MAX的桶数组中的每一个单元都看作一个"桶"。在排序时,逐个遍历数组a,将数组a的值,作为"桶数组r"的下标。当a中数据被读取时,就将桶的值加1。例如,读取到数组a[3]=5,则将r[5]的值+1。
特征:(1)桶排序是稳定的; (2)桶排序是常见排序算法中最快的一种,大多数情况下比快排和归并排序还要快 (3)桶排序非常快但是也非常消耗空间,典型的以空间换时间,基本上是最耗内存的一种排序算法。
3.10.3 演示图
bucketSort(a, n, max)是作用是对数组a进行桶排序,n是数组a的长度,max是数组中最大元素所属的范围[0,max)。假设a={8,2,3,4,3,6,6,3,9}, max=10。此时,将数组a的所有数据都放到需要为0-9的桶中。如下图:
3.10.4 源码
public class BucketSort {
/*
* 桶排序
* 参数说明:
* a -- 待排序数组
* max -- 数组a中最大值的范围
*/
public static void bucketSort(int[] a, int max) {
int[] buckets;
if (a==null || max<1)
return ;
// 创建一个容量为max的数组buckets,并且将buckets中的所有数据都初始化为0。
buckets = new int[max];
// 1. 计数
for(int i = 0; i < a.length; i++)
buckets[a[i]]++;
// 2. 排序
for (int i = 0, j = 0; i < max; i++) {
while( (buckets[i]--) >0 ) {
a[j++] = i;
}
}
buckets = null;
}
public static void main(String[] args) {
int i;
int a[] = {8,2,3,4,3,6,6,3,9};
System.out.printf("before sort:");
for (i=0; i<a.length; i++)
System.out.printf("%d ", a[i]);
bucketSort(a, 10); // 桶排序
System.out.printf("after sort:");
for (i=0; i<a.length; i++)
System.out.printf("%d ", a[i]);
}
}
before sort:8 2 3 4 3 6 6 3 9
after sort:2 3 3 3 4 6 6 8 9